docs(metadata-core): re-anchor the dead tracker citations to the commits and ADR that decided them (stage 9 of #20595) - #21525
Conversation
…its and ADR that decided them Stage 9 of the domain:engine dead-citation lane: 30 comment and docblock sites on 29 lines in 14 files of packages/metadata-core that cited tracker numbers now answering 404 cite an object this repository controls instead (ruling C+D, form C): 10 numbers by commit, #16864 by ADR-0087, and the objectui pair respelled objectui#6110 + objectui#6111. Comments only; every file keeps its line count. Claude-Session: https://claude.ai/code/session_01DDZNkDVwPQnevTFcYE47H3 Co-authored-by: Claude <noreply@anthropic.com>
…d declaration files Clause-②: no Claude-Session: https://claude.ai/code/session_01DDZNkDVwPQnevTFcYE47H3 Co-authored-by: Claude <noreply@anthropic.com>
…ions Claude-Session: https://claude.ai/code/session_01DDZNkDVwPQnevTFcYE47H3 Co-authored-by: Claude <noreply@anthropic.com>
📓 Docs Drift Check1 anchor(s) derived from 1 changed package(s); no hand-written page names any of them. What this run could not see
Coarse fallback — 4 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): Which tree this was computed onThis run read A worktree cut from an older # while this PR is open — GitHub drops the merge commit once it closes
git fetch origin d02f6ed3eebf8c244f2c8c88129276ca8a1993b3 && git checkout d02f6ed3eebf8c244f2c8c88129276ca8a1993b3
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 88fb5e85a02009344e9e2cf1abc929ae694c051f 99f2cfdf0620fc55a0b496ace5f77ee6c0a8b05b && git checkout -B drift-repro 88fb5e85a02009344e9e2cf1abc929ae694c051f && git merge --no-ff 99f2cfdf0620fc55a0b496ace5f77ee6c0a8b05b
node scripts/docs-audit/affected-docs.mjs --json 88fb5e85a02009344e9e2cf1abc929ae694c051f |
Part of #20595
Clause-②: no
What changed
Stage 9 of the
domain:enginelane of the dead-citation sweep:packages/metadata-core/**, comment and docblock prose only, per the claim (5964472656). Stages 1 to 8 landed asa7d9768ec,d150c3039,4bf4e7e70,13a24ece2,db0cf2231,85986144c,48fa7a381andc205b6c35. #20595 stays open: the other half of this lane is the packages this stage does not touch (drivers/driver-turso14,drivers/driver-mongodb9,formula4,metadata-fs2 on the census after this stage, 29 in all), plus the test-string sites the card carries for a widened stage.Every comment or docblock site in the package that cited a tracker number answering 404 is rewritten in ruling C+D's form C (record
5749154545on #19123), in the form #20234 applies it to the spec tree: the ADR when one records the decision, otherwise the commit in this repository's history that made it. That is 30 sites on 29 lines in 14 files, covering 12 numbers:src/): the wholeallocated-but-absentpopulation of the gate's own census in this package at the base;tsup.config.ts:43([finding] a THIRD hardcoded discoveryversionliteral —getDiscovery()inpackages/metadata-protocol/src/protocol.ts, identical defect to #10993, different producer/package #11235, a//line), stage 1's and stage 6'stsup.config.tsprecedent;src(v17 GA 交付物:AI 一键升级客户元数据项目(升级 skill,骑在 D2 conversions 之上) #6111,/metaorg scope is decided from the RAW url spelling:translations/email_templatesread and write env-wide where their singular twin is org-scoped #10340, Four morecurrent_userbinding-text sites are stale — including the form SECTION slot, whose verdict flipped when objectui#6110 / #6111 landed #12914,retiredFromLoadPath: truedoes not keep a conversion off any load path — three runtime seams replay every retired entry withincludeRetired: true, andapply.tssays onlymigrate metadoes #16864);form-predicate-root-policy.test.ts:149carries two numbers on one line.No comment-id citation is dead here: the package's one comment id, ruling
5865890672on #20390 (artifact-forward-conversion.test.ts:486), answers 200 (see Census).Anchors: 10 numbers by commit, 1 by ADR, 1 by repository qualifier; 10 distinct shas. 9 numbers reuse the anchor another lane or stage already used for them, and #6111 takes stage 6's respelling. Measured here are
b5a239815(#12930) and, for #16864, the ADR-0087 passage this sentence needs (the spec lane anchored the same number to ADR-0087 for a different sentence; see the table).Only comments changed. Every file keeps its line count (29 lines out, 29 in, plus the changeset), so no line citation into any of them moves. No code token moves (the guard below). No citation number is added: on every changed line the numbers on the new text are a subset of those on the old (the only numbers on
+lines are #10101 twice and #7894, each already on its line and each answering 200, and theobjectui#…references, which are cross-repository).A
patchchangeset: 13 of the 20 rewritten non-test lines are in the publisheddist(the.d.tskeeps JSDoc on exported members), anddistis not byte-identical with the base text (see Changeset).H0: the package and its size
The gate's own
node scripts/check-issue-citations.mjs --census --jsonat basec205b6c35(the before run below),allocated-but-absentper remainingdomain:enginepackage:metadata-coredrivers/driver-tursodrivers/driver-mongodbformulametadata-fscore,metadata-protocol,objectql,metadata,drivers/driver-sql,drivers/driver-memory,drivers/driver-sqlite-wasm,plugins/plugin-pinyin-search,platform-objectsThe lane total goes 48 to 29.
metadata-coreis the largest remaining package and reads 19, as at stage 8's head census (a4c483901), so the stage went ahead.Census:
metadata-core, before and afterInstrument (A1). The gate's own
node scripts/check-issue-citations.mjs --census --json, read-only and unchanged. The count is itsallocated-but-absentfindings underpackages/metadata-core/.allocated-but-absentc205b6c35, run 02:14:44Z to 02:18:05Zfcee5ffe3, run 02:32:19Z to 02:35:31ZThe whole-repo drop is 19, and the two finding sets differ by exactly the 19 rows of this package, removed; none was added.
resolves(35,468) andresolves-as-pull-request(2,388) did not move;cross-repo-unjudgedwent 1,244 to 1,247, the three census-surfaceobjectui#6111respellings.The head's later commits are the changeset and one merge of
main. The census was run a third time at the head99f2cfdf0(02:45:55Z to 02:49:05Z, 194 pages, frontier #21524, 19,345 records, newest #21522 before and #21524 after): whole-repo 144,metadata-core0, and itsallocated-but-absentfinding set is identical to the after run's (0 removed, 0 added). Itsresolvesreads 35,483, 15 more than above, from the mergedmaincommits outside this package.Supplementary instrument, the whole package. The census reads neither test files nor strings nor files outside
src. A second reading runs the gate's own exportedextractCitations(whole-file and comment-prose projections) over every tracked file in the package (57) and classifies each citation with the gate'sclassifyCitationagainst one board enumerated by the gate'senumerateBoard(194 pages, frontier #21521, 19,342 records, read 02:18:40Z to 02:21:51Z), the same board for both readings. Every one of the 12 numbers was then read on its own over the issues endpoint (02:29:32Z): all 12 answer 404; the lit controls#5286and#12624answer 200, and so do the two numbers that stay on changed lines (#10101, #7894).tsup.config.tscommentc205b6c35fcee5ffe3The citation count drops by 25: the 30 rewritten sites less the 5
objectui#6111respellings, which stay citations as cross-repository ones (src comment cross-repo 16 to 19, test comment 6 to 8). The live counts did not move (src comment: 325 resolve, 6 as pull requests; test comment: 49 and 4). A third, raw reading (every#followed by 2 to 6 digits, whatever surrounds it,CHANGELOG.mdaside) counts 477 before and 452 after: also a drop of 25.Comment ids. Every ten-digit run under
packages/metadata-core(itsCHANGELOG.mdaside) was read: two lines.artifact-forward-conversion.test.ts:486cites ruling5865890672, which answers 200 (the #20390 ruling comment; the control5964472656, the claim, answers 200 too).contract-suite.ts:331is a zero-filledsha256:fixture, not a citation.The objectui number.
objectui#6111was read in this session: it answers 200 (a closed issue, 「AuthoredFormSection.visibleWhenis dropped by all four plugin-form layouts」), besideobjectui#6110andobjectui#6010, both 200. Stage 6 could not read objectui from its container and reused the spec lane's reading; this one is direct.Per-number table
censuscounts census sites,outsidethe one site outside the census glob,testthe test-comment sites. Every sha matches exactly one commit (git rev-parse --disambiguate, count 1) and is an ancestor of the basec205b6c35(git merge-base --is-ancestor, exit 0 for all 10; the clone is not shallow). The+lines carry exactly these 10 nine-hex spans as new ones. Each commit names the number it replaces, in its message, its diff or both (b5a239815in its subject's squash suffix only;f887e5249in its message only).git blameat the base puts 10 of the 23 commit-anchored lines on their anchor; the other 13 were written by a commit that cites the number as an earlier decision (200d255e7citing #12914 and #12930,1272f0a6bciting #8707 and #8778,15eb2c97fciting #10340 and #8919,46644e25aciting #11021,15d55fb24citing #11235), and in each case the anchor is the commit that made the change the sentence credits to the number.sourcesays whether another lane or stage already used this anchor for this number (reused) or it was measured here (measured).#6111objectui#61112123fcca3)#87071408fe385[#8707 / #10101]reads[commit 1408fe385 / #10101], the plugin-audit lane's spelling of the same pair#87787901b2dd2#8919b5378550e/metapublish and rollback onmanage_metadata#10062fa5d137abcode-artifact-provenance.ts#1034026f3588fb/metaorg scope on the folded type; it corrected the measured-false parity claim inmeta-write-org-scope.tsand wrote that file's test header#10842f334d662ewatch(_, since)replays from history; it settled that card and deleted theresumableWatchdeclaration the example quoted#110217d81c889fclose()terminates watch iterators instead of emitting a drain event; it wrote the invariant's MUST NOT#11235376c70f98version; it addedshims: truetopackages/metadata-protocol/tsup.config.ts, the line this one mirrors#12914f887e5249visibleWhenbindscurrent_usertoo: it re-measured the section contract sentence#12930b5a239815visibleWhenbindscurrent_user: it re-measured the field prose (2026-08-28, the same day as the vocabulary correction2852accef)#16864a8acee28danchored #16864 to ADR-0087's 2026-09-13 addendum for the three-seam sentence24a86923dwith the 2026-09-13 addendum (「the code half is #16864's」).29dd1a6dd, the commit that settled that card and wrote the flag's own docblock, says the same; the ADR comes firstNo ADR or ruling record names any of the 12 numbers:
git grepoverdocs/adrandscripts/adr-anchorsfinds none of them. ADR-0087 records #16864's determination without naming the number.Wordings to check
Most rewrites swap a tag in place (
[#N]to[commit SHA],(#N)to(commit SHA),#N re-measuredtocommit SHA re-measured, a#N —header toCommit SHA —, stage 1's form). These say more than the tag:record-organization.ts:19): 「> Ruled: Option A — extend the spec: audit stamping needs a read-neutral organization declaration —tenancy.tenantFieldcannot servesys_api_keywithout walling the credential table (#8707 remainder) #8778 ruling: …」 became 「> Ruled: Option A — extend the [commit 7901b2d] ruling: …」. The square brackets are an editorial substitution, stage 5's form for a dead number inside a quotation (memory-driver-document-not.test.ts, 「[commit 9dac1ae]'s」), so the quotation stays recognisable as one and says where it was edited. The rest of the quotation is unchanged, and the ruling itself stands on cloud#1395. Stage 5's case was a note quoting itself; this is a maintainer ruling, so it is listed here for the seat's check. The alternative is to leave the quotation untouched and carry its one site (the census would then readmetadata-core1).contract-suite.ts:90): 「Value is the tracking issue, e.g.'#10842'.」 became 「Value is the tracking issue, e.g. the one commit f334d66 closed.」. The example wasSysMetadataRepository's own declaration, whichf334d662edeleted when it closed the number (its message says that declaration 「is deleted; the pin it swapped in went red when replay landed」), as the docblock's next sentence says. Stage 8's 「the one commit 83a3b1f closed」 form; stage 1 wrote the same value asresumableWatch: ….artifact-forward-conversion.ts:101and its test:360): 「(retiredFromLoadPath: truedoes not keep a conversion off any load path — three runtime seams replay every retired entry withincludeRetired: true, andapply.tssays onlymigrate metadoes #16864's determination, and the flag's own docblock now says so)」 became 「(ADR-0087's recorded determination, and the flag's own docblock now says so)」; 「(retiredFromLoadPath: truedoes not keep a conversion off any load path — three runtime seams replay every retired entry withincludeRetired: true, andapply.tssays onlymigrate metadoes #16864's / determination, landed)」 became 「(ADR-0087's recorded / determination, landed)」, with:361unchanged./metaorg scope is decided from the RAW url spelling:translations/email_templatesread and write env-wide where their singular twin is org-scoped #10340 measurement inmeta-write-org-scope.ts」 became 「the measurement commit 26f3588 wrote inmeta-write-org-scope.ts」 (meta-write-capability.ts:116), and 「the/metaorg scope is decided from the RAW url spelling:translations/email_templatesread and write env-wide where their singular twin is org-scoped #10340 measurement」 became 「the measurement commit 26f3588 wrote」 (meta-write-capability.test.ts:124).current_userbinding-text sites are stale — including the form SECTION slot, whose verdict flipped when objectui#6110 / #6111 landed #12914 replaced that sentence」 became 「Commit f887e52 replaced that sentence」 (form-predicate-root-policy.test.ts:108), and 「silent. Four morecurrent_userbinding-text sites are stale — including the form SECTION slot, whose verdict flipped when objectui#6110 / #6111 landed #12914 replaced that contract」 became 「silent. Commit f887e52 replaced that contract」 (:279)./metaorg scope is decided from the RAW url spelling:translations/email_templatesread and write env-wide where their singular twin is org-scoped #10340 / meta-plural-url-bypass:PUT /meta/fields/<name>walks around the whole two-tier registry gate — 4 registry types have no entry inPLURAL_TO_SINGULAR#7894」 became 「commit 26f3588 / meta-plural-url-bypass:PUT /meta/fields/<name>walks around the whole two-tier registry gate — 4 registry types have no entry inPLURAL_TO_SINGULAR#7894」 (meta-write-org-scope.test.ts:22);[#8707 / #10101]keeps#10101(index.ts:120,record-organization.ts:4).eslint.config.mjsdeclares no line-length rule, and a reflow would move neighbouring lines and every line citation into the file).Sites left
meta-write-org-scope.test.ts:39, thedescribetitle 「/metaorg scope is decided from the RAW url spelling:translations/email_templatesread and write env-wide where their singular twin is org-scoped #10340 org scope composed with the boundary fold」./metaorg scope is decided from the RAW url spelling:translations/email_templatesread and write env-wide where their singular twin is org-scoped #10340 is in this stage's table (26f3588fb). Strings are outside this stage's surface; non-test strings cite none.src: the release-ownedCHANGELOG.mdnames dead numbers on 9 sites; left.Mechanical guard: no code token moves
The guard (stages 2 to 8's) compares base
c205b6c35against the tree over all 14 touched files, with TypeScript 6.0.3:forEachChildwalk. Comments are trivia there, and JSDoc is never visited. A leaf that is not itself a token is re-scanned with trivia skipped.getChildrenwalk, JSDoc nodes skipped. String, template and numeric literals are compared in full on both readings.Results, at
fcee5ffe3:form-predicate-root-policy.ts): 0 files changed (exit 0).BOUND_FORM_FIELD_PREDICATE_ROOTStoXBOUND_…,form-predicate-root-policy.ts): DIFFER on both readings (exit 1).contract-suite.ts): DIFFER on both readings (exit 1).setTimeout(resolve, 100)to101,contract-suite.ts): DIFFER on both readings (exit 1).sourcemap: truetofalse,tsup.config.ts): DIFFER on both readings (exit 1).Each mutation went through
scripts/ablation-replace.mjs(wrap mode, anchor hit 1 to 0, blob changed) under a shell trap that restores by absolute path fromHEAD. Each restore was proven equal to itsHEADblob (ce90e5aab5fa,cc43d4b043b5,23fa6b1bd3aa), withgit diff HEADempty and a clean tree afterwards. The identifier control's first attempt was refused byablation-replacebefore the guard ran (its replacement contained the anchor, so the anchor count moved 1 to 1); the anchor was changed and the whole control set re-run, and the numbers above are that run's.Changeset:
patch(distmeasured)files[]isdist,README.mdandCHANGELOG.md, and the package is not private. In one script under the shared verify lock (VERDICT command-exit 0, held 89s), atfcee5ffe3: the dependency closure was built first (pnpm --filter '@objectstack/metadata-core^...' build), then the package's ownbuild(tsup andcheck-dts-emitted) ran three times:distfiles hashed. Of the 20 rewritten non-test lines, 13 appear verbatim indist, all in declaration files (index.d.ts/index.d.cts, the shared chunkrepository-DHMpxysr.d.ts, andtesting.d.tsforcontract-suite.ts). The 7 that do not are module docblocks,//lines and the docblock of a non-exported constant (ORG_OVERRIDABLE_TYPES):artifact-forward-conversion.ts:101,index.ts:57and:120,record-organization.ts:4and:19,meta-write-org-scope.ts:77,tsup.config.ts:43.index.d.ts,index.d.cts,repository-DHMpxysr.d.ts,testing.d.ts); the JavaScript files and their sourcemaps do not.scripts/ablation-dist-preflight.mjsfinds the base marker 「the/metaorg scope is decided from the RAW url spelling:translations/email_templatesread and write env-wide where their singular twin is org-scoped #10340 measurement in」 in 2 built files (index.d.ts,index.d.cts; exit 0).HEADblob,git diff HEADempty, porcelain empty): all 12 files are byte-identical to leg 1, and the preflight's--absentreading exits 0 with a clean tree, so the build is deterministic and the difference is the rewrite.So the rewrite ships, and
.changeset/20595-metadata-core-provenance-anchors.mddeclares apatchfor@objectstack/metadata-core, comment text only, with the claim'sClause-②: noline. It names every anchor that is not a commit: ADR-0087 for the tworetiredFromLoadPathsites, the fiveobjectui#6111respellings, and the bracketed substitution inside the quoted ruling. The changeset commit touches no file underpackages/metadata-core.Gates (head
99f2cfdf0)node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commandsat99f2cfdf0(15 paths against merge base88fb5e85a, 78 changed lines) derived 62 commands. All 62 ran, each exit code captured before any pipe: 62 exit 0.--ranreports 「62 derived, 62 run, 0 NOT-MEASURED, 0 UNRUN」 (a derived zero) and exits 0. The PM's lead derivation (54 commands, treec205b6c35) is a subset: the extra 8 are the families the.changeset/path adds (the ADR-0087 registration and empty-changeset pairs,check:objectui-changeset,check:pm-changeset-deadline-censusand two release self-tests).node scripts/check-changeset-fixed.mjs,pnpm check:authz-resolver,pnpm check:error-code-casingandpnpm check:filter-alias-parity: 4 exit 0.node scripts/check-issue-citations.mjsexits 0 (「every citation this change adds resolves (or is a declared cross-repo reference)」: 8 judged across 9 files: 6 cross-repo and 2 live, theobjectui#…pairs and PromoteresolveRecordOrganizationFieldto the shared platform-row resolver (approvals + automation runs), per the ruled cloud#1395 Option A #10101 kept on changed lines);pnpm check:issue-citationsexits 0 (self-test, 173 cases, 9 batteries);pnpm check:doc-authoringexits 0 (the sibling-package prose-id baseline holds, no growth);pnpm check:nul-bytesexits 0 (9,870 files, no raw control bytes), and a control-byte grep over the 15 changed files finds none (exit 1). The four changeset gates (check-changeset-no-major,check-adr-0087-registration,check-empty-changesetwith--base origin/main, andcheck:changeset-gate-self-tests) exit 0.turbo run build --filter='./packages/*' --filter='./packages/*/*' --concurrency=2, 71 of 71 tasks, 8 cached; VERDICT command-exit 0, held 246s); then at99f2cfdf0pnpm --filter @objectstack/metadata-core test: 16 test files pass (16), 298 tests pass (298);pnpm --filter @objectstack/metadata-core typecheck(tsc --noEmit && tsc --noEmit -p tsconfig.test.json) exits 0 (VERDICT command-exit 0, held 18s).tsc --listFilesOnlyputs all 16 tracked test files intsconfig.test.json's program and the 9 changed non-testsrcfiles intsconfig.json's;tsup.config.tsis in neither, and the token guard covers it. No importing package owes a run: the declaration files change only in comment text.99f2cfdf0: eslint with inline config disabled, over the 14 touched.tsfiles plusdist/index.jsas the control: 15 results, 0 errors and 1 warning, the control's ignore notice; none of the 14 is reported ignored.eslint.config.mjsnever enables type-aware linting (its lines 327 and 328 say so), so a comment edit cannot move the verdict on an untouched file. The repo-widepnpm lintis CI's run.Acceptance notes
c205b6c35and mergesmainonce, pinned to88fb5e85a(merge99f2cfdf0, no conflict). The three commits it brought (2df621af3,verify;e9dec3dab,metadata-protocol;88fb5e85a,plugin-approvals) touch neitherpackages/metadata-core,check-issue-citations.mjsnordispatch-gates.mjs; the workspace was rebuilt after the merge, before the tests and gates. The net diff againstmainis the 14 rewritten files (+29/−29) and the changeset (+20).metadata-fs(this lane's later stage) names [finding]SysMetadataRepository.close()cannot drain a filtered or numeric-sincewatcher — the pendingnext()never settles and the consumer'sfor-awaithangs #11021 insrc/repository.tsandsrc/sync.ts, where7d81c889fis the anchor; other lanes' test files carry/metaorg scope is decided from the RAW url spelling:translations/email_templatesread and write env-wide where their singular twin is org-scoped #10340,SysMetadataRepository.watch()never replays fromsys_metadata_history— contract invariant 6 (resumability) is unimplemented in the repository backing every production metadata write #10842, Audit rows are stamped from the ACTOR's active organization in preference to the record's own — and the record-side fallback cannot seesys_api_key.active_organization_id#8707, spec: audit stamping needs a read-neutral organization declaration —tenancy.tenantFieldcannot servesys_api_keywithout walling the credential table (#8707 remainder) #8778, The REST/metapublish and rollback doors carry nomanage_metadatagate, so the authoring capability the PUT/DELETE doors enforce is reachable around #8919, [finding]SysMetadataRepository.close()cannot drain a filtered or numeric-sincewatcher — the pendingnext()never settles and the consumer'sfor-awaithangs #11021, [finding] a THIRD hardcoded discoveryversionliteral —getDiscovery()inpackages/metadata-protocol/src/protocol.ts, identical defect to #10993, different producer/package #11235 andretiredFromLoadPath: truedoes not keep a conversion off any load path — three runtime seams replay every retired entry withincludeRetired: true, andapply.tssays onlymigrate metadoes #16864;scripts/check-undeclared-dep-imports.mjsandscripts/check-dual-build-cjs-loads.mjsname [finding]service-datasource's published source type-imports from a dev-only workspace dependency — the one instance repo-wide, and nothing checks the class #10062 and [finding] a THIRD hardcoded discoveryversionliteral —getDiscovery()inpackages/metadata-protocol/src/protocol.ts, identical defect to #10993, different producer/package #11235 (gate scripts, outside the census surface); the release pages name Audit rows are stamped from the ACTOR's active organization in preference to the record's own — and the record-side fallback cannot seesys_api_key.active_organization_id#8707, spec: audit stamping needs a read-neutral organization declaration —tenancy.tenantFieldcannot servesys_api_keywithout walling the credential table (#8707 remainder) #8778 and The REST/metapublish and rollback doors carry nomanage_metadatagate, so the authoring capability the PUT/DELETE doors enforce is reachable around #8919 (release-owned).resumableWatchcontract asks for a tracking issue.DeclaredDivergences.resumableWatchis documented as 「the tracking issue」,runRepositoryContractTestsrefuses a blank one, and its value is printed into a test title (「DECLARED DIVERGENCE …」) of the published./testingsuite. No implementation declares one today, so nothing ships a number; a future declaration would put a tracker number into that title. Not this stage's surface (a contract, not a comment); noted, not filed.Generated by Claude Code