Skip to content

Commit 8b39b77

Browse files
refactor(driver-sql): move the canonical-encoding convergence out of sql-driver.ts into sql-driver-schema-sync.ts (#25521 cut 12) (#26357)
<!-- claude-projects-attribution: {"github_login":"os-project-manager"} --> _Requested by **Leehom** · [project thread](https://claude.ai/code/project/chan_01TenzsRopZj4jMbvjX8uz1X?thread=cmsg_01TenzsRopZj4jMbvjX8uz1X1Cna1X7zGXBer8k5CGbdU4)_ Part of #25521 Clause-②: no Metadata-protocol: none Ruling-ref: 6270097840 Ruling-ref: 6348891160 Cut 12 of the `sql-driver.ts` split (#24586 ruling A, recorded in `6270097840`). The claim is `6427355170`. The cut opens with a fresh seam table (below), taken on `origin/main` `83e9d7187` (cut 11's merge), and takes the area that pays best without breaking a series limit. The bodies of eight `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 canonical-encoding convergence that schema sync runs at init: - the SQLite canonical backfills: `backfillCanonicalDatetimes`, `backfillCanonicalTimes` and `backfillCanonicalJsonEncoding`, with `countNonTemporalTextRows` and `convergeJsonColumn`, which they call; - of the MySQL widening of legacy zero-precision columns: the two `information_schema` reads `legacyMysqlTimestampColumns` and `legacyMysqlTimeColumns`, and `runWideningAlters`, which runs the ALTERs under the #9354 lock-wait bound. 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, no gate edit and no new gate. Branch `claude/project-thread-c22v4e` (the platform assigned it to this card thread, as the claim records). The move is one commit, `ff5b18102`, on `83e9d7187`. Then `origin/main` was merged once before opening (`274ac8541`), which brought no `driver-sql` file. **Net line count (the seat's rule).** `sql-driver.ts` goes from 20,827 lines to 20,492: **-335**. - **Out:** the eight bodies, 278 lines (60 + 55 + 37 + 37 + 9 + 20 + 20 + 40). Also out are four module-level declarations that only these bodies read, 139 lines with their docblocks plus 4 separator lines: `SQLITE_JSON_BACKFILL_PAGE_SIZE` (6), `DEFERRED_DDL_LOCK_WAIT_TIMEOUT_SECONDS` (52), `isMysqlLockWaitTimeout` (27) and `deferredDdlLockWaitError` (54). Finally, the import line of `recoverUnencodedJsonText`, whose last reader moved; two `{@link}` mentions of it remain in docblocks. - **In:** the eight stub bodies, 85 lines (each is 4 lines, plus 2 per field read and 1 per method read, plus its closing brace), and 2 comment lines on the existing schema-sync import. - The module goes from 1,154 lines to 1,653. **Serial with the file's in-flight PRs.** I read the full file list of every open PR twice: at start, on cut 11's head (29 PRs), and again after cut 11 merged, before the first edit (28 PRs). No open PR touches `sql-driver.ts`, `sql-driver-schema-sync.ts`, `turso-driver.ts`, `check-tenant-chokepoint.mjs`, `check-durability-degradation-log-level.mjs` or the driver-sql anchor entries. #26138 touches `driver-sql/src/builtin-column-collision.ts` only. #26267 (the `vitest.config.ts` of driver-turso and driver-sqlite-wasm) merged in between, and the merge commit carries it. #25640 is the release PR (changelogs and `package.json` versions). ## Seam table, fresh, on `83e9d7187` - **Size and churn.** `sql-driver.ts` is 20,827 lines. The 30-day column counts `git blame` lines from commits on or after 2026-09-26T22:04:20Z, 30 days before the base commit. History reaches 2026-09-12. `scripts/pm/git-history.mjs count` proves 72 commits on the file in 30 days and 30 in the last 7. In all, 3,527 lines (16.9%) are from the last 30 days. - **Class shape.** `SqlDriver` spans lines 5594-20752 with 350 members: 243 instance methods, 13 accessors and 32 statics, all 32 private or protected. - **How it was measured.** The same TypeScript compiler AST script cuts 9-11 used, which strips casts, parentheses, `!` and `satisfies` before it reads `this`. Sections are the class's own banner comments. | Lines | Section | First … last member | Lines | 30-day | Methods (public) | Inferred public | getBuilder sites | Statics (private/protected) | |:--|:--|:--|--:|--:|:--|--:|--:|:--| | 1-5593 | Module preamble | - | 5,593 | 850 | - | - | - | - | | 5594-7185 | Class head: fields, statics, constructor, capability getters | `name` … `assertDialectEmits` | 1,592 | 290 | 8 (1) | 1 | 0 | 18 (18) | | 7186-7617 | Lifecycle | `connect` … `disconnect` | 432 | 155 | 8 (3) | 0 | 0 | 6 (6) | | 7618-10128 | CRUD — IDataDriver core | `find` … `delete` | 2,511 | 736 | 43 (7) | 0 | 12 | 0 (0) | | 10129-10740 | Bulk & Batch Operations | `bulkCreate` … `count` | 612 | 119 | 9 (6) | 0 | 7 | 0 (0) | | 10741-10837 | Raw Execution | `rawStatementFault` … `noteRuntimeIndexDdl` | 97 | 28 | 3 (1) | 0 | 0 | 0 (0) | | 10838-10930 | Transactions | `beginTransaction` … `rollbackTransaction` | 93 | 46 | 7 (5) | 0 | 0 | 0 (0) | | 10931-11462 | Aggregation | `aggregate` … `unresolvableAggregateColumnRefusal` | 532 | 60 | 5 (1) | 0 | 1 | 0 (0) | | 11463-11750 | Distinct | `distinct` … `unresolvableDistinctColumnRefusal` | 288 | 17 | 3 (1) | 0 | 1 | 0 (0) | | 11751-11866 | Window Functions | `findWithWindowFunctions` … `findWithWindowFunctions` | 116 | 4 | 1 (1) | 0 | 1 | 0 (0) | | 11867-11965 | Query Plan Analysis | `explain` … `analyzeQuery` | 99 | 1 | 2 (2) | 0 | 1 | 0 (0) | | 11966-12024 | Schema Sync (syncSchema / init) | `syncSchema` … `reclaimSpace` | 59 | 34 | 3 (3) | 0 | 0 | 0 (0) | | 12025-13766 | Schema Sync (syncSchema / init) › Data-lifecycle rotation (ADR-0057 P2) | `rotationStateByTable` … `migrateMysqlTimeColumns` | 1,742 | 72 | 26 (4) | 0 | 0 | 0 (0) | | 13767-14023 | Schema Sync (syncSchema / init) › Deferred schema DDL (#3917) | `setDeferredDdl` … `flushDeferredSchemaDdl` | 257 | 25 | 6 (4) | 0 | 0 | 0 (0) | | 14024-14838 | Schema Sync (syncSchema / init) › Managed-schema drift & reconcile (#2186) | `dialectName` … `createNullSafeUniqueIndex` | 815 | 237 | 23 (3) | 0 | 0 | 0 (0) | | 14839-14890 | Schema Introspection | `listPhysicalColumns` … `introspectSchema` | 52 | 46 | 2 (2) | 0 | 0 | 0 (0) | | 14891-15161 | Internal helpers | `getKnex` … `auditMissingTenant` | 271 | 73 | 8 (1) | 0 | 1 | 0 (0) | | 15162-17602 | Internal helpers › Filter helpers | `tableNameForBuilder` … `applyFalseConstant` | 2,441 | 438 | 44 (2) | 0 | 0 | 0 (0) | | 17603-17778 | Internal helpers › Field mapping | `mapSortField` … `mapAggregateFunc` | 176 | 2 | 6 (0) | 0 | 0 | 0 (0) | | 17779-17826 | Internal helpers › Window function builder | `buildWindowFunction` … `buildWindowFunction` | 48 | 26 | 1 (0) | 0 | 0 | 0 (0) | | 17827-19773 | Internal helpers › Column creation helper | `nowColumnDefault` … `applyDeclaredColumnDefault` | 1,947 | 92 | 19 (0) | 0 | 0 | 8 (8) | | 19774-20071 | Internal helpers › Database helpers | `sqliteFilename` … `isJsonField` | 298 | 76 | 9 (2) | 0 | 0 | 0 (0) | | 20072-20548 | Internal helpers › SQLite serialisation | `formatInput` … `formatOutput` | 477 | 27 | 2 (0) | 0 | 0 | 0 (0) | | 20549-20752 | Internal helpers › Introspection internals | `introspectColumnOrder` … `introspectUniqueConstraints` | 204 | 73 | 5 (0) | 0 | 0 | 0 (0) | | 20753-20827 | After the class | - | 75 | 0 | - | - | - | - | ### Pricing the areas Eligibility is as cut 11 defined it: an annotated return type or not public; no `getBuilder`, `this.FIELD.delete/insert/update(`, private-static, `super`, bare-`this` or `this`-write site; not `insertOnlyUpsertColumns`. 158 members are eligible on this base. Each is priced as its stub minus its body, and the sum per section counts every eligible body that has a negative price and no `sql-driver.ts#…` pointer: Filter helpers -550 (22 bodies), the Rotation sub-banner -388 (15), CRUD -157 (12), every other section -93 or less. This cut then priced what each area nets **once the module-level declarations it shares with members that stay are kept in place** (⛔ no second copy of a helper). The declarations that only the moved bodies read move with them. | Area (section) | Bodies | Bodies net | Helpers that move along | Disposition | |:--|--:|--:|--:|:--| | **Canonical-encoding convergence** (the last 703 lines of the Rotation sub-banner, 25 of them from the last 30 days) | 8 | **-193** | **-143** | **Moves (this cut)** into the existing schema-sync module. -335 measured. | | ↳ `migrateMysqlDatetimeColumns`, `migrateMysqlTimeColumns` (same block) | 2 | -71 | (needs `isDeferredDdlLockWaitRefusal`) | **Stay.** See "What stays" below. | | Filter helpers: cross-field and `$now` comparison cluster (`applyCrossFieldComparison`, `applyNowAnchorComparison`, `crossFieldOffsetExpr`, `nowAnchorBaseExpr`) | 4 | -291 | -172 | Not taken. Two of the helpers that would move (`uncompilableFieldReferenceError`, `uncompilableNowAnchorError`) are seam callers that `sql-driver-compile-refusal-seam.test.ts` enumerates by parsing `sql-driver.ts` alone and pins equal to its DOORS. A new module would be needed, and #26261 added `$now` there on 2026-10-26. | | Rotation proper (`rotationShardKey`, `ensureRotation`, `warnBuiltinColumnCollisions`, `rotationColumnList`) | 4 | -81 | 0 | Not taken: it pays the least. `ensureShardTable` (-64) is named by path in `check-object-def-param-keys.mjs`. | | CRUD remainder (the upsert conflict-target path and small members) | 12 | -157 | -118 | Not taken. Spread across the section, and four of its conflict-target declarations are shared with `upsert`. `autoNumberValueExists` and `scanMaxNumericTail` hold 2 of the 4 D8 tenant predicates. | ## Why this area - **It pays the most of any area that moves cleanly**, and its bodies belong in an existing module. `sql-driver-schema-sync.ts` already carries schema sync's drift pipeline (cuts 6, 8-10). The canonical backfills and the MySQL widening are the rest of what `initObjects` runs against a physical table. - **Both files are anchored.** `scripts/adr-anchors/` has an entry for `sql-driver.ts` and for `sql-driver-schema-sync.ts`, so ADR citations inside the moved bodies stay on the anchored surface, and the decision-letter count does not move (below). - **What stays, and why.** - `migrateMysqlDatetimeColumns` and `migrateMysqlTimeColumns` stay whole in the class. They were part of the first plan. With them moved, `check:durability-log-level` went **red** (exit 1). Each of them catches around `runWideningAlters` (a vocabulary callee) and reports through `this.logDurabilityFailure(…)`, and the gate resolves that helper only when it is declared **in the same file** as the catch. In the module the helper is not declared, so the gate read both catches as "swallows the failure with no log at all". Keeping the two catches beside the helper keeps the gate's reading exact: **48 seams at base and 48 at head, all loud**. ⛔ No gate edit and no second copy of the helper. - `withPinnedSession` (+4 if moved) and the `sqlite*Sql` expression builders stay. The moved bodies reach them through the host. - **Shared declarations, exported in place (cut 2/11 shape).** `AUDIT_TIMESTAMP_COLUMNS`, `SQLITE_NON_TEMPORAL_TEXT_REFS`, `SQLITE_TIME_EXPR_REFS`, `isMultiValuedColumn` and `LOCK_WAIT_REFUSAL` are each also read by members that stay. `LOCK_WAIT_REFUSAL` is the marker the moved `deferredDdlLockWaitError` sets and `isDeferredDdlLockWaitRefusal` reads in the two migrate catches. Each gains a leading `export` and nothing else, and the module imports them back. The module reads them only inside function bodies, so the import cycle is evaluation-safe. `index.ts` names its exports one by one; the export lists below prove none of the five leaks. - **No series limit is touched.** None of the eight builds through `getBuilder`, reads a static, has a `this.FIELD.delete/insert/update(` site, uses `super` or a bare `this`, or writes to `this`. All eight are protected or private with annotated returns. `check:tenant-chokepoint`'s D8 predicates, measured with its own `nullArmsOf`: **4 at base and 4 at head**, all in `sql-driver.ts` (0 in the moved bodies). - **Subclasses.** `TursoDriver` and `SqliteWasmDriver` override none of the eight. The stubs keep their signatures, and every member the bodies call is a late-bound closure on the instance. - **Path readers, screened.** No script, test, doc or ADR names any of the eight, or the four moved declarations, by a `sql-driver.ts#…` pointer or a `sql-driver.ts::…` key. The five cli `generate-*.pin.test.ts` files read `sql-driver.ts` text: the `createColumn` switch, `DEFAULT_STRING_VARCHAR_CHARS`, `MEDIA_ID_VARCHAR_CHARS`, `fieldHasColumn`, and `if (isMultiValuedColumn(` inside `createColumn`, all unchanged. Four of those files ran green at head (62 tests); the fifth sits in the cli integration tier, which this run did not include and CI runs. `logger-receiver-detach.test.ts` scans `sql-driver.ts`; the repo-wide gate `check:logger-receiver-detach` walks every package (0 detaches at both ends). ## The shape (#23050 shape B, as cuts 1-11 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 12]`, and returns the moved function's own promise. - Fields go as GETTERS that read the driver at call time: `canonicalDatetimeFields`, `canonicalTimeFields`, `datetimeFields`, `flushingDeferredDdl`, `jsonFields`, `timeFields` (new to the host), and `isMysql`, `isSqlite`, `knex`, `logger` (already there). 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 (probe below). - `SchemaSyncHost` grew by exactly the members these bodies read that it did not list yet: the six fields above and `convergeJsonColumn`, `countNonTemporalTextRows`, `legacyMysqlTimeColumns`, `legacyMysqlTimestampColumns`, `runWideningAlters`, `sqliteCanonicalDatetimeSql`, `sqliteCanonicalTimeSql`, `sqliteNonTemporalTextSql` and `withPinnedSession`. Each is typed as the class member's indexed access, inserted in the list's own order. There is no cast, and no member becomes public. - **Module imports.** `StandardErrorCode` (`@objectstack/spec/api`, for the moved refusal), `recoverUnencodedJsonText` (`./unencoded-json-text.js`) and the five shared declarations from `./sql-driver.js`. The former `import type` line becomes one import with `type` markers on `IntrospectedColumn` and `SqlDriver`. - **Comments.** A `[#25521 cut 12]` paragraph in the module header says what moved, what stayed and why. The `sql-driver.ts` import comment names cut 12 in 2 lines. The header cites no `ADR-NNNN Dn`. - **How the move was made.** A script produced it from the source text; nothing was retyped. Each body line is re-indented two columns, **except three lines that start inside a template literal**: the continuation lines of the two MySQL `information_schema` queries. Those keep their bytes, indentation included, because they are the SQL statement's own text. ## ADR anchors - **The count.** `check:adr-anchors` counts **1879** decision-letter citations at base `83e9d7187` (a clean detached worktree) and **1879** at head `ff5b18102`, over 90 anchored files at both ends. Both runs exit 0. - **Why equal.** Every comment inside the moved bodies and declarations moves from one anchored file to another. - **No entry needs a new home.** The `sql-driver.ts` entry's ADR-0053 paragraph describes `formatOutput` and `presentReadValue`; the schema-sync entry's ADR-0120 D4 paragraph describes the tightening ceremony. Neither describes a moved body. No `scripts/adr-anchors/**` file changed. ## The published `.d.ts` does not change This is measured under the series bar (ruling `6348891160`, item 1). - **How each end was built.** `pnpm turbo run build --filter=@objectstack/driver-sql --force` at each end, against the same dependency tree. The base leg writes base `sql-driver.ts` and `sql-driver-schema-sync.ts` into this worktree, under an EXIT/INT/TERM trap that restores HEAD. The restore is proved by the HEAD blob hashes (`b38c9983`, `f191f7ca`), 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, `0f33bf3febaac3c7aa96f68a4226d752766b8203ab7acade523b69f9aaa44d74` (unchanged since cut 8), and `cmp` finds them identical. - **Positive control.** I dropped `protected` from `backfillCanonicalDatetimes` in the head `index.d.ts`. The result hashes to `b6627836855a2c0adf2bbed61211d8d467f5ce088f81f5299336f847d45d397b`, with a 1-line diff and `cmp` exit 1. - **Export lists.** CJS `require` and ESM `import` of the built dist give 59 and 59 names at both ends, the same set (sha1 `05757ce5ae`). None of the five newly exported declarations is among them. - **The head dist carries the move.** `.call(host` sites in `index.mjs` go from 35 at base to 43 at head. ## Move-only proof The proof compares TypeScript compiler AST leaf tokens (JSDoc nodes skipped), base `83e9d7187` against head `ff5b18102`. It is a scratch script and is not committed. | Moved body | Body tokens | Comments | Parameter tokens less `this` | Return type tokens | Body lines | |:--|--:|--:|--:|--:|--:| | `backfillCanonicalDatetimes` | 349 | 8 | 6 | 4 | 60 | | `countNonTemporalTextRows` | 79 | 0 | 14 | 4 | 9 | | `backfillCanonicalJsonEncoding` | 193 | 5 | 6 | 4 | 37 | | `convergeJsonColumn` | 334 | 3 | 17 | 4 | 37 | | `backfillCanonicalTimes` | 349 | 3 | 6 | 4 | 55 | | `legacyMysqlTimestampColumns` | 211 | 1 | 11 | 15 | 20 | | `runWideningAlters` | 224 | 7 | 19 | 4 | 40 | | `legacyMysqlTimeColumns` | 205 | 0 | 11 | 15 | 20 | - **Each moved body.** In every row, 0 tokens differ and every comment is identical. `async` matches, and there are no type parameters. Each body line is the base line less two leading spaces, or byte-identical where it starts inside a template literal (1 line in `legacyMysqlTimestampColumns`, 2 in `legacyMysqlTimeColumns`). - **The eight stubs.** Leading trivia (the docblock), modifiers and the signature are identical to base, less `async`. - **The class.** It has 350 members at both ends, in the same order, and the 342 unmoved members are byte-identical, trivia included. - **Module-level declarations.** The four that moved are byte-identical in the module, leading docblock included, and gone from `sql-driver.ts`. The five shared ones differ from base by a leading `export` and nothing else. At `sql-driver.ts`'s top level, 199 of 211 base statements are byte-identical. The rest are the two edited imports, the five exports, the four moved declarations and the class. - **Result.** `PROOF OK`, exit 0. - **Negative control.** I changed `.limit(SQLITE_JSON_BACKFILL_PAGE_SIZE)` to `.limit(SQLITE_JSON_BACKFILL_PAGE_SIZE + 1)` in a module copy (1 hit, checked on disk). The proof then reports `convergeJsonColumn` 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 `83e9d7187` against head `ff5b18102`. Every part's exit code was captured on its own (build, probe, three suites: all 0 at both ends). | Suite | Base | Head | Lost / changed / added | |:--|:--|:--|:--| | `@objectstack/driver-sql` | 4060 (3846 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` | 746 (746 passed; 39 files) | same | 0 / 0 / 0 | - **Type check and lint.** `tsc --noEmit -p .` in driver-sql exits 0, also after the merge. `pnpm exec eslint --no-inline-config packages/drivers/driver-sql` exits 0. - **Late-binding probe.** A scratch script, not committed. It runs node against each end's built dist on in-memory SQLite. Its 45 readings print identical output at base and head (`diff` is empty). They cover: 1. both SQLite backfills, reaching instance spies on `countNonTemporalTextRows` and the three `sqlite*Sql` builders, with `canonicalDatetimeFields` and `canonicalTimeFields` replaced after construction (the certified sets land in the replaced objects); 2. a SUBCLASS override of `countNonTemporalTextRows`, reached with its arguments (the withheld-rows warning follows), and `datetimeFields` emptied after construction (nothing scanned); 3. the JSON backfill, reaching an instance spy on `convergeJsonColumn`, then a subclass override that throws (warned, not thrown), and `jsonFields` emptied after construction; 4. the MySQL path on a driver whose `isMysql` is shadowed on the instance and whose `knex.raw`, `withPinnedSession`, `nowColumnDefault`, `runWideningAlters` and `logDurabilityFailure` are replaced on the instance: - the two column reads; - both migrations' ALTER statements; - the lock-wait bound set and restored around the ALTER; - outside a flush, the raw lock error; - with `flushingDeferredDdl` flipped on the instance, the `DATABASE_ERROR`/500 refusal. `migrateMysqlTimeColumns` (in the class) recognises it through the exported `LOCK_WAIT_REFUSAL` and rethrows it. Outside a flush the failure goes to `logDurabilityFailure`; 5. bad arguments on each of the ten methods. - **Not run.** No ablation, because no tested behaviour changed. The two migrate methods did not move. - **`test:repo`.** The `repo` projects of types, core, rest and objectql pass locally. Runtime's two files could not load (`@objectstack/service-datasource` has no dist in this container), so that one is NOT MEASURED locally. CI's Test Core runs all of them. ## Gates - **Derived list.** `node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack` derives 53 commands for the 2 changed paths (merge base `83e9d7187`). On `ff5b18102`, 52 exit 0. `check:dual-build-cjs-loads` exits 3, `PREREQUISITE NOT MET` (no full build here): **NOT MEASURED** locally, CI runs it. The teardown self-test's pinned commit was fetched first, and objectql was built for `check:lean-entry-closure`. - **Reconciliation.** `dispatch-gates --ran`: 53 derived families accounted for, 52 run, 1 NOT-MEASURED (derived from the recorded exit 3), exit 0. After the merge the derived list is still 53 commands. - **Card-named gates, read at base (a clean detached worktree at `83e9d7187`) and at head, all exit 0:** | Gate | Base | Head | |:--|:--|:--| | `check:adr-anchors` | 90 files, 1879 decision-letter citations | 90 files, 1879 | | `check:adr-symbol-anchors` | 2664 resolve | 2664 resolve | | `check:tenant-chokepoint` | 24 bindings / 3 files, D8 clean; 4 tenant predicates | same; 4 tenant predicates | | `check:durability-log-level` | 48 seams, all loud (7 propagating) | 48 seams, all loud (7 propagating) | | `check:driver-conformance` | 55 covered / 0 DEBT | same | | `check:keyed-text-bounds` | 112 files / 238 index entries | same | | `check:object-def-param-keys` | 3310 files / 542 classes / 7 positions | same | | `check:system-context-census` | 153 sites / 132 symbols | same | | `check-tenant-audit-census` | 243 sites | same | | `check:logger-receiver-detach` | 0 detaches, 3750 files | 0 detaches, 3751 files (the head worktree also holds the build-generated, git-ignored `packages/spec/src/migrations/registry.ts`) | - `check:nul-bytes` passes. The pre-push `check:commit-card-trailers` passed on push. ## Acceptance notes - **The #9354 lock-wait family now spans two files.** The bound, its recognizer and the refusal builder (`deferredDdlLockWaitError`) moved with `runWideningAlters`. The marker `LOCK_WAIT_REFUSAL` and its reader `isDeferredDdlLockWaitRefusal` stay in `sql-driver.ts` with the two migrate catches. `isDeferredDdlLockWaitRefusal`'s docblock links `{@link deferredDdlLockWaitError}`, which is now in the module. Left as is: the cut is move-only. - **`check:durability-log-level` resolves loud helpers by file.** The gate reads `this.logDurabilityFailure(…)` as loud only when that helper is declared in the same file as the catch. Any later cut that moves a catch around a vocabulary callee out of `sql-driver.ts` meets the same red. Noted, not filed: the gate is right to refuse an unresolved callee, and changing its resolver would be a gate change. - **Prose that names a moved body by file.** ADR-0053 names `backfillCanonicalDatetimes` and `backfillCanonicalTimes` without a path. `TursoDriver`'s docblocks (`remote-canonical-backfill.ts`, `remote-codec-residue-backfill.ts`, `turso-driver.ts`) name `SqlDriver.backfillCanonicalDatetimes`, `backfillCanonicalTimes`, `backfillCanonicalJsonEncoding` and `countNonTemporalTextRows`. Each is still a member of `SqlDriver`. No `docs/adr/**` edit in this series. - **Next slice, priced on this base.** Filter helpers' cross-field and `$now` cluster pays about -460 gross. It needs `sql-driver-compile-refusal-seam.test.ts`'s enumeration migrated to read the receiving module, and a new seam module. The CRUD upsert conflict-target path needs four shared declarations exported in place. --- _Generated by [Claude Code](https://claude.ai/code/session_011aqraB3u125sQzkMQNboVa)_ 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_011aqraB3u125sQzkMQNboVa Co-authored-by: Claude <noreply@anthropic.com>
1 parent 4548ba3 commit 8b39b77

2 files changed

Lines changed: 601 additions & 437 deletions

File tree

0 commit comments

Comments
 (0)