Skip to content

Commit 13a24ec

Browse files
docs(driver-sql): re-anchor the dead tracker citations to the commits and ADR that decided them (stage 4 of #20595) (#21357)
Part of #20595 Clause-②: no ## What changed Stage 4 of the `domain:engine` lane of the dead-citation sweep: `packages/drivers/driver-sql/**`, comment and docblock prose only, per the claim (`5946343111`). Stages 1 to 3 landed as `a7d9768ec`, `d150c3039` and `4bf4e7e70`; #20595 stays open for the next 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 `5749154545` on #19123): the ADR when one records the decision, otherwise the commit in this repository's history that made it. That is **291 sites on 287 lines in 54 files, covering 39 numbers**, plus one dead comment-id citation on two lines: - **126 census sites** (122 lines, 6 files under `src/`): the whole `allocated-but-absent` population of the gate's own census in this package at the base, the slash-joined `#7737/#10629` and `#14079/#15683/#17343` from the post-landing census (`5923084795`, now at `sql-driver.ts:12236` and `:16167`) included. That census's third driver-sql site, the URL-spelled `#17590` at `:3933`, left the file in `58a77dbde2` before this base; - **165 test-comment sites** (165 lines, 48 test files), which the census defers. They carry 37 numbers: 24 the census itself reads as dead in this package's `src`, 5 more it reads as dead elsewhere in the repository, and 8 it never judges (they stand only in test files), which the board and a single read each settle; - **the dead comment id `5448627494`** (on #11152, which is live), on two test-comment lines. **Anchors: 38 numbers by commit, 1 by ADR (ADR-0104's 2026-09-05 addendum), 0 by words alone; the comment id by commit.** 16 numbers reuse the anchor another lane or stage already measured for them, 23 were measured here (one of them split across two commits), and the comment id was measured here. 39 distinct shas. Only comments changed. Every file keeps its line count (291 lines out, 291 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, and the diff-scoped gate judged the 10 citations left on changed lines: all 10 resolve. **A `patch` changeset**: 57 of the 122 rewritten non-test lines are in the published `dist` (the `.d.ts` keeps JSDoc on exported members, and esbuild keeps some comments in the JS), and `dist` is not byte-identical with the base text (see Changeset). ## Census: `driver-sql`, before and after **Instrument (A1).** The gate's own `node scripts/check-issue-citations.mjs --census --json`, read-only and unchanged. The count is its `allocated-but-absent` findings under `packages/drivers/driver-sql/`. | reading | tree | board | whole-repo `allocated-but-absent` | sites | lines | files | numbers | |---|---|---|---|---|---|---|---| | before | base `222ecc27f`, run 05:52:18Z to 05:55:43Z | enumerated, 192 pages, frontier #21345, 19,166 records (newest number read before and after the run: #21345) | 456 | **126** | 122 | 6 | 26 | | after | `286316ebd`, run 06:17:50Z to 06:21:10Z | enumerated, 192 pages, frontier #21352, 19,173 records (newest before: #21351, after: #21352) | 330 | **0** | 0 | 0 | 0 | The whole-repo drop is 126, and the two finding sets differ by exactly the 126 rows of this package, removed; none was added. `resolves` (34,789), `resolves-as-pull-request` (2,378) and `cross-repo-unjudged` (1,155) did not move. The card's 128 was taken at `f11b5f20a2` with the older extractor; the base here reads 126. The head's later commits are a merge of `main` that touches no file under `packages/drivers/driver-sql` and the changeset (outside the census surface). **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 exported `extractCitations` (whole-file and comment-prose projections) over every tracked file in the package (238 `.ts`, `package.json`, `tsconfig.json`, `README.md`, `CHANGELOG.md`, `LICENSE`), and classifies each citation with the gate's `classifyCitation` against one board enumerated by the gate's `enumerateBoard` (192 pages, frontier #21346, 19,167 records, 05:56:41Z to 05:59:55Z). Every one of the 39 numbers in the population was then read on its own over the issues endpoint: **all 39 answer 404**, and the lit controls `#5286` and `#12624` answer 200. | reading | citations | dead | src comment | test comment | test string | changelog | |---|---|---|---|---|---|---| | before, `222ecc27f` | 4,920 | **411** | 126 | 165 | 51 | 69 | | after, working tree at `286316ebd` | 4,629 | **120** | 0 | 0 | 51 | 69 | The drop of 291 citations is exactly the rewritten sites, and the live counts did not move (src comment: 1,352 resolve, 24 as pull requests; test comment: 1,545 and 69). A third, raw reading (every `#` followed by 2 to 6 digits, whatever surrounds it) counts 4,939 before and 4,654 after: a drop of 285, which is the 291 less the 6 URL-spelled sites that carry no `#`. **Comment ids.** Nine distinct comment-id citations stand on 19 sites in this package (`CHANGELOG.md` aside). Each was read over the issue-comments endpoint: `5448627494` answers 404 (it was a comment on #11152, which itself answers 200), and the other eight answer 200 (`5302931807`, `5404884704`, `5556979386`, `5618311630`, `5861435168`, `5865693155`, `5881556735`, `5934879010`; control `5946343111`, 200). So the one dead id is in the population. `5642107998`, the ruling record `e04a0aff2`'s message names for #17590, also answers 404; it is not cited in this package. ## Per-number table `src` counts census sites, `test` counts test-comment sites. Every sha below matches exactly one commit (`git rev-parse --disambiguate`, count 1) and is an ancestor of the merge base `04f0cc499` (`git merge-base --is-ancestor`, exit 0 for all 39 shas; the clone is not shallow). The `+` lines introduce exactly these 39 nine-hex spans and no other. The message or the diff of each one names the number it replaces: 23 in the message and the diff, 11 in the diff alone (`#11065`, `#11374` for `d0e3a885b`, `#12978`, `#12999`, `#13015`, `#13324`, `#14438`, `#14628`, `#16649`, `#16711`, `#17586`), 3 only in the subject's squash suffix (`#6076`, `#14434`, `#17876`, where the dead number was that pull request's own and the commit is its squash), and one exception, `#10629`, explained under Wordings to check. `f6fa22ce1`'s diff names the comment id three times. Where a sentence credits a ruling, a measurement, a review or a note to the number, the anchor's own message or diff carries it (checked per site; the ones that needed a reworded sentence are listed below). `source` says whether another lane or stage already used this anchor for this number (`reused`) or it was measured here (`measured`). | number | src | test | anchor | kind | source | what it decided | |---|---|---|---|---|---|---| | `#6075` | 1 | 3 | `d367f03d6` | commit | measured | five drivers' query parameters follow `DriverQuery` | | `#6076` | 0 | 1 | `6513c1749` | commit | measured | `IDataDriver`'s query parameter becomes `DriverQuery` (that pull request's squash) | | `#8778` | 0 | 2 | `7901b2dd2` | commit | reused | stamp-only `tenancy.organizationField` | | `#8823` | 0 | 3 | `4dfa369a9` | commit | reused | drop the caller value MySQL inlines in its duplicate-entry diagnostic | | `#9542` | 5 | 6 | `8bbf45947` | commit | measured | bound the metadata-lock wait on boot schema-sync's MySQL widening ALTER too, keeping boot's swallow | | `#10165` | 0 | 1 | `801296050` | commit | reused | lifecycle `ttl.onlyWhen` row filter with the canonical null predicate | | `#10629` | 1 | 0 | `199ec4712` | commit | reused | bind federated objects whatever the boot order (stage 3's anchor; the live #7737 carries the citation) | | `#10836` | 0 | 2 | `7ab286e44` | commit | measured | live pg + mysql legs for the `ttl.onlyWhen` `$null` suite | | `#11065` | 0 | 1 | `20950404c` | commit | reused | count a boolean aggregand as 1/0 in avg and sum | | `#11067` | 10 | 10 | `479fba50d` | commit | measured | stamp `updated_at` when the driver never ran DDL | | `#11374` | 16 | 7 | `d0e3a885b` + `107bb4ba4` | commit | measured | emit `varchar(maxLength)` for a text field a declared index keys on / carry an over-long UNIQUE index on a hash-shadow column, the route the 2026-08-24 ruling chose | | `#12380` | 9 | 13 | `4045b954d` | commit | reused | make the SQLite `Field.json` codec injective, one encoding across all three dialects | | `#12978` | 0 | 2 | `e4902d2b9` | commit | reused | declare sourced `maxLength` on the keyed text columns of the `sys_notification_*` objects | | `#12998` | 19 | 7 | `df1c75c4b` | commit | measured | hash-shadow UNIQUE indexes carry the NULL-safe organization key part (ADR-0120 D3) | | `#12999` | 3 | 1 | `ebcc34e89` | commit | measured | name the real remedy when a bounded field sits over a stale TEXT column | | `#13015` | 17 | 6 | `cd1348802` | commit | measured | a shadow-carried UNIQUE is not index drift, and its remedy dropped the constraint | | `#13279` | 1 | 1 | `6a180e42d` | commit | reused | fail loud when a permission-store read fails; it moved the classifier pins into `driver-error-classification.test.ts` | | `#13324` | 1 | 4 | `4cda78c9b` | commit | reused | require a missing-table error to name the table that was read | | `#14434` | 0 | 1 | `93940d492` | commit | measured | declare the not-found arm on `IDataDriver.update()` (that pull request's squash) | | `#14438` | 1 | 3 | `2200f8ec8` | commit | measured | `update()` publishes the contract's record-or-null, not `any` (the squash of PR #15280) | | `#14628` | 1 | 1 | `6392b9c2b` | commit | measured | budget the 8th live-cell hook, reached through `rawDriver()` | | `#14902` | 7 | 9 | `61821e54c` | commit | reused | a plain unique index over duplicate rows is loud and non-fatal (the squash of PR #15477) | | `#15041` | 2 | 5 | ADR-0104, 2026-09-05 addendum | ADR | reused | the media family's column holds the bare `sys_file` id; the step's abort requirement, its SQL sketch, and the generator as the side that does not move | | `#16570` | 0 | 5 | `b72226f48` | commit | measured | declare the `indexes` key `initObjects` / `registerObjectMetadata` already read | | `#16609` | 1 | 5 | `78bc4ad58` | commit | measured | `findWithWindowFunctions` presents its rows like every other read door, and the alias-collision ruling | | `#16619` | 2 | 1 | `45cfa1b88` | commit | measured | canonical ISO-Z read presentation (that pull request's squash; its message records the contract review) | | `#16649` | 1 | 0 | `613bfbd3d` | commit | reused | register the remaining `door: 'none'` codes, re-registering `MONGODB_MULTI_TENANT_UNSUPPORTED` | | `#16657` | 0 | 1 | `5a95b0e93` | commit | reused | read the dialect text out of `cause` for operator-facing records | | `#16711` | 2 | 5 | `7862fb711` | commit | reused | object-definition parameters declare the keys they are read for | | `#16729` | 1 | 1 | `0f38ab084` | commit | reused | an explicit tenancy opt-out survives a partial `syncSchema` re-registration | | `#17343` | 3 | 8 | `82cb69fed` | commit | measured | a `multiple: true` boolean column keeps its `$contains` membership filter | | `#17586` | 4 | 2 | `d46deba19` | commit | measured | keep multi-valued boolean/toggle columns out of the read-coercion registry | | `#17590` | 5 | 20 | `e04a0aff2` | commit | reused | compile `$contains` on a JSON column as a per-dialect MEMBERSHIP test; its message records the director's 2026-09-12 Ruling A | | `#17639` | 5 | 10 | `7c2c5aedd` | commit | measured | envelope the `distinct()` backend fault | | `#17690` | 4 | 8 | `be5c60291` | commit | measured | eight more `IDataDriver` doors publish their declared return type | | `#17857` | 4 | 2 | `9ccc4179e` | commit | measured | attribute an unresolvable `distinct()` column to the clause the caller named | | `#17876` | 0 | 2 | `be5c60291` | commit | measured | that pull request's squash, which installed the `ContainsAny` detector | | `#17879` | 0 | 4 | `eb9334915` | commit | measured | measure the `ContainsAny` phantom-leg sweep across eight door pins | | `#17970` | 0 | 2 | `47e6601c5` | commit | measured | collapse `ContainsAny`'s distributivity so union-shaped doors assert | | comment `5448627494` | 0 | 2 | `f6fa22ce1` | commit | measured | the 2026-08-28 maintainer ruling: min/max over booleans answer numbers on every face, superseding #11249's false/true | ## Wordings to check Most rewrites swap a tag in place (`[#N]` to `[commit SHA]`, `(#N)` to `(commit SHA)`, `#N's X` to `commit SHA's X`, `PR #N` to its squash commit, a URL `@see` to `@see commit SHA`), the form the landed stages use. These say more than the tag: - **`#10629`, one site** (`sql-driver.ts:12236`): 「the same ruling #7737/#10629 already made for FEDERATED objects」 became 「the same ruling #7737 already made for FEDERATED objects (commit 199ec47)」, stage 3's judged form (contract review `5943182373`): `199ec4712` is #7737's fix and its message states the ruling; it names #7737, not #10629. - **`#11374`, split by subject.** 20 sites describe the keyed-text rule (`varchar(maxLength)` for a field a declared index keys on, the shard path, `boundedObject()`, the named refusal): `d0e3a885b`, the driver commit whose diff names #11374 fourteen times and introduced `explainUnkeyableTextColumn` and `boundedObject()`. 3 sites credit 「the maintainer's 2026-08-24 ruling」 that chose the hash route: `107bb4ba4`, whose message moves 「#11374's refusal pins」 and records 「the ruling deliberately replaced」 and 「the prefix constraint the ruling rejected」. Its message does not carry the date, so those sites keep their date and read 「landed as commit 107bb4b」 / 「recorded in commit 107bb4b」 (stage 1's `#9741` precedent). Other lanes anchored #11374 to their own packages' bound declarations (`e4902d2b9`, `f64668d3c`, `3954fb7df`), a different subject; the driver's own rule is `d0e3a885b`. - **`#15041`, seven sites**: 「the #15041 addendum」 / 「the ruling on #15041」 became 「the ADR-0104 2026-09-05 addendum」 / 「the ruling in ADR-0104's 2026-09-05 addendum」, the spelling stage 3 and the cli lane used. The addendum names #15041 as its provenance and carries each claim the sites make: the abort-on-the-first-non-JSON-string requirement, the `USING (col #>> '{}')` sketch, and 「the driver is the side that moves」 for the generator. `sql-driver-15989-file-family-bare-id.test.ts:7` keeps the maintainer's verbatim quote untouched (it carries no `#`). - **The comment id** (`sql-driver-11635-boolean-aggregand-answers.test.ts:9`, `sql-driver-11782-boolean-row-read-presentation.test.ts:36`-`:37`): 「applied in its comment 5448627494」 became 「landed as commit f6fa22c」. `f6fa22ce1` applies 「the 2026-08-28 maintainer ruling (option A, superseding #11249's false/true)」 to driver-sql's result presentation, and its diff names the comment id. #11152 stays. - **`#17590` where it was written while the card was open** (the `17639` and `17857` suites, `sql-driver.ts:11060`, `sql-driver-17586-…test.ts:101`): those sentences park the json-column `distinct()` question 「with #17590」. `e04a0aff2` closed that card with Ruling A on `$contains` only, so a bare swap would credit it with a `distinct()` verdict it never made. They now read 「the card commit e04a0af closed, which owned the sibling divergence」, 「if a card after commit e04a0af rules that a json column should ANSWER a distinct read」 (both retirement clauses), 「the ruling commit e04a0af records cannot move it」, 「that question belonged to the card commit e04a0af closed」, 「Nothing here touches the card commit e04a0af closed」 and 「the divergence commit e04a0af ruled on the filter side」. `sql-driver-17639-distinct-fault-envelope.test.ts:206` is the **one changed line that carries no number**: its verb (「owns」 to 「owned」) had the number on `:205` as its subject. - **Where the number named the defect, not the change**: 「## Not #11067」 became 「## Not the defect commit 479fba5 fixed」; 「#13015 was the price of the split」 became 「The defect commit cd13488 fixed was the price of the split」; 「#17343: a `multiple: true` BOOLEAN column lost …」 became 「The defect commit 82cb69f fixed: …」; 「The defect that produced the table (#12380)」 became 「(fixed in commit 4045b95)」 and 「let #12380 survive」 became 「let the defect commit 4045b95 fixed survive」; 「family as #11067 / #11176 / #11223」 became 「family as #11176 / #11223 / the one commit 479fba5 fixed」. - **`pre-` / `post-` spellings**: 「a pre-#12998 shadow」 became 「a shadow from before commit df1c75c」 (and the same shape for `#12380` and `#17590`); 「post-#12998」 became 「since commit df1c75c」; 「The pre-#12998 shadow over RAW columns」 became 「The older (before commit df1c75c) shadow over RAW columns」. - **Reviews and reports**: 「the contract review of PR #16619」 became 「the contract review recorded in commit 45cfa1b」 (and 「measured in the contract review commit 45cfa1b records」, 「commit 45cfa1b's contract review's finding」); `45cfa1b88` is that pull request's squash, and its message records FINDING-1 to FINDING-3 and the hand-made TEXT-affinity measurement. 「the #17879 report」 became 「commit eb93349's message」 (two sites); that message records that both repair candidates were measured. - **`#16649`** (`dialect-emission-refusal.ts:66`): 「a removal #16649 reversed」 became 「a removal that commit 613bfbd reversed」. `613bfbd3d` re-registered `MONGODB_MULTI_TENANT_UNSUPPORTED` 「under the ruling」; the runtime lane's `44c917a47` is #16649's vocabulary-gate half, a different subject. - **`#14628`** (`live-dialect-matrix.testkit.ts:451`, `sql-driver-datetime-mysql-storage.test.ts:57`): `6392b9c2b` budgeted the eighth live-cell hook and wrote the `(#14628)` line itself; the seven before it were `13b520069` (PR #14629), which cites #14213, the number kept beside it. - **Squash rewrites**: 「after #6076 merged」 became 「after commit 6513c17 landed」; 「#13878 / PR #14434」 became 「#13878 / commit 93940d4」; 「`ContainsAny` (#17876)」 became 「(commit be5c602)」, the squash that installed it; 「#14438 (PR #15280)」 became 「Commit 2200f8e (PR #15280)」, the live pull-request number kept beside its squash as a convenience. - **Slash pairs**: 「[#9542/#9609]」 became 「[commit 8bbf459, #9609]」; 「#5181/#6075」 became 「#5181 and commit d367f03」; 「#11374/#11627 exist to end」 became 「#11627 and commit d0e3a88 exist to end」; 「the #11627/#12998 suites」 became 「the #11627 and commit df1c75c suites」; 「[#14079/#15683/#17343]」 became 「[#14079/#15683, commit 82cb69f]」; 「(#11374, #12999)」 became 「(commits d0e3a88 and ebcc34e)」. - **Other single rewrites**: 「(#16711 验收口径 item 4)」 became 「(验收口径 item 4 of the card commit 7862fb7 closed)」, whose message records the TS2353 negative control; 「the defect #8287 removed and #8778 must not reintroduce」 became 「… and commit 7901b2d's stamp-only key must not reintroduce」; 「the gap #17639 left FILED」 became 「the gap commit 7c2c5ae left FILED」 (`9ccc4179e`'s message: 「the attribution arm #17639 deliberately left filed」); 「See the ruling recorded on #12380.」 became 「See the decision recorded in commit 4045b95's message.」 (that message records the posture, 「refuses to guess at the two that the pre-fix encoding made ambiguous」, and calls it no ruling). - **Capitalisation**: where the number opened a sentence, the new text opens with 「Commit」. - No line was reflowed, so some are longer than their block's wrap (`eslint.config.mjs` declares no line-length rule, and a reflow would move neighbouring lines and every line citation into the file). ## Sites left - **In comments (src, test, `vitest.config.ts`): none.** - **String literals: 51 test-string sites, 24 numbers, 26 files** (`describe` and `it` titles, assertion arguments): `#11374` 5, `#12380` 4, `#17590` 4, `#17639` 4, `#12998` 3, `#13015` 3, `#16609` 3, `#17343` 3, `#17586` 3, `#17857` 3, `#11067` 2, `#14902` 2, and 12 more once each. Every one of the 24 is in this stage's population, so the table above holds an anchor for each. Non-test strings carry none. Strings are outside this stage's surface. - **Outside `src`:** the release-owned `CHANGELOG.md` names dead numbers on 69 sites (31 numbers); left. `package.json`, `tsconfig.json`, `vitest.config.ts`, `README.md` and `LICENSE` cite no dead number. ## Mechanical guard: no code token moves The guard (stages 2 and 3's) compares base `222ecc27f` against the working tree over all 55 touched files, with TypeScript 6.0.3: - **Reading 1**: the parser's leaf nodes, from a `forEachChild` walk. Comments are trivia there, and JSDoc is never visited. A leaf that is not itself a token (an empty block) is re-scanned with trivia skipped. - **Reading 2**: the full token stream in parser context, from a `getChildren` walk, JSDoc nodes skipped. String, template and numeric literals are compared in full on both readings. Results: - Real run at the head: 165,798 base tokens, **0 files with a token change** (exit 0). - Comment control (「which IS its value」 to 「which IS ITS value」, `sql-driver.ts`): 0 files changed (exit 0). - Positive control, an identifier (`hashShadowColumnFor` to `hashShadowColumnForX`, `schema-drift.ts`): DIFFER on both readings (exit 1). - Positive control, a string literal (`'storage'` to `'storageX'` in `FieldKeyClass`, `builtin-column-collision.ts`): DIFFER on both readings (exit 1). - Positive control, a numeric literal (`MEDIA_ID_MOVE_WIDTH = 2048` to `2049`, `media-column-move.ts`): DIFFER on both readings (exit 1). Each mutation went through `scripts/ablation-replace.mjs` (wrap mode, anchor hit 1 to 0, replacement 0 to 1) under a shell trap that restores by absolute path from `HEAD`. Each restore was proven equal to its `HEAD` blob (`cd55a4ca44f3`, `72e45a83968f`, `86ebb324c1b6`, `fe0ce2c4d5bd`), with `git diff HEAD` empty and a clean tree afterwards. ## Changeset: `patch` (`dist` measured) `files[]` is `dist`, `README.md` and `CHANGELOG.md`, and the package is not private. The whole workspace was built first (`turbo run build` over `./packages/*` and `./packages/*/*`, 71 of 71 tasks, VERDICT command-exit 0). Then the package's own `build` (tsup plus `check-dts-emitted`) ran three times in one script under the shared verify lock (VERDICT command-exit 0): - **Leg 1**, at `02d2a034a`: 6 `dist` files hashed. Of the 122 rewritten non-test lines, 57 appear verbatim in `dist`: 52 from `sql-driver.ts`, 3 from `schema-drift.ts`, 1 each from `media-column-move.ts` and `dialect-emission-refusal.ts`; in `index.d.ts` / `index.d.mts` (51) and `index.js` / `index.mjs` (52). - **Leg 2**, the base text put back in the 6 non-test files (6 of 6 proven equal to their base blob): 4 of the 6 files differ from leg 1 (`index.d.ts`, `index.d.mts`, `index.js`, `index.mjs`). - **Leg 3**, after the proven restore (6 of 6 equal to their `HEAD` blob, `git diff HEAD` empty): all 6 files are byte-identical to leg 1, so the build is deterministic and the difference is the rewrite. So the rewrite ships, and `.changeset/20595-driver-sql-provenance-anchors.md` declares a `patch` for `@objectstack/driver-sql`, comment text only, with the claim's `Clause-②: no` line. ## Gates (head `fad95aff7`) - **Derived gates:** `node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands` at `fad95aff7` (56 paths against merge base `04f0cc499`) derived 63 commands. All 63 ran, each exit code captured before any pipe: 63 exit 0. `--ran` reports 「63 derived, 63 run, 0 NOT-MEASURED, 0 UNRUN」 (a derived zero) and exits 0. The roster families the derivation flags under a touched directory also ran, each exit 0: `node scripts/check-changeset-fixed.mjs`, `pnpm check:authz-resolver`, `pnpm check:error-code-casing`, `pnpm check:filter-alias-parity`. - **Against the newer `main`:** `main` moved six commits after the merge, none touching a file here. A probe tree at `origin/main` `f9bcd08be` with this diff applied derived 64 commands, the one addition being `node scripts/check-dts-emitted.mjs --self-test` (that script is unchanged across the range). It ran, exit 0, and `--ran` on the probe reports 「64 derived, 64 run, 0 NOT-MEASURED, 0 UNRUN」. - **Named in the dispatch:** `node scripts/check-issue-citations.mjs` exits 0 (「every citation this change adds resolves」, 10 judged across 6 files); `pnpm check:issue-citations` exits 0 (self-test, 173 cases, 9 batteries); `pnpm check:doc-authoring` exits 0 (the sibling-package prose-id baseline holds, no growth); `pnpm check:nul-bytes` exits 0 (9,641 files, no raw control bytes). - **Tests and typecheck, under the verify lock, at `fad95aff7`:** `pnpm --filter @objectstack/driver-sql test`: 215 test files pass and 11 skip (226); 3,594 tests pass and 202 skip. The 11 skipped files are the live-dialect suites (PostgreSQL and MySQL cells), declared un-run locally; CI's `Temporal Conformance (live PG + MySQL)` job runs them. `pnpm --filter @objectstack/driver-sql typecheck` exits 0; `tsc --listFiles` puts all 226 tracked test files and all 55 changed files in its program. - **Lint, as a proven narrowing:** eslint with inline config disabled, over the 55 touched `.ts` files plus `dist/index.js` as the control: 56 results, 0 errors and 1 warning, the control's ignore notice; none of the 55 is reported ignored. `eslint.config.mjs` never enables type-aware linting (its lines 327-328 say so), so a comment edit cannot move the verdict on an untouched file. The repo-wide `pnpm lint` is CI's run. ## Acceptance notes - **Base.** The branch was cut at `222ecc27f` and merged with `main` once, at `04f0cc499`, before the gates (merge `02d2a034a`, no conflict, no file under `packages/drivers/driver-sql`, and neither `check-issue-citations.mjs` nor `dispatch-gates.mjs`). Tests, typecheck and gates ran on the head after it. The net diff against `main` is the 55 rewritten files (+291/−291) and the changeset. - **Two sentences now state something that is no longer true, and this stage changes only their citation.** `sql-driver-12998-shadow-null-safe-key.test.ts:266` says `initObjects`' parameter type does not declare `indexes`; `b72226f48` declared it, and the citation now reads 「(the gap commit b72226f closed)」. `sql-driver.ts:18142` says 「#11374's remaining half may still reshape the text side」; it was written on 2026-08-24 (`c49afd0886`), a day before `107bb4ba4` settled that half, and now reads 「the half commit d0e3a88 left open」. Comment accuracy, outside a citation sweep. - **The token guard's first reading-1 run was wrong, and was replaced.** It reported `sql-driver.ts` as DIFFER on reading 1 alone: an empty `catch { }` block has no child nodes, so its leaf text carried the comment inside it (`// Pre-#12380 row: …`). Reading 2 agreed with no change throughout. Reading 1 now re-scans such a leaf with trivia skipped; the real run and all four controls above are from the corrected guard. - **Wording only:** no line without a number was changed, except `sql-driver-17639-distinct-fault-envelope.test.ts:206`, listed above. --- _Generated by [Claude Code](https://claude.ai/code/session_017xfMoEjKUuSh2xYB8sCozp)_ --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent 6961245 commit 13a24ec

56 files changed

Lines changed: 306 additions & 291 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
---
2+
'@objectstack/driver-sql': patch
3+
---
4+
5+
Provenance comments in `@objectstack/driver-sql` cite the commits and ADR that decided them, not tracker numbers that no longer resolve
6+
7+
Clause-②: no
8+
9+
Docblocks and comments across the package cited issue-tracker numbers that now answer 404 on GitHub.
10+
Each one now cites the commit in this repository's history that made the decision it describes, or the
11+
ADR that records it (ADR-0104's 2026-09-05 addendum). Some of these docblocks sit on exported members,
12+
so the reworded text appears in the published `index.d.ts` / `index.d.mts`, and comments that esbuild
13+
keeps appear in the JavaScript output.
14+
15+
Comment only: no export, type, error code, status, message text or runtime behaviour changes.

‎packages/drivers/driver-sql/src/builtin-column-collision.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ export type FieldKeyClass = 'storage' | 'presentation';
7272
export const FIELD_KEY_STORAGE_CLASS: Readonly<Record<string, FieldKeyClass>> = Object.freeze({
7373
// ---- storage: the column's own shape -------------------------------------
7474
type: 'storage', // `createColumn`: the column type itself
75-
maxLength: 'storage', // `createColumn`: varchar(n) vs TEXT, and the #11374 keyable decision
75+
maxLength: 'storage', // `createColumn`: varchar(n) vs TEXT, and commit d0e3a885b's keyable decision
7676
multiple: 'storage', // `createColumn`: a multi-value field is a JSON column
7777
precision: 'storage', // numeric column shape (this driver does not read it yet)
7878
scale: 'storage', // numeric column shape (this driver does not read it yet)

‎packages/drivers/driver-sql/src/dialect-emission-refusal.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@
6363
* `engine.syncObjectSchema` → `SqlDriver.syncSchema` → the DDL gate, on a
6464
* server that is already serving HTTP. That was the exact test #8035 applied
6565
* when it UNregistered `MONGODB_MULTI_TENANT_UNSUPPORTED` for failing it — a
66-
* removal #16649 reversed under the #16404 door-or-no-door rule, which takes
66+
* removal that commit 613bfbd3d reversed under the #16404 door-or-no-door rule, which takes
6767
* registration out of that test's reach entirely: every `code` that ships in
6868
* `dist` carries a ledger row, and wire-reachability now decides only what a
6969
* door ANSWERS with. This one can be carried, so the door serves it under its

‎packages/drivers/driver-sql/src/live-dialect-matrix.testkit.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -448,7 +448,7 @@ export function declareUnprovisionedCell(cell: DialectCell, matrix: string): voi
448448
* Nothing in the corridor (15_000, 600_000) is distinguishable by measurement,
449449
* so the value is fixed by this package's OWN existing answer for live-touching
450450
* sites: 60 explicit `60_000` budgets across 22 files — #13688 and its sweep
451-
* #13902 put them on live test BODIES, #14213 and #14628 on the hooks that pay
451+
* #13902 put them on live test BODIES, #14213 and commit 6392b9c2b on the hooks that pay
452452
* a live connect. Adopting it leaves the live matrix with ONE live budget
453453
* instead of two, so a red at 60_000 ms is unambiguous about which bound it hit.
454454
*

‎packages/drivers/driver-sql/src/media-column-move.pin.test.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
* refusing.
1212
*
1313
* ⛔ The single most load-bearing assertion here is that the PostgreSQL retype
14-
* arm's pre-check exists at all. The #15041 addendum prescribed the retype with
14+
* arm's pre-check exists at all. The ADR-0104 2026-09-05 addendum prescribed the retype with
1515
* NO pre-check, and that form was measured on live PostgreSQL 16.13 to accept a
1616
* row holding an inline metadata blob and flatten it to its own literal text.
1717
* The director ruling (decision batch #120 item 1) replaced the clause; a pin

‎packages/drivers/driver-sql/src/media-column-move.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
* `sys_file` id, and the pre-check that ABORTS instead of destroying a row the
77
* backfill never converted.
88
*
9-
* The ruling on #15041 gave this step one requirement in words — abort *"on
9+
* The ruling in ADR-0104's 2026-09-05 addendum gave this step one requirement in words — abort *"on
1010
* the first cell that is not a JSON string"* — and one sketch in SQL beside
1111
* it. **The sketch does not implement the requirement, and that was measured
1212
* rather than argued** (director ruling, decision batch #120 item 1): on live

‎packages/drivers/driver-sql/src/schema-drift.base-type-mismatch.test.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -364,7 +364,7 @@ describe('diffManagedTable — a SINGLE-VALUE JSON-class field over a stale text
364364
// The card's scope, asserted rather than described: the fork applies to
365365
// every single-value member of the writer's set.
366366
//
367-
// ⚠️ [#15989] #15041 has since been ruled — option A, the file family's
367+
// ⚠️ [#15989] ADR-0104's 2026-09-05 addendum has since ruled — option A, the file family's
368368
// column holds the bare `sys_file` id — so the family is no longer a member
369369
// of {@link JSON_COLUMN_FIELD_TYPES}: it is asked per deployment, and
370370
// `diffTags` omits `fileColumnsMoved`, i.e. every call here is about a

‎packages/drivers/driver-sql/src/schema-drift.ts‎

Lines changed: 17 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -417,7 +417,7 @@ export const HASH_SHADOW_SUFFIX = '__hash';
417417
* orphan pass reports as `unmapped_column` with a `drop_column` op. Dropping it
418418
* would take the UNIQUE index it carries with it, silently returning the object
419419
* to "registered but its declared uniqueness unenforced" — the very state
420-
* #11374/#11627 exist to end, reached this time through the migration tool
420+
* #11627 and commit d0e3a885b exist to end, reached this time through the migration tool
421421
* rather than through a refused DDL.
422422
*
423423
* Matched by SUFFIX rather than by a registry of known names, deliberately: the
@@ -448,7 +448,7 @@ export function isHashShadowColumn(name: string): boolean {
448448
* 64-character identifier limit.
449449
*
450450
* ⚠️ Lives HERE, beside {@link isHashShadowColumn}, rather than in the driver:
451-
* #13015 was the price of the split. The ORPHAN-column pass knew the shadow
451+
* The defect commit cd1348802 fixed was the price of the split. The ORPHAN-column pass knew the shadow
452452
* vocabulary and the INDEX differ did not, so a healthy shadow-carried UNIQUE
453453
* had its column protected from a drop while the index that column carries was
454454
* proposed for a destructive rebuild. Both passes now ask the same module the
@@ -473,7 +473,7 @@ export function hashShadowColumnFor(indexName: string): string {
473473
/**
474474
* One key part a hash shadow hashes: the column identity, and whether the
475475
* generation expression folds it through the NULL-safe `COALESCE(col, ...)`
476-
* form (ADR-0120 D3, carried into the shadow by #12998).
476+
* form (ADR-0120 D3, carried into the shadow by commit df1c75c4b).
477477
*/
478478
export interface HashShadowKeyPart {
479479
column: string;
@@ -482,16 +482,16 @@ export interface HashShadowKeyPart {
482482

483483
/**
484484
* Read the DECLARED key parts back out of a hash shadow's stored
485-
* `GENERATION_EXPRESSION` (#13015).
485+
* `GENERATION_EXPRESSION` (commit cd1348802).
486486
*
487487
* This is what makes a shadow-carried key COMPARABLE rather than merely
488-
* skippable. Since #12998 the expression carries the NULL-safe parts in their
488+
* skippable. Since commit df1c75c4b the expression carries the NULL-safe parts in their
489489
* COALESCE spelling, so the FORM of the key — which columns, and which of them
490490
* are folded — survives the round trip, and the differ can ask the real
491491
* question ("does this shadow enforce what metadata declares?") instead of the
492492
* blind one ("is this a shadow at all?").
493493
*
494-
* ⛔ Why the blind question is not good enough: a shadow created BEFORE #12998
494+
* ⛔ Why the blind question is not good enough: a shadow created BEFORE commit df1c75c4b
495495
* hashes the RAW columns, so `CONCAT` returns NULL for every NULL-organization
496496
* row and the rows the COALESCE bucket exists to constrain are constrained by
497497
* nothing (#5030's shape). It is indistinguishable BY NAME from a healthy one.
@@ -858,7 +858,7 @@ export function diffManagedTable(args: {
858858
columns: PhysicalColumn[];
859859
dialect: SqlDialectName;
860860
/**
861-
* Which columns an index KEYS ON (#11374), keyed by field name — the exact
861+
* Which columns an index KEYS ON (commit d0e3a885b), keyed by field name — the exact
862862
* map {@link indexedKeyColumns} builds. Consulted ONLY by the varchar-length
863863
* branch below, through {@link varcharColumnChars}, to answer the same
864864
* question `createColumn` asks before it sizes a text-family column.
@@ -1703,7 +1703,7 @@ export interface PhysicalIndex {
17031703
/**
17041704
* When this index is physically carried by a #11627 hash shadow, the
17051705
* DECLARED key parts that shadow hashes, read back from the generation
1706-
* expression (#13015 via #12998) by `SqlDriver.introspectIndexes`.
1706+
* expression (commit cd1348802 via commit df1c75c4b) by `SqlDriver.introspectIndexes`.
17071707
*
17081708
* Absent both when the index is NOT shadow-carried and when it is but the
17091709
* expression could not be read. {@link isHashShadowCarrier} tells those two
@@ -2029,7 +2029,7 @@ export function diffUnbuildableIndexes(args: {
20292029
* field-level `unique` through {@link uniqueIndexesFromFields}, object-level
20302030
* `indexes[]` through {@link normalizeDeclaredIndex} — so "which columns end up
20312031
* in a key" has ONE answer, shared by the index sync that creates them and by
2032-
* the DDL that has to make them keyable in the first place (#11374).
2032+
* the DDL that has to make them keyable in the first place (commit d0e3a885b).
20332033
*
20342034
* ⚠️ Deliberately NOT filtered by `physicalColumns`, unlike `expectedIndexes`:
20352035
* its caller runs BEFORE the columns exist — deciding a column's TYPE is the
@@ -2275,15 +2275,15 @@ function indexSignature(
22752275
* Answerable from the index alone, by NAME: the shadow is derived from the
22762276
* index name ({@link hashShadowColumnFor}), so a carrier is an index whose sole
22772277
* key column is its own shadow. That is what makes this the FAIL-SAFE half of
2278-
* #13015 — it holds even when the generation expression cannot be read, and a
2278+
* commit cd1348802 — it holds even when the generation expression cannot be read, and a
22792279
* carrier is never a thing this differ may propose destroying on a guess.
22802280
*/
22812281
export function isHashShadowCarrier(index: PhysicalIndex): boolean {
22822282
return index.columns.length === 1 && index.columns[0] === hashShadowColumnFor(index.name);
22832283
}
22842284

22852285
/**
2286-
* The key an index ENFORCES, which is not always the key it STORES (#13015).
2286+
* The key an index ENFORCES, which is not always the key it STORES (commit cd1348802).
22872287
*
22882288
* For an ordinary index the two are the same. For a #11627 shadow-carried
22892289
* UNIQUE the stored key is one VARBINARY(32) generated column and the enforced
@@ -2358,7 +2358,7 @@ export function diffManagedIndexes(args: {
23582358
if (!p || p.primary || isRuntimeManagedIndex(p, runtimeCreated, tenantField)) return false;
23592359
if (!p.unique || p.partial === true) return false;
23602360
if ((p.expressions?.length ?? 0) > 0 || (p.nullSafeColumns?.length ?? 0) > 0) return false;
2361-
// #13015: nor is a hash-shadow carrier. Its stored key is one generated
2361+
// Commit cd1348802: nor is a hash-shadow carrier. Its stored key is one generated
23622362
// column, so the identity comparison below already excludes it — stated
23632363
// outright because the exclusion must survive that comparison changing,
23642364
// and because `replace_unique_index` DROPS the legacy name.
@@ -2428,7 +2428,7 @@ export function diffManagedIndexes(args: {
24282428
// Same normalization on BOTH sides (#4884, ADR-0120 D3): column identity
24292429
// AND key-part form, literal-agnostic on the COALESCE literal — asked of
24302430
// the key the index ENFORCES, which for a #11627 shadow-carried UNIQUE is
2431-
// not the column it stores (#13015).
2431+
// not the column it stores (commit cd1348802).
24322432
const pk = enforcedIndexKey(p);
24332433
if (
24342434
p.unique === e.unique &&
@@ -2445,7 +2445,7 @@ export function diffManagedIndexes(args: {
24452445
// (`recreate_index` → drop first) this differ cannot undo. Not ours to
24462446
// reconcile (#4884).
24472447
if (isRuntimeManagedIndex(p, runtimeCreated, tenantField)) continue;
2448-
// #13015, fail-safe half: a hash-shadow carrier whose generation
2448+
// Commit cd1348802, fail-safe half: a hash-shadow carrier whose generation
24492449
// expression could NOT be read (`shadowKey` unresolved). We know by name
24502450
// that the index is driver-owned and that its stored key is a digest, so
24512451
// the identity comparison above is meaningless for it — but we do not know
@@ -2455,7 +2455,7 @@ export function diffManagedIndexes(args: {
24552455
// ⛔ The `!p.shadowKey` half is load-bearing, and was measured: without it
24562456
// this guard swallows the RESOLVED carriers too, which silently demotes the
24572457
// whole fix to the blind skip — every shadow-carried index unreportable,
2458-
// including a pre-#12998 one hashing the RAW columns whose constraint does
2458+
// including one from before commit df1c75c4b hashing the RAW columns whose constraint does
24592459
// not cover NULL-organization rows at all. Green, quiet, and the exact
24602460
// trade this fix exists to refuse.
24612461
if (isHashShadowCarrier(p) && !p.shadowKey) continue;
@@ -2471,7 +2471,7 @@ export function diffManagedIndexes(args: {
24712471
// clean → recategorised `safe` (dev autoMigrate may apply); duplicates →
24722472
// blocked with a row report, the old index left in place.
24732473
//
2474-
// #13015: read through the ENFORCED key, so a pre-#12998 shadow — same
2474+
// Commit cd1348802: read through the ENFORCED key, so a shadow from before commit df1c75c4b — same
24752475
// columns, hashed RAW instead of through the NULL-safe COALESCE — is
24762476
// recognised as exactly this tightening and gets the same duplicate
24772477
// pre-flight before anything is dropped. The explicit "physical side is
@@ -2530,7 +2530,7 @@ export function diffManagedIndexes(args: {
25302530
// (#4884 — the boot advised dropping `idx_sys_metadata_overlay_draft`, the
25312531
// partial UNIQUE enforcing draft-overlay uniqueness, on a healthy fresh DB).
25322532
if (isRuntimeManagedIndex(p, runtimeCreated, tenantField)) continue;
2533-
// #13015: an orphaned shadow carrier is still an orphan — its declaration
2533+
// Commit cd1348802: an orphaned shadow carrier is still an orphan — its declaration
25342534
// is gone, and `drop_index` is the right remedy — but the report must name
25352535
// the constraint it enforced, not the digest column it stored.
25362536
const po = enforcedIndexKey(p);

‎packages/drivers/driver-sql/src/sql-driver-11176-bulk-and-merge-updated-at.test.ts‎

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,9 @@
44
* [#11176] The two write doors that did not advance `updated_at`: `updateMany()`
55
* on every dialect, and `upsert()`'s merge branch on Postgres and MySQL.
66
*
7-
* ## Not #11067, and the difference is what this file is set up to show
7+
* ## Not the defect commit 479fba50d fixed, and the difference is what this file is set up to show
88
*
9-
* #11067 is about `tablesWithTimestamps` being filled only by DDL, so a
9+
* Commit 479fba50d is about `tablesWithTimestamps` being filled only by DDL, so a
1010
* `skipSchemaSync` deployment never stamped. These two are missing on EVERY
1111
* deployment — so almost every table here is built by the driver's own
1212
* `initObjects`, with `tablesWithTimestamps` correctly populated. That is the
@@ -57,7 +57,7 @@
5757
* ## §6 The narrowing, stated as a measurement rather than a claim
5858
*
5959
* The upsert stamp reads `observedUpdatedAtColumn` — DDL-observed, or settled
60-
* `present` by a successful stamped UPDATE — and deliberately NOT #11067's
60+
* `present` by a successful stamped UPDATE — and deliberately NOT commit 479fba50d's
6161
* `presumed` state. `presumed` exists so an UPDATE can speculate and then
6262
* RECOVER (`updateWithPresumedTimestamp`); the upsert door has no such recovery,
6363
* and a wrong presumption there would name a missing column in an INSERT column
@@ -66,7 +66,7 @@
6666
* would break first if the narrowing were ever widened without a recovery.
6767
*
6868
* `updateMany` has no such narrowing: it is an UPDATE door, so it reuses
69-
* #11067's machinery whole (§7).
69+
* commit 479fba50d's machinery whole (§7).
7070
*
7171
* ## Reverse verification (direction predicted before running)
7272
*
@@ -87,7 +87,7 @@ const OPTS = { bypassTenantAudit: true } as any;
8787
/**
8888
* The instant a row is backdated to before the write under test.
8989
*
90-
* A sentinel far in the past rather than a sleep, for #11067's reason: a stamp
90+
* A sentinel far in the past rather than a sleep, for commit 479fba50d's reason: a stamp
9191
* taken a moment after an insert default can legitimately land on the same
9292
* stored value. Backdating removes the race without weakening the assertion —
9393
* the stamp either moved to ~now or did not move at all, and those are six
@@ -338,7 +338,7 @@ function measure(cell: DialectCell): void {
338338
// ── §6 The declared narrowing, measured at the property that would break ──
339339

340340
it('§6 still upserts a hand-migrated table that has NO `updated_at` column', async () => {
341-
// The upsert stamp reads the OBSERVED answer, never #11067's presumption,
341+
// The upsert stamp reads the OBSERVED answer, never commit 479fba50d's presumption,
342342
// because this door has no recovery to fall back on. If that narrowing is
343343
// ever widened without one, this is the call that stops working.
344344
const id = 'n1';
@@ -349,7 +349,7 @@ function measure(cell: DialectCell): void {
349349
expect(after.row.title).toBe('b');
350350
});
351351

352-
// ── §7 `updateMany` reuses #11067's machinery whole ──────────────────────
352+
// ── §7 `updateMany` reuses commit 479fba50d's machinery whole ──────────────────────
353353

354354
it('§7 stamps a `skipSchemaSync` table, and still updates one without the column', async () => {
355355
// The presumption and its recovery, exercised through the bulk door: it is
@@ -363,7 +363,7 @@ function measure(cell: DialectCell): void {
363363
expect(presumed.updatedAt).toBeGreaterThan(BACKDATED_MS);
364364
expect(presumed.row.title).toBe('b');
365365

366-
// The other half of #11067's pair: a table that genuinely lacks the column
366+
// The other half of commit 479fba50d's pair: a table that genuinely lacks the column
367367
// must NOT gain a new rejection.
368368
await driver.create(NO_COL, { id: 'n2', title: 'a', status: 'bulk' }, OPTS);
369369
const touched = await driver.updateMany(NO_COL, { where: { status: 'bulk' } }, { title: 'b' }, OPTS);

‎packages/drivers/driver-sql/src/sql-driver-11224-update-stamp-precision.test.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@
3838
* audit answer comparing the two, a "modified since creation?" badge, and above
3939
* all a millisecond-precision delta cursor (`updated_at > cursor`), which
4040
* SKIPS every row whose stamp was truncated back below it — the same
41-
* silent-wrong-answer family as #11067 / #11176 / #11223, reached by a fourth
41+
* silent-wrong-answer family as #11176 / #11223 / the one commit 479fba50d fixed, reached by a fourth
4242
* mechanism. §2 asserts that skip is gone by issuing the cursor comparison as
4343
* real SQL on the server rather than comparing numbers in JS.
4444
*

0 commit comments

Comments
 (0)