Repository navigation
[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
Description
Origin: surfaced by the os-dev run for #12215 (repointing the type/body/method anchors in packages/spec/liveness/action.json) while spot-checking the sibling entries that PR #12214 repointed the day before. Filed unassigned, recording only — awaiting triage.
The gap
Three checks now bound a live entry's evidence citation, and each closed the previous one's blind spot:
- the cited file must exist (
check:liveness的 stale-evidence 检测只 ⚠ 不判红 —— ADR-0087 证据路径可以静默腐烂,且摘要行的 “N resolved” 数的是路径总数不是解析成功数 #5623) - a cited line must be inside that file ([finding] check:liveness validates an evidence citation's FILE but never its line, so a moved consumer rots silently — permission.tabPermissions cites hono-plugin.ts:1200 in a 718-line file that no longer mentions the key #11210)
- the cited file must name the key ([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)
A fourth case satisfies all three and is still wrong: the cited line is in range, the file does name the key, and the consumer has simply moved to a different line of the same file. #11210's own guidance names this ("a consumer that moved WITHIN the file it is cited to leaves every line in range"), and #11457 answered the no-line-at-all form of it — but a citation that carries a specific, now-incorrect line is caught by neither.
Measured, on origin/main @ 7b95536
Both entries below were repointed by PR #12214 on 2026-08-25 with fresh line numbers, and both had drifted by 2026-08-26:
| entry | cited | what is actually there now | the real consumer |
|---|---|---|---|
action.target |
packages/runtime/src/action-execution.ts:725 — "type:'flow' server dispatch — automation.execute(action.target, …)", with :718 "rejecting an unknown flow name by that same value" |
:716-726 is the FlowActionRefusal brand assignment and the recognition-predicate docblock — no automation.execute, no flow-name rejection |
automation.execute(action.target, { is at :809, inside dispatchFlowAction (declared :780) |
action.requiredPermissions |
packages/runtime/src/domains/actions.ts:172-180 — "ADR-0066 D4 dual-surface gate — resolves the action's declared requiredPermissions and rejects 403" |
:172-180 is path-part parsing (parts[1]/parts[2]) and the invalidBody validation-failure constructor |
the requiredPermissions reads are at :393-400, :473 and :526 |
pnpm --filter @objectstack/spec check:liveness is green on both, and says so explicitly: every 'path:NNN' citation names a line that file actually has and every cited file names the property it is evidence for. It is telling the exact truth; the pointers are still wrong.
Note the shape: the more precisely an entry is cited, the faster it rots. action-execution.ts is 1670 lines and actively edited, so a citation pinned to a line there has a short half-life — the two repaired entries drifted within roughly one day of being written, which is a stronger drift rate than the rot #12214 was repairing.
Why this is worth a card rather than a shrug
The ledger's whole contract is that a live verdict is its evidence pointer. A pointer that resolves to real code which is not the consumer is worse than a missing one: it reads as verified, and the next reader who spot-checks it finds plausible code at the address and moves on. That is the same failure the ledger README spends its length warning about, one level in.
Possible directions (not adjudicated — for triage)
- A symbol anchor instead of / beside the line. Cite
dispatchFlowActionand let the check locate it, so the pointer survives movement within the file. Costs a symbol-resolution step; kills the whole class. - A drift warning keyed on line churn: flag a
verifiedAtcitation whose cited file has been modified since that date. Cheap, no parsing, but noisy on hot files. - Accept it and shorten the clock for entries citing hot files — the weakest option, since nothing measures "hot".
- Explicitly do nothing: rule that a line is a convenience and the file+key anchoring is the contract. Legitimate, but then the line numbers should probably stop being written as if they were load-bearing.
Scope note
Deliberately NOT folded into the #12215 PR: that card's accept set is the type/body/method entries, and these two are different entries whose citations came from an already-merged PR. #12215's own repoint writes line numbers too, so it inherits this same exposure and is not a fix for it.
Dedup (searched 2026-08-26)
#11210 (closed, built the line bound) and #11457 (closed, built the key-mention check) each name this residual in passing but neither addresses it; #7133 / #7142 (closed) are cross-repo citation repair; #12462 (open) is about the shrink-only baseline being pinned non-empty, not about citation accuracy. No open card covers in-range line drift.
GET /repos/objectstack-ai/objectstack/issues → HTTP 403), so the REST-first dedup rule could not be followed; this search used the MCP semantic search plus a local grep instead.
Generated by Claude Code
Activity
os-support-ai commentedon Aug 26, 2026
Triage: lands in the
packages/specliveness-ledger toolchain (check:liveness+liveness/*.jsoncitation contract) →domain:spec,pm:queue, type Task — graded at first touch rather than left as a bare finding: the gap is real, measured onorigin/main(two entries repointed 2026-08-25 drifted within one day, all checks honestly green), and it degrades the ledger's core contract that aliveverdict IS its evidence pointer.Not escalated to the decision inbox: no product semantics or public contract shape at stake — this is internal gate design, which is lane discretion. The four directions in the card are the dispatch's option space; the symbol-anchor route kills the class and the
verifiedAt-vs-file-mtime drift warning is the cheap floor, but choosing (or ruling "lines are convenience, stop writing them as load-bearing") belongs to the spec seat's dispatch judgment, not the maintainer.Size/model suggestion: M — gate-semantics design per the tiering table (judgment tier at minimum; the spec seat may weigh the fable gate-design escalation if the symbol-anchor route is taken).
Dedup confirmed as the card states: #11210 / #11457 closed the adjacent forms and each names this residual without covering it; no open card covers in-range line drift.
Generated by Claude Code
feat(spec): liveness citations can anchor the consuming symbol — path…
chore(spec): re-anchor action + object liveness citations to consumin…
chore(spec): re-anchor five liveness ledgers to consuming symbols (ob…
chore(spec): re-anchor six liveness ledgers to consuming symbols (obj…
chore(spec): re-anchor the closing twelve liveness ledgers to consumi…