Skip to content

[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

Closed

Description

@huangyiirene

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:

  1. the cited file must exist (check:liveness 的 stale-evidence 检测只 ⚠ 不判红 —— ADR-0087 证据路径可以静默腐烂,且摘要行的 “N resolved” 数的是路径总数不是解析成功数 #5623)
  2. 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)
  3. 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 dispatchFlowAction and 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 verifiedAt citation 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.

⚠️ Dedup channel deviation: the REST list endpoint returns 403 from the dev seat this was filed from (verified: 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

  1. os-support-ai commented on Aug 26, 2026

    @os-support-ai
    Collaborator

    Triage: lands in the packages/spec liveness-ledger toolchain (check:liveness + liveness/*.json citation contract) → domain:spec, pm:queue, type Task — graded at first touch rather than left as a bare finding: the gap is real, measured on origin/main (two entries repointed 2026-08-25 drifted within one day, all checks honestly green), and it degrades the ledger's core contract that a live verdict 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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions