Skip to content

[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

Description

@os-steve

Found while executing #11210 (PR #11449), which bounds a path:NNN evidence citation by
the cited file's line count. That bound closes the past-EOF case. It structurally cannot
see the complementary case: a consumer that moves within the file it is cited to,
or a citation written with no line at all. The line still exists, the file still exists,
and nothing fires.

#11210 offered a second signal for exactly this — warn when the cited file contains no
occurrence of the property's own key — and left the call to the implementer on
false-positive cost. I measured it instead of guessing, and the measurement says: the
signal is real, and it is not a one-line addition. Filing the census rather than
bolting a permanently-noisy warning onto a gate tightening.

The census (post-#11209 ledgers, the gate's own parser)

Walked every live entry, took each resolvable repo-local cited file, and asked whether
that file mentions the entry's own leaf key as a word:

  • 403 (entry, cited local file) pairs checked
  • 14 where the cited file never mentions the key — 3.5%

Why a naive matcher is wrong — the false positives are STRUCTURAL

At least 5 of the 14 are generated by a rule the platform mandates everywhere. AGENTS.md
Prime Directive #3: TS config keys are camelCase, machine names are snake_case. So for
every property persisted as a column, the consumer file names the snake_case form and
never the authoring key:

entry cited file reads it as
email_template.bodyHtml plugin-email/src/email-service.ts body_html
email_template.bodyText same body_text
email_template.fromOverride same from_*
permission.managedBy plugin-security/src/bootstrap-declared-permissions.ts sys_permission_set.managed_by
object.tenancy.organizationField plugin-audit/src/audit-writers.ts organization_*

The bodyHtml entry's own evidence string even writes the mapping out —
bootstrap-declared-email-templates.ts:83 (bodyHtml -> body_html). A \bkey\b match on the
authoring key is guaranteed to miss the consumer for this entire class, so the warn needs a
real design (case-folding across the naming convention, destructured/renamed locals,
probably an opt-out key on the entry), not a grep.

The true positives — these are real rot, and one is confirmed

permission.objects.allowExport — cites
packages/plugins/plugin-hono-server/src/hono-plugin.ts (annotateEffectiveApiOperations, the /me/permissions projection the frontend renders). Measured: annotateEffectiveApiOperations
has 0 occurrences in hono-plugin.ts and now lives in
packages/plugins/plugin-hono-server/src/current-user-endpoints.ts. This is the same
code movement that rotted permission.systemPermissions (repaired in #11209) and
permission.tabPermissions (repaired in PR #11449) — and it is invisible to the line bound
because that citation carries no line at all. The other two pointers in the entry
(rest-server.ts enforceExportPermission, security-plugin.ts canExport) were not
re-measured here.

action.target / action.requiredPermissions / action.bodyShape / action.bodyExtra
— all four cite packages/runtime/src/http-dispatcher.ts. That file mentions action 48
times but the string target 0 times in 2188 lines. Either the four keys are read
through renamed locals (false positives) or the consumer moved (a 4-entry rot cluster).
Unclassified — it needs a call-graph closure, which is the work, not a mechanical edit.

field.requiredWhenobjectql/src/validation/record-validator.ts (0 occurrences of
requiredWhen, 20 lines mentioning required) — same unclassified shape.

The remaining 3 of the 14 are the permission.tabPermissions pointers already repaired in
PR #11449.

Suggested shape

  1. Re-measure each of the 14 and repair or reclassify (the allowExport one is already
    measured above and is a repoint).
  2. Only then decide the warn's design, with the repaired set as its baseline — the
    census above is what makes a shrink-only ratchet possible without shipping a warning
    list that is non-empty on day one. evidence.mts's header records why that matters: the
    48-of-227 era produced a warning nobody read, and the one genuine rot inside it sat
    unnoticed.

Filed unassigned. Reproduce with the parser PR #11449 adds — scanEvidence().localCitations
plus scan.local — over packages/spec/liveness/*.json.

Back-links: #11210, PR #11449, #11209 / #10959 (the sibling repair), #7133 / #7142 (the
objectui citation-repair bundle), #5623 (made a missing evidence FILE red).


Generated by Claude Code

Metadata

Metadata

Assignees

Type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions