Skip to content

refactor(docs-audit): a route SOURCE of two kinds — admit spec contract declarations behind a runtime-registration guard - #15274

Merged
baozhoutao merged 4 commits into
mainfrom
claude/issue-11857-route-source-evidence
Sep 4, 2026
Merged

refactor(docs-audit): a route SOURCE of two kinds — admit spec contract declarations behind a runtime-registration guard#15274
baozhoutao merged 4 commits into
mainfrom
claude/issue-11857-route-source-evidence

Conversation

@claude

@claude claude Bot commented Sep 4, 2026

Copy link
Copy Markdown
Contributor

Fixes #11857

Implements maintainer ruling A (comment 5537060359, 2026-09-04 decision batch #31): a spec contract declaration IS a route source for doc nomination — admitted under a renamed concept, behind a runtime-registration guard.

All figures below are measured on this branch at f0dd5ab79 (base origin/main 460134af8), re-measured rather than copied from the card.

1. The control, re-derived FIRST

The card's rule: if the control does not reproduce, the delta means nothing.

control card (a6eca9223) today (460134af8)
route sources scanned 12 12
route tails produced 43 44
reachable rows 45 47

The file count reproduces exactly; tails and reachable have each moved by tree content since 2026-08-25. That drift is not mechanism drift, and it was isolated rather than assumed: running today's tool against the card's own ref a6eca9223 reproduces 12 / 43 / 45 exactly, so nothing in PR #12146's ceiling work moved this reading. (Ledger files went 7 → 11 and rows 269 → 290 over the same window — the tree grew, the recognizer did not change.) Control reproduces; proceeding.

2. What changed: one word, two constructs

Before this card REGISTRAR_FILE_RE, its docblock and its --self-test all meant the file that registers the route, while the widening the card measured admits packages/spec Zod contract declarations, which register nothing. Per the ruling the rename is a condition of admitting them, not a tidy-up after.

A route source is a file whose source declares a route, of two kinds:

kind admitted by count
registration call site CALL_SITE_FILE_RE, the existing filename convention — unchanged 12
spec contract declaration evidence: a non-test packages/**/*.ts whose masked source declares a route beside the HTTP method it answers 5

Renamed throughout: REGISTRAR_FILE_RECALL_SITE_FILE_RE, parseRegistrarSourceparseRouteSource, REGISTRAR_HANDLER_WINDOWROUTE_SOURCE_HANDLER_WINDOW, registrarByTailrouteSourceByTail, the no-in-repo-registrar cause → no-in-repo-declaration, and the user-visible route anchor provenance string. One forced caller change, declared: scripts/docs-audit/README.md documents this concept and would otherwise have kept the retired word with the retired meaning — the exact "one term, two things" the ruling forbids.

3. The guard is a signal, not a file name

The ruling allowed "a runtime-registration signal or an explicit, self-tested exclusion" for packages/spec/src/conversions/registry.ts. This implements the signal: a route declaration names the HTTP verb it serves; a data payload carrying a path: key does not.

Measured on 460134af8 — of registry.ts's 61 literal path: sites (its /api/v1/health is a connector-action input inside an automation fixture), zero carry an HTTP method; the five contract declarations carry one at 61 of their 62 sites. The 62nd has its method: five lines but zero properties up, behind a JSDoc that maskComments blanks — the lookaround skips blank lines for exactly that reason. The separation is total at every lookaround from 1 to 6, so the constant is a margin, not a threshold. A name-based exclusion would have kept one file out and taught the recognizer nothing; the signal keeps out its whole class.

The fixture and the benchmark are not re-excluded by name. isTestFile grew those arms in #12965, so the walk never offers them to either kind — pinned in --self-test over the two real repo paths, per the ruling.

4. Before/after ledger table (re-measured)

node scripts/docs-audit/affected-docs.mjs --bridge-coverage on 460134af8f0dd5ab79:

reading before after
route sources scanned 12 17 (12 call sites + 5 contract declarations)
route tails produced 44 78
client-bound rows reachable 47 of 219 61 of 219
discovery-gap rows 14 0
storage ledger unreachable 7 of 7 0 of 7 — closed
i18n ledger unreachable 3 of 3 1 of 3
rest ledger unreachable 42 of 84 40 of 84
runtime ledger unreachable 64 of 69 61 of 69
plugin-auth unreachable 56 of 56 56 of 56 — unchanged; declared upstream, catch-all-mounted

No reach regression is possible by construction: selectsFrom is a some() over the tail list, so added tails can only add selections. discovery-gap → 0 is the load-bearing reading — every row the ceiling said a widened discovery could reach is now actually reached.

One narrowing, declared: route ledgers are excluded from kind (a). Most ledger names match the call-site convention (route-ledger.ts carries route), so every ledger used to be parsed as a registrar too. All 11 live ledgers declare zero route tails, so this moved nothing — asserted live in --self-test rather than argued, so it reds the day one starts.

5. Do the newly reachable rows actually nominate docs? (reported, not required to fix)

Tried on a real diff: bd4aa4e49 ("name the terminally-failed-but-repairable run on AutomationResult.status as 'stranded'", #14636), which touches packages/spec/src/api/automation-api.zod.ts — a newly admitted contract declaration.

before after
pages nominated 3 3 — unchanged
anchors 3 (2 symbol, 0 route, 1 literal) 4 (2 symbol, 1 route, 1 literal)
advisory reach line 47 of 219 61 of 219

The new anchor is route /api/automation/:name/trigger, "bridged from symbol TriggerFlowResponseSchema" — produced through the newly admitted declaration, and absent before.

Reach is necessary, not sufficient, and this diff shows exactly that. The evidence route did fire on a real change and produced a route anchor the run did not previously have; every page that anchor selects was already nominated by other anchors, so the page set did not move. On this diff the gain is in the advisory's stated reach and in anchor provenance, not in the doc list. A diff whose only bridgeable symbol lives in a contract declaration is where the page set should move; this one had two other symbol anchors carrying the same pages.

Verification

  • --self-test: 545 → 568 cases, roster 28 → 29 batteries, SELF_TEST_BATTERY_FLOOR 28 → 29, new battery floored at 23. The verdict handshake is unchanged; the new cases register through the existing battery() helper.
  • Derived gate family (node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack) — 23 commands, all rc=0, re-derived on the final head f0dd5ab79 and identical to the first derivation; plus the always-runs tail (check:pm-dispatch-gates, check:self-test-wired, check:declared-population-live, check:ratchet-remedy-authority, check:nul-bytes) all rc=0. The script's own suite — --self-test, --bridge-coverage, normal mode on a real diff, check-affected-docs.mjs, check-drift-comment.mjs, check:docs-audit-scope — all rc=0.
  • Ablation (a) — guard removed (requireMethodSignal: false): registry.ts is admitted (contract declarations 5 → 6, tails 78 → 79) and --self-test names it by path.
  • Ablation (b) — evidence route disabled: reach falls back to exactly the call-site set — 12 sources / 44 tails, the control — and --self-test names the missing kind.
    • ⚠️ It did not, first time round: the shape pin over the contract population was an every() that passes vacuously on an empty list, so the whole battery stayed green with kind (b) switched off. Fixed by flooring the population size (commit fa12687d1) before the ablation was re-run; that is the only reason the pin exists.
    • Both ablations ran under an EXIT INT TERM trap with absolute paths, proved the mutation landed on disk by blob hash before reading any result, and restored to blob-hash equality with HEAD afterwards. One mutation attempt matched zero anchors and was caught by that check rather than being read as a clean ablation. No rebuild leg applies — this script is run directly as .mjs, with no dist/.
  • Lint, narrowed and measured: eslint --no-inline-config over the changed files — 0 errors. The narrowing is safe by three readings: the population comes from eslint's own config (it reports the .md as "ignored because no matching configuration was supplied", identically on origin/main), the count comes from --format json (2 entries, 1 linted), and type-aware linting is not enabled (no parserOptions.project), so this diff cannot move any untouched file's verdict.
  • Control-byte scan clean on both edited files and on every body posted.

Figure restatement posted per the ruling: #10534 · #9572. Both read back byte-identical.

skip-changeset: this publishes nothing from any package (scripts/** only). Label applied and read back.


🤖 Generated with Claude Code

https://claude.ai/code/session_012zGPuVVX3deAx9LdjK8jCk


Generated by Claude Code

… declarations

Rename the "registrar" concept in affected-docs.mjs to a route SOURCE with two
kinds -- a registration call site (the existing filename convention) and a spec
contract declaration (admitted by evidence) -- and implement the evidence route
behind a runtime-registration guard.

The guard is the HTTP method the declaration answers on, read beside the path,
rather than a hand-kept exclusion list: it keeps out the whole class of data
payloads that merely carry a `path:` key, of which the connector-action input in
packages/spec/src/conversions/registry.ts is today's only instance.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012zGPuVVX3deAx9LdjK8jCk
@github-actions github-actions Bot added the size/m label Sep 4, 2026
@github-actions

github-actions Bot commented Sep 4, 2026

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 0 changed package(s)), so this run has no opinion about the docs.

What this run could not see
  • 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 — 0 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 4dd5041bd0beba5a34458dd9b583121a2985409cpackageMentionDocs.

@claude claude Bot added the skip-changeset PR has no user-facing published change; bypasses the changeset gate label Sep 4, 2026
…n them

Adds the #11857 self-test battery (21 cases): the guard admits a contract
declaring its verb beside its path, declines a connector-action input, and the
counterfactual proves the parser reads that payload's tail so the guard is what
declines it. Live pins for the real registry.ts, the five admitted declarations,
and the measured no-op of excluding route ledgers from kind (a).

Roster 28 -> 29 batteries, floor raised with the cases.
Renames the concept in scripts/docs-audit/README.md to match the script.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012zGPuVVX3deAx9LdjK8jCk
@github-actions github-actions Bot added size/l documentation Improvements or additions to documentation and removed size/m labels Sep 4, 2026
…annot pass vacuously

Ablation B (evidence route disabled) left the battery green: `every()` over an
empty list is true, so the pin that says WHAT kind (b) admits said nothing about
WHETHER it admits. Adds the size floor and names the declaration the storage
ledger's closure rests on.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012zGPuVVX3deAx9LdjK8jCk
The `route` anchor's own `from` note still read "its registrar handler names
it" -- the one place the old word reached a user-visible string rather than a
comment. Found by running the tool in normal mode on a real diff.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012zGPuVVX3deAx9LdjK8jCk
@baozhoutao
baozhoutao marked this pull request as ready for review September 4, 2026 08:44
@baozhoutao
baozhoutao enabled auto-merge September 4, 2026 08:44
@baozhoutao
baozhoutao added this pull request to the merge queue Sep 4, 2026
Merged via the queue into main with commit e77635b Sep 4, 2026
34 of 36 checks passed
@baozhoutao
baozhoutao deleted the claude/issue-11857-route-source-evidence branch September 4, 2026 09:25
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation size/l skip-changeset PR has no user-facing published change; bypasses the changeset gate

Projects

None yet

2 participants