docs(service-analytics): re-anchor the dead tracker citations to the commits that decided them - #20729
Conversation
…commits that decided them Every comment or docblock site under packages/services/service-analytics/src that cited a tracker number answering 404 now cites the commit in this repository's history that decided what the line describes (ruling C+D, form C): 76 sites on 76 lines in 22 files, 14 numbers, 13 anchor commits. Two further lines are reflowed; every file keeps its line count. Comments only: no code token moves, and the 8 dead numbers inside test titles and test strings are left as they are. Claude-Session: https://claude.ai/code/session_01XY5uCwTjZj7884yYtyur4H Co-authored-by: Claude <noreply@anthropic.com>
…ring The rewritten docblocks and inline comments reach dist/ (measured on the built package), so the change ships bytes and takes a patch changeset. Claude-Session: https://claude.ai/code/session_01XY5uCwTjZj7884yYtyur4H Co-authored-by: Claude <noreply@anthropic.com>
📓 Docs Drift CheckThis PR changes 1 package(s): 1 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:
⛔ 3 release-owned page(s) also name something this change touched. These are read-only:
What this run could not see
Coarse fallback — 10 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 4093826c903c928374236a5c36e0ebbea3e0a21d && git checkout 4093826c903c928374236a5c36e0ebbea3e0a21d
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 671d4c164f11882e23f2196708e02b610ad7f254 82d2b40b21bc9ac0dc6d3c3af3d6dd04537c53ed && git checkout -B drift-repro 671d4c164f11882e23f2196708e02b610ad7f254 && git merge --no-ff 82d2b40b21bc9ac0dc6d3c3af3d6dd04537c53ed
node scripts/docs-audit/affected-docs.mjs --json 671d4c164f11882e23f2196708e02b610ad7f254
|
Contract reviewServed-tier: ① Derived judgmentsRead against
② Semver level
③ Boundary flagsThe dev report (
Nothing is escalated. Two readings for the seat, neither a flag on this PR: the Docs Drift Check ( Implemented-by: VERDICT: PASS Generated by Claude Code |
Part of #20596
Clause-②: no
What changed
This is the eighth stage of the
domain:serviceslane of the dead-citation sweep. It coverspackages/services/service-analytics/src/**and nothing else. By the seat's census at the claim (5899485578), it is the largest package in the lane that no in-flight work holds. Later stages cover the other packages, so this PR saysPart ofand the card stays open.Every comment or docblock site in scope that cited a tracker number answering 404 has been rewritten in ruling C+D's form C (comment 5749154545 on #19123), by the method of stages 1 to 7 (PR #20609 as
422db788a, PR #20626 asb80ab579d, PR #20634 as4d04b6be3, PR #20658 as9a4b2bb38, PR #20693 as0e9ad74fb, PR #20708 as9b384f63a, PR #20717 ascbaf04c1f). That is 76 sites on 76 lines in 22 files, covering 14 numbers:The raw scan found no dead site the gate's grammar cannot see (see Acceptance notes), so there is no third class this time.
Each rewritten line now cites the commit in
origin/mainhistory that decided what the line describes, and says in its own words what was decided: 13 distinct shas. No number in this package has an ADR or ruling record of its own in the repository (a grep ofdocs/adr/for all 14 finds none, and a grep of the rest ofdocs/finds none either), so every anchor is a commit, per ruling C's order. No number was dropped.Only comments changed. Every touched source file keeps its line count (78 lines out, 78 in, over 22 files), so no line citation into these files moves. 2 of those 78 lines hold no dead citation: they are reflow lines, listed under Wordings below. No code token moves (see the guard below).
No citation number is added. Every tracker number on an added line was already on the line it replaces:
#10861(5 lines),#12776(3),#10413(2),#16750(2), and#10759,#11152,#5716and the decision-batch ordinal#59once each. Each tracker number among them resolves. Over the whole diff, added minus removed is 0 or negative for every number, and no number is new to the diff. No PR number is the citation on an added line: the twoPR #Nspellings in scope became their pull request's squash commit, and#16750stays only as the convenience link besideed7243d52, on the line it already stood on.Eight dead sites are left on purpose, all of them test strings (see the list below).
One more file: a
patchchangeset for@objectstack/service-analytics, because the rewritten docblocks and inline comments ship (see Changeset below).The
AnalyticsResultWithDrilltype and its four sidecar members are not touched: its docblocks carry no dead number (#20644,#3214and#1752all resolve).Census:
service-analytics, before and afterInstrument (A1). The gate's own
node scripts/check-issue-citations.mjs --census --json, read-only and unchanged. The count below is itsallocated-but-absentfindings underpackages/services/service-analytics/. Each run counts as a reading only because its board frontier equals the newest issue number, read by a separate request just before and just after the run.allocated-but-absentcbaf04c1f, run 2026-09-29T21:41:53Z to 21:45:05Z967d73531, run 21:55:23Z to 21:58:36ZThe before count matches the seat's census at the claim and A1 (42 sites): the two comments PR #20712 rewrote in
analytics-service.tsdid not move it. The whole-repo drop is 42, exactly this diff's census sites. Theresolvestally is 32,991 in both runs, andresolves-as-pull-request(1,984) andcross-repo-unjudged(995) did not move either. The after run was taken on967d73531; the head82d2b40b2adds only the changeset. No run was truncated or discarded: both enumerations read 186 pages at the newest frontier.Supplementary instrument, the whole scope. The census does not read test files or strings, and this stage's scope includes test comments. So a second reading runs the gate's own exported
extractCitations(whole-file and comment-prose projections) andnamesThisRepositoryover every.tsfile underservice-analytics/src(162 files). It takes its verdicts from the before census's own board reading rather than from a second enumeration: a number is dead when that census reported itallocated-but-absent, and alive when that census judged it on this board anywhere (its--listextraction) and did not report it. The 21 numbers the census never saw, because they stand only in test files or strings here, were read one by one on the issues endpoint: 17 answer 200, and#16778,#16860,#16918and#17125answer 404.cbaf04c1f967d73531Its src-comment column equals the census's 42, which is the control on the second instrument. The 3,410 live citations and the 20 cross-repo citations are the same in both readings, and the drop of 76 citations is exactly the rewritten sites. A third, raw reading (every
#followed by 2 to 6 digits, whatever surrounds it) finds 3,598 occurrences and 84 dead before, 3,522 and 8 after; its residue equals the gate's residue site for site, and it sees no dead site beyond the gate.Per-number table
Sites and files count every dead occurrence in scope at the base (comments and strings, tests included).
rewritten / leftcounts the sites rewritten and the sites left. Each anchor was read in its message and diff, not only its subject, andgit blameat the base puts every rewritten line in its anchor commit or in a later commit that descends from it (merge-base --is-ancestorexit 0 for each pair).#11461399ecad58: a cross-object leaf in one measure's ownfilter(the third producer, lowered ontoaggregations[].filter) is refused on both ObjectQL doors withINVALID_FIELD/ 400 naming the measure, folded into the one member view, with insertion order keeping every earlier refusal's message. The last line of its message names#11461as the card it settles. New to the sweep#1713054b3d1d4a(PR #17336): the row-scope resolution refusals carryREAD_SCOPE_COMPILE_FAILED/ 500 through one constructor, soqueryDataset's catch re-throws them instead of reading their words, every message byte-unchanged; plus the source-derived wording-collision guard. Named in its diff only (18 added lines carry the tag). New to the sweep#1712486c505286(PR #17593):explicitDateRangeWindowis the one reading ofdateRange's array arm on all four faces, and an array that is not two string bounds is refused withANALYTICS_DATE_RANGE_UNRECOGNIZED/ 400. Named in its diff only (its changeset file is17124-daterange-array-arm-arity.md). New to the sweep#12209017130a09(PR #12318): a custom-SQL measure is refused on the ObjectQL aggregate path withINVALID_FIELD/ 400, keyed on theEXPRESSION_METRIC_TYPESpartition shared withNativeSQLStrategy. Its message records the two failure modes the lines describe (driver-sqlblaming afunctionkey, the in-memory evaluator answeringnullper bucket). Named in its diff only. New to the sweep#16778357f4992b: the compile-leg refusal of an aggregate a datetime measure's field type cannot carry, scoped to temporal source fields. The squash commit of the pull request that was#16778; its subject carries the number. New to the sweep#12940aa16721b6(PR #13361): this package's consumer-localexecuteAggregateconfig mirrors (the plugin options andAnalyticsServiceConfig) narrowaggregations[].methodtoAggregationFunction, after#12776narrowed the contract. Named in its diff only. New to the sweep#170150da638cd9: the closeddateRangepreset vocabulary is lowered once and the rest refused, the[range, range]fallback is removed from the faces it reached, and the shared conformance kit holds them. The squash commit of the pull request that was#17015. New to the sweep#16860041d9fdc6: the object-level read grant is asked at the analytics door, and its bridge to thesecurityservice resolves an explicit three-way (absent admits; throwing or method-less denies aterror, finding F3 in its message). The squash commit of the pull request that was#16860. New to the sweep#122488425c17cc: the five ruled engine members,getDriverForObject?andresolveEffectiveDatasourceamong them, adopted ontoIDataEngine, andgetObjecttyped. Its subject names it. Stage 5's and the spec stage's anchor#16685ed7243d52(PR #16750):boolean/toggleaccepted forsum/avg/min/maxin the aggregate × field-type table, holding maintainer ruling#11152. Its subject names it. The spec stage's anchor#171255d12b16e7: the row-scope bridge tells an absent security service from a broken one, so a broken one refuses the query. The squash commit of the pull request that was#17125(404 on the pulls endpoint too). New to the sweep#169185d12b16e7: the same commit. Its changeset's headline names#16918as the card it answers, and its diff writes the line (admission-bridge-resolution.test.ts:120)#612359d1933f9:err.codelands aterror.code, noterror.details.code; the commit that wrote this very line. Theruntimestage's anchor#132796a180e42d: permission-store read failures fail loud, and the same commit renamesmetadata/src/utils/schema-sync-errors.tstopackages/types/src/driver-error-classification.ts, the move the line describes. The anchor of stages 2, 5 and 6, and of thetypes,restandruntimestagesEvery cited sha matches exactly one commit (
git rev-parse --disambiguate, count 1 for each of the 13), and every one is an ancestor of the base (merge-base --is-ancestor, exit 0 for all 13; control leg: stage 1's landing422db788aexit 0; the history is complete,--is-shallow-repositoryfalse, 15,135 commits). Each of the 14 numbers answers 404 on the issues endpoint, read one by one;#16778,#16860,#17015and#17125answer 404 on the pulls endpoint too.Wordings to check
[#N]became[commit SHA], as in stage 7;[#10861 / #11461]and[#10861, #11461]keep the live#10861beside the new sha.measure-result-type.ts:115-116andaggregate-datetime-measure-refusal.test.ts:65-66. 「[Decision] Two maintainer rulings collide on boolean aggregates — batch #59's "every other pair refused" would refuseavg(flag), which ruling #11152 pins on six backends as having no per-aggregate exception #16685 ruled A, landed as feat(spec): accept boolean / toggle for sum / avg / min / max in the aggregate × field-type table (#16685) #16750」 and 「[Decision] Two maintainer rulings collide on boolean aggregates — batch #59's "every other pair refused" would refuseavg(flag), which ruling #11152 pins on six backends as having no per-aggregate exception #16685 was ruled A and feat(spec): accept boolean / toggle for sum / avg / min / max in the aggregate × field-type table (#16685) #16750 added」 became 「commit ed7243d (feat(spec): accept boolean / toggle for sum / avg / min / max in the aggregate × field-type table (#16685) #16750) added those rows」 and 「commit ed7243d (feat(spec): accept boolean / toggle for sum / avg / min / max in the aggregate × field-type table (#16685) #16750) added」. 「ruled A」 named an option on the dead card;ed7243d52's message records the decision itself. Line 116 of the first file and line 66 of the second are the 2 reflow lines: each keeps the#16750it already carried.read-scope-resolution-envelope.test.ts:25andrefusal-wording-collision.test.ts:21. 「PR fix(service-analytics): row-scope bridge tells absent from broken security service #17125's refusal」 became 「Commit 5d12b16's refusal」, the pull request's squash commit.read-scope-refusal.ts:29. 「[finding] a bare-Error refusal reaching queryDataset is classified by WORDING — a security refusal whose text happens to contain "not registered" becomes a 200 with an empty chart #17130 exists to remove it」 became 「commit 54b3d1d was made to remove it」, the form stage 6 used.refusal-wording-collision.test.ts:49. 「the exact move [finding] a bare-Error refusal reaching queryDataset is classified by WORDING — a security refusal whose text happens to contain "not registered" becomes a 200 with an empty chart #17130 forbids」 became 「the exact move commit 54b3d1d ruled out」; its message says the fix is the declaration, not a luckier string.read-scope-resolution-envelope.test.ts:161. The verb after the number moved from present to past tense with the sha.measure-expression-both-strategies.test.ts:45and:166. 「deleting the A custom-SQL measure reachesengine.aggregateun-refused on the ObjectQL path and answers a silentnull— the repair #12053's probe scoped #12209 arm in」 became 「deleting the arm commit 017130a added in」, and 「every A custom-SQL measure reachesengine.aggregateun-refused on the ObjectQL path and answers a silentnull— the repair #12053's probe scoped #12209 refusal」 became 「every custom-SQL refusal (commit 017130a)」.dataset-executor.ts:609. 「fix(analytics): lower the closed dateRange preset vocabulary once, and refuse the rest (#16322) #17015's kit」 became 「commit 0da638c's kit」, the conformance kit that commit built.plugin.ts:116. 「and in A custom-SQL measure reachesengine.aggregateun-refused on the ObjectQL path and answers a silentnull— the repair #12053's probe scoped #12209:」 became 「and in commit 017130a:」, whose message records the two ways the engine failed.analytics-service.ts:238. 「[finding] a permission-store read failure resolves as an AUTHENTICATED caller holding ZERO capabilities — the package door answers 403 FORBIDDEN, byte-identical to a genuine capability denial #13279 moved it there」 became 「commit 6a180e4 moved it there」; that commit's diff is the rename.The 8 sites left
describe/ittitles:crossobject-conjunct-refusal.test.ts:589(#11461);aggregate-nontemporal-measure-refusal.test.ts:243(#16778);date-range-array-arm-arity.test.ts:213and:294(#17124);read-scope-resolution-envelope.test.ts:155,:199and:226, andrefusal-wording-collision.test.ts:336(#17130).Mechanical guard: no code token moves
The guard compares the TypeScript parser's leaf nodes, with comments as trivia and JSDoc nodes never visited, base
cbaf04c1fagainst head. Template literals are therefore read in context. It ran over all 22 touched.tsfiles.plugin.ts(「refusal buys is in」 to 「refusal earns is in」): 0 files changed, as expected (exit 0).plugin.ts(field: a.field,givenas string): DIFFER (exit 1).date-range-array-arm-arity.test.ts:213): DIFFER (exit 1).Every mutation went through
scripts/ablation-replace.mjs, and each landed (anchor 1 to 0, blob changed). Each restore was proven byte-identical to the HEAD blob (ad3dc9fff4d3,a606ffbb6ead), withgit diff HEADempty and a clean tree afterwards.Changeset
This change ships bytes, so a
patchchangeset for@objectstack/service-analytics(.changeset/20596-service-analytics-provenance-anchors.md) is included. Its body is stage 7's, word for word, with the package name changed.Measured on the built package (A3):
files[]isdist,README.mdandCHANGELOG.md. After the build (a cache miss for this package, sodistis this head's source), the rewritten comments reachdist:399ecad586 times in each ofdist/index.js,index.cjs,index.d.tsandindex.d.cts;86c505286twice in each JS file and once in each declaration file;54b3d1d4aonce in all four;aa16721b6once in each JS file and twice in each declaration file;017130a09once in each JS file. Positive controls: the unchanged line 「none of the coverage: a compiled measure's own」, in the same docblock as the shipped rewrite atobjectql-strategy.ts:744, is found once in each of the four files, and the unchanged line 「back into line. Widening it here again would not be a local matter」 beside the shipped rewrite atanalytics-service.ts:559once in each declaration file. A never-written negative phrase appears nowhere indist. None of the 14 dead numbers is left anywhere indist.Gates (head
82d2b40b2)pnpm check:issue-citations(self-test) exits 0.node scripts/check-issue-citations.mjsexits 0: the diff-scoped run judged 11 citations across 10 files; 10 resolve and 1 resolves as a pull request (#16750, the convenience link that already stood on its line).pnpm check:doc-authoringexits 0.node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstackat82d2b40b2derived 62 commands: all 56 derived at dispatch, pluscheck:engine-double-contract,check:objectql-double-limit,check:query-options-erasure,check:type-check-coverage,check:type-check-debtandcheck:where-matcher. Each ran with its exit code captured before any pipe, and all 62 exit 0.--ran, fed each command with its exit code, reports 62 run, 0 NOT MEASURED (a derived zero), 0 unrun, and exits 0. A fullturbo run buildof./packages/*and./packages/*/*ran first under the shared verify lock (71 of 71 tasks, exit 0), so no gate hit an unbuilt workspace.node scripts/check-changeset-fixed.mjs,pnpm check:authz-resolver,pnpm check:error-code-casingandpnpm check:filter-alias-parity, each exit 0.pnpm --filter @objectstack/service-analytics test: 137 files pass and 3,216 tests pass. That is every test file in the package, the 12 touched ones included.pnpm --filter @objectstack/service-analytics typecheckexits 0 (tsc --noEmitontsconfig.json).--listFiles: the program holds all 162 files undersrc/, the 137 test files and all 22 touched files included.eslint --no-inline-config --format jsonover the 22 touched.tsfiles gives 22 files, 0 errors and 0 warnings. All 22 are in eslint's own population (isPathIgnoredis false for each; adistfile, as the control, is ignored).eslint.config.mjsnever enables type-aware linting (noparserOptions.project, as its own lines 327-328 state), so a comment edit here cannot move the verdict on any untouched file. The repo-widepnpm lintis CI's run.pnpm check:nul-bytesexits 0, and a raw scan of the 23 changed files for control bytes finds none.Acceptance notes
CITATION_RErefuses a hyphen after the digits and a/before the#(check-issue-citations closeout (extractor spellings):CITATION_RErefuses a hyphen after the digits, so a dead#N-wordcitation (#13398-class) is invisible to the diff gate and to the census #20636), andNON_CITATION_HEADSexcuses a number after the word 「option」. In this package:#N-word: 8 lines by a plain grep, and 7 once a hyphen before the#is excluded too, which is the claim's 7. The eighth is 「pre-/api/v1/analytics/querystill drops per-measure and dataset-levelfilteron the ObjectQL path —engine.aggregatereceives no filter at all #10413-phase-2」 (execution-context-bridge.test.ts:223). The numbers,#10413,#5298,#13570and#13640, all resolve.#A/#B: 29 lines, the claim's 29, over 28 distinct numbers. All resolve;#2149, which the census never judged, was read on its own.option #N: none.So nothing here needed a rewrite beyond the gate, and the raw scan agrees.
queryDataset's catch, not changed. Nine comment lines sayqueryDataset's catch. Since10c36cc43that catch sits in the privateanswerDataset, whose docblock calls it the body ofqueryDataset, so the lines still hold at the level of the public method. This is not a dead citation, so it is outside this stage.#11461→399ecad58;#17130→54b3d1d4a;#17124→86c505286;#12209→017130a09;#16778→357f4992b;#12940→aa16721b6;#17015→0da638cd9;#16860→041d9fdc6;#17125and#16918→5d12b16e7.mainatcbaf04c1f.mainhas since moved four commits (3711e0b76,61455de27,6afccda5a,671d4c164). They touchpackages/spec,packages/metadata/package.json,pnpm-lock.yaml, docs and changesets, and no file underservice-analyticsor in this diff, so no merge was taken; the merge queue rebuilds on the merged generation. One of them,671d4c164, declares the four drill-through sidecars onAnalyticsResultin the spec. This diff leaves the localAnalyticsResultWithDrilluntouched, as the claim requires.Generated by Claude Code