Repository navigation
Commit f7b0298
Part of #25521
Clause-②: no
Metadata-protocol: none
Ruling-ref: 6270097840
Ruling-ref: 6348891160
Cut 1 of the `sql-driver.ts` split (#24586 ruling A, recorded in
`6270097840`), with the fresh seam table the card asks for first. The
claim is `6382121925`. Five `SqlDriver` method bodies move unedited into
a new module,
`packages/drivers/driver-sql/src/sql-driver-introspection.ts`: the
protected catalog-introspection helpers `introspectColumnOrder`,
`introspectColumns`, `introspectForeignKeys`, `introspectPrimaryKeys`
and `introspectUniqueConstraints`.
It is move-only, and the card stays open for the later cuts. Branch
`claude/issue-25521-sql-driver-cut-1`, cut from `origin/main` at
`e649c2d91`.
**Serial with the file's in-flight PRs.** At claim the PM read 13 open
PRs, none touching the file. Re-measured at my start (12 open PRs) and
again just before this PR was opened (13 open PRs), each PR's file list
read in full: none touches
`packages/drivers/driver-sql/src/sql-driver.ts` or
`packages/drivers/driver-turso/src/turso-driver.ts`. `origin/main` did
not move between the base and this PR.
## Seam table (fresh, at `e649c2d91`)
`sql-driver.ts` is 22,158 lines. 30-day lines are `git blame
--line-porcelain` lines whose committer time is on or after
2026-09-24T08:02Z, 30 days before the base commit; history was deepened
to 2026-09-15 first, and `scripts/pm/git-history.mjs count` proves 67
commits on the file in that window. In all, 3,790 lines (17.1%) are from
the last 30 days. The class `SqlDriver` spans lines 5471-22083 with 347
members: 251 methods (49 public, 202 private or protected), 32 statics.
One public member has an inferred return type (`supports`, in the class
head). Sections are the class's own banner comments, cut by contiguous
line range:
| Lines | Section | Lines | 30-day lines | Members (public fn) |
|:--|:--|--:|--:|:--|
| 1-5470 | Module preamble: imports, types, top-level helpers (197
statements) | 5,470 | 1,394 | - |
| 5471-7062 | Class head: fields, statics, constructor, capability
getters (`supports`) | 1,592 | 290 | 88 (4) |
| 7063-7494 | Lifecycle | 432 | 155 | 15 (3) |
| 7495-10173 | CRUD (IDataDriver core) | 2,679 | 691 | 45 (7) |
| 10174-10785 | Bulk and batch operations | 612 | 140 | 9 (6) |
| 10786-10911 | Raw execution | 126 | 12 | 4 (1) |
| 10912-10989 | Transactions | 78 | 0 | 7 (5) |
| 10990-11521 | Aggregation | 532 | 61 | 5 (1) |
| 11522-11809 | Distinct | 288 | 20 | 3 (1) |
| 11810-11925 | Window functions | 116 | 4 | 1 (1) |
| 11926-12024 | Query plan analysis | 99 | 1 | 2 (2) |
| 12025-15779 | Schema sync (syncSchema / init) | 3,755 | 373 | 62 (17)
|
| 15780-15915 | Schema introspection (`listPhysicalColumns`,
`introspectSchema`) | 136 | 65 | 2 (2) |
| 15916-21385 | Internal helpers: tenancy, filter compilation, read
presentation, DDL emitters, hash shadows, `formatInput` / `formatOutput`
| 5,470 | 565 | 99 (5) |
| **21386-22083** | **Internal helpers: catalog introspection (this
cut)** | **698** | **19** | **5 (0)** |
| 22084-22158 | After the class: `UniqueConstraintMember`,
`singleColumnUniqueColumns` | 75 | 0 | - |
Limits measured per area, for pricing the next cuts:
- **`check:tenant-chokepoint` scans `SqlDriver` methods for
`this.getBuilder(` read builders.** Aggregation, Distinct, Window
functions, Query plan analysis, CRUD and Bulk build through
`getBuilder`, so a cut of any of them must also carry the new module
into that gate's `SCAN_FILES` (and teach it module-level functions with
a `this` parameter), or the gate would go silently narrower. That is a
gate edit; it was kept out of cut 1.
- **Protected statics.** The hash-shadow family (19116-19628) reads
`SqlDriver.hashShadowColumnFor` and
`SqlDriver.MAX_KEYABLE_VARCHAR_CHARS`; the row-size explainers
(19682-19977) read four protected statics. A module function cannot read
a protected static, so those bodies stay unless a ruling says otherwise.
- **`this.FIELD.delete/insert/update(`.** One site in the whole class:
`releaseTransaction` (`this.activeTransactions.delete(`, a `Map`), in
Transactions; three in Schema sync. `driver-sql` is outside the
tenant-audit census corpus (`SURFACE_ROOTS` is `packages/services` and
`packages/plugins`), so the `UNTYPED_RECEIVERS` limit does not bite
here; cut 1 has no such site either way.
**Proposed cut 2: Raw execution (10786-10911, 126 lines, 12 from the
last 30 days).** `rawStatementFault`, `execute` (public, annotated
`unknown`-promise return) and `noteRuntimeIndexDdl`. They read no
`getBuilder` and no static; the field `runtimeCreatedIndexes` stays on
the class. `TursoDriver` overrides `execute` and calls
`rawStatementFault`, which the stub shape keeps working. The bigger cold
areas (Distinct, Aggregation, Query plan analysis) need the
`check:tenant-chokepoint` question answered first.
## Why this area for cut 1
The five helpers form a closed cluster at the tail of the class: 693
lines of methods, 19 from the last 30 days, no public member, every
return type annotated, no static, no `this.FIELD.delete/insert/update(`
site, no `getBuilder`. Between them they read only `knex`, `isPostgres`,
`isMysql`, `isSqlite` and each other (`introspectColumns` calls
`introspectColumnOrder`). Because the cluster ends the class, no line
above 21386 moves, so no line reference into the file above that point
shifts. They are also among the file's best-covered members: 17 test
files in this package name them, most reaching the protected methods
through a test subclass, and `driver-turso` overrides
`introspectColumns` and calls `super`.
## The shape (#23050 shape B, as #25467 cut 1 / PR #25494 applies it)
- The class keeps each method with its name, signature, docblock and
position, because tsup emits members with their JSDoc into the `.d.ts`.
Each body is now one host literal plus `fn.call(host, ...)`, marked
`[#25521 cut 1]`. The methods drop `async` and return the moved
function's promise.
- `IntrospectionHost` is new. It types every member as the class member
itself (an indexed access on `SqlDriver`). Each moved function types its
`this` as the `Pick` of the host over exactly the members its body reads
(measured from the body, and asserted by the generator), and each stub
annotates its literal as the `ThisParameterType` of the moved function.
No cast, and no member becomes public.
- `knex` (a field) and the three dialect getters go as getters, so each
read happens when the body makes it. `introspectColumnOrder` goes as a
late-bound closure, so a subclass override or a spy on the instance is
still the one `introspectColumns` reaches.
- `this.knex(tableName)` now calls knex with the host as its receiver;
knex's callable reads its own closure (`createQueryBuilder(knex.context,
...)` in `make-knex.js`), never its receiver.
- **Module-level declarations.** The bodies read
`UniqueConstraintMember`, `singleColumnUniqueColumns`,
`IntrospectedColumn` and `IntrospectedForeignKey`. All four stay in
`sql-driver.ts`, which already exports them (a test imports
`singleColumnUniqueColumns` from there), and the module imports them
back. The one value import is read only inside a function body, so the
module cycle is evaluation-safe. No `export` was added anywhere.
- The move was produced by a script from the source text, not retyped:
each method's text from its name onward, re-indented two columns, except
lines that continue a template literal, which keep their bytes because
there the indentation is SQL text.
## The published `.d.ts` does not change
Under the series bar (ruling `6348891160`, item 1), measured on base
`e649c2d91` and on head `021043d57`, both built against the same
dependency tree (`turbo run build --filter=@objectstack/driver-turso...`
at base, 8/8 tasks, 0 cached; then `pnpm --filter
@objectstack/driver-sql build` at head).
- This package emits `dist/index.d.ts` and `dist/index.d.mts` (there is
no `.d.cts`); the two are identical to each other at both ends.
- Raw sha256 of both files, base and head, all four the same:
`93da45168e82136b7613944525e47402d5dc6f9f8daa9efa4bdceeaa11b52a89`. The
raw diff is 0 lines.
- Normalised sha256, base and head the same:
`93da45168e82136b7613944525e47402d5dc6f9f8daa9efa4bdceeaa11b52a89`. The
normaliser is the ruling's, verbatim, run from
`packages/drivers/driver-sql`:
```sh
node -e 'const s=require("fs").readFileSync(process.argv[1],"utf8");process.stdout.write(s.replace(/"[^"\n]*"(?:\s*\|\s*"[^"\n]*")+/g,r=>r.split(/\s*\|\s*/).sort().join(" | ")))' dist/index.d.ts | sha256sum
```
- Note: this `.d.ts` spells its string-literal unions with single quotes
(31 such unions, 0 double-quoted), so the normaliser rewrites nothing
here and normalised equals raw. The proof therefore rests on the raw
identity, which is stronger.
- Positive control on a copy of the head `dist/index.d.ts`: line 8165,
`protected introspectColumns(...)`, made public gives raw and normalised
`e85d733d6d77d1a644666cc45167bf25bd4f1e13bef0a80af977a469339b5823`,
different from the real hash.
- Export tables: the ESM and CJS bundles each export the same 56 names
at base and head, loaded at runtime (`import()` and `require` of the
built bundles). The sorted list hashes
`d61a9dd3a02661602b1bb2dbe18152ac43986148f36b2746f40c0884bd8ffdec` on
all four.
- The head build carries the move: `dist/index.mjs` and `dist/index.js`
each contain the 5 host `.call(` sites.
No changeset: `skip-changeset`, per ruling `6348891160` item 4.
## Proof of move-only
- **Bodies, by AST token** (recursive `getChildren` leaves): each moved
function's parameters (less the added `this`), return type and body have
the same token sequence as the base method; `async` matches on all five.
1,546 tokens compared, 0 differ. The only tokens whose text moved are
the three parameter-level JSDoc comments on `onFailure`, equal after
removing exactly two columns per continuation line.
- **Lines**: 591 lines compared; each is the base line with two leading
spaces removed, or (86 lines) byte-identical where the base line
continues a template literal.
- **Negative control**: one changed token in a template literal of
`introspectColumnOrder` makes the check report a difference, at token
and at line level.
- **Class side**: 347 members at base and head. The 342 members that did
not move are byte-identical to base, leading trivia included. Each of
the 5 stubs keeps its docblock and signature byte-identical, less
`async`.
- **Top level of `sql-driver.ts`**: all 199 base statements other than
the class are byte-identical, in order. The one new statement is the
import of the new module.
- **Size**: +738 / -556 over 2 files, under the 3,000-line manual-merge
line. `sql-driver.ts` goes from 22,158 to 21,675 lines (each stub keeps
its docblock and carries its host literal);
`sql-driver-introspection.ts` has 665.
## Readers of `sql-driver.ts` by path, checked against the moved text
None needed an edit; each was read against the moved region (lines
21386-22083) and run at both ends where it is a gate or test:
- **ADR anchors.**
`scripts/adr-anchors/packages__drivers__driver-sql__src__sql-driver.ts.json`
carries ADR-0053 (`formatOutput`, `presentReadValue`) and ADR-0120 D4;
neither paragraph describes a moved body, so per card ask 3 the new
module gets no entry of its own. The moved text cites only a bare
`ADR-0015` (no decision letter), which is not in the entry.
`check:adr-anchors`: 84 anchored files at base and head; decision-letter
citations on the anchored surface 1,883 at base and 1,883 at head.
- **Symbol anchors.** ADR and platform-checklist anchors name
`sql-driver.ts#introspectSchema` (unmoved); no anchor names a moved
member, and the stubs keep every name anyway.
`check:adr-symbol-anchors`: 2,384 anchors resolve at both ends.
- **Source scans**, none of whose patterns occurs in the moved text:
`logger-receiver-detach.test.ts` (the moved text has no `logger` read),
`sql-driver-compile-refusal-seam.test.ts` (no `INVALID_FILTER` builder),
the five `packages/cli` `generate-*.pin.test.ts` files (`createColumn`
and the varchar constants), `check-keyed-text-bounds`
(`varcharColumnChars`), `check-tenant-chokepoint` (24 `getBuilder()`
bindings across 3 files at both ends; none in the moved text),
`check-object-def-param-keys` (`TursoDriver`'s override of
`introspectColumns` still matches the stub's signature).
- **Path lists** that name the file for cache inputs or fixtures
(`turbo.json`, `scripts/cross-package-test-inputs.mjs`,
`scripts/pm/check-half-states.mjs`,
`scripts/check-changeset-no-major.mjs`, `packages/spec/liveness/*.json`
evidence): the readers they serve read `createColumn`, the varchar
constants or unmoved symbols.
- **`driver-turso`**: its only reader of a moved member is
`introspectColumns`, an override that calls `super.introspectColumns`;
it reaches the stub and is unchanged. Its suite was run at both ends
(below).
- `check:driver-conformance` at base and head: 55 covered, 0 in the DEBT
ledger, 0 exempt, both times.
## Verification
- `tsc --noEmit` (driver-sql) exit 0; `eslint --no-inline-config` on
both source files exit 0 (base file also exit 0).
- `packages/drivers/driver-sql` suite: base 3,999 tests (3,787 passed, 0
failed, 212 skipped; 247 files); head the same. Compared per test name
and status from vitest's JSON reporter: no test lost, none changed
status, none added.
- `packages/drivers/driver-turso` suite (run against the rebuilt head
`dist`): base 2,526 tests (2,492 passed, 0 failed, 34 skipped; 95
files); head the same, per test name and status no difference.
- `dispatch-gates --commands` on this diff: 55 derived commands, each
run at head with its exit code; `--ran` reconciles 55 derived, 54 run, 1
NOT MEASURED, 0 unrun. The NOT MEASURED one is
`check:dual-build-cjs-loads` (exit 3, PREREQUISITE NOT MET: it needs
every package's `dist`); CI runs it. `check-plugin-teardown-shape
--self-test` and `check:lean-entry-closure` first answered exit 3 for
prerequisites (a pinned fixture commit outside the shallow clone;
`objectql`'s `dist`), and exit 0 after fetching that commit and building
`objectql`.
- No reverse verification or ablation: the diff changes no tested
behaviour.
## Acceptance notes
- The claim's file surface lists "one changeset"; ruling `6348891160`
item 4 says no changeset and `skip-changeset` for a cut that meets item
1, and the dispatch says the same. This PR follows the ruling.
- The dispatch's assumption A2 (an `adr-anchors` entry per new module)
is the reference PR's shape, not a rule; card ask 3 makes the entry
conditional on a paragraph describing a moved body, and none does here.
---
_Generated by [Claude
Code](https://claude.ai/code/session_01JVTd3PprStGa26aez5omLB)_
Co-authored-by: Claude <noreply@anthropic.com>
1 parent 7265b33 commit f7b0298
2 files changed
Lines changed: 738 additions & 556 deletions
0 commit comments