Skip to content

fix(qa-checklist): re-point nine bad-citation anchors and drain their residual rows - #19181

Merged
os-try-charles merged 1 commit into
mainfrom
claude/issue-18104-drain-bad-citation-residual
Sep 19, 2026
Merged

os-try-charles merged 1 commit into
mainfrom
claude/issue-18104-drain-bad-citation-residual

Conversation

@os-try-charles

Copy link
Copy Markdown
Collaborator

Part of #18104

Clause-②: no

The slice, and why these three

SHARED_RESOLVER_RESIDUAL carries 47 bad-citation rows across 12 area files. The card
allows landing per area file, so this is the first slice: areas/access-security.json,
areas/api-backend.json and areas/automation.json — 9 rows, 10 anchor occurrences.

They were picked because they are one reading, not three: 8 of the 9 cite a route
ledger
— packages/rest/src/rest-route-ledger.ts, packages/runtime/src/route-ledger.ts
and packages/triggers/trigger-api/src/trigger-api-route-ledger.ts — and every one of them
named something a route TABLE carries as data (a client-method name, a route path
parameter) rather than something the file declares. Repairing them is one judgement applied
nine times, which keeps the PR reviewable; the remaining 38 rows are untouched and stay on
the ledger.

Per row: which kind it was (acceptance item 2)

All nine are genuinely wrong citations, re-pointed. None of them is an accept-set case,
so none of them belongs to #18101.
Each old symbol was checked against
scripts/symbol-anchors.mjs#symbolResolutionClass directly and returns null; each new one
returns declaration.

area file old anchor what the old symbol actually was in that file re-pointed to
access-security rest-route-ledger.ts#saveItem CLIENT METHOD name, inside a client: string value #REST_ROUTE_LEDGER
access-security rest-route-ledger.ts#shareId route PATH PARAMETER, inside a route string #REST_ROUTE_LEDGER
api-backend rest-route-ledger.ts#REST (2 occurrences, 1 row) the bare word REST, a substring of longer tokens #REST_ROUTE_LEDGER
api-backend runtime/route-ledger.ts#getLegalNextStates CLIENT METHOD name, inside a dotted string value #ROUTE_LEDGER
api-backend trigger-api-route-ledger.ts#flowName route PATH PARAMETER, inside the route string #TRIGGER_API_ROUTE_LEDGER
automation runtime/route-ledger.ts#getScreen CLIENT METHOD name, inside a dotted string value #ROUTE_LEDGER
automation runtime/route-ledger.ts#runId route PATH PARAMETER, inside the route string #ROUTE_LEDGER
automation runtime/route-ledger.ts#getRuntimeStatus CLIENT METHOD name, inside a dotted string value #ROUTE_LEDGER
automation objectstack.config.ts#ConnectorRestPlugin an IMPORTED class — the file USES it, its own package declares it #plugins

The eight ledger rows are not renames. Each citation's prose already named the routes or the
families it means; what it was missing was a symbol the cited file declares, and in a route
ledger that is the exported table. This is the corpus's own established spelling — the
line two entries above one of the repaired ones already reads
packages/plugins/plugin-auth/src/auth-route-ledger.ts#AUTH_ROUTE_LEDGER.

The ninth is a different reading. objectstack.config.ts imports the three connector plugin
classes and constructs them; the declaration the item is about — "this host wires these
connectors" — is the host's own plugins list, which is an object-literal key at the start
of a line and resolves as a declaration. The original parenthetical naming all three
plugin classes is kept intact.

No #symbol was dropped, and no floor moved

--anchor-census is byte-identical before and after, all 17 family files:

node scripts/check-platform-checklist.mjs --anchor-census   (before vs after: no differences, 17/17 files)

That is the mechanical statement of it: the floor population is resolved + residual, so a
repair moves an occurrence from one side to the other and leaves every per-file count where
it was. Ten occurrences moved: 577/633 resolved -> 587/633, residual 56 -> 46.

scripts/checklist-symbol-anchor-baseline.json is untouched.

Rows left by repair, and the ceiling came down with them

SHARED_RESOLVER_RESIDUAL 55 rows -> 46. SHARED_RESOLVER_RESIDUAL_CEILING 55 -> 46, in
the same edit. The header's shape tallies are re-counted with the rows
(string-substring 29 -> 21, import-only 9 -> 8, bad-citation 47 -> 38); accept-set
stays 8 and is untouched.

The binding measurement in that header ("56 of 633, at #16898") is left standing as the
dated reading it is, with a note saying so and pointing a reader at
SHARED_RESOLVER_RESIDUAL.length and the console line for the live count.

Positive control (acceptance item 3)

Two legs, each mutating the committed tree, proving the mutation landed on disk before
reading any verdict, then restoring with git checkout HEAD -- the mutated path and proving the
restore by blob hash against HEAD plus an empty git diff HEAD — never by an exit code.
Control run first: bare gate exit 0, zero ABSENT SYMBOL lines.

Leg A — a repaired anchor is still being judged, and it resolves. One repaired citation
in api-backend.json was re-pointed to REST_ROUTE_LEDGER_ABLATION_NOT_DECLARED (on-disk
proof: target text before=1, injected marker after=1). Gate exit 1:

areas/api-backend.json: ABSENT SYMBOL - `...#REST_ROUTE_LEDGER_ABLATION_NOT_DECLARED`:
... is not declared in packages/rest/src/rest-route-ledger.ts by
`scripts/symbol-anchors.mjs#symbolResolutionClass`

Restored (blob 64486cd5... == HEAD, git diff HEAD empty), gate back to exit 0. So
the green on these citations is the shared resolver answering declaration, not the gate
having gone quiet on them.

Leg B — an unrelated row still reds. The un-repaired
areas/studio-authoring.json / rest-route-ledger.ts#getHistory row was deleted from the
ledger without repairing its citation (on-disk proof: row before=1, after=0). Gate
exit 1 with ABSENT SYMBOL naming that anchor; restored (blob b52b00f3... == HEAD),
back to exit 0. So the nine rows left this ledger by repair — the population is still
firing for everything that was not repaired.

Gates

node scripts/pm/dispatch-gates.mjs --commands re-derived against the real changed set
(4 paths, committed) yields 31 families, matching the dispatch's derivation.
All 31 were run and all 31 exited 0 — reconciled with
dispatch-gates --ran RECORDFILE --repo objectstack-ai/objectstack:
31 derived, 31 run, 0 NOT-MEASURED, 0 UNRUN, with an exit code recorded per command so
that zero is derived rather than claimed.

scripts/check-platform-checklist.mjs is itself a gate, so both halves were run
separately
, not just the aggregate: bare invocation exit 0 (OK - 15 areas, 264 items ... 587/633 resolved ... 46 on the named #16898 residual, 17 file floors held) and
--self-test exit 0 (221 assertions). pnpm check:platform-checklist, which chains
checklist-select --self-test in front of both, also exits 0.

One family first reported exit 3 — PREREQUISITE NOT MET, not a finding:
@objectstack/lint run check:doc-formula-expressions wants @objectstack/formula and
@objectstack/lint built. Built them (under the shared verify lock) and re-ran: exit 0.

No import relationship changed and no TypeScript program's view moved — the diff is one
ESM gate script's data plus three JSON documents — so no per-package typecheck is owed
beyond what the derivation already places.

"All 31 derived families green" is not "CI green". The derivation itself names what sits
outside those 31: 53 artifact-roster families, 11 declared-wide-population families, 14
families that apply once a changeset exists, and 1 path-scheduled CI job. CI is the
authority on those.

Changeset

No changeset: nothing published moves. Measured rather than assumed — the root package is
private, and no package manifest in the tree declares a files[] entry escaping its own
directory, while this diff touches only repo-root scripts/ and docs/qa/, neither of
which is inside any package. skip-changeset applies.

Acceptance notes

  • The baseline JSON's $comment is now inaccurate, and this PR deliberately does not fix
    it.
    It reads "Each entry is the count of anchors that RESOLVED in that family file";
    since [finding] the platform-checklist corpus resolves symbol anchors with its OWN rule, not the shared resolver — a permissive token match where the ruling says there is to be exactly one implementation #16898 an entry is resolved + residual, which the gate's own --anchor-census
    footer states outright. scripts/checklist-symbol-anchor-baseline.json line 3 declares
    itself ⛔ MAINTAINER-ONLY, and reading that authority narrowly — as scoping only to
    lowering a floor — is the move the line exists to stop. Reported, not touched. It is a
    one-sentence maintainer edit.
  • The detector-artifact row is not in this slice, and the card's repair instruction for
    it does not survive contact with the tree at eeaa88245.
    The card says "repairing it
    means fixing the detector, not the citation". But platform-checklist reuses the shared resolver's RULE but is still not a registered corpus — the anchor grammar stays forked, and the two copies have already drifted (23 extensions vs 8) #18107 moved this corpus's detector into
    the shared core: scripts/check-platform-checklist.mjs imports extractAnchors,
    sweepCorpus and ANCHORABLE_EXTENSIONS from scripts/symbol-anchors.mjs and carries no
    anchor grammar of its own — its own --self-test asserts exactly that. The phantom
    #access is produced by SYMBOL in the shared module, whose character class admits no
    hyphen. So "fix the detector" is now a change to scripts/symbol-anchors.mjs, which all
    three cards in this split forbid by name. Whoever takes areas/identity-auth.json needs
    either a maintainer ruling on that widening or a citation-side repair — and the
    citation-side repair on the current bytes is not obvious either, because the anchor there
    is written ...access-security.json#access followed by a space, with the item-id
    access-security.scope-depth-asymmetry in the parenthetical after it. Noted for triage,
    not filed as a separate card from here.

Generated by Claude Code

…ng route-ledger anchors

First slice of the SHARED_RESOLVER_RESIDUAL drain. Nine `bad-citation` rows
across three area files are repaired at the citation, not at the resolver:
each anchor named a client-method name, a route path parameter or an imported
class, none of which is a declaration site in the cited file. Every one is
re-pointed at the declaration the item actually means -- the route table the
file exports, or the host plugin list -- following the convention this corpus
already uses for `auth-route-ledger.ts#AUTH_ROUTE_LEDGER`.

No `#symbol` is dropped, so the floor population is byte-identical: the
anchor census prints the same per-file counts before and after, with ten
occurrences moving from `residual` to `resolved` (577/633 -> 587/633).

The ledger's ceiling comes down with the rows, 55 -> 46, and the shape and
verdict tallies in its header are re-counted in the same edit.

Claude-Session: https://claude.ai/code/session_017ef78bLdybu3AffehKkhfk
Co-authored-by: Claude <noreply@anthropic.com>
@os-try-charles os-try-charles added the skip-changeset PR has no user-facing published change; bypasses the changeset gate label Sep 19, 2026 — with Claude
@os-try-charles
os-try-charles marked this pull request as ready for review September 19, 2026 09:09
@os-try-charles
os-try-charles added this pull request to the merge queue Sep 19, 2026
Merged via the queue into main with commit 82b3225 Sep 19, 2026
37 checks passed
@os-try-charles
os-try-charles deleted the claude/issue-18104-drain-bad-citation-residual branch September 19, 2026 09:54
akarma-synetal pushed a commit to akarma-synetal/framework that referenced this pull request Sep 28, 2026
…rs and drain their residual rows (objectstack-ai#19196)

Part of objectstack-ai#18104

Clause-②: no

## The slice

The **second slice** of the `SHARED_RESOLVER_RESIDUAL` drain:
**`areas/identity-auth.json` — 13 rows, 13 anchor occurrences.** It is
the largest single-file block of the 38 `bad-citation` rows the first
slice (objectstack-ai#19181, landed at `82b322585`) left behind, and it is one file,
so every judgement in it is made against one item ledger.

## ⚠️ This file is NOT empty of residual rows afterwards, and that is
correct

`areas/identity-auth.json` also carries **2 `accept-set` rows** —
`packages/spec/src/kernel/public-auth-features.ts#sys_user` and
`#sys_invitation`, both the `dotted-string-head` shape. **They are
objectstack-ai#18101's, not this card's**, and they stay exactly where they are.
Reaching them means widening `scripts/symbol-anchors.mjs`, which is the
red line objectstack-ai#18104 / objectstack-ai#18101 / objectstack-ai#18107 share by name. After this slice the
ledger carries **33 rows** — `bad-citation` 25 + `accept-set` 8 — and
`areas/identity-auth.json` accounts for 2 of them.

## Per row: which kind it was (acceptance item 2)

**All 13 are genuinely wrong citations, re-pointed. None is an
`accept-set` case, so none belongs to objectstack-ai#18101.** Each old symbol was put
to `scripts/symbol-anchors.mjs#symbolSegmentResolution` directly and
returns `null`; each new one returns `declaration`.

| # | old anchor | what the old symbol actually was in the cited file |
re-pointed to |
|---|---|---|---|
| 1 | `areas/access-security.json#access` | not a key that JSON declares
at all | `#items` |
| 2 | `seed-approval-demo.ts#PHONE_DEMO_USER` | IMPORTED — the constant
lives elsewhere | `demo-personas.ts#PHONE_DEMO_USER` (the **path**
moved, the symbol did not) |
| 3 | `sys-member.object.ts#BUILTIN_MEMBERSHIP_ROLE_OPTIONS` | IMPORTED
from `@objectstack/spec/identity` | `#SysMember` |
| 4 | `sys-oauth-application.object.ts#OAuth` | the bare word, only
inside `label` / `description` strings | `#SysOauthApplication` |
| 5 | `auth-route-ledger.ts#bootstrapStatus` | CLIENT METHOD name,
inside a dotted string value | `#AUTH_ROUTE_LEDGER` |
| 6 | `auth-route-ledger.ts#linkSocial` | CLIENT METHOD name, inside a
dotted string value | `#AUTH_ROUTE_LEDGER` |
| 7 | `auth-route-ledger.ts#revokeOthers` | CLIENT METHOD name, inside a
dotted string value | `#AUTH_ROUTE_LEDGER` |
| 8 | `auth-route-ledger.ts#sendVerificationEmail` | CLIENT METHOD name,
inside a dotted string value | `#AUTH_ROUTE_LEDGER` |
| 9 | `auth-route-ledger.ts#setActive` | CLIENT METHOD name, inside a
dotted string value | `#AUTH_ROUTE_LEDGER` |
| 10 | `auth-route-ledger.ts#updateUser` | CLIENT METHOD name, inside a
dotted string value | `#AUTH_ROUTE_LEDGER` |
| 11 | `security-plugin.ts#__referentialFieldClear` | a CONTEXT KEY,
read only as a member access on `opCtx.context` | `#SecurityPlugin` |
| 12 | `membership-role-vocabulary.dogfood.test.ts#PermissionSet` | in a
comment and an `it(...)` title | `#CLOSED_VOCABULARY` |
| 13 | `rest-route-ledger.ts#describeDelegableScope` | CLIENT METHOD
name, inside a `client:` string value | `#REST_ROUTE_LEDGER` |

Six of them (5–10) are the first slice's reading applied again: a route
ledger's `client` field is DATA the table carries, and the declaration
the item means is the exported table. That spelling is already this
corpus's own — `areas/api-backend.json` has read
`auth-route-ledger.ts#AUTH_ROUTE_LEDGER` since before this card.

Three needed a reading of their own:

- **Row 1 is the `detector-artifact` row**, and it was repaired
**citation-side**, per the PM's ruling in comment `5740561848`. The
ledger's own fields say why: `detector-artifact` is the **`shape`** (why
the withdrawn permissive rule used to resolve it), while the
**`verdict`** has always read `bad-citation`. Reading the shape as the
disposition sends the next author at the detector, which since objectstack-ai#18107 IS
`scripts/symbol-anchors.mjs` — the file this card forbids by name. And
the truncation is not happening on today's bytes anyway: the citation's
fragment is followed by a SPACE, so the symbol was simply what the
author wrote. What they meant is the item id in the parenthetical, which
the checklist JSON carries as a VALUE, never a key — so the citation now
names the `items` block and the item id stays in the prose beside it.
The ledger header records this so the next reader is not sent the same
way.
- **Row 2 is a MOVE, not a rename.** `seed-approval-demo.ts` says so in
its own header: the demo identities "now live in `demo-personas.ts`,
because the SEED needs them too". The file only imports the persona and
provisions it; the constant, `phone_number` included, is declared next
door. The path is what was stale, so the path is what moved. This is the
one change that adds a cited source (309 → 310).
- **Row 3 is the mirror of row 2 and went the other way**, deliberately.
The same item ALREADY anchors
`packages/spec/src/identity/membership-role.ts#BUILTIN_MEMBERSHIP_ROLES`
two rows above, so re-pointing this one at the spec too would have been
a duplicate. This citation is about the **object's role select**, and
what `sys-member.object.ts` declares is the object.

## No `#symbol` was dropped, and no floor moved (acceptance item 3)

`areas/identity-auth.json`'s census is **83** against a floor of **82**
— one of headroom — so this was measured, not assumed. The per-file
census is **identical before and after, 17/17 files**:

```
node scripts/check-platform-checklist.mjs --anchor-census
  before vs after: the 17 per-file counts are byte-identical; only the summary line moves
  587/633 resolved, plus 46 on the named residual   ->   600/633 resolved, plus 33
```

That is the mechanism, not luck: the floor population is `resolved +
residual`, so a repair moves an occurrence from one side to the other
and leaves the per-file count where it was. The bare gate says it in its
own words — **`17 file floors held`**.
`scripts/checklist-symbol-anchor-baseline.json` is untouched.

## Rows left by repair, and the ceiling came down with them (acceptance
items 1 and 4)

`SHARED_RESOLVER_RESIDUAL` 46 rows → **33**;
`SHARED_RESOLVER_RESIDUAL_CEILING` 46 → **33**, in the same edit. The
header's tallies are re-counted off the surviving rows rather than
adjusted by hand: `string-substring` 21 → 12, `import-only` 8 → 6,
`member-access` 3 → 2, `detector-artifact` 1 → 0, `bad-citation` 38 →
25; `accept-set` stays **8** and is untouched.

The `detector-artifact` block is kept at zero rows on purpose, carrying
the ruling above — the shape reading is what a future author needs, and
deleting it would delete the reason this row is not a detector bug.

## Positive control (acceptance item 3) — two legs, opposite directions

Both legs mutate the **committed** tree, prove the mutation landed on
disk before any verdict is read, restore with `git checkout HEAD --
PATH` under an `EXIT INT TERM` trap on an absolute path, and prove the
restore by blob hash against HEAD plus an empty `git diff HEAD` — never
by an exit code. Control run first: bare gate **exit 0**, zero `ABSENT
SYMBOL` lines.

**Leg A — a repaired anchor is still being judged, and it resolves
through the shared resolver.** Row 1's repaired citation was re-pointed
to a symbol `areas/access-security.json` does not declare (on-disk
proof: target text before=1 after=0, injected marker before=0 after=1).
Gate **exit 1**:

```
areas/identity-auth.json: ABSENT SYMBOL - `docs/qa/platform-checklist/areas/access-security.json#itemsAblationNotDeclared`:
  `itemsAblationNotDeclared` is not declared in docs/qa/platform-checklist/areas/access-security.json
  by `scripts/symbol-anchors.mjs#symbolResolutionClass`
```

Restored (blob `f6baec3c...` == HEAD, `git diff HEAD` empty), gate back
to **exit 0**. So the green on these citations is the shared resolver
answering `declaration`, not the gate having gone quiet on them.

**Leg B — an unrelated row on THIS SAME FILE still reds.** One of the
two `accept-set` rows this slice deliberately leaves behind
(`public-auth-features.ts#sys_user`) was deleted from the ledger without
repairing its citation (on-disk proof: row before=1, after=0). Gate
**exit 1** with `ABSENT SYMBOL` naming that anchor; restored (blob
`4ae200e4...` == HEAD, `git diff HEAD` empty), back to **exit 0**. So
`areas/identity-auth.json` is still swept and its residual rows still
fire — the 13 left this ledger **by repair**, not because the document
went dark.

## Gates

`node scripts/pm/dispatch-gates.mjs --commands --repo
objectstack-ai/objectstack` re-derived against the real changed set (2
committed paths, `+42/-33`) yields **31 families** — the same count as
the first slice, on a different path set.

`scripts/check-platform-checklist.mjs` is itself a gate, so **both
halves were run separately**, not only the aggregate:

- bare invocation **exit 0** — `OK — 15 areas, 264 items … symbol
anchors: 600/633 resolved … 33 on the named residual, 17 file floors
held`
- `--self-test` **exit 0** — 221 assertions
- `pnpm check:platform-checklist` (which chains `checklist-select
--self-test` in front of both) **exit 0**

One family reported **exit 3 — PREREQUISITE NOT MET**, which is not a
finding: `@objectstack/lint run check:doc-formula-expressions` wants
`@objectstack/formula` and `@objectstack/lint` built.

No import relationship changed and no TypeScript program's view moved —
the diff is one ESM gate script's data and comments plus one JSON
document, neither of which any `tsconfig` includes — so no per-package
`typecheck` is owed beyond what the derivation already places.

**"All 31 derived families green" is not "CI green".** The derivation
names what sits outside those 31: 53 artifact-roster families, 11
declared-wide-population families, 14 families that apply once a
changeset exists, 2 families taking a value from the workflow, and 1
path-scheduled CI job. CI is the authority on those.

## Changeset

No changeset: nothing published moves. Measured rather than assumed —
the diff touches only repo-root `scripts/` and `docs/qa/`, neither of
which is inside any package directory, so no package manifest's
`files[]` can ship either path. `skip-changeset` applies.

## Acceptance notes

- **The baseline JSON's `$comment` is still inaccurate, and this PR
deliberately does not fix it.** It reads "Each entry is the count of
anchors that RESOLVED in that family file"; since objectstack-ai#16898 an entry is
resolved + residual, which this gate's own `--anchor-census` footer
states outright. `scripts/checklist-symbol-anchor-baseline.json` line 3
declares itself `⛔ MAINTAINER-ONLY`. Reported, not touched — a
one-sentence maintainer edit, already recorded on the card.
- **The card's original repair instruction for the `detector-artifact`
row is left standing as history**, with the PM's correction under it.
This PR follows the correction.

---
_Generated by [Claude
Code](https://claude.ai/code/session_017ef78bLdybu3AffehKkhfk)_

Co-authored-by: Claude <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size/s skip-changeset PR has no user-facing published change; bypasses the changeset gate

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants