You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Bound a liveness evidence citation's line, not just its file (#11449)
* Bound a liveness evidence citation's LINE, not just its file
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
* Use fixture paths in the citation-arithmetic tests, not real cross-package 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
---------
Co-authored-by: Claude <noreply@anthropic.com>
"note": "the pipeline itself: source/target/transform/params all consumed. none/constant/map/split/join applied in applyMappingToRows (`params.separator` :124, `.value` :132, `.valueMap` :137); `lookup` copies through for the pipeline's metaMap reference resolution; `javascript` is REJECTED with a 400 (no server sandbox — implement-or-reject-loudly). SUB-WALK BOUNDARY, resolved: `params`' lookup-specific keys (`object`/`fromField`/`toField`/`autoCreate`) were read by nothing — reference resolution comes from the target object's own field definitions, not from these — and were REMOVED in the 17.x line (#10329, ADR-0049 enforce-or-remove; strict deletion, so nothing dead remains one level below the drill). Every spelling — the four canonical keys and their eleven ex-aliases — now lands on a guidance prescription at parse; the mapping-lookup-params-removed conversion (protocol 18) strips them from stored sources. The surviving params keys (`value`/`valueMap`/`separator`) are all consumed by applyMappingToRows."
"note": "Lines re-measured 2026-08-23 (#11210): the second range ended at :167 in a 164-line file — the file shrank under the citation and no gate could see it, since the FILE still resolved. The consumer never moved: the javascript rejection is the loop at :95-102 (was :98-105) and the pipeline is applyMappingToRows at :112-164 (was :115-167); the three inline line refs below were off by the same three lines and are corrected with them. the pipeline itself: source/target/transform/params all consumed. none/constant/map/split/join applied in applyMappingToRows (`params.separator` :121, `.value` :129, `.valueMap` :134); `lookup` copies through for the pipeline's metaMap reference resolution; `javascript` is REJECTED with a 400 (no server sandbox — implement-or-reject-loudly). SUB-WALK BOUNDARY, resolved: `params`' lookup-specific keys (`object`/`fromField`/`toField`/`autoCreate`) were read by nothing — reference resolution comes from the target object's own field definitions, not from these — and were REMOVED in the 17.x line (#10329, ADR-0049 enforce-or-remove; strict deletion, so nothing dead remains one level below the drill). Every spelling — the four canonical keys and their eleven ex-aliases — now lands on a guidance prescription at parse; the mapping-lookup-params-removed conversion (protocol 18) strips them from stored sources. The surviving params keys (`value`/`valueMap`/`separator`) are all consumed by applyMappingToRows."
"evidence": "packages/plugins/plugin-hono-server/src/hono-plugin.ts:1200 (tabRank merges all four values — hidden/default_off/default_on/visible — across resolved sets, most-visible wins; app hidden ⇒ dropped from /me/apps) + packages/runtime/src/security/resolve-execution-context.ts:205 + packages/rest/src/rest-server.ts:1551 (projection)",
157
-
"note": "Re-verified 2026-07-30 — the previous note ('only hidden is read') UNDERSTATED it: the rank merge reads every value. Proof BOUND the same day after the dogfood test was extended to actually AUTHOR the property on a permission set (it had only mentioned tabPermissions in its header): hidden drops the app from /me/apps, a more-visible sibling grant wins it back."
156
+
"evidence": "packages/plugins/plugin-hono-server/src/current-user-endpoints.ts:947 (tabRank ranks all four values — hidden 0 < default_off 1 < default_on 2 < visible 3) + packages/plugins/plugin-hono-server/src/current-user-endpoints.ts:952-958 (the merge across the caller's resolved permission sets, most-visible wins; an unrecognised value is skipped rather than defaulted) + packages/plugins/plugin-hono-server/src/current-user-endpoints.ts:963 (the /me/apps drop — tabs[app.name] === 'hidden' ⇒ the app is not returned, and this filter runs BEFORE the fail-open branch, so it survives a missing SecurityPlugin) + packages/plugins/plugin-hono-server/src/current-user-endpoints.ts:772 (the same rank table for the /auth/me/permissions half) + packages/plugins/plugin-hono-server/src/current-user-endpoints.ts:862 (projected onto that response) + packages/plugins/plugin-security/src/permission-set-projection.ts:287 (the sys_permission_set.tab_permissions column parsed onto the resolved set) + packages/core/src/security/assemble-execution-context.ts:328 (onto ExecutionContext.tabPermissions)",
157
+
"note": "Re-verified 2026-08-23 (#11210) and every pointer REPLACED — all three of the previous ones were dead, in two different ways, and the gate could see neither. (a) `plugin-hono-server/src/hono-plugin.ts:1200` was past EOF: that file is 717 lines and contains no `tabPermissions` reference at all — the same code movement that killed this entry's sibling `systemPermissions` pointer (#10959/#11209) moved /me/apps and /auth/me/permissions into `current-user-endpoints.ts`. (b) `runtime/src/security/resolve-execution-context.ts:205` and `rest/src/rest-server.ts:1551` were dead WITHIN bounds — both files are long enough for the cited line, and neither mentions `tabPermissions` anywhere (0 occurrences, measured); the handler's own comment at current-user-endpoints.ts:922-926 records why, namely that resolveCtx() carries neither systemPermissions nor tabPermissions, so /me/apps re-resolves the sets itself. THE VERDICT IS UNCHANGED and was never in question — `live` with a bound dogfood proof, and the behaviour still ships; what was dead is the citation, which is exactly the rot #11210's line bound now makes impossible to leave behind silently. The 2026-07-30 reading survives the re-measurement verbatim: the rank merge reads all four values, not only 'hidden'. Two consumers, deliberately both cited: /me/apps ENFORCES (a hidden app is dropped), /auth/me/permissions PROJECTS the merged map for the renderer."
0 commit comments