Uh oh!
There was an error while loading. Please reload this page.
fix(scripts): read the declared type the same way in both check-doc-authoring type anchors - #13171
Merged
Merged
Conversation
…uthoring anchors `inStrictOptions()` read `decl.type` alone, so a `StrictObjectOptions` options table written `… satisfies StrictObjectOptions` climbed past the `SatisfiesExpression` to a `VariableDeclaration` with no `type` and answered false. Route it through `declaredTypeText()` — the helper its sibling anchor `collectTextSinkConsts()` already uses — so "what type does this const declare" has one answer in this file. The verdict does not move, and that is measured rather than hoped: the main run is byte-identical (392 / 48 / 13352 strings across 673 spec sources; message 778 · strictObject 3325 · tombstone 751 · describe 8498), and a satisfies-spelled options table was already reported before this change, because `collectTextSinkConsts()` registers the const by its declared type and `customerTextPosition()` resolves the string at `via CONST` without consulting the position test. What this restores is the redundancy: with the sibling anchor reverted to the annotation-only read, this widening alone recovers the hoisted shapes and nothing else. Because the two anchors are indistinguishable end to end, the `--self-test` case the extend-the-detector rule requires is asserted on the PREDICATE — annotation, `satisfies`, `as const satisfies`, the `*_STRICT_OPTIONS` name branch and a foreign satisfied type — plus a regression pin on the nested guidance prose of a satisfies-spelled options table, labelled as a pin rather than as the reverse proof it cannot be. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CPrUz21stTFhJRUirdc4yw
…tisfies-strict-options
This was referenced Aug 29, 2026
os-elon
marked this pull request as ready for review
August 29, 2026 07:36
os-elon
enabled auto-merge
August 29, 2026 07:36
Uh oh!
There was an error while loading. Please reload this page.
This was referenced Aug 29, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for freeto join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes#13105
inStrictOptions()readdecl.typealone, so an options table written... satisfies StrictObjectOptionsclimbed past theSatisfiesExpressionto aVariableDeclarationwith notypeand answered false. It now routes throughdeclaredTypeText()— the helper its sibling anchorcollectTextSinkConsts()already uses (PR #13104) — so "what type does this const declare" has exactly one
answer in this file. That agreement between the two anchors is the deliverable
the card names, and it is the property whose absence let one of them fall a
spelling behind the other, silently, with every gate green.
The card's mechanism holds. Its consequence does not — measured.
The card's walk is correct and was re-verified on today's
main, nottranscribed: the climb from a
guidance:property passes through aSatisfiesExpression, which is neither aCallExpressionnor aVariableDeclaration, so the loop continues to the declaration, wherep.typeis undefined and the predicate returns false.
The card's consequence — "a prescription written like this would not be
recognised as customer-facing prose" — is false on today's
main, and thisPR does not pretend otherwise.
collectTextSinkConsts()already registers such aconst by its declared type, and
customerTextPosition()resolves the string atvia CONSTNAMEwithout ever consulting the position test. Six shapes measuredagainst the detector as it stands, all already RED:
mainsatisfies StrictObjectOptions, nested guidanceas const satisfies StrictObjectOptionsretiredFormsin anew MapconststrictObject(...)That zero-difference column is a reading rather than a dead instrument: the same
harness with
collectTextSinkConsts()reverted to the annotation-only read dropsfour of those six to 0, so it demonstrably can return a difference.
What the widening actually restores is the redundancy. With the sibling
anchor blinded, this change alone recovers exactly the hoisted shapes — a
guidance table lifted out of the options object and reached through the seed path
in
collectTextSinkConsts()— and nothing else. So thesatisfiesspelling iscarried by one mechanism today, and the seed path would not have compensated if
that one were ever narrowed. After this PR it is carried by two.
No current verdict moves
Byte-identical main run before and after, measured by checking out the
pre-change file and running it:
The latency claim was re-measured on
mainat 901355c with its control:satisfies StrictObjectOptionsunderpackages/is still zero, while thesame instrument returns
packages/spec/src/ui/dashboard.zod.ts:189for thesibling type's
as const satisfies readonly KeySetGuidance[]. The eleven textmatches for the bare token decompose to one comment, three type-only imports, two
annotated consts (both of which also match the name branch), one interface, one
interface property, two parameter annotations and one factory return type — no
options table in the
satisfiesspelling.The self-test case is on the PREDICATE, deliberately
The gate's extend-the-detector rule requires a
--self-testcase for the newspelling. A case written as a scan would be a phantom check here: it passes
with this change reverted, because the sibling anchor reports the same string. So
the reverse proof is asserted on
inStrictOptions()itself — annotation,satisfies,as const satisfies, a foreign satisfied type, and the*_STRICT_OPTIONSNAME branch with no type written at all, each paired with thematching
declaredTypeText()read so the two anchors are shown to agree aboutthe same declaration. The battery also asserts its own case count, so a future
edit that empties it reds instead of congratulating itself.
The end-to-end fixture is still added (RED #13: an options table in the
satisfiesspelling, const deliberately not named*_STRICT_OPTIONS, with abare internal id one object deep in
guidance), and its comment states plainlythat it is a regression pin on the class rather than the reverse proof for this
widening.
Ablation, predicted before running and matched exactly. Reverting only the
one-line type read on top of the committed implementation reds 2 assertions
and no others:
The annotation row, the foreign-type row, all four "both anchors agree" rows,
both NAME-branch rows, RED #13's three end-to-end rows and every pre-existing
case stayed green, and the main run's verdict line did not move. The mutation was
confirmed on disk in both directions by grep counts plus a
git hash-objectcomparison against the HEAD blob, restore likewise; no build or
dist/isinvolved, since this gate is a script node runs directly.
Verification
Union re-run on the final commit,
9adb9bf045:pnpm lint(whole repo,eslint . --no-inline-config) — clean,VERDICT command-exit 0node scripts/check-doc-authoring.mjs --self-test— exit 0node scripts/check-doc-authoring.mjs— exit 0, verdict quoted abovescripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack, re-derived after syncing toorigin/mainbecause the first derivation printed STALE TREE and still exited 0 — all exit 0node scripts/pm/bare-root-worklist.mjs --self-test("OK self-test: 50 live row(s) ... none stale, none missing, none contradicted") andpnpm check:pm-dispatch-gates("✓ dispatch-gates self-test: 879 cases pass.")node scripts/check-test-completeness.mjsanswers a standalone run with itsusage line, not a finding — that is #13110, not a red here.
No changeset, derived rather than asserted:
pnpm check:published-filesreports 69 publishable packages of 78 workspace members, every one declaring a
package-relative
fileswhitelist; the repo-rootscripts/directory sitsoutside all of them, and the root manifest is private with no
filesfield. Thisdiff publishes nothing.
Two side-findings, reported and not touched here
Both are out of this card's defect class and are described in the dev report;
filing them as issues was blocked by an exhausted GitHub API quota in this
session, so they are handed to the PM seat.
inStrictOptions()is blind to an options table produced by a FACTORY,and this one is LIVE rather than latent.
navItemSurfaceinpackages/spec/src/ui/app.zod.ts:203is an arrow function whose return typeis
StrictObjectOptions; the climb hits theisArrowFunctionguard andreturns false before any type anchor is consulted, and the declaration itself
carries no type, so
collectTextSinkConsts()does not rescue it either.Measured on the shape: 0 violations and
strictObject seenof 0 — the 21string literals in that factory, including the per-variant
surfaceand thealiasesprescriptions, are not merely unflagged but outside the populationthe per-bucket blindness floor is computed over. A const-typed control with
the identical prose reds. This is a POSITION gap, not a type-spelling gap,
and closing it would move live verdicts, which this card forbids.
check-doc-authoring.mjs --self-testpasses while registering zero cases.Success is
failures.length === 0with no assertion-count floor. Measured: anearly return at the top of
selfTestRule3()leaves the whole Rule 3 batteryunrun and the script exits 0, printing its full success line claiming every
one of those cases "holds". The success condition and the total-failure
condition are the same output. The battery added in this PR asserts its own
case count, but the file-wide floor is still missing.
Generated by Claude Code