Uh oh!
There was an error while loading. Please reload this page.
Bound a liveness evidence citation's line, not just its file - #11449
Merged
Conversation
check:liveness resolved a `path/to/file.ts:NNN` pointer with existsSync on the path alone. The parser did not even retain the `:NNN` — bareToken() stripped it before classification — so nothing downstream could bound it. A consumer that moved out of a file which still EXISTS therefore kept a passing pointer, was counted under the word "resolved", and left its entry reading as freshly verified. That is worse than a missing file: a dead citation with a precise line survives review because it looks measured, and the next agent re-verifying the entry follows it, finds nothing, and rebuilds the call graph by hand. - evidence.mts retains the line (`localCitations`, deduped on path:line so a file cited at three call sites contributes three), and `checkCitationLines` bounds each one. A range `:12-34` is bounded by its END — a tail past EOF overruns the file even when its head is inside. - The bound is a separate function, not a third parameter on checkEvidence: an optional parameter a caller omits is a silent opt-out, and empty-state.mts shares that resolver without having measured its own citations this way. - check-liveness.mts applies it to `producer` as well as `evidence` — those already share one resolver by decision (#4837), and a standard applied to one and not the other leaves the weaker one as the place a rotted line hides. Zero producer violations today; the hole is closed before it opens. - The run prints citations checked beside citations in range, the #5623 two-number discipline: printing only the second reads as a pass on a run where the parser extracted nothing. Two shipped instances, both repaired, both measured: - permission.tabPermissions cited hono-plugin.ts:1200 in a 717-line file with zero `tabPermissions` occurrences. All THREE of its pointers were dead — one past EOF, and resolve-execution-context.ts:205 / rest-server.ts:1551 within bounds with zero occurrences each. Repointed at the real consumers in current-user-endpoints.ts (the /me/apps rank merge and hidden-drop, and the /auth/me/permissions projection) plus the resolution chain, verifiedAt stamped. Same code movement that #11209 repaired for the sibling entry. - mapping.fieldMapping cited :115-167 of a 164-line file. The consumer never moved; the file shrank under the citation. Part of #11210 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01T9cDbY2NBiVJWYx3BpWfH2
Contributor
📓 Docs Drift CheckNothing in this diff resolved to a documentable surface (no symbol, route or SDK anchor derived from 1 changed package(s)), so this run has no opinion about the docs. What this run could not see
Coarse fallback — 126 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): |
…veness-line-citation
…ckage files check:cross-package-test-inputs went red on the real scan (self-test 104/104 green throughout): evidence.test.ts named `packages/rest/src/import-mapping.ts` and `packages/plugins/plugin-hono-server/src/hono-plugin.ts`, and @objectstack/spec's declared globs cover neither package — so a change to either would not re-run spec's tests, which is the #7802 class the gate exists to stop. Fixed by removing the reads-that-are-not-reads rather than by widening the globs. These three cases assert the ARITHMETIC of the bound with the line count INJECTED (`const lines = (n) => () => n`) — they open no file, and the 717/164 are stub constants that do not track either real file. Declaring those two packages as spec test inputs would have asserted a dependency that does not exist: every hono-server and rest change would re-run spec's whole suite forever, and the frozen constants would read as if they tracked those files. That is the same declared-≠-actual defect the gate guards, pointed backwards. The coupling that IS real keeps its coverage: `check:liveness` bounds the shipped ledgers in CI, and the two contract tests at the bottom of this file walk the real ledgers and read every cited path — dynamically, out of the ledger data, so no glob could enumerate them and none is claimed. A comment on the describe block records why the paths are synthetic, so the next author does not "restore" the real ones and re-red the gate. Verified in-tree after merging origin/main: node scripts/check-cross-package-test-inputs.mjs --self-test -> exit 0, 104 cases node scripts/check-cross-package-test-inputs.mjs -> exit 0, "OK: 14 package(s) read outside themselves, all declared" vitest scripts/liveness/ -> 9 files, 213 tests pnpm --filter @objectstack/spec check:liveness -> exit 0, 293/293 pnpm --filter @objectstack/spec check:generated -> 14 artifacts current Part of #11210 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01T9cDbY2NBiVJWYx3BpWfH2
os-steve
marked this pull request as ready for review
August 23, 2026 21:16
Uh oh!
There was an error while loading. Please reload this page.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for freeto join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Part of #11210
check:livenessresolved apath/to/file.ts:NNNevidence pointer withexistsSyncon the path alone. The parser did not even retain the:NNN—bareToken()stripped it before classification — so nothing downstream could bound it. A consumer that moved out of a file which still exists therefore kept a passing pointer, was counted under the word "resolved", and left its ledger entry reading as freshly verified.A dead citation with a precise line is worse than a missing one: it survives review because it looks measured, and the next agent re-verifying the entry follows it, finds nothing, and rebuilds the call graph by hand — which is the cost #10959 actually paid.
What the gate now checks
evidence.mtsretains the line (localCitations), deduped onpath:linerather than on path, so a file cited at three call sites contributes three citations instead of one.:12-34is bounded by its END. A tail past EOF overruns the file even when its head is inside; a head past EOF implies a tail past EOF, so the end is both the stricter bound and the one that subsumes the other. This is what catches the second instance below.+-joined multi-consumer entry is bounded, not only the first — that concatenated form is the house style for a property with several consumers, and seeing only its head would leave the rest of each chain exactly as unfalsifiable as before. Pinned in both the unit tests and the gate self-test.produceris bounded too, not justevidence. Those two already resolve through one resolver by decision (liveness 台账把「消费端存在读取代码」当作 live 的证据,会漏掉「没有任何生产者传值」的死键(Seed.env 即如此) #4837: a call-site claim must not get a weaker standard than the consumer pointer it completes), and a bound applied to one and not the other leaves the weaker one as the place a rotted line goes to hide. Zero producer violations today (5 producer citations, all in range) — the hole is closed before it opens.objectui: …/cloud: …/packages/services/service-ai/…are counted, never resolved, and now never bounded either — those files are legitimately absent here, and bounding them would make the gate unsatisfiable for every renderer-side property. Pinned by a self-test case.check:liveness的 stale-evidence 检测只 ⚠ 不判红 —— ADR-0087 证据路径可以静默腐烂,且摘要行的 “N resolved” 数的是路径总数不是解析成功数 #5623 discipline one level down: the run prints how many citations it checked beside how many are in range, so a parser that degraded to extracting nothing cannot read as a pass.✗, not⚠, from the start. #5623's evidence check earned its warning phase because the pre-#3857 parser flagged 48 of 227 entries at a ~100% false-positive rate; a line past EOF is arithmetic, and both shipped instances are real.The measurement
Over the post-#11209 ledgers, using the gate's own parser:
liveevidence citations writtenpath:NNNproducercitations writtenpath:NNNThe card's figure was 252; 284 is what the gate's parser actually sees, because it counts every citation in a multi-consumer entry rather than the head of each entry. No ratchet baseline was needed: the standing set is two instances, and repairing both is smaller and less durable debt than a shrink-only file.
The two repairs, measured
permission.tabPermissions— all three of its pointers were dead, in two different ways:plugin-hono-server/src/hono-plugin.ts:1200— past EOF (the file is 717 lines) and zerotabPermissionsoccurrences in it. Same code movement that killed the siblingsystemPermissionspointer, repaired in chore(spec): adopt the adjudicated proof→ledger bindings, and correct the stale systemPermissions note #11209.runtime/src/security/resolve-execution-context.ts:205andrest/src/rest-server.ts:1551— dead within bounds: both files are long enough for the cited line and neither mentionstabPermissionsanywhere (0 occurrences, measured). The handler's own comment atcurrent-user-endpoints.ts:922-926records why —resolveCtx()carries neithersystemPermissionsnortabPermissions, so/me/appsre-resolves the sets itself.Repointed at the real consumers, read rather than inferred: the
/me/appsrank table (:947), the most-visible-wins merge across resolved sets (:952-958), the hidden-drop that runs before the fail-open branch (:963), the/auth/me/permissionshalf (:772,:862), and the resolution chain (permission-set-projection.ts:287,assemble-execution-context.ts:328).verifiedAtstamped per the gate's own remedy text. The verdict is unchanged and was never in question —live, bound proof, behaviour still ships; what was dead is the citation.mapping.fieldMapping— cited:115-167of a 164-line file. The consumer never moved; the file shrank under the citation. Repointed to the measured spans (:95-102javascript rejection,:112-164applyMappingToRows), and the three inline line refs in its own note, off by the same three lines, corrected with them. Reported here rather than silently: this is a second instance the dispatch did not name, found by the new check and repaired because it is the same mechanical class.Why no key-occurrence WARN in this PR
The card offers an optional second signal — warn when the cited file contains no occurrence of the property's own key — to catch the move-within-bounds case a line bound structurally cannot. I measured it rather than guessing, and it is not a one-line addition:
evidence.mts's own header records what that costs: the 48-of-227 era made a warning nobody read, and the one real rot it was burying sat unnoticed.email_template.bodyHtmlis read asbody_html,permission.managedByasmanaged_by. That entry's own evidence string even writes the mapping out —(bodyHtml→body_html). At least 5 of the 14 are exactly this.permission.objects.allowExportciteshono-plugin.ts (annotateEffectiveApiOperations …)and that symbol now lives incurrent-user-endpoints.ts— the same movement as this card's named instance, and invisible to any line bound because that citation carries no line at all.So: ship the arithmetic bound as a hard failure, and file the census as a finding rather than bolt a noisy warning onto a tightening. The
✗guidance text says this in-repo so the next reader is told why the obvious detector is not obviously right.Verification
pnpm --filter @objectstack/spec check:liveness— red before the repairs, naming exactly the two instances and their real file lengths; green after,293 pointer(s) written path:NNN, 293 inside the cited file.--ledger-rooton a copy so no repo file is mutated: a fabricated past-EOF citation on a file that exists reds with✗ 1; a rotted line in the tail of a+-joined entry reds; the same line under anobjectui:marker stays green.wc -lboundary (>not>=), multi-citation retention, per-path dedupe, and the "cannot read the file → say nothing, the existence check owns that verdict" case.packages/spec/liveness/**is in the package'sfilesarray, so the ledger repair ships in the tarball — this takes a changeset (patch,@objectstack/spec) rather than theskip-changesetlabel.Generated by Claude Code