Repository navigation
fix(qa-checklist): re-point nine bad-citation anchors and drain their residual rows - #19181
Merged
os-try-charles merged 1 commit intoSep 19, 2026
Merged
Conversation
…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
marked this pull request as ready for review
September 19, 2026 09:09
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>
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 free
to 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 #18104
Clause-②: no
The slice, and why these three
SHARED_RESOLVER_RESIDUALcarries 47bad-citationrows across 12 area files. The cardallows landing per area file, so this is the first slice:
areas/access-security.json,areas/api-backend.jsonandareas/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.tsand
packages/triggers/trigger-api/src/trigger-api-route-ledger.ts— and every one of themnamed 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-setcase,so none of them belongs to #18101. Each old symbol was checked against
scripts/symbol-anchors.mjs#symbolResolutionClassdirectly and returnsnull; each new onereturns
declaration.rest-route-ledger.ts#saveItemclient:string value#REST_ROUTE_LEDGERrest-route-ledger.ts#shareId#REST_ROUTE_LEDGERrest-route-ledger.ts#REST(2 occurrences, 1 row)#REST_ROUTE_LEDGERruntime/route-ledger.ts#getLegalNextStates#ROUTE_LEDGERtrigger-api-route-ledger.ts#flowName#TRIGGER_API_ROUTE_LEDGERruntime/route-ledger.ts#getScreen#ROUTE_LEDGERruntime/route-ledger.ts#runId#ROUTE_LEDGERruntime/route-ledger.ts#getRuntimeStatus#ROUTE_LEDGERobjectstack.config.ts#ConnectorRestPlugin#pluginsThe 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.tsimports the three connector pluginclasses and constructs them; the declaration the item is about — "this host wires these
connectors" — is the host's own
pluginslist, which is an object-literal key at the startof a line and resolves as a
declaration. The original parenthetical naming all threeplugin classes is kept intact.
No
#symbolwas dropped, and no floor moved--anchor-censusis byte-identical before and after, all 17 family files:That is the mechanical statement of it: the floor population is
resolved + residual, so arepair 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.jsonis untouched.Rows left by repair, and the ceiling came down with them
SHARED_RESOLVER_RESIDUAL55 rows -> 46.SHARED_RESOLVER_RESIDUAL_CEILING55 -> 46, inthe same edit. The header's shape tallies are re-counted with the rows
(
string-substring29 -> 21,import-only9 -> 8,bad-citation47 -> 38);accept-setstays 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.lengthand 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 therestore 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 SYMBOLlines.Leg A — a repaired anchor is still being judged, and it resolves. One repaired citation
in
api-backend.jsonwas re-pointed toREST_ROUTE_LEDGER_ABLATION_NOT_DECLARED(on-diskproof: target text before=1, injected marker after=1). Gate exit 1:
Restored (blob
64486cd5...== HEAD,git diff HEADempty), gate back to exit 0. Sothe green on these citations is the shared resolver answering
declaration, not the gatehaving gone quiet on them.
Leg B — an unrelated row still reds. The un-repaired
areas/studio-authoring.json/rest-route-ledger.ts#getHistoryrow was deleted from theledger without repairing its citation (on-disk proof: row before=1, after=0). Gate
exit 1 with
ABSENT SYMBOLnaming that anchor; restored (blobb52b00f3...== 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 --commandsre-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 sothat zero is derived rather than claimed.
scripts/check-platform-checklist.mjsis itself a gate, so both halves were runseparately, 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-testexit 0 (221 assertions).pnpm check:platform-checklist, which chainschecklist-select --self-testin front of both, also exits 0.One family first reported exit 3 — PREREQUISITE NOT MET, not a finding:
@objectstack/lint run check:doc-formula-expressionswants@objectstack/formulaand@objectstack/lintbuilt. 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
typecheckis owedbeyond 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 afiles[]entry escaping its owndirectory, while this diff touches only repo-root
scripts/anddocs/qa/, neither ofwhich is inside any package.
skip-changesetapplies.Acceptance notes
$commentis now inaccurate, and this PR deliberately does not fixit. 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-censusfooter states outright.
scripts/checklist-symbol-anchor-baseline.jsonline 3 declaresitself
⛔ MAINTAINER-ONLY, and reading that authority narrowly — as scoping only tolowering a floor — is the move the line exists to stop. Reported, not touched. It is a
one-sentence maintainer edit.
detector-artifactrow is not in this slice, and the card's repair instruction forit does not survive contact with the tree at
eeaa88245. The card says "repairing itmeans 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.mjsimportsextractAnchors,sweepCorpusandANCHORABLE_EXTENSIONSfromscripts/symbol-anchors.mjsand carries noanchor grammar of its own — its own
--self-testasserts exactly that. The phantom#accessis produced bySYMBOLin the shared module, whose character class admits nohyphen. So "fix the detector" is now a change to
scripts/symbol-anchors.mjs, which allthree cards in this split forbid by name. Whoever takes
areas/identity-auth.jsonneedseither 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#accessfollowed by a space, with the item-idaccess-security.scope-depth-asymmetryin the parenthetical after it. Noted for triage,not filed as a separate card from here.
Generated by Claude Code