Skip to content

fix(scripts): doc-authoring Rule 3 reaches a GENERATED guidance table - #13283

Draft
os-elon wants to merge 1 commit into
mainfrom
claude/issue-13172-doc-authoring-factory-tables
Draft

fix(scripts): doc-authoring Rule 3 reaches a GENERATED guidance table#13283
os-elon wants to merge 1 commit into
mainfrom
claude/issue-13172-doc-authoring-factory-tables

Conversation

@os-elon

Copy link
Copy Markdown
Collaborator

Part of #13172

The card's named instance was already discharged before this round

#13172 measured that navItemSurface in packages/spec/src/ui/app.zod.ts — an
arrow function whose return type is StrictObjectOptions — was outside Rule 3's
population entirely. That was true when it was filed (07:20Z). PR #13151 landed
at 13:21Z the same day with the "fourth population" clause, which teaches the
climb to leave a function that BUILDS an options record, and it closed exactly
that shape.

Re-measured on the live tree by planting a distinct id in every one of the 62
string literals of that options table and the five tables it references, then
reading which ones the gate reports:

declarationliteralsreached BEFORE this PR
navItemSurface (the factory itself)2322
NAV_ITEM_ALIASES1414
NAV_EXPANDED_ALIASES_ON_GROUP44
NAV_EXPANDED_ALIASES_ELSEWHERE44
SEPARATOR_NAV_ITEM_ALIASES44
SEPARATOR_NAV_ITEM_GUIDANCE130

The one unreached literal inside the factory is extraKeys: ['children'], which
is a key NAME and is deliberately excluded from STRICT_OPTION_KEYS.

The card's own count re-derives cleanly: the factory holds exactly 21
ts.StringLiteral nodes (23 literals minus the two template-literal parts of
surface), and the gate now attributes exactly 21 customer-facing strings to it
(functionBuilt 21). But only 13 of the 23 are refusal prose — the other 10 are
nine variant comparison operands and the one extraKeys key name, none of which
should ever be judged as prose.

What was still live: the GENERATED table

SEPARATOR_NAV_ITEM_GUIDANCE is not written as an object literal. It is BUILT —
one prescription filed under each of a list of keys:

// (type annotation elided — angle brackets do not survive this body)constSEPARATOR_NAV_ITEM_GUIDANCE=Object.fromEntries(['label','title','icon', ...].map((key)=>[key,`\`${key}\` is not a separator key — ...`]),);

Its four prose literals are spread into the guidance of the very options table
#13172 names, printed verbatim at a refusing author, and they were outside the
population entirely. A second instance is live in packages/spec/src/ui/view.zod.ts,
generated inline at the guidance: key itself.

The mechanism is the card's own defect one shape over. The climb reaches the
.map() callback boundary, asks customerTextPosition about the arrow function
— the one-sentence rule the fourth population rests on — and the answer is
undefined, because the callback's parent is a map call and its grandparent a
fromEntries call, neither of which the climb recognised. The factory clause
never got the chance to fire.

The change

fromEntries, map and flatMap join TRANSPARENT_CALLS. Transparency only
lets the climb CONTINUE — it must still terminate at a recognised position
(message: / error:, .describe(), a tombstone argument, a
STRICT_OPTION_KEYS key under a strictObject call, or a collectTextSinkConsts
sink). A .map() in an ordinary helper reaches none of those and stays silent.
What it buys is that a table's SPELLING — literal or generated — stops deciding
whether its prose is judged.

Three self-test cases in the same edit, as this file requires of any newly
recognised spelling: the hoisted generated table reaching an options factory
(red/green pair), the same generation written inline at the guidance: key
(red/green pair), and the precision case the widening owes — a generated VALUE
table reaching no sink stays unswept.

Measured

  • Population: +19 strings, strictObject 3349 -> 3364, functionBuilt 217 -> 221.
  • New violations: 0. None of the newly reached prose carries an internal id.
    So the card's open severity question — whether the blind spot was hiding an
    actual Rule 3 violation today, or only the capacity to hide one — measures as
    the second: capacity only, in both instances.
  • After this PR: 61 of the 62 planted ids red, the exception being the
    extraKeys key name above.
  • Reverse verification (from the committed state, mutation confirmed on disk
    by grep counts and blob hash, restore proven byte-identical against the HEAD
    blob): reverting TRANSPARENT_CALLS to ['freeze'] fails exactly the four new
    red assertions and returns the population to 13801 / strictObject 3349 / functionBuilt 217, so the +19 is attributable to this change alone. No rebuild
    leg applies — scripts/check-doc-authoring.mjs is executed from source by node
    and has no dist.

Local checks, at e89f70ac69

Green: check:doc-authoring (and its --self-test), check:agent-test-spelling,
check:bash32-floor, check:cli-command-ids, check:cross-package-test-inputs,
check:entry-guard, check:parse-guard, check:pnpm-filter-targets,
check:watch-hint-literal, check:nul-bytes, check-ci-filter-parity.mjs,
check-cross-package-test-inputs.mjs, check-shard-attestation.mjs, plus the two
gate-script convention obligations bare-root-worklist.mjs --self-test and
check:pm-dispatch-gates.

check-test-completeness.mjs exits 3, PREREQUISITE NOT MET — it grades a saved
turbo run test log and none exists locally. Recorded as NOT MEASURED, not as a
red, exactly as its own text prescribes.

ESLint was run narrowed to the one changed file rather than repo-wide, and the
narrowing is a measurement: eslint's own config resolution puts the file in the
population, --format json reports 1 file with 0 errors and 0 warnings, and
eslint.config.mjs never enables type-aware linting for any file
("no parserOptions.project, no typed @typescript-eslint rules"), so a
one-file diff cannot move the verdict of any untouched file.

Scope

No changeset: this touches scripts/ only and publishes nothing — the same
shape as #13171, which changed this file alone and carried none. skip-changeset
applies.

No contract accept/reject behaviour changes and the published surface is
unchanged: packages/spec is not edited, and this gate reads sources without
altering any schema. The only behaviour change is which strings a CI gate reads.

The self-test assertion-count floor raised in #13173 is out of scope here and is
not addressed by this PR; that card stays open. Nothing in this change makes it
harder — the two new red/green pairs are ordinary registrations that any
file-level floor would count.

Generated by Claude Code


Generated by Claude Code

A guidance table does not have to be written as an object literal. Two in
packages/spec are BUILT -- one prescription filed under each of a list of keys
via `Object.fromEntries(keys.map((k) => [k, '...']))`:
- SEPARATOR_NAV_ITEM_GUIDANCE (ui/app.zod.ts), spread into the `guidance` of
the per-variant nav options table;
- the container-key prescription at `guidance:` in ui/view.zod.ts, generated
inline at the key itself.
Both are printed verbatim at a refusing author and both sat outside the rule's
population entirely: the climb died on the unrecognised `map` / `fromEntries`
call between the callback and its sink, so the fourth population's factory
clause -- which teaches the climb to leave a function that BUILDS an options
record -- never got the chance to fire.
Measured by planting a distinct id in every literal of the nav options table
and the five tables it references: 48 of 49 candidate ids red, and the whole of
SEPARATOR_NAV_ITEM_GUIDANCE (four prose literals) silent.
`fromEntries` / `map` / `flatMap` join TRANSPARENT_CALLS. Transparency only lets
the climb CONTINUE -- it must still terminate at a recognised position -- so a
`.map()` in an ordinary helper stays silent, pinned as a precision case beside
the two new red/green pairs.
Population +19 strings (strictObject 3349 -> 3364, functionBuilt 217 -> 221),
0 new violations: none of the newly reached prose carries an internal id.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CPrUz21stTFhJRUirdc4yw
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size/mskip-changesetPR has no user-facing published change; bypasses the changeset gate

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@os-elon@claude