Repository navigation
Commit 817cb89
<!-- claude-projects-attribution: {"github_login":"os-project-manager"}
-->
_Requested by **Leehom** · [project
thread](https://claude.ai/code/project/chan_01TenzsRopZj4jMbvjX8uz1X?thread=cmsg_01TenzsRopZj4jMbvjX8uz1XYRE6GPE8YzTknPF34Y6dqS)_
Part of #25521
Clause-②: no
Metadata-protocol: none
Ruling-ref: 6270097840
Ruling-ref: 6348891160
Cut 10 of the `sql-driver.ts` split (#24586 ruling A, recorded in
`6270097840`). The claim is `6423425118`. The cut opens with a fresh
seam table (below) and takes the area it prices best that breaks no
series limit. The bodies of six `SqlDriver` methods move unedited into
the existing seam module
`packages/drivers/driver-sql/src/sql-driver-schema-sync.ts`, through its
existing host `SchemaSyncHost`. Together they are the managed-schema
drift pipeline, the code that sits on top of the reconcilers and the
index machinery cuts 6, 8 and 9 already moved there:
- `detectTableDrift`, which diffs one table's columns against its
physical schema;
- `detectTableIndexDrift`, which diffs that table's indexes and runs the
ADR-0120 pre-flight on the result;
- `detectManagedDrift` (public), which runs the table diff over every
managed table;
- `planMediaColumnMove` (public), the read-only plan of the ADR-0104
media column move;
- `reconcileAndWarnDrift`, the boot-time reconcile-or-warn;
- `applyMigrationEntries` (public), which applies a set of drift
entries.
The change is move-only, and the card stays open for later cuts. There
is no `docs/adr/**` edit, no `packages/spec/**` edit, no anchor entry
change and no new gate. Branch `claude/project-thread-5jymjn` (the
platform assigned it to this card thread, as the claim records). One
commit, `5fba834b4`, on `origin/main` `48fba2b178`.
**Net line count (the seat's rule).** `sql-driver.ts` goes from 20,990
lines to 20,739: **-251**.
- Out: the six bodies, 314 lines (52 + 32 + 21 + 75 + 57 + 77), and 15
import names whose last reader was a moved body (one line each).
- In: the six stub bodies, 76 lines (12 + 10 + 11 + 13 + 16 + 14, each 4
+ 2 per field or getter read + 1 per method read), and 2 import-comment
lines.
- Priced before the move at -238 for the bodies alone; the import names
are the rest.
- The module grows from 777 to 1,154 lines.
**Serial with the file's in-flight PRs.** I read the full file list of
every open PR at start (37 PRs) and again before `pr_create` (35 PRs, 17
of them new). No open PR touches `sql-driver-schema-sync.ts`,
`turso-driver.ts`, the drift section of `sql-driver.ts` or the
driver-sql anchor entries.
- #26261 (`$now` comparand) is the one that edits `sql-driver.ts`. Its
hunks are in the module preamble and in Filter helpers
(`applyFilterCondition`'s operator loop and a new
`applyNowAnchorComparison` beside the cross-field comparison), not in
the drift section. Whichever of the two lands second merges `main`.
- #26138 touches `driver-sql/src/builtin-column-collision.ts` only, and
#26267 the `vitest.config.ts` of driver-turso and driver-sqlite-wasm
only. The release PR #25640 touches driver `package.json` and
`CHANGELOG.md` files only.
## Seam table, fresh, on `48fba2b178`
`origin/main` is at `48fba2b178`, after cut 9 (`ab112ea8d5`).
- **Size and churn.** `sql-driver.ts` is 20,990 lines. 30-day lines are
`git blame` lines from commits on or after 2026-09-26T17:15:20Z, 30 days
before the base commit. I deepened history to 2026-09-18 first, and
`scripts/pm/git-history.mjs count` proves 69 commits on the file in that
window (27 in the last 7 days). In all, 3,202 lines (15.3%) are from the
last 30 days.
- **Class shape.** `SqlDriver` spans lines 5513-20915 with 348 members:
241 instance methods (49 public), 13 accessors and 32 statics, all 32 of
them private or protected. 24 of the instance methods are already stubs
from cuts 1-9.
- **Inferred return types.** One public member has one: the `supports`
getter, in the class head.
- **How it was measured.** A TypeScript compiler AST script, which
strips casts, parentheses, `!` and `satisfies` before it reads `this`.
Sections are the class's own banner comments (`// ===` blocks and `//
──` sub-banners), as cut 9 cut them; cut 9's table also folds the
docblock heading "The sequence semantics a second face shares" into
CRUD, and so does this one. "Methods" counts instance method
declarations, without accessors.
| Lines | Section | First … last member | Lines | 30-day | Methods
(public) | Inferred public | getBuilder sites | Statics
(private/protected) |
|:--|:--|:--|--:|--:|:--|--:|--:|:--|
| 1-5512 | Module preamble | - | 5,512 | 754 | - | - | - | - |
| 5513-7104 | Class head: fields, statics, constructor, capability
getters | `name` … `assertDialectEmits` | 1,592 | 290 | 8 (1) | 1 | 0 |
18 (18) |
| 7105-7536 | Lifecycle | `connect` … `disconnect` | 432 | 155 | 8 (3) |
0 | 0 | 6 (6) |
| 7537-10241 | CRUD — IDataDriver core | `find` … `delete` | 2,705 | 739
| 43 (7) | 0 | 12 | 0 (0) |
| 10242-10853 | Bulk & Batch Operations | `bulkCreate` … `count` | 612 |
119 | 9 (6) | 0 | 7 | 0 (0) |
| 10854-10950 | Raw Execution | `rawStatementFault` …
`noteRuntimeIndexDdl` | 97 | 28 | 3 (1) | 0 | 0 | 0 (0) |
| 10951-11043 | Transactions | `beginTransaction` …
`rollbackTransaction` | 93 | 46 | 7 (5) | 0 | 0 | 0 (0) |
| 11044-11575 | Aggregation | `aggregate` …
`unresolvableAggregateColumnRefusal` | 532 | 60 | 5 (1) | 0 | 1 | 0 (0)
|
| 11576-11863 | Distinct | `distinct` …
`unresolvableDistinctColumnRefusal` | 288 | 17 | 3 (1) | 0 | 1 | 0 (0) |
| 11864-11979 | Window Functions | `findWithWindowFunctions` …
`findWithWindowFunctions` | 116 | 4 | 1 (1) | 0 | 1 | 0 (0) |
| 11980-12078 | Query Plan Analysis | `explain` … `analyzeQuery` | 99 |
1 | 2 (2) | 0 | 1 | 0 (0) |
| 12079-12137 | Schema Sync (syncSchema / init) | `syncSchema` …
`reclaimSpace` | 59 | 34 | 3 (3) | 0 | 0 | 0 (0) |
| 12138-13879 | Schema Sync (syncSchema / init) › Data-lifecycle
rotation (ADR-0057 P2) | `rotationStateByTable` …
`migrateMysqlTimeColumns` | 1,742 | 72 | 26 (4) | 0 | 0 | 0 (0) |
| 13880-14136 | Schema Sync (syncSchema / init) › Deferred schema DDL
(#3917) | `setDeferredDdl` … `flushDeferredSchemaDdl` | 257 | 25 | 6 (4)
| 0 | 0 | 0 (0) |
| 14137-15189 | Schema Sync (syncSchema / init) › Managed-schema drift &
reconcile (#2186) | `dialectName` … `createNullSafeUniqueIndex` | 1,053
| 193 | 23 (3) | 0 | 0 | 0 (0) |
| 15190-15241 | Schema Introspection | `listPhysicalColumns` …
`introspectSchema` | 52 | 46 | 2 (2) | 0 | 0 | 0 (0) |
| 15242-15512 | Internal helpers | `getKnex` … `auditMissingTenant` |
271 | 73 | 8 (1) | 0 | 1 | 0 (0) |
| 15513-17765 | Internal helpers › Filter helpers |
`tableNameForBuilder` … `applyFalseConstant` | 2,253 | 250 | 42 (2) | 0
| 0 | 0 (0) |
| 17766-17941 | Internal helpers › Field mapping | `mapSortField` …
`mapAggregateFunc` | 176 | 2 | 6 (0) | 0 | 0 | 0 (0) |
| 17942-17989 | Internal helpers › Window function builder |
`buildWindowFunction` … `buildWindowFunction` | 48 | 26 | 1 (0) | 0 | 0
| 0 (0) |
| 17990-19936 | Internal helpers › Column creation helper |
`nowColumnDefault` … `applyDeclaredColumnDefault` | 1,947 | 92 | 19 (0)
| 0 | 0 | 8 (8) |
| 19937-20234 | Internal helpers › Database helpers | `sqliteFilename` …
`isJsonField` | 298 | 76 | 9 (2) | 0 | 0 | 0 (0) |
| 20235-20711 | Internal helpers › SQLite serialisation | `formatInput`
… `formatOutput` | 477 | 27 | 2 (0) | 0 | 0 | 0 (0) |
| 20712-20915 | Internal helpers › Introspection internals |
`introspectColumnOrder` … `introspectUniqueConstraints` | 204 | 73 | 5
(0) | 0 | 0 | 0 (0) |
| 20916-20990 | After the class | - | 75 | 0 | - | - | - | - |
### Pricing the eligible methods
A method is eligible to move when it meets every condition below. 168 of
the 241 instance methods do (24 are already stubs, 19 build through
`getBuilder`, 17 read a private static, 12 write to `this`, 3 have a
`this.FIELD.delete/insert/update(` site, and one is
`insertOnlyUpsertColumns`).
- Its return type is annotated, or it is not public.
- It has no `getBuilder` site, no `this.FIELD.delete/insert/update(`
site, no private or protected static read, no `super` read, no bare
`this` pass and no write to `this`.
- It is not `insertOnlyUpsertColumns`.
**The net-line rule.** Each candidate is priced as its stub minus its
body. A stub costs 4 lines, plus 2 for each field or getter read and 1
for each method read.
The highest-priced candidates, and why each one stays or moves:
| Method (section) | Net | Disposition |
|:--|--:|:--|
| `createColumn` (Column creation helper) | -463 | **Stays.** ADR-0113
cites `sql-driver.ts#createColumn`, and five `packages/cli`
`generate-*.pin.test.ts` files read this file's text. |
| `applyFilterCondition` (Filter helpers) | -357 | **Stays.** ADR-0139
cites `sql-driver.ts#applyFilterCondition`. |
| `formatOutput` (SQLite serialisation) | -236 | **Stays.** ADR-0104
cites it, and the `sql-driver.ts` anchor entry's ADR-0053 paragraph
describes it. |
| `initObjects` (Rotation) | -195 | **Stays.**
`packages/spec/liveness/object.json` cites it. |
| `formatInput` (SQLite serialisation) | -183 | **Stays.** ADR-0053 and
ADR-0104 cite it. |
| `applyCrossFieldComparison` (Filter helpers) | -179 | **Not taken.**
Open PR #26261 is editing the cross-field area of Filter helpers right
now, and the body reads four module-local helpers
(`crossFieldComparisonClass`, `crossFieldComparisonError`,
`fieldReferenceOffsetOf`, `uncompilableFieldReferenceError`) that other
class members also call, so it would need exports or a helper move. |
| `assertConflictTargetHonoured` (CRUD) | -89 | Not taken: it reads five
module-local declarations of the upsert conflict-target refusal. |
| `backendStatementFault` (Bulk & Batch) | -75 | Not taken: a single
method, and no seam module holds the fault doors yet. |
| `compileFilters` (Filter helpers) | -75 | Not taken: Filter helpers
(see #26261 above). |
| `getNextSequenceValue` (CRUD) | -66 | Not taken here; proposed below
with the rest of the sequence area. |
| **`applyMigrationEntries`** (drift) | **-63** | **Moves (this cut).**
|
| **`planMediaColumnMove`** (drift) | **-62** | **Moves (this cut).** |
| **`reconcileAndWarnDrift`** (drift) | **-41** | **Moves (this cut).**
|
| **`detectTableDrift`** (drift) | **-40** | **Moves (this cut).** |
| **`detectTableIndexDrift`** (drift) | **-22** | **Moves (this cut).**
|
| **`detectManagedDrift`** (drift) | **-10** | **Moves (this cut).** |
**Priced by area** (every eligible body with a negative price and no
`sql-driver.ts#…` pointer, summed per section): Filter helpers -448 (20
bodies), Rotation -388 (15), CRUD -351 (18), Managed-schema drift -275
(10), and every other section -93 or less.
## Why this area
- **It pays the most of any area that moves cleanly.** The three
sections priced above it each fail on the card's own terms:
- **Filter helpers** is where open PR #26261 is editing, so the serial
rule takes it out of this cut.
- **Rotation**'s paying bodies read eight module-local declarations that
other class members also read (`SQLITE_NON_TEMPORAL_TEXT_REFS`,
`isMultiValuedColumn`, `isDeferredDdlLockWaitRefusal`,
`isNowDefaultValue`, `AUDIT_TIMESTAMP_COLUMNS` and more). Moving them
needs exports added to `sql-driver.ts` or a helper moved away from its
other callers. It is also three areas under one banner: rotation, the
canonical datetime and JSON backfills, and the MySQL column migrations.
- **CRUD**'s paying bodies are the sequence and upsert code:
`SEQUENCES_TABLE` and five conflict-target helpers are module-local and
shared.
- **The six bodies read no module-local declaration of
`sql-driver.ts`.** Every declaration they read is imported from the
module that declares it (`schema-drift.ts`, `media-column-move.ts`,
`@objectstack/spec/system`) or is the exported type
`IntrospectedColumn`. No `export` was added anywhere, and no helper
moved.
- **They belong in an existing module and host.**
`sql-driver-schema-sync.ts` already holds what this pipeline calls:
`applyDriftOpInPlace` and `rebuildSqliteTablePatched` (cut 6),
`applyIndexDriftOp` (cut 8), `applyNullSafeUniquePreflight` (cut 9).
`SchemaSyncHost` already listed 8 of the 26 members these bodies read.
- **No series limit is touched:**
- none of the six builds through `getBuilder`;
- none reads a static;
- none has a `this.FIELD.delete/insert/update(` site
(`this.driftWarned.add(` is a Set write, read through the host getter,
and the tenant-audit census reads the same 243 sites at both ends);
- none uses `super`, a bare `this` or a cast-wrapped `this`, and none
writes to `this`;
- the three public ones (`detectManagedDrift`, `planMediaColumnMove`,
`applyMigrationEntries`) have annotated return types; the other three
are protected.
- **Subclasses.** `TursoDriver` overrides `detectManagedDrift`,
`applyMigrationEntries` and `planMediaColumnMove`, and each calls
`super` on its local path. The stubs stay on the class with the same
signatures, so `super` still reaches them, and the moved bodies reach
every method through a late-bound closure on the instance, so Turso's
`introspectColumns`, `introspectIndexes` and `applyMigrationEntries`
overrides are still the ones that run. One driver-sql test
(`sql-driver-11722-sqlite-rebuild-applied-honesty.test.ts`) overrides
`detectTableDrift` in a subclass; it passes at both ends, and the probe
below covers the same shape.
- **Path readers, screened.** No script, test, doc or ADR names any of
the six by a `sql-driver.ts#…` path, and no test or gate slices their
text out of `sql-driver.ts` (the files that read `sql-driver.ts` by path
are the five cli `generate-*` pins, `logger-receiver-detach.test.ts`,
`sql-driver-compile-refusal-seam.test.ts` and the gates in the table
below; none of them reads these bodies). See Acceptance notes for prose
that names them.
## The shape (#23050 shape B, as cuts 1-9 apply it)
- Each class method keeps its docblock and signature, byte-identical
less `async`. Its body is now one host literal plus `NAME.call(host,
...)`, marked `[#25521 cut 10]`, and it returns the moved function's own
promise.
- Fields and getters go as GETTERS that read the driver at call time:
`autoMigrate`, `dialectName`, `driftWarned`, `fileColumnsMoved`,
`isSqlite`, `knex`, `logger`, `managedObjectFields`,
`managedObjectIndexes`, `mediaFields`, `runtimeCreatedIndexes` and
`schemaMode`. Methods go as closures that call the method on the driver
at call time. So a subclass override, an instance spy, or a field
replaced after construction is still the one the body reaches (see the
probe below). `this.logger.info(...)` and `this.logger.warn(...)` still
call through the logger itself, as the #12792 comment in
`reconcileAndWarnDrift` requires.
- Each moved function types its `this` as the Pick of `SchemaSyncHost`
over exactly the members it reads. Each stub annotates its literal as
the ThisParameterType of the moved function. There is no cast, and no
member becomes public.
- `SchemaSyncHost` gains 18 members, each typed as the class member's
indexed access, in the interface's alphabetical order:
`applyDriftOpInPlace`, `applyMigrationEntries`,
`applyNullSafeUniquePreflight`, `assertSchemaMutable`, `autoMigrate`,
`detectTableDrift`, `detectTableIndexDrift`, `driftWarned`,
`fileColumnsMoved`, `introspectIndexes`, `isProductionEnv`, `isSqlite`,
`isTenantKeyCertified`, `mediaFields`, `rebuildSqliteTablePatched`,
`runtimeCreatedIndexes`, `schemaMode` and `varcharColumnChars`.
- **Module-level declarations.**
- The module now imports from `schema-drift.ts`
`carriesNullSafeTenantKeyPart`, `ceremonyOwnedDropFinding`,
`diffManagedIndexes`, `diffManagedTable`, `diffUnbuildableIndexes`,
`driftKey`, `expectedIndexes`, `indexedKeyColumns`,
`isCeremonyOwnedColumnDrop`, `legacyUniqueReplacements` and the type
`PhysicalColumn`; from `media-column-move.ts` `isJsonColumnType`,
`mediaColumnMoveDialect`, `mediaColumnMovePlan` and the three
`MediaColumnMove…` types; `StorageNameMapping` from
`@objectstack/spec/system`; and the type `IntrospectedColumn` from
`sql-driver.ts` (a type-only import, as the module already does for
`SqlDriver`).
- `sql-driver.ts` drops the 15 of those names it no longer reads (all
but `diffManagedIndexes`, `indexedKeyColumns`, `StorageNameMapping`,
`IntrospectedColumn` and `MediaColumnMoveScan`, which other code there
still reads). `isIndexDriftOp` is one of the 15, because
`applyMigrationEntries` was its last reader there.
- `index.ts` re-exports nothing from the module, so the published
surface does not move.
- **Comments.** The module header gains a `[#25521 cut 10]` paragraph
that cites no decision letter. The `sql-driver.ts` import comment names
cut 10 in 2 lines.
- **How the move was made.** A script produced it from the source text;
nothing was retyped. Each body line is re-indented two columns. No line
in the moved bodies starts inside template-literal text.
## ADR anchors
`check:adr-anchors` counts decision-letter citations on the anchored
surface: each anchor's `invariant`, plus the full text of each anchored
file.
- **Both files are anchored.** `sql-driver.ts` has its ADR-0053 entry,
and `sql-driver-schema-sync.ts` has the ADR-0120 entry cut 8 created.
- **The citations move between anchored files.** The moved bodies carry
their `ADR-0131 D1` and `ADR-0131 D10` citations from one anchored file
to the other, and the new header paragraph cites no letter.
- **The count.** It is **1881 decision-letter citations at base
`48fba2b178` and at head `5fba834b4`**, over 90 anchored files at both
ends. Both runs exit 0.
- **No entry needs a new home.** Neither entry's paragraph describes a
moved body: the ADR-0053 paragraph describes `formatOutput` and
`presentReadValue`, and the ADR-0120 D4 paragraph describes the
tightening ceremony that cuts 8 and 9 moved.
## The published `.d.ts` does not change
This is measured under the series bar (ruling `6348891160`, item 1).
- **How each end was built.** I built each end with `pnpm turbo run
build --filter=@objectstack/driver-sql --force`, against the same
dependency tree. The base leg writes the base text of the two source
files into this worktree after the commit, under an EXIT/INT/TERM trap
that restores HEAD. The restore is proved by the HEAD blob hashes
(`d4d08264`, `90b63682`), an empty `git diff HEAD` and 0 status entries.
- **The hashes.** The raw sha256 of `dist/index.d.ts` and
`dist/index.d.mts` is the same at base and at head:
`5028e040e1501b0bd72b91fafcf4e1cb372393c59baa5bab8b936b240dd2d3f5` (cut
9's hash). `cmp` finds them identical.
- **Positive control.** I dropped `protected` from `detectTableDrift` in
the head `index.d.ts`. The result hashes to
`a9d58e424308f85ac7429096c80df2554b41abe53d67410e030799a463072be5`, with
a 1-line diff and `cmp` exit 1.
- **Export lists.** CJS `require` and ESM `import` of the built dist
give 56 / 56 names at both ends, identical.
- **The head dist carries the move.** `.call(host` sites in `index.mjs`
go from 24 at base to 30 at head.
## Move-only proof
The proof compares TypeScript compiler AST leaf tokens (JSDoc nodes
skipped), base `48fba2b178` against head `5fba834b4`. It is a scratch
script and is not committed.
| Moved body | Body tokens | Comments | Parameter tokens less `this` |
Return type tokens | Body lines |
|:--|--:|--:|--:|--:|--:|
| `detectTableDrift` | 231 | 16 | 17 | 6 | 52 |
| `detectTableIndexDrift` | 149 | 15 | 27 | 6 | 32 |
| `detectManagedDrift` | 239 | 0 | 28 | 6 | 21 |
| `planMediaColumnMove` | 412 | 4 | 0 | 4 | 75 |
| `reconcileAndWarnDrift` | 351 | 15 | 17 | 4 | 57 |
| `applyMigrationEntries` | 470 | 18 | 17 | 16 | 77 |
- **Each moved body.** In every row, 0 tokens differ and every comment
is identical. `async` is on both ends, and there are no type parameters.
Each body line is the base line less two leading spaces.
- **The six stubs.** Leading trivia (the docblock), modifiers and the
signature are identical to base, less `async`.
- **The class.** It has 348 members at both ends, in the same order, and
the 342 unmoved members are byte-identical, trivia included.
- **`sql-driver.ts` top level.** It has 206 statements at both ends. The
only base statements that differ are the `schema-drift.js`,
`media-column-move.js` and `sql-driver-schema-sync.js` imports, and the
class.
- **`sql-driver-schema-sync.ts`.** It goes from 12 to 20 statements. The
base statements that differ are the first import (which carries the
module header), the `schema-drift.js` and `sql-driver.js` imports, and
`SchemaSyncHost`. The new statements are the `@objectstack/spec/system`
and `media-column-move.js` imports and the six functions.
- **Result.** `PROOF OK`, exit 0.
- **Negative control.** I changed `could not introspect` to `could not
intr0spect` in a module copy. The proof then reports
`reconcileAndWarnDrift` with its body tokens and lines differing, prints
`PROOF FAILED`, and exits 1.
## Tests
Each end ran under one `os-verify-lock` hold, after building that end's
driver-sql dist. I compared the vitest JSON per test name and status,
base `48fba2b178` against head `5fba834b4`. Every part's exit code was
captured on its own (build, three suites: all 0 at both ends).
| Suite | Base | Head | Lost / changed / added |
|:--|:--|:--|:--|
| `@objectstack/driver-sql` | 4010 (3796 passed, 214 skipped; 248 files)
| same | 0 / 0 / 0 |
| `@objectstack/driver-turso`, against that end's built driver-sql dist
| 2526 (2492 passed, 34 skipped; 95 files) | same | 0 / 0 / 0 |
| `@objectstack/driver-sqlite-wasm` | 704 (704 passed; 39 files) | same
| 0 / 0 / 0 |
- **Type check and lint.** `pnpm exec tsc --noEmit -p .` in driver-sql
exits 0. `pnpm exec eslint --no-inline-config` on the two touched files
exits 0, with 0 problems.
- **Late-binding probe.** This is a scratch script, not committed. It
runs node against each end's built dist on in-memory SQLite. Its 27
readings print identical output at base and head (`diff` is empty). They
cover:
1. `detectManagedDrift` reaching a SUBCLASS override of
`detectTableDrift` (from the walk and with explicit objects), and
`managedObjectFields` replaced on the instance after construction;
2. `detectTableDrift` reaching instance spies on `introspectColumns` and
`detectTableIndexDrift`, with the physical column set and the NOT NULL
witness it passes;
3. `detectTableIndexDrift` reaching instance spies on
`introspectIndexes` and `applyNullSafeUniquePreflight` (twice, around a
`runtimeCreatedIndexes` entry added on the instance; the index result is
empty both times at both ends, so that reading proves the calls, not the
ledger read);
4. `reconcileAndWarnDrift` with `autoMigrate: 'safe'` on a managed
schema, through a `logger` and a `driftWarned` replaced after
construction: the warn-once lines, the production arm through an
instance spy on `isProductionEnv`, and the introspection-failure warning
through an instance override of `detectTableDrift`;
5. `applyMigrationEntries` on SQLite: a destructive entry skipped, the
column entries through an instance spy on `rebuildSqliteTablePatched`,
an index entry through an instance override of `applyIndexDriftOp`, and
the empty set;
6. `planMediaColumnMove`: a planned `unquote`, `mediaFields` replaced
after construction (an absent column refused), and a subclass
`dialectName` getter answering the unsupported-dialect refusal;
7. `detectTableDrift` with `fileColumnsMoved` flipped on the instance
(an empty result both ways at both ends on SQLite, so it proves only
that the read does not throw);
8. bad arguments to each moved method answering a rejection (or the same
resolution base gives), never a synchronous throw.
- **Not run.** No ablation, because no tested behaviour changed.
`test:repo` was not run locally: the diff touches no repo-project test,
and CI's Test Core runs it.
## Gates
All local runs are on head `5fba834b4`.
- **Derived gates.** `node scripts/pm/dispatch-gates.mjs --commands
--repo objectstack-ai/objectstack` derived 55 commands from the change
set against merge base `48fba2b17` (2 paths, 802 changed lines). I
captured each exit code before any pipe.
- 52 exit 0 on the first run.
- `check-plugin-teardown-shape.mjs --self-test` exited 3 on the shallow
clone, then 0 after `git fetch origin 621a487` (its positive control).
- `check:lean-entry-closure` exited 3 with no objectql dist, then 0
after `pnpm turbo run build --filter=@objectstack/objectql` under the
lock.
- `check:dual-build-cjs-loads` exited 3, PREREQUISITE NOT MET: it needs
every package's dist. It is NOT MEASURED locally, and CI runs it.
- **Reconciliation.** `--ran` reports "55 derived famil(ies) accounted
for — 54 run, 1 NOT-MEASURED (1 DERIVED from a recorded exit 3)", exit
0.
- **Lint.** driver-sql has no `lint` script, so I ran the root lint's
command scoped to the package: `pnpm exec eslint --no-inline-config
packages/drivers/driver-sql`, exit 0.
- **Card-named gates.** I read each at base in a clean detached worktree
at `48fba2b178` and at head `5fba834b4`. All exit 0 at both ends, and
each prints the same reading at both ends except the one noted.
| Gate | Base | Head | Note |
|:--|:--|:--|:--|
| `check:adr-anchors` | 90 anchored files, 1881 citations | same | |
| `check:adr-symbol-anchors` | 2664 anchors resolve | same | |
| `check:tenant-chokepoint` | 24 `getBuilder()` bindings across 3 files;
rule 2 clean | same | See the first acceptance note. |
| `check:logger-receiver-detach` | 0 detaches, 3740 non-test TS files |
0 detaches, 3741 files | The head has one more walked file: the base
worktree lacks the gitignored generated
`packages/spec/src/migrations/registry.ts`. |
| `check:driver-conformance` | 55 covered, 0 DEBT, 0 exempt | same | |
| `check:keyed-text-bounds` | 112 object files, 238 index entries | same
| |
| `check:object-def-param-keys` | 3300 source files, 540 classes, 7
override positions | same | It reads `sql-driver.ts` by path. |
| `check:system-context-census` | 151 read sites, 144 symbols cited |
same | |
| `check-tenant-audit-census` | 243 write call sites certified | same |
|
- **Not owed.** `check-platform-checklist --changed-from` is not owed:
no exported symbol was renamed or deleted. Pre-push
`check:commit-card-trailers` and `check:nul-bytes` pass.
- **`origin/main` moved while this ran.** It moved 19 commits, to
`2409c9c8d7`. In `packages/drivers/**`, `packages/types/src`,
`packages/spec/src/system`, `scripts/adr-anchors/**`,
`check-adr-anchors.mjs` and `check-tenant-chokepoint.mjs`, only
`packages/types/src/node.test.ts` changed (one line). `git merge-tree`
against it is clean. I did not merge, because CI tests the merge ref.
## Acceptance notes
- **`check:tenant-chokepoint` rule 2 visits a little less.** Rule 2 (the
D8 null-arm rule) scans class members of the three driver files, so it
no longer visits what moved: the `resolveTenantField` bindings in
`detectTableDrift` and `detectTableIndexDrift`. Neither says `IS NULL`
against a tenant column, and neither is a `where` arm. The verdict is
unchanged, and the gate is not empty (24 bindings, 3 files). This is the
same disclosure cuts 5-9 made, and the card already carries the
`SCAN_FILES` precondition.
- **`logger-receiver-detach.test.ts` ③ reads `sql-driver.ts` only.** Its
structural pin no longer sees `reconcileAndWarnDrift`'s receiver-kept
log calls, which now live in the module. The repo-wide gate
`check:logger-receiver-detach` walks every non-test TS file, the module
included, and reads 0 detaches at head. The runtime case in the same
test file (`reconcileAndWarnDrift` through a class-based logger) still
runs and passes.
- **Prose that names the moved bodies still holds.**
`docs/qa/platform-checklist/areas/cli.json` cites
`sql-driver.ts#SqlDriverConfig` with a parenthetical describing
`reconcileAndWarnDrift`'s gate and `applyMigrationEntries`'
`allowDestructive: false` call, and one `verify` line says
"(sql-driver.ts)". The anchor symbol `SqlDriverConfig` did not move, and
both methods are still on the class, so the symbol anchor resolves
(`check-platform-checklist` judges the symbol, not the parenthetical);
the behaviour described now runs from the module. `schema-drift.ts`,
`turso-driver.ts` and several tests name `SqlDriver.detectTableDrift`
and the others in prose; each is still a `SqlDriver` method with the
same docblock.
- **Two comments in `sql-driver.ts` now point across files.** The
docblock of `driftWarned` says "keyed by {@link driftKey}", and the
`media-column-move.js` import comment says the column step's statements
are "Built here". `driftKey` and the statement builders are now imported
by the module rather than by `sql-driver.ts`. I left both comments
unedited, since the cut is move-only.
- **Proposed next slice: the sequence area of CRUD**
(`getNextSequenceValue` -66, `fillAutoNumberFields` -52,
`issueAutonumbers` -40, `maxAutonumberCounter` -19,
`resyncSequenceToDataMax` -13 and their small helpers, about -200). It
would need a new seam module. Before pricing it at dispatch, screen
`SEQUENCES_TABLE` (module-local, read by other members; the class
already has a `sequencesTableName` getter), and `TursoDriver`'s
overrides of `getNextSequenceValue`, `autoNumberValueExists` and
`resyncSequenceToDataMax`, which the "stated once" banner says a second
face must call, never copy.
`skip-changeset`: the change is move-only, so nothing a published
package ships changes. The `.d.ts` is byte-identical, and the emitted JS
runs the same bodies through the host.
---
_Generated by [Claude
Code](https://claude.ai/code/session_01JTp8gt1Ujr3CiXttYmfFS9)_
Co-authored-by: Claude <noreply@anthropic.com>
1 parent 8faba89 commit 817cb89
2 files changed
Lines changed: 464 additions & 338 deletions
0 commit comments