Skip to content

Commit 817cb89

Browse files
refactor(driver-sql): move the schema-drift pipeline (detect, reconcile, apply) out of sql-driver.ts into sql-driver-schema-sync.ts (#25521 cut 10) (#26287)
<!-- 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

File tree

0 commit comments

Comments
 (0)