Uh oh!
There was an error while loading. Please reload this page.
fix(scripts,spec): doc-authoring Rule 3 reaches a guidance const consumed only cross-module - #13104
Merged
Merged
Conversation
…y cross-module `collectTextSinkConsts` anchored a hoisted const on in-file evidence alone: a `strictObject(...)` call, a `StrictObjectOptions`-typed const, or a `*_STRICT_OPTIONS` name. A `KeySetGuidance` table declared in a shared module and handed to `guidanceSets:` from OTHER files has none of those in its own file, so the whole const walked free while its prescription was printed verbatim at refusing authors. The type is the anchor: `KeySetGuidance` exists solely to be handed to `guidanceSets:`, so it is as unambiguous as `StrictObjectOptions`. Anchoring on the type closes the CLASS rather than one instance -- a named list of guidance modules would have to be edited again for the next one, which is the same blind spot moved one level up. Both spellings count. `declaredTypeText()` reads the annotation, or the trailing `satisfies` when there is none, because this tree writes guidance tables both ways and a detector blind to one of them reopens the gap for the next const written the modern way. The INITIALIZER is deliberately not searched: `new Set<KeySetGuidance>()` mentions the type without being one. Three `--self-test` cases, per the gate's own extend-the-detector rule: the cross-module const in the annotated spelling, the same in `as const satisfies`, and the precision pin that an inferred local merely MENTIONING the type is not a sink. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CPrUz21stTFhJRUirdc4yw
…tability refusal The prescription an author reads when they write `disabled` / `readonly` / `editable` on a select option carried a tracker id that resolves to nothing for the customer it is printed at. Stripped under the gate's own criterion: the citation keeps its durable half, ADR-0049 and ADR-0068 being named in the same sentence, so nothing is lost by dropping the id. The verdict, the vocabulary and the rest of the wording are unchanged -- this curates a message, it does not move the shape. The internal anchor survives in the module's own TSDoc, which is a comment and out of the rule's population by construction. Also pins the absence: the twin now asserts the prescription matches no issue id and still names both ADRs -- the negative pin the gate's remediation text asks for. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CPrUz21stTFhJRUirdc4yw
Contributor
📓 Docs Drift CheckNothing in this diff resolved to a documentable surface (no symbol, route or SDK anchor derived from 1 changed package(s)), so this run has no opinion about the docs. What this run could not see
Coarse fallback — 126 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): |
This was referenced Aug 29, 2026
os-elon
marked this pull request as ready for review
August 29, 2026 05:36
os-elon
enabled auto-merge
August 29, 2026 05:36
Uh oh!
There was an error while loading. Please reload this page.
This was referenced Aug 29, 2026
Merged
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#13032
SELECT_OPTION_EDITABILITY_GUIDANCEprinted a bare internal issue id at a refusing authoron two faces, and
pnpm check:doc-authoringreported the tree clean. The gate could notsee it.
The mechanism, re-measured on today's
mainRule 3's hoisted-const pass (
collectTextSinkConsts) anchors a const on in-fileevidence alone: a
strictObject(...)call, aStrictObjectOptions-typed const, or a*_STRICT_OPTIONSname. AKeySetGuidancetable declared in a shared module and handed toguidanceSets:from OTHER files has none of those in its own file, so the whole const walksfree.
The card's mechanism holds — with one correction worth recording. It reads the neighbouring
EDITABILITY_BOUNDARY_GUIDANCEas having "the same shape and the same exposure". Sameshape, different exposure: that const is already reachable, because it happens to be
consumed in-module by
VISIBILITY_ONLY_STRICT_OPTIONS, which isStrictObjectOptions-typed.The blind spot is a property of the consumption site, not of the const — which is why no
amount of care at the declaration would have avoided it.
How big is this card really
Every
KeySetGuidanceconst in the tree, and whether the OLD detector already reached it:EDITABILITY_BOUNDARY_GUIDANCEshared/editability-boundary.tsVISIBILITY_ONLY_STRICT_OPTIONSSELECT_OPTION_EDITABILITY_GUIDANCEshared/editability-boundary.tsWIDGET_GUIDANCE_SETSui/dashboard.zod.tsguidanceSets:in the same fileCOMPONENT_NODE_VISIBILITY_GUIDANCEui/component.zod.tsCOMPONENT_NODE_KEYS_GUIDANCEui/component.zod.tsCOMPONENT_LEVEL_GUIDANCEui/component.zod.tsOBJECT_CALENDAR_FLAT_FIELD_GUIDANCEui/component.zod.tsN = 1. The widening admits exactly one new const and produces exactly one violation, so
this card is the size it was filed at — no follow-on sweep is hiding behind it.
Why the type anchor rather than a named-sink list
The card offered both. The measurement above decides it: a named list would have to name
editability-boundary.tstoday and be edited again for the next shared guidance const —the same blind spot moved one level up. The type closes the class.
KeySetGuidanceexists solely to be handed to
guidanceSets:, andstrictUnknownKeyErrorprints theprescriptionit carries verbatim, so it is as unambiguous an anchor asStrictObjectOptionsalready is.declaredTypeText()reads the annotation or a trailingsatisfieswhen there is noannotation, because this tree writes guidance tables both ways (
COMPONENT_LEVEL_GUIDANCE: readonly KeySetGuidance[]andWIDGET_GUIDANCE_SETS = [ ... ] as const satisfies readonly KeySetGuidance[]). Covering only the annotated spelling would ship a detector already blindto the more modern half of its own population. The initializer is deliberately not
searched:
new Setparameterised byKeySetGuidance(shared/suggestions.zod.ts) mentionsthe type without being one, and that precision is pinned.
The two-sided proof
A one-sided green measures nothing, so all three states were run:
main@db39dfc1c90446dc3292)cdcf4ec545)State 2 was predicted before it was run, on six dimensions, and matched all six — file,
line, id, via-const, bucket, count:
The two states are separate commits, so the red is reproducible: check out
0446dc3292andrun the gate.
The census arithmetic closes too, which is the part that proves nothing else moved:
+13, all of it in one bucket — the newly-reached const'snamestring plus its 12prescription operands. The other three buckets are byte-identical, so the widening reached
exactly the one const it was aimed at and nothing else.
The strip
Legitimate under Rule 3's own criterion, re-verified on today's tree rather than taken from
the card: ADR-0049 and ADR-0068 sit in the same concatenated
prescriptionstring asthe id (lines 139 and 140 against line 135), so the citation keeps its durable reference. A
decomposed census found 16 text hits for those digits and exactly one in the customer-
facing population — 14 are TSDoc or line comments (out of the rule's population by
construction, and where the internal anchor survives), one is a vitest
describetitle in atest file the gate excludes, and one is coincidental digits inside a
tax_idfixture.The twin now carries the negative pin the gate's own remediation text asks for: the
prescription must match no issue id, and must still name both ADRs.
Self-test, per the gate's extend-the-detector rule
Three cases, all in
--self-test: the cross-module const in the annotated spelling(
RED #11), the same inas const satisfies(RED #12), and the precision pin that aninferred local merely mentioning the type is not a sink. The printed summary line was
updated in the same edit.
Verification
All readings below are from head
cdcf4ec545, the final commit.pnpm check:doc-authoring— green, self-test and scan (census above)pnpm --filter @objectstack/spec exec vitest runon the 8 test files that can observe thechanged string — 501 passed / 8 files. Narrowed deliberately: the full package suite
was killed by the container's 10-minute foreground cap (SIGTERM, no test lines printed —
read as NOT MEASURED, not as a failure), and the two sibling packages that name
SelectOptionSchemawere checked and pin the shape, never the message text.pnpm lint— the whole repo, 5408 files, 0 errors, 0 warnings (74s)dispatch-gates --repo, all exit 0, including the two thegate-script convention triggers (
check:pm-dispatch-gates,bare-root-worklist --self-test) and the four the changeset triggerspnpm --filter @objectstack/spec typecheck— green, but reported as NOT MEASURED forthe edited test file:
--listFilesshows the package's tsc program does not includeeditability-boundary.test.ts. Its correctness is covered by the vitest run above, andcheck:type-check-coverage(the structural half of the TEST_DEBT accounting) is green.The gate family was derived twice. The first derivation printed STALE TREE and still
exited 0; after
git fetchand a rebase ontoe9c1055ea2the fresh derivation carries nowarning and lists an identical 45 families — so the staleness did not change the answer
here, which is now a measurement rather than an assumption.
Changeset
Derived, not assumed.
packages/spec'sfileswhitelist publishesdist, the compiledhome of this string, and
.changeset/config.jsonhas an emptyignorelist — so thechanged refusal text does ship to consumers. Patch.
Generated by Claude Code
Generated by Claude Code