Skip to content

feat(spec): make a liveness citation name the property it is evidence for - #12214

Merged
os-litant merged 2 commits into
mainfrom
claude/issue-11457-liveness-within-file-rot
Aug 25, 2026
Merged

feat(spec): make a liveness citation name the property it is evidence for#12214
os-litant merged 2 commits into
mainfrom
claude/issue-11457-liveness-within-file-rot

Conversation

@os-litant

Copy link
Copy Markdown
Collaborator

Fixes#11457

The existence check (#5623) bounds a citation's file; the line bound (PR #11449, #11210) bounds its line. Both bound it from the outside, and between them sits a case neither can see: a consumer that moves within the file it is cited to, or a citation written with no line at all. The file is present, every named line is in range, and the pointer is wrong.

This implements the second signal #11210 designed and left to the implementer — the key-mention check — with the exception classes the census measured rather than a \bkey\b grep.

The census, re-measured on today's tree

The issue's census is dated 2026-08-23. Re-run against origin/main @ 22c42c9 with the PR #11449 parser (scanEvidence().localCitations + scan.local):

2026-08-23 (issue)today
(entry, cited local file) pairs403403
pairs whose file never mentions the key1411 (2.7%)

The three that left are the permission.tabPermissions pointers PR #11449 repaired, exactly as the issue predicted. The other 11 reproduce identically.

One of the issue's classifications does not survive the re-measure. It listed object.tenancy.organizationFieldaudit-writers.ts among the naming-convention false positives ("reads it as organization_*"). It is real rot: resolveRecordOrganizationField was promoted into @objectstack/metadata-core by #10101, and audit-writers.ts:220 says so itself in the re-export comment it left behind. So the split is 7 real / 4 convention, not 6/5.

The seven repairs

entrywas citednowcause
permission.objects.allowExporthono-plugin.tscurrent-user-endpoints.ts:493-502annotateEffectiveApiOperations moved — the same movement that rotted systemPermissions and tabPermissions
object.tenancy.organizationFieldaudit-writers.tsmetadata-core/src/record-organization.ts:177-180resolver promoted to another package (#10101)
action.targethttp-dispatcher.tsaction-execution.ts:725, :472actions domain extracted; http-dispatcher.ts retains 0 occurrences
action.requiredPermissionshttp-dispatcher.tsdomains/actions.ts:172-180same extraction
action.bodyShapehttp-dispatcher.tsobjectui @a76b18cf2useConsoleActionRuntime.tsx:359-360realm was wrong: type:'api' actions are client-dispatched
action.bodyExtrahttp-dispatcher.tsobjectui @a76b18cf2useConsoleActionRuntime.tsx:397-398, :474-475same
field.requiredWhenrecord-validator.tsrule-validator.ts:1792-1810, :762-763cited its sibling: record-validator.ts enforces the static required contract, the CEL predicate lives one file over

Two of these say something worth keeping. bodyShape/bodyExtra were never readable in this repo at all — action-execution.ts:468-477 refuses type:'api' server-side with "it dispatches on target, not through the action registry" — so the in-repo citation was never right, rather than having rotted. And field.requiredWhen is the plausible-neighbour shape: both files exist, neither citation carried a line, so no check the gate had could reach it.

Why the matcher is not a grep

The remaining 4 are the convention Prime Directive #3 mandates — TS keys camelCase, machine names snake_case — so a property persisted as a column is read as body_html / managed_by and never as the authoring key. Three are handled structurally: the matcher folds the key across the naming convention, in both directions. They are not exemptions and there is nothing to maintain for them.

The match is word-bounded, which is load-bearing rather than tidy: an unbounded match lets required satisfy requiredWhen, and that is precisely the citation that stayed hidden — its cited file mentions required on twenty lines.

The single residual is a compound child-key remap: fromOverride.addressfrom_address, fromOverride.namefrom_name. No fold of the parent key reaches it, and widening the matcher to a prefix would buy this one exemption at the cost of blinding the check to every future pointer landing in a file merely adjacent to the reader — the requiredWhen shape again. It is one explicit row in scripts/liveness/key-mention.baseline.json, and its why names the spelling the file actually uses.

That baseline is shrink-only in both directions, like undrilled-containers.baseline.json beside it: a row whose pair later anchors fails, so the debt cannot be overstated for free.

The check asks evidence only, never producer. A producer cites who supplies a second input (#4837) — by definition a call site that need not name the key (seed.env's producer points at resolveEnvConfig). Demanding the key there would report that field's whole population as rot the day it was switched on.

On the ledger-format fork

The triage said: if a clean design needs a ledger-format addition, stop and report rather than invent one inline. No ledger-format addition was needed — authors' liveness/*.json is untouched as a format, the one exemption is gate-side, and the precedent for that is undrilled-containers.baseline.json in the same directory. The residual design question (whether that exemption would eventually read better as an entry-level anchor key) is recorded in the report rather than decided here.

Verification

check:liveness before/after, both at the same tree except for this branch:

  • before (22c42c9, a clean comparison worktree): exit 0, green — 403 local paths, all resolved; 293 line citations, all in range. It was green while 11 citations were unanchored and 7 of those were real rot.
  • after (938512b): exit 0key-mention anchoring: 402 (entry, cited file) pair(s) asked, 401 anchored, 1 exempt.

Reverse verification, two legs, each mutation proven on disk before the reading and restored by an EXIT INT TERM trap (no build/dist involved — tsx runs the .mts gate from source and reads the ledger JSON at runtime):

  • revert field.requiredWhen to its rotted citation → gate exit 1, 1 UNANCHORED
  • add a baseline row whose pair anchors → gate exit 1, 1 stale key-mention exemption(s)

Gates, all at final HEAD 938512b and read from each gate's own verdict line: check:liveness, check:empty-state, check:strictness-ledger, pnpm --filter @objectstack/spec typecheck (incl. check:scripts-typecheck + check:test-typecheck), the liveness suite (235/235, 22 of them new), pnpm lintwhole repo (no narrowing), check:published-files, check:engine-double-contract, check:where-matcher, check:cross-package-test-inputs, check:query-options-erasure, check:nul-bytes, and the changeset family. Gate list re-derived from the actual diff with scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack.

One pre-existing self-test assertion needed repair: it pinned "not reported as a missing file" by the bare entry → path line, which is not unique to one check. It now pins the missing-file heading — the claim actually being made.


Generated by Claude Code

… for
The existence check (#5623) and the line bound (#11210) both bound a citation
from the outside. Between them a consumer can move WITHIN the file it is cited
to, leaving the file present and every named line in range while the pointer is
wrong.
Measured before switching anything on: 403 (entry, cited local file) pairs, 11
where the cited file never names the key, 7 of them real rot — all repaired
here. The remaining 4 are the mandated camelCase -> snake_case convention;
three are folded structurally by the matcher and the one compound child-key
remap is a single explicit row in a shrink-only baseline that fails in both
directions.
Asked of `evidence` only, never `producer`: a producer cites who supplies a
second input (#4837), a call site that need not name the key.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NDGG54XF5gbTLdQzCtnaVV
…three checks share
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NDGG54XF5gbTLdQzCtnaVV
@github-actionsgithub-actionsBot added size/l documentation Improvements or additions to documentation tests tooling labels Aug 25, 2026
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

⚠️4 changed file(s) yielded no anchor (packages/spec/liveness/action.json, packages/spec/liveness/field.json, packages/spec/liveness/object.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
  • 4 changed file(s) yielded no anchor (packages/spec/liveness/action.json, packages/spec/liveness/field.json, packages/spec/liveness/object.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 494279cb31f1d92adab959763085e19c923a8652packageMentionDocs.

@os-litant
os-litant marked this pull request as ready for review August 25, 2026 14:17
@os-litant
os-litant added this pull request to the merge queueAug 25, 2026
Merged via the queue into main with commit 20b0fdbAug 25, 2026
35 checks passed
@os-litant
os-litant deleted the claude/issue-11457-liveness-within-file-rot branch August 25, 2026 14:46
os-trump pushed a commit that referenced this pull request Aug 28, 2026
…#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
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentationImprovements or additions to documentationsize/lteststooling

Projects

None yet

2 participants

@os-litant@claude