Skip to content

fix(scripts,spec): doc-authoring Rule 3 reaches a guidance const consumed only cross-module - #13104

Merged
os-elon merged 2 commits into
mainfrom
claude/issue-13032-keysetguidance-sink
Aug 29, 2026
Merged

fix(scripts,spec): doc-authoring Rule 3 reaches a guidance const consumed only cross-module#13104
os-elon merged 2 commits into
mainfrom
claude/issue-13032-keysetguidance-sink

Conversation

@os-elon

Copy link
Copy Markdown
Collaborator

Fixes#13032

SELECT_OPTION_EDITABILITY_GUIDANCE printed a bare internal issue id at a refusing author
on two faces, and pnpm check:doc-authoring reported the tree clean. The gate could not
see it.

The mechanism, re-measured on today's main

Rule 3's hoisted-const pass (collectTextSinkConsts) anchors a 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 walks
free.

The card's mechanism holds — with one correction worth recording. It reads the neighbouring
EDITABILITY_BOUNDARY_GUIDANCE as having "the same shape and the same exposure". Same
shape, different exposure: that const is already reachable, because it happens to be
consumed in-module by VISIBILITY_ONLY_STRICT_OPTIONS, which is StrictObjectOptions-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 KeySetGuidance const in the tree, and whether the OLD detector already reached it:

constfilereached before this PR
EDITABILITY_BOUNDARY_GUIDANCEshared/editability-boundary.tsyes — via VISIBILITY_ONLY_STRICT_OPTIONS
SELECT_OPTION_EDITABILITY_GUIDANCEshared/editability-boundary.tsno — consumed cross-module only
WIDGET_GUIDANCE_SETSui/dashboard.zod.tsyes — guidanceSets: in the same file
COMPONENT_NODE_VISIBILITY_GUIDANCEui/component.zod.tsyes
COMPONENT_NODE_KEYS_GUIDANCEui/component.zod.tsyes
COMPONENT_LEVEL_GUIDANCEui/component.zod.tsyes
OBJECT_CALENDAR_FLAT_FIELD_GUIDANCEui/component.zod.tsyes

N = 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.ts today and be edited again for the next shared guidance const —
the same blind spot moved one level up. The type closes the class. KeySetGuidance
exists solely to be handed to guidanceSets:, and strictUnknownKeyError prints the
prescription it carries verbatim, so it is as unambiguous an anchor as
StrictObjectOptions already is.

declaredTypeText() reads the annotation or a trailing satisfies when there is no
annotation, because this tree writes guidance tables both ways (COMPONENT_LEVEL_GUIDANCE: readonly KeySetGuidance[] and WIDGET_GUIDANCE_SETS = [ ... ] as const satisfies readonly KeySetGuidance[]). Covering only the annotated spelling would ship a detector already blind
to the more modern half of its own population. The initializer is deliberately not
searched: new Set parameterised by KeySetGuidance (shared/suggestions.zod.ts) mentions
the 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:

statetreeresult
1. the defectold detector, id in tree, main @ db39dfc1c9GREEN — 13338 strings clean
2. instrument can firenew detector, id in tree (commit 0446dc3292)RED, exactly 1
3. the fixnew detector, id stripped (commit cdcf4ec545)GREEN — 13351 strings clean

State 2 was predicted before it was run, on six dimensions, and matched all six — file,
line, id, via-const, bucket, count:

packages/spec/src/shared/editability-boundary.ts:135 #8201 [via SELECT_OPTION_EDITABILITY_GUIDANCE]
1 string(s): strictObject 1.

The two states are separate commits, so the red is reproducible: check out 0446dc3292 and
run the gate.

The census arithmetic closes too, which is the part that proves nothing else moved:

before: message 778 · strictObject 3311 · tombstone 751 · describe 8498 (13338)
after: message 778 · strictObject 3324 · tombstone 751 · describe 8498 (13351)

+13, all of it in one bucket — the newly-reached const's name string plus its 12
prescription 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 prescription string as
the 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 describe title in a
test file the gate excludes, and one is coincidental digits inside a tax_id fixture.

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 in as const satisfies (RED #12), and the precision pin that an
inferred 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 run on the 8 test files that can observe the
    changed 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
    SelectOptionSchema were checked and pin the shape, never the message text.
  • pnpm lint — the whole repo, 5408 files, 0 errors, 0 warnings (74s)
  • 24 further gates from dispatch-gates --repo, all exit 0, including the two the
    gate-script convention triggers (check:pm-dispatch-gates, bare-root-worklist --self-test) and the four the changeset triggers
  • pnpm --filter @objectstack/spec typecheck — green, but reported as NOT MEASURED for
    the edited test file
    : --listFiles shows the package's tsc program does not include
    editability-boundary.test.ts. Its correctness is covered by the vitest run above, and
    check: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 fetch and a rebase onto e9c1055ea2 the fresh derivation carries no
warning 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's files whitelist publishes dist, the compiled
home of this string, and .changeset/config.json has an empty ignore list — so the
changed refusal text does ship to consumers. Patch.

Generated by Claude Code


Generated by Claude Code

…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
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

Nothing 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
  • 1 name(s) were too generic to anchor anything (single lowercase words)
  • a page that states a rule by its inputs shares no identifier with the emitter that implements the rule, so an emitter-only diff cannot list it — not on this run and not on any run. Measured on fix(driver-sql): emit varchar(maxLength) for a text field a declared index keys on #11430: content/docs/protocol/objectql/types.mdx documents the text-family column mapping by the ObjectQL type names it maps FROM (text / textarea / html) while the diff changed createColumn; it went unlisted, and it was the page that diff falsified, in four places. No shared token exists to detect this on, so a rule your change carries has to be re-read by hand in the pages that restate it.

Coarse fallback — 126 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json d48929efe794b467c169825ecb68a2d015f09d8dpackageMentionDocs.

@github-actionsgithub-actionsBot added documentation Improvements or additions to documentation tests tooling labels Aug 29, 2026
@os-elon
os-elon marked this pull request as ready for review August 29, 2026 05:36
@os-elon
os-elon enabled auto-merge August 29, 2026 05:36
@os-elon
os-elon added this pull request to the merge queueAug 29, 2026
Merged via the queue into main with commit ca1965fAug 29, 2026
35 checks passed
@os-elon
os-elon deleted the claude/issue-13032-keysetguidance-sink branch August 29, 2026 06:00
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentationImprovements or additions to documentationsize/mteststooling

Projects

None yet

2 participants

@os-elon@claude