docs(devx): anchor the isSystem census page by symbol, not by line number - #16614
Merged
baozhoutao merged 7 commits intoSep 7, 2026
Merged
Conversation
…stem-context-symbol-anchors
baozhoutao
marked this pull request as ready for review
September 7, 2026 12:57
baozhoutao
enabled auto-merge
September 7, 2026 12:58
…stem-context-symbol-anchors
…nvironment Its throwaway red-first corpus ran `git init` / `git add -A` with the ambient environment. Under `pre-commit` — where `check-regen-pending` invokes this gate, and where an os-regen merge lap lands — git has exported GIT_DIR, GIT_WORK_TREE and GIT_INDEX_FILE, so the corpus was never created and the repository's own index was written instead: 8,190 paths staged as deleted, with every self-test case still printing ok. The self-test now strips every GIT_* key before its first case and the corpus builder passes the stripped environment explicitly. A regression pin injects a bogus GIT_DIR and requires the throwaway tree to come back with its own two files staged. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Vbw3RPgdtqesx4azk9SbW8
baozhoutao
deleted the
claude/issue-15921-system-context-symbol-anchors
branch
September 7, 2026 13:52
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.
Fixes #15921
The census page stops living on line numbers. Every anchor it writes is now a
path#symbolcitation resolved by the repo's shared symbol-anchor resolver, and the gate's population check becomes per file at symbol granularity.Implements decision batch #77 (comment 5567940532) on this card: batch #56's option A in direction, amended in its population half, which the measured tree cannot satisfy position-free.
What the ruling asked for, and where each item landed
1. Anchors migrate to
path#symbol, file-level only where no declaration can be named, resolved through the resolver behindcheck-adr-symbol-anchorsas a thin corpus registration — no widening ofscripts/symbol-anchors.mjs.scripts/symbol-anchors.mjsis UNTOUCHED by this diff (git diff --statbelow shows three files, and it is not one of them).check-system-context-census.mjsgains adefineCorpuscall —CORPUS,docRoots: ['content/docs/permissions'],docPatternnaming this one file,checkBarePaths: true— and callssweepCorpusonce. Resolution is delegated whole: the gate re-decides none of it, and a sweep it could not run is a refusal ([no-sweep]), not a skip.One file-level anchor exists, and it is the honest case rather than a shortfall:
packages/plugins/plugin-auth/src/last-admin-guard.ts, whose cited claim lives in the module docblock, which no declaration encloses. Every other citation names a real symbol.2. The population check becomes per file.
--fixstops rewriting line numbers.Per file: every file the census finds a read in carries at least one anchor, and the SET of symbols the page cites into that file equals the set the census plus
NON_READ_ANCHORSrequire.One deliberate strengthening, declared rather than slipped in: the ruling names a COUNT equality, and this ships SET equality, which is the same predicate plus the ability to name which symbol is missing. A count that only says "5 against 6" cannot tell an author what to write; the refusal carries both counts anyway, in the same sentence. The literal count-of-READS reading is not what shipped and could not be: it is unsatisfiable on the nine collision files that are the reason the rule became per-file at all.
--fixwrites nothing. It is not silently accepted either — a--fixthat writes nothing and exits 0 reads exactly like a repair that worked, so it prints what it did not do and why, then returns the ordinary verdict.gen:system-context-censusandscripts/regen-artifacts.mjsboth stay wired and stay honest.3. The precision loss is written where a reader meets the anchors.
On the page, in the
Calloutat the top (before the table, so nobody reaches a row without it) and again in "What CI holds", where it is priced with the numbers: 9 of the 45 anchored files hold more than one read inside a single symbol. Both numbers are ENFORCED by newDECLARED_COUNTSentries, so the sentence that prices the loss cannot quietly stop being true in either direction. The gate's own header carries the same statement, and the self-test pins the gap as a PASSING case so the next reader finds it declared instead of rediscovering it as a defect.4. Option A (a span-aware resolver) is not refused and is not folded in. Recorded on the page and in the gate header as its own card, to be taken if the gap is ever measured to have let a deletion through. This diff does not widen the shared resolver.
5. Serial and the #15927 answer. Re-confirmed against the actual diff, below.
The numbers, re-derived on this tree (⛔ every figure in the card's thread was stale)
Derived with the repo's own instruments, never a regex.
scripts/doc-line-anchors.mjs#extractLineAnchorsover the page as it stood on the merge basef2f6684cd5:scripts/isystem-census.mjs, same tree:The nine collision files, re-verified (mechanism assumption 2 in the dispatch — it holds, and it is the reason the population rule is per file):
packages/objectql/src/engine.tspackages/plugins/plugin-approvals/src/approval-service.tspackages/plugins/plugin-reports/src/report-service.tspackages/plugins/plugin-security/src/security-plugin.tspackages/rest/src/rest-server.tspackages/plugins/plugin-sharing/src/share-link-service.tspackages/plugins/plugin-audit/src/comment-access-hooks.tspackages/services/service-storage/src/attachment-access-hooks.tspackages/runtime/src/domains/meta.tspackages/rest/src/package-routes.ts(Ten rows:
approval-service.tsis listed for completeness and is NOT one of the nine — its 8 reads live in 8 distinct symbols.)After the migration, on the merged head:
The ledger is re-keyed, and the overlap is DECLARED
NON_READ_ANCHORSrows were keyed byneedle, a literal of source text that every reformatting moved. They are now{ file, symbol }— the same pair the page writes — and the gate asks the SHARED resolver whether the file still declares the symbol, so the ledger and the page can never mean different things by "the symbol is there".Symbol granularity created a case the needle version did not have: a citation can share its symbol with a census read site. The
owner_idguard block and the short-circuit that skips it are both insidepackages/plugins/plugin-security/src/security-plugin.ts#start. Such a row stops EXCUSING anything while itswhyand itsrowSeamsare still worth keeping, so it declarescollapsesOntoRead: true, and the gate refuses when the declaration and the census disagree in EITHER direction. An undeclared overlap reads as a row that excuses an anchor when it does not; a declared overlap that has ended is a row nobody re-examined.Red-first evidence — the pair the ruling names, on the real tree
Both legs ran on the actual repository against the committed implementation, each mutation proved on disk before the gate was read, each restore proved by
git diff HEADand a blob-hash comparison against the HEAD blob. Full transcript in the report comment on #15921.A pure line move must NOT red. 40 lines inserted above
packages/objectql/src/engine.ts, the file carrying the most anchors:A symbol rename must RED.
getReadFilterrenamed insecurity-plugin.ts, page untouched:Three findings from three different checks — resolution, population and classification each see it independently.
And the BEFORE control, so the property is shown to be new rather than asserted. The base version of the page and both scripts checked out into the same tree, the SAME 40-line insertion applied:
Same tree, same mutation, opposite verdicts. That is the whole of what the migration buys.
Ruling item 5, re-confirmed against THIS diff
#15927 is about the two
PAGE_ROW_REFERENCESentries declaredunheld— two prose sentences whose wording derives no seam key. Re-checked after the change, not before it:unheldmechanism is untouched:PAGE_ROW_REFERENCES,checkRowReferences,extractRowReferencesandextractTableRowsare unchanged in this diff, and bothunheldentries still resolve to their line (2 declared unheldin the gate's OK line).--fixleg Two census-pagerow Nreferences are declaredunheld, so a row insertion can still silently falsify them — the residual #15869 left behind #15927 names is the prose-renumbering leg, which never existed and still does not. The leg this PR removes is the ANCHOR line-number rewriter — a different program, and Two census-pagerow Nreferences are declaredunheld, so a row insertion can still silently falsify them — the residual #15869 left behind #15927's answer does not depend on it.check-system-context-census.mjsnever renumbered prose, deliberately (its own header said so, and that paragraph is preserved).row Nreferences are declaredunheld, so a row insertion can still silently falsify them — the residual #15869 left behind #15927's:held.whygoes 9 to 8, because twoengine.tsledger rows collapsed onto#buildDriverOptionsand were merged into one row carrying onerow 23mention.held.pagestays 10,held.unheldstays 2. The self-test pin was updated to 8 in the same commit.⇒ #15927 is UNAFFECTED, confirmed after the change rather than before it. It also remains blocked on its own hold condition, and this PR is one more open PR touching the page.
Verification
Derived gate families, reconciled:
node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstackderived 62 families; all 62 run;--ranreports62 derived famil(ies) accounted for — 62 run, 0 NOT-MEASURED. Four of them first answeredPREREQUISITE NOT MET(unbuilt@objectstack/lint/formula/client-react), were re-run after building those packages, and passed.Repo-wide lint, NOT narrowed — the full set ran and there was nothing to narrow:
pnpm exec eslint . --no-inline-config --format jsonat6db5435952linted 6291 files, 0 errors, 0 warnings.turbo ls --affectedagainst the merge base reports 0 packages — this diff isscripts/**andcontent/docs/**only, so no package test or typecheck is owed.Changeset
No
.changeset/*.md: this diff publishes nothing from any released package (one docs page, two repo scripts). Batch #56's ruling saidskip-changesetapplies and batch #77 does not move that. ⛔ The label is deliberately NOT applied by this seat — the dispatch reserved it for the PM. That instruction conflicts with the repo's standing clause for os-dev (which makes labelling the executor's step); the conflict is raised in the report'sopen_questionsrather than settled here.Out of scope, filed
#16612 — once this page carries no line numbers, the os-regen
mixed: 'line-anchors'comparator (blankAnchorLineNumbers) becomes the IDENTITY, so the routing's stated safety argument and three prose blocks go stale together. Measured: base 141 line anchors, comparator not the identity; this branch 0 line anchors, comparator IS the identity. ⛔ Not repaired here — the dispatch fenced the merge driver off by name, and the choice among "retiremixed", "move toNOT_DRIVER_MANAGED" and "fix only the prose" is a maintainer's.This branch touches a
merge=os-regenrouted path, so it meets the driver on everymainmove. The merge already in this branch went throughscripts/pm/os-regen-merge.sh(step 2 correctly KEPT the branch's bytes — the branch changed the page andmaindid not). That script and the driver are unchanged by this diff.os-regen lap 2 (merged
68437d4d95, heada3b057a9c8) — and one real defect it exposedmainmoved and the branch wentdirty. The cause was PR #16587, whose only touch to the files this card owns was two rows of this page, both of them pure line-number re-anchorings ofdomains/automation.tsproduced by the OLD--fix(989to1079, and254/545/635to255/546/636).Main's side carried nothing this branch needs, and that is measured, not assumed. The repo's own comparator for exactly this question says so:
So main's edit is entirely inside the generated half, and that half no longer exists here.
scripts/pm/os-regen-merge.shstep 2 correctly reported "both sides changed it" and took main's side into the worktree; its own runbook sentence covers this case — "if the branch's edit here was a HAND edit that no regeneration reproduces, restore the branch bytes before regenerating" — and this branch's page is exactly that, because--fixno longer regenerates anything. The merge commit keeps the branch's page bytes; nothing was lost in either direction.#16587 changed nothing the census measures. Re-derived on the merged tree:
Four reads moved down the file and none moved between symbols, arrived or vanished. That is precisely the class of change this card exists to stop caring about: the page needed no edit, and the gate is green with zero findings.
The defect the lap exposed, and its fix
The
pre-commithook runs this gate, and this branch's new red-first battery builds a throwawaygitcorpus. It rangit initandgit add -Awith the AMBIENT environment — and under a hook git has exportedGIT_DIR,GIT_WORK_TREEandGIT_INDEX_FILE. So the throwaway repository was never created and the real repository's index was written instead: 8,190 paths staged as deleted, with every self-test case still printingok. Recovered withgit reset --hardonto the merge commit, which already held the correct tree.Fixed in
a3b057a9c8: the self-test strips everyGIT_*key before its first case, the corpus builder passes the stripped environment to each child explicitly, and a regression pin injects a bogusGIT_DIR, builds the corpus under it, and requires the temp tree to come back with its own two files staged. The measured incident is written into the helper's header so the next reader does not rediscover it.The fix is proved by the same path that broke: the commit carrying it went through
pre-commit, the hook ran the gate and its self-test, the marker cleared (system-context.mdx — current), andgit status --porcelainafterwards is empty.sweepCorpusresolves throughgit ls-filesand passes no environment of its own, so a sweep of a SYNTHETIC root inherits the caller'sGIT_DIR. Every in-repo caller is unaffected — the inherited values name this repository, which is the right answer — so this is not widened here (the ruling forbids touching the shared resolver); the caveat is recorded beside theCORPUSregistration for whoever registers the next corpus.Verification at
a3b057a9c8🤖 Generated with Claude Code
https://claude.ai/code/session_01Vbw3RPgdtqesx4azk9SbW8
Generated by Claude Code