Repository navigation
tooling: a structural gate that keeps a registered facade class delegation-only - #30030
Conversation
WIP: the gate, its self-test and an empty facade registry. Claude-Session: https://claude.ai/code/session_01WGufzfcbZEJQm3rajN7rri Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WGufzfcbZEJQm3rajN7rri Co-authored-by: Claude <noreply@anthropic.com>
…te runs nothing Claude-Session: https://claude.ai/code/session_01WGufzfcbZEJQm3rajN7rri Co-authored-by: Claude <noreply@anthropic.com>
…s as wiring Claude-Session: https://claude.ai/code/session_01WGufzfcbZEJQm3rajN7rri Co-authored-by: Claude <noreply@anthropic.com>
|
domain:devx#1 · os-support-ai · session_01WGufzgBDcBNVmSmo7VFHpj Stamp: 2026-11-04T09:13Z Contract reviewServed-tier: Inputs: card #30019 (body and all three comments: triage 6624652906, claim 6626207129, os-dev-report 6626941227), rulings 6624258595 and 6624345766 on #29465, PR #30030 body, file list (4 files, +926/-0) and net diff against Gate verdicts on the head: every non-skipped check-run concluded ① Derived judgmentsAccept-set and public-surface changes the diff implies, each judged:
② Semver levelThe diff publishes nothing from any released package: root ③ Boundary flagsThe dev's flags are in the os-dev-report (6626941227) and the PR body's acceptance notes;
④ Design judgment
Does the gate refuse a method body in a registered facade file? Yes. Does it accept exactly the codemod shapes? Yes on the shapes read (① above, both heads), with the accepted widenings named in ① (self-calls, Does it refuse an exception row without a card? Yes (E2, E3), and a facade row without one (N4). Does it fail rather than pass silently on a missing file or zero delegations? Yes: a missing file, a class declared 0 or 2 times, and zero delegations each push a problem (N1, N2, N3); an unparseable source exits 3 through Is the Findings (none must-fix):
Implemented-by: VERDICT: PASS |
…mber key Contract review follow-ups on the facade delegation-only gate: - A forwarding call or `new` that reaches a function, class, enum or variable declared at the top level of the facade file itself is now a body: a delegation must reach another module. Self-test H13; ablating the check reds H13. - An exception row whose member lacks its `static ` or `get `/`set ` prefix is refused with "did you mean `static x`?". Self-test E9. - A state write must hang off `this`, or off the instance parameter of a host factory arrow; a write to a module binding, an imported name or a caller's object is a body. Self-test H14, and A4 now covers a host factory setter. - The awaited-return check reads through stripAwait. Self-test: 35 cases, floor 35. Both facade heads (bba9d24, f48078b) pass with their measured exception rows; no member forwards into its own file. Claude-Session: https://claude.ai/code/session_01WGufzfcbZEJQm3rajN7rri Co-authored-by: Claude <noreply@anthropic.com>
|
domain:devx#1 · os-support-ai · session_01WGufzgBDcBNVmSmo7VFHpj Stamp: 2026-11-04T09:25Z Contract reviewServed-tier: Delta review. The record 6627146481 on Check-runs on ① Derived judgmentsAccept-set changes the delta implies, each judged:
② Semver levelUnchanged from 6627146481: nothing publishes, ③ Boundary flags
④ Design judgment
Does the delta close what it set out to close, without loosening anything? Yes. Every change is a narrowing of the accept set or a message improvement; no previously refused shape is now accepted. The host-parameter arm is the one addition to the accept set, and it admits exactly the setter shape the codemod's static host factory generates, scoped to an arrow or function expression parameter and stopped at the class element boundary so a member's own parameters do not qualify. Findings (none must-fix):
Implemented-by: VERDICT: PASS |
…red names
A facade member could still reach a top-level function of its own file
through an alias: `const h = helper; return h(this, a)`, a constructor
slot `this.h = helper`, or a field `h = helper`. A value-position
reference to a same-file function, class, enum or function-initialised
variable is now a body ("it takes a reference to `helper`"). Data
variables stay legal as arguments (`ObjectQLStatics`). Type positions,
and a parameter or local that shadows the name, are not references.
Destructured top-level names are now collected, so a call through one
is refused too.
Self-test H15 covers the three aliases and the destructured call, which
are red, and the data argument, the shadowing parameter and the
type-only use, which pass. Ablating the reference arm, the destructured
collection, the shadow check or the type-position check reds H15 alone.
36 cases, floor 36. Both facade heads keep their verdicts.
Claude-Session: https://claude.ai/code/session_01WGufzfcbZEJQm3rajN7rri
Co-authored-by: Claude <noreply@anthropic.com>
|
domain:devx#1 · os-support-ai · session_01WGufzgBDcBNVmSmo7VFHpj Stamp: 2026-11-04T09:32Z Contract reviewServed-tier: Second delta review. The records 6627146481 ( Check-runs on ① Derived judgmentsAccept-set changes the delta implies, each judged:
② Semver levelUnchanged from 6627146481: nothing publishes, ③ Boundary flags
④ Design judgment
Does the delta close should-fix 4 without loosening anything? Yes. Every change narrows the accept set or refines a refusal message; no previously refused shape is now accepted. The three exclusions are precision devices, each bounded to the member and each pinned by an H15 arm (data argument, shadowing parameter, type-only use). No must-fix. The remaining observations are evasion-grade and below the bar for another logic round:
Implemented-by: VERDICT: PASS |
…ed exceptions Claude-Session: https://claude.ai/code/session_01WGufzfcbZEJQm3rajN7rri Co-authored-by: Claude <noreply@anthropic.com>
|
domain:devx#1 · os-support-ai · session_01WGufzgBDcBNVmSmo7VFHpj Stamp: 2026-11-04T10:50Z Contract reviewServed-tier: Final delta review. The records 6627146481 ( Check-runs on ① Derived judgments
② Semver levelUnchanged: nothing publishes ( ③ Boundary flags
④ Design judgment
Does every row name a real member of the landed file that is in fact a non-delegation body? Yes, all 60, read one by one above. Does each cite a card? Yes, No must-fix. Findings:
Implemented-by: VERDICT: PASS |
|
domain:devx#1 · os-support-ai · session_01WGufzgBDcBNVmSmo7VFHpj Stamp: 2026-11-04T10:52Z 维护者速读
席位核对
|
Requested by Yibo Wang · project thread
Part of #30019
This PR is the whole of that card. It says
Part ofrather than a closing keyword because this PR's head branch is a harness-pinnedclaude/project-thread-*branch, whichcheck:closing-target-claimcannot match to the card'sClaim:. The seat records the card done when this lands.维护者速读
scripts/check-facade-delegation-only.mjs和登记表scripts/facade-delegation-registry.json,接入lint.yml的 repo & tooling 车道。登记为 facade 的类,每个成员必须是一行委托(按 AST 语法判定),否则报出文件、成员和修法;例外行必须写明负责搬走它的卡号。protocol.ts/engine.ts各自以 facade 形态落地后再加一行登记。回滚即 revert。bba9d24d:277 个委托、30 个例外;refactor(objectql): generate the engine.ts section split with a deterministic codemod #30012 头f48078bb:251 个委托、30 个例外);③ 其中 5 + 3 个例外是门禁比两份报告更严的地方,需席位判断。node scripts/check-facade-delegation-only.mjs --self-test,预期 32 例全过;对 refactor(metadata-protocol): generate the protocol.ts section split with a deterministic codemod #29998 头运行--root <checkout> --registry <临时登记表>预期退出码 0。.github/workflows/lint.yml(Tier H),需要您批准;与两个 facade PR 同一窗口或紧随其后落地。Clause-②: no
Metadata-protocol: none
Keep one value on each of the two lines above, each line starting with its key.
Human Minimum
item 5 says what they mean.
What changes
scripts/check-facade-delegation-only.mjs(new). It reads every class registered in the facade registry and refuses any member whose body is more than a delegation, naming the file, the line, the class, the member and the fix line. The delegation grammar is spelled over the AST in the script header (rationale there). In short:ref(arg, …)whererefis a name or a.-chain off a name orthis, and every argument is a name,this, a literal, a.-chain, a spread, one level of nested forwarding call (protocolHost(this)), or a forwarding arrow.return <forwarding call>,<forwarding call>;,return <.-chain>, or<.-chain> = <argument>. It may be preceded byconstdeclarations of forwarding arrows or host object literals of forwarders, which is the hand series'.call(host, …)stub.scripts/facade-delegation-registry.json(new), two lists:facadesrows{ file, class, card }, andexceptionsrows{ file, member, card, reason? }. It starts empty (see Landing order).#<number>, a row naming an unregistered file, a member the class does not have, a member that is already a delegation (its card landed, so the row must go), two rows for one member, and an unknown key are all red.ts-parse.mjs, exit 3) are all red. An empty registry passes, and its success line says in words that the gate guards nothing yet.package.jsongainscheck:facade-delegation-only(self-test, then the gate), andlint.yml'sLint · repo & tooling gateslane runs it as one tolerate-and-collect step besidecheck:stack-collection-maps. This makes the PR Tier H.scripts/,package.jsonscripts and a workflow only; nothing publishes (skip-changeset).Why
Ruling 6624258595 on #29465, item 3: 「once a file lands in facade form, a gate refuses a method body in the facade file (delegations only)」. It replaces the 13,000-line bar for those files. Ruling 6624345766 item 3 lets today's hot bodies stay as named exceptions, each with the card that will move it (#30020).
Landing order
The registry is empty in this PR, so the gate cannot red
mainwhichever PR lands first. A file joins with onefacadesrow only once it is in facade form onmain:mainand adds both rows and their exception lists before it leaves draft.Either way the rows are measured from the landed head, never from an estimate.
Verification
Measured on
protocol.tsfacade head (#29998bba9d24d, provisional)Run with a scratch registry holding one
facadesrow and 30 exception rows (--root <#29998 checkout> --registry <file>), exit 0:const host = {…}; return fn.call(host, …))return Fn(host(this), …))return fn(…args))installProtocolHost(…))Without the exception rows each run is red with exactly its 30 members named.
The 30 exceptions against the #29985 report (6626233635): 25 of its 26 listed members are refused by the gate, and its 26th,
static resetEnvWritableCache, is already a one-line forwarding call, so it needs no row (a row for it would be refused as stale). The constructor and the static host factory, which the report also lists, pass as wiring. The gate refuses 5 members the report counts as delegations, and these are for the seat to judge:applySeedBodies: a hand-series host slot computes (() => this.getServicesRegistry?.().get(FIELD_FILE_SERVICE) as …, an optional call and a call on its result).static OVERLAY_ALLOWED_TYPES,static STATIC_REGISTRY_TYPES,static RUNTIME_CREATE_ALLOWED_TYPES,static OVERLAY_CAPABLE_TYPES: each is a static table built by an initializer IIFE with a loop body.Measured on
engine.tsfacade head (#30012f48078bb, provisional)Same run against a #30012 checkout, with one
facadesrow (ObjectQL) and 30 exception rows, exit 0:.call(host, …)and hand-series.call)(this as any)._x = v)return (this as any)._x)Object.defineProperties(ObjectQLStatics, …))The 30 exceptions against the #29986 report (6626513858): 27 of its 31 listed members are refused by the gate. Its other 4 (
setHookMetricsRecorder,setStrictHookBinding,setWarnLegacyHandler,getHookMetricsRecorder) are one-line state writes and reads, so they need no row. The gate refuses 3 members the report does not list, for the seat to judge:constructor:this.logger = hostContext.logger || createLogger(…)and anifonprocess.env.OBJECTQL_STRICT_HOOKS.projectHomeAmountsOntoPatch: its parameter default callsthis.homeAmountServing(context).serveReferenceNames: a hand-series host slot holds atryblock.ObjectRepositoryandScopedContextin the same file are not facades and are not judged.Both provisional registries and their union are in the project files (
issue-30019/registry-provisional-protocol.json,registry-provisional-engine.json,registry-final-both.json).Self-test and gates
node scripts/check-facade-delegation-only.mjs --self-test: 32 cases in 32 pinned batteries (12 harm, 6 accepted shapes, 8 exception rules, 6 anti-no-op), with the battery-floor and verdict handshake copied fromcheck-agent-model-declared.mjs.judgeDelegationBodyaccepts everything), the self-test goes red on 9 cases (H1–H5, H9, H11, H12 and E1, whose allow-listed row then reads as stale). Restored, 32 pass. It ran in a throwaway checkout holding the committed blob (git hash-objectequal).dispatch-gates --commands: 65 derived. At PR open, 42 have run: all exit 0 exceptcheck:entry-guard, which was red on the first commit (a top-level loop in an exporting script) and is green after the fix in this PR, andcheck:dts-closure,check:dual-build-cjs-loadsandcheck:lean-entry-closure, which exit 3 (they need built package dist; this diff touches no package). The rest of the sweep is reported on the card.Acceptance notes
protocol.tsholds two top-level functions (packagePersistFailureMessage,packagePersistFailureError, 24 lines) and one class.Human Minimum
packages/specholds no runtime logic. (not touched)pnpm lintpasses.main, one pull request.packages/spectouched: its generated artifacts are regenerated and committed. (not touched)content/docs/references/,packages/*/CHANGELOG.mdorcontent/docs/releases/.🤖 Generated with Claude Code
https://claude.ai/code/session_01WGufzfcbZEJQm3rajN7rri