Skip to content

feat(spec): liveness citations can anchor the consuming symbol — path#symbol survives in-range line drift - #13004

Merged
os-trump merged 2 commits into
mainfrom
claude/issue-12516-liveness-in-range-drift
Aug 28, 2026
Merged

feat(spec): liveness citations can anchor the consuming symbol — path#symbol survives in-range line drift#13004
os-trump merged 2 commits into
mainfrom
claude/issue-12516-liveness-in-range-drift

Conversation

@os-trump

Copy link
Copy Markdown
Collaborator

Fixes#12516

What this delivers

A liveness citation could rot in range: the cited file exists, the cited line is inside it, the file names the key — and the consumer has moved to a different line of the same file, so check:liveness stays green while the pointer is wrong. Measured on the card: both action.json entries repointed with fresh line numbers by PR #12214 on 2026-08-25 had drifted this way by 2026-08-26.

This PR adds a symbol anchor to the citation grammar — packages/path/file.ts#dispatchFlowAction, the proof-ref # convention applied to evidence/producer citations — and re-anchors the two measured-rotted entries. A symbol moves with the consumer, so the anchor survives exactly the movement that rots a line; when the consumer is renamed or deleted the symbol is gone and the gate goes red — a direction a stale line can never produce. A line may still ride along (file.ts#symbol:150, either order): it stays a bounded human convenience; the symbol is the load-bearing half. The ledger README now documents the anchored form as the preferred citation (the option-D honest consequence, adopted alongside the mechanism: line numbers stop being presented as load-bearing).

Route decision (per the PM route ruling — decided by measurement)

  • Symbol anchor, text-level, opt-in per citation. The check already reads every cited file's text (the key-mention check), so anchor resolution is a word-bounded (identifier-bounded, $-aware) match on content the gate already loads — no TypeScript program, no new dependency, no measurable CI cost (check:liveness held the shared verify lock 2s on this tree).
  • No ledger schema change at all. The anchor lives inside the existing evidence/producer string, so PM mechanism-assumption 3's migration question dissolves: adoption is per-citation, and only the two repaired entries adopt here.
  • Census before switch-on (the [finding] A liveness citation can rot WITHIN its file — 14 measured candidates the new line bound structurally cannot see, incl. permission.objects.allowExport citing a symbol that moved repos-internally #11457 discipline): zero pre-existing path#symbol-shaped tokens across every ledger evidence/producer string and the empty-state registry — the check starts at a zero population and is red from day one (the orphan-proof zero-census precedent). A malformed anchor (not one identifier) also fails, the verifiedAt bad-date asymmetry one field over: parsed-to-nothing must be loud, not a silently dropped standard.
  • The line-window alternative was measured and rejected: requiring the key within a window of the cited line misses 173/298 live line citations at W=10, 143 at W=20, 117 at W=40 on the current tree — a population indistinguishable from matcher noise without re-measuring every entry, i.e. the 48-of-227 era the evidence parser's header warns about. Filed as follow-up [worklist] Migrate liveness line citations to symbol anchors — census: 117-173 of 298 live line-cited pairs fail key-proximity at line granularity today #13003 rather than switched on.
  • The verifiedAt-vs-mtime floor was considered and declined: file mtime is checkout-time in CI (git does not preserve mtimes), and a git-log-based freshness signal needs per-file subprocess calls plus is noisy on hot files by the card's own admission. The anchor mechanism supersedes it for adopters.

Regression pair (hard requirement 2) — predicted directions, on-disk proof

The measured rot shape is "the consumer moved while file+line+key checks all stay green". Replayed as a mutate/restore ablation on this tree (no build leg applies: the gate runs from .mts source via tsx and reads the cited file as text — nothing resolves through dist/; the mutation was proven on disk by anchored grep counts instead):

  • Mutation: perl -pi rename of dispatchFlowAction to dispatchFlowActionMoved in packages/runtime/src/action-execution.ts; disk proof: old spelling 4 to 0, new spelling 0 to 4.
  • Leg A (anchored ledger, mutated tree — predicted RED): exit 1, symbol anchors: 5 pointer(s) ... 4 naming a symbol the cited file contains, 1 UNRESOLVED, heading 1 anchored citation(s) name a symbol the cited file does not contain naming action/target.
  • Leg B (the entry's 2026-08-25 BEFORE-state line citation, same mutated tree — predicted GREEN): exit 0, line citations: 302 pointer(s) ... 302 inside the cited file — the line citation is blind to the very movement the anchor catches.
  • Restore: git checkout HEAD -- FILE (absolute path, trap-guarded); proof: git diff HEAD empty and worktree blob hash 20ce1f47b56... byte-identical to the HEAD blob.
  • The committed self-test (check-liveness.test.ts, --ledger-root copies, real gate spawned) pins the same pair permanently: symbol-gone RED, malformed RED, before-state GREEN (the honest residual, pinned so the red case stays attributable), and the two-number summary line with a non-vacuity floor.

Honest residual, stated: a bare path:NNN citation that drifts in range remains green (text cannot tell "the consumer" from "plausible code at the address"), and a symbol that survives while its body stops reading the key is out of reach at text level — the file-level key-mention check remains that case's backstop.

The two entries now point at the truth (hard requirement 1)

  • action.targetaction-execution.ts#dispatchFlowAction (contains automation.execute(action.target, ...) and the flowIsUnknown rejection by the same value) + #headlessActionTypeError (reads action.target for the client-dispatched refusal text). Re-verified on this ref; verifiedAt: 2026-08-28.
  • action.requiredPermissionsaction-execution.ts#actionPermissionError (the single-sourced read of actionDef.requiredPermissions) + domains/actions.ts#handleActionsRequest (the ADR-0066 D4 call site, 403) + #resolveRouteActionDeclaration. The card's own measured lines (actions.ts:393-400/473/526) are comment mentions; the code read is actionPermissionError — the anchor cites the code. verifiedAt: 2026-08-28.

Path-leg declaration and changeset

  • The diff touches only packages/spec/scripts/liveness/** (gate + tests) and packages/spec/liveness/** (two ledger entries + README prose). No packages/spec/src/** file is in the diff — the clause-② path leg does not fire.
  • No changeset: internal gate tooling + ledger governance data; nothing user-visible releases. (liveness/ is in the spec package files array, but ledger JSON is governance data, not behavior.) Per the dispatch, the skip-changeset decision is recorded here for the PM; the label has been applied per the repo's standing labeling rule.

Gates (all quoted from each gate's own verdict line, exit codes captured before any pipe)

At 93ce7444c (pre-merge) and re-run at 385b24ec7 (after merging same-day main, which touched sibling key-mention.test.ts):

  • check:liveness green — symbol anchors: 5 pointer(s) written path#symbol, 5 naming a symbol the cited file contains; RED on the regression legs above.
  • Twin suites: vitest run on evidence.test.ts, check-liveness.test.ts, key-mention.test.ts, empty-state.test.tsTest Files 4 passed, Tests 124 passed (verbose run confirms all 17 new [finding] A liveness citation can rot IN RANGE: action.target and action.requiredPermissions point at the wrong lines one day after being repointed, and every check stays green #12516 cases executed).
  • check:scripts-typecheck green (tsc --listFiles confirms both edited .mts and both edited .test.ts files are in that program — tsconfig.test.json stops at src/**, so scripts-typecheck is the program that measures these files, verified not assumed).
  • Derived union (dispatch-gates.mjs, no args, stderr line confirms derivation from this repo at this commit): cross-package-test-inputs, merge-driver, objectql-double-limit, page-declaration-shape, pm-governed-merges, published-files, slot-lookup, test-source-alias, type-source-resolution, ci-filter-parity, comment-mask-adoption, query-options-erasure, engine-double-contract, where-matcher, bare-root-worklist self-test, pm-dispatch-gates, type-check-coverage, empty-state, strictness-ledger, variant-docs, affected-docs, nul-bytes — all green at 385b24ec7.
  • check:type-check-debtdeclared narrowing (not run locally; CI runs it in full): the re-measure re-runs tsc per DEBT/TEST_DEBT ledger entry; the only changed TS files live in packages/spec/scripts/liveness/ whose governing program (tsconfig.scripts.json, include scripts/**/*) compiles green, and spec's TEST_DEBT program (tsconfig.test.json, include src/**/*) contains 0 of the changed files by --listFiles count — no debt entry's measured population contains any changed file, so the ratchet cannot move from this diff.
  • docs-audit/check-drift-comment.mjs is CI-owned (needs PR context).

Scope and follow-up

Per the dispatch: the mechanism + the two entries only — no sweep of other citations. #13003 is the follow-up migration card (anchor adoption for the remaining ~298 line-cited pairs, carrying the census numbers above as its evidence); #13003 remains open and is not addressed here.


Generated by Claude Code

…#symbol survives in-range line drift
A line citation rots IN RANGE: the consumer moves within its cited file,
the file exists, the line is inside it, the file names the key — all three
citation checks stay green and the pointer is wrong. Measured on the two
action.json entries PR #12214 repointed 2026-08-25: both drifted within a
day. A path#symbol anchor moves WITH the consumer; when the consumer is
renamed or deleted the gate goes red — a direction a stale line cannot
produce. Census before switch-on: zero pre-existing path#symbol tokens, so
the check starts green (the orphan-proof zero-census precedent), and the
line-window detector alternative was censused at 117-173/298 misses —
unshippable noise (the 48-of-227 lesson). Both rotted entries re-anchored.
Part of #12516
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LpRNHxWZgSUgVnFT9mQQo4
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

⚠️2 changed file(s) yielded no anchor (packages/spec/liveness/README.md, packages/spec/liveness/action.json), so the pages documenting them are NOT COVERED by this run — this is not a clean bill of health for those files. Nothing else in this diff resolved to a documentable surface (no symbol, route or SDK anchor derived from 1 changed package(s)).

What this run could not see
  • 2 changed file(s) yielded no anchor (packages/spec/liveness/README.md, packages/spec/liveness/action.json) — pages documenting those are invisible to this run
  • 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 feab4bff9d0c01e0dbf7db52f5b6e57eb3b03f2bpackageMentionDocs.

@github-actionsgithub-actionsBot added documentation Improvements or additions to documentation tests labels Aug 28, 2026
@os-trumpos-trump added the skip-changeset PR has no user-facing published change; bypasses the changeset gate label Aug 28, 2026 — with Claude
@os-trump
os-trump marked this pull request as ready for review August 28, 2026 14:32
@os-trump
os-trump enabled auto-merge August 28, 2026 14:33
@os-trump
os-trump added this pull request to the merge queueAug 28, 2026
Merged via the queue into main with commit 4f14859Aug 28, 2026
38 checks passed
@os-trump
os-trump deleted the claude/issue-12516-liveness-in-range-drift branch August 28, 2026 14:54
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentationImprovements or additions to documentationsize/lskip-changesetPR has no user-facing published change; bypasses the changeset gateteststooling

Projects

None yet

2 participants

@os-trump@claude