Conversation
Invoking a parameter, constructing a class, dispatching through `this` or `super`, and reaching a member a base interface declares each resolve to the body that runs, under conditions that fail closed where a body could have been replaced. The reviewed catalog gains the legacy request object, the document and window surfaces, the style, dataset and class-list surfaces, the URI functions, and the standard constructors and conversions. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Adversarial review reproduced 16 unsound proofs in the public-method side of class dispatch. Linking `this.m()` and a call on a receiver the caller supplies needed the analyzed file set to be closed, and a barrel re-export, a bare side-effect import, a dynamic `import()`, a triple-slash reference and an ambient subclass declaration each leave it open; the nominality argument failed as well, because an optional `private` member imposes nothing on an object literal. Only what is exact remains: `new C()` and `super(...)` run the constructor they name, and `this.#m()` runs the body its declaration fixes, because a `#` name is not a property. A property site whose member the checker resolves to no declaration is now unknown rather than a silent proof. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
A class body this path cannot identify now resets the scope instead of leaving the enclosing class's, so `this.#m()` and `super()` inside an anonymous nested class no longer link to the outer class's declarations. A call through a frozen effect table and an immediately invoked function now supply their arguments to the discharge pass the way a plain call does. A boundary that owes an obligation of its own cannot discharge another, because whatever fills its parameter is chosen inside its callee rather than at the call. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…owed boundaries A static block and a static field initializer run when the class declaration is evaluated, not at construction, so they no longer open the construction boundary a sibling instance initializer opens; where that boundary already covers them by span, the scope that declares the class is unresolved instead of a proof. Composing a boundary as a value is not calling it. A contract's callback argument, a handler assigned to a property, and an argument that discharges an invoked-parameter obligation are all supplied by whoever calls that boundary elsewhere, so one that still owes an obligation of its own cannot be composed here. The rule previously applied only to an inline argument. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
A construction located its boundary by probing one character past the class body's brace and accepting whatever fact had a name ending in "constructor". That missed an `accessor` field, matched a nested class of the same name, and fell through when the first member abutted the brace — each time returning an empty target list, which is a proof that construction performs nothing. One rule now gives both the syntax pass and the checker the same span, a class whose body the run did not resolve to a boundary is unresolved rather than effect-free, and a decorator anywhere on the class or its members withdraws the link, because its return value replaces what the construction runs. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…tart A call expression shares its start offset with every call chained onto it, so `this.#m().write()` matched the private-call entry twice and linked the outer `.write()` to `#m`'s body. That kept the outer site off the unresolved path entirely, proving a method that writes a cookie performs nothing. The callee token is unique to each site and is what a call site already reports. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The construction boundary widens over the whole class body once an instance initializer needs it, so a decorator expression and a computed member key — both evaluated when the declaration is evaluated, not when an instance is constructed — were charged to a constructor that never runs them, leaving the function that declares the class an empty proof. The declaring scope is now unresolved whenever that widened boundary would cover definition-time work. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
… by callee A class decorator runs before the class is defined, in the scope that declares it, so its `#` names and its `super` are the enclosing ones; the walk was giving it the decorated class's scope and composing the wrong body. A frozen effect table's call was keyed by the call expression's start offset, which every call chained onto it shares, so `table.open().write()` linked the outer call to the frozen member and lost the real callee. Both now use the token a call site reports as its callee. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…stic An exclusion is a construct this path recorded no site for: a conditional or coalesced callee, a conditional constructee, a tagged template. The boundary containing one therefore composed nothing and stayed a proof of effect freedom while the construct ran — including the invoked-parameter obligation a callee would otherwise have owed. Two exclusion reasons already failed closed; all of them do now. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Pre-merge review found 16 unsound proofs; the leaky half is cutFive adversarial reviewers re-attacked this branch by execution before merge. They reproduced 16 unsound proofs — cases where a summary claimed What was cutLinking a public
What remains, and why it needs no assumption
Two further fixes came out of the same review: a property site whose member the checker resolves to no declaration — a nullable receiver, an One reported defect is left unfixed and is pre-existing on CostProduction summaries on the external codebase move to 954 unknown / 144 trusted / 319 inferred, against 1,094 / 17 / 299 before this branch and 892 / 175 / 350 with the unsound mechanism in. Roughly 60 summaries and most of the 🤖 Generated with Claude Code |
The default Corsa check resolved only free-function calls and exact
Owner#membercontracts, so a class-based, callback-heavy browser codebase reached almost nothing. Four mechanisms close that, each fail-closed by construction.Higher-order composition
A function that invokes one of its own plain-identifier parameters carries
InvokeUserCode, and every call of that boundary — a plain call, a construction,super, or in-class dispatch — owes an analyzed boundary at that argument position or becomes unknown. Obligations propagate to a fixed point, so a caller that forwards its own parameter inherits one rather than discharging it.Inherited contracts
A reviewed contract is keyed by the interface that declares the member and is now reached through the inheritance
lib.dom.d.tsitself declares:Node#ownerDocumentapplies to anHTMLElementreceiver, while a receiver that reaches no interface carrying that member stays unknown —NodeList#lengthstill never selectsStorage#length. Three interfaces whose named members the standard leaves open (CSSStyleDeclarationBase,DOMStringMap,DOMTokenList) carry one whole-surface contract, consulted only after every member-specific key on the chain has missed.Class construction and dispatch
new C(),super(...)andsuper.m(...)link to the class body they run.this.m()and a member call on a nominal class link to the declared body only when nothing in the analyzed files can replace it: aprivateor#member always qualifies; a public one additionally needs no subclass declaring that name, no assignment that could install a function on that receiver type, an unnarrowed file list, and every statically imported binding declared in a file the run read.Reviewed contracts
The catalog gains the legacy request object, the document tree, the window surface, the style, dataset and class-list surfaces, the URI functions, and the standard constructors and conversions.
mutateis read as the ownership fact it is rather than an unmodelled primitive,cloneandtransferrender as their capabilities, and a contract's callback may be the function assigned to a handler property.Review
Five independent adversarial reviews ran against this change — three spec reviews of the catalog and two attempts to break the linking mechanisms by execution. They reproduced 10 unsound proofs and over 40 contract defects; every one is fixed and pinned by a regression test. The ones worth naming:
PromiseLike#thenhad been givenPromise.prototype.then's semantics, though it is a structural interface any value with athenmethod satisfies, so the call is synchronous, arbitrarily repeating user code.DOMRectredeclaresDOMRectReadOnly's geometry members as writable, so the inheritance walk landed on the read-only entry and proved that assigning them was harmless.thisparameter shifted every argument index, silently dropping the obligation it named.new C(cb),this.m(cb)andsuper.m(cb)never discharged the callee's obligation at all.class extends Base,extends Alias, a mixin call, a decorator,C.prototype.m = fandthis.m()written inside an object literal each defeated override detection.Over-claims were corrected in the same pass, which is why some effect counts go down:
new Error("literal")no longer carriesInvokeUserCode(the declared message is astring, so its coercion is total), and theEventconstructor andnew Promiseno longer carry aTypeErrorthat typed code cannot reach.Measured
On a 224-file browser ad delivery codebase, analyzed with no changes to it, counting production sources only:
Netbecomes observable for the first time. This repository's own check runs in 29 s against 33 s before, while reporting 75 syntax errors where the earlier build reported 1,024.One limit is worth recording because it is a property of the analyzed codebase rather than of the checker: 576 member-call sites there are blocked by a single deep-merge helper whose
targetparameter isanyand whose assignment is computed. That write could install a function on any object, so no public method of any class can be proved un-replaced.Verification
tsc --noEmitacross all four project configurationsci/check-examples.mjsandci/check-skills.mjspassparts[1], the repaired one does notmain— a 2 GB WASM limit in the Z3 backend, unrelated to this change🤖 Generated with Claude Code