Skip to content

refactor(driver-sql): move the canonical-encoding convergence out of sql-driver.ts into sql-driver-schema-sync.ts (#25521 cut 12) - #26357

Merged
objectstack-fleet[bot] merged 2 commits into
mainfrom
claude/project-thread-c22v4e
Oct 27, 2026
Merged

objectstack-fleet[bot] merged 2 commits into
mainfrom
claude/project-thread-c22v4e

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Requested by Leehom · project thread

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 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 [Decision] Should the MySQL deferred-DDL flush bound its lock_wait_timeout so a blocked os migrate fails loudly instead of hanging (default: one year)? #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

🤖 Generated with Claude Code

https://claude.ai/code/session_011aqraB3u125sQzkMQNboVa

…sql-driver.ts into sql-driver-schema-sync.ts

Cut 12 of the sql-driver.ts split. The bodies of eight SqlDriver methods move
unedited into the existing schema-sync seam module through SchemaSyncHost:
the SQLite canonical backfills (backfillCanonicalDatetimes,
backfillCanonicalTimes, backfillCanonicalJsonEncoding, countNonTemporalTextRows,
convergeJsonColumn) and, of the MySQL widening, the two column reads and the
ALTER runner (legacyMysqlTimestampColumns, legacyMysqlTimeColumns,
runWideningAlters). migrateMysqlDatetimeColumns and migrateMysqlTimeColumns
stay whole: their catches report through logDurabilityFailure, which the
durability log-level gate resolves only as a same-file helper. The four
module-level declarations only the moved bodies read move with them and stay
module-local; the five they share with members that stay gain `export` in
place and are imported back. Move-only: no behaviour change, the published
.d.ts does not move.

Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011aqraB3u125sQzkMQNboVa
@objectstack-fleet objectstack-fleet Bot added the skip-changeset PR has no user-facing published change; bypasses the changeset gate label Oct 26, 2026
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 1 package(s): @objectstack/driver-sql, touching 43 documentable anchor(s).

13 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:

  • content/docs/api/wire-format.mdx (via updated_at (literal, a string literal in AUDIT_TIMESTAMP_COLUMNS))
  • content/docs/automation/webhooks.mdx (via updated_at (literal, a string literal in AUDIT_TIMESTAMP_COLUMNS))
  • content/docs/data-modeling/queries.mdx (via updated_at (literal, a string literal in AUDIT_TIMESTAMP_COLUMNS))
  • content/docs/data-modeling/seed-data.mdx (via updated_at (literal, a string literal in AUDIT_TIMESTAMP_COLUMNS))
  • content/docs/deployment/cli.mdx (via updated_at (literal, a string literal in AUDIT_TIMESTAMP_COLUMNS))
  • content/docs/deployment/seed-tenancy-repair.mdx (via updated_at (literal, a string literal in AUDIT_TIMESTAMP_COLUMNS))
  • content/docs/permissions/system-context.mdx (via updated_at (literal, a string literal in AUDIT_TIMESTAMP_COLUMNS))
  • content/docs/protocol/kernel/http-protocol.mdx (via updated_at (literal, a string literal in AUDIT_TIMESTAMP_COLUMNS))
  • content/docs/protocol/kernel/realtime-protocol.mdx (via updated_at (literal, a string literal in AUDIT_TIMESTAMP_COLUMNS))
  • content/docs/protocol/objectql/schema.mdx (via updated_at (literal, a string literal in AUDIT_TIMESTAMP_COLUMNS))
  • content/docs/protocol/objectql/security.mdx (via updated_at (literal, a string literal in AUDIT_TIMESTAMP_COLUMNS))
  • content/docs/protocol/objectql/state-machine.mdx (via updated_at (literal, a string literal in AUDIT_TIMESTAMP_COLUMNS))
  • content/docs/ui/views.mdx (via updated_at (literal, a string literal in AUDIT_TIMESTAMP_COLUMNS))

⛔ 6 release-owned page(s) also name something this change touched. These are read-only:

  • content/docs/releases/v15.mdx (via updated_at (literal, a string literal in AUDIT_TIMESTAMP_COLUMNS))
  • content/docs/releases/v16.mdx (via updated_at (literal, a string literal in AUDIT_TIMESTAMP_COLUMNS))
  • content/docs/releases/v17/17-3.mdx (via updated_at (literal, a string literal in AUDIT_TIMESTAMP_COLUMNS))
  • content/docs/releases/v17/17-5.mdx (via updated_at (literal, a string literal in AUDIT_TIMESTAMP_COLUMNS))
  • content/docs/releases/v17/17-6.mdx (via updated_at (literal, a string literal in AUDIT_TIMESTAMP_COLUMNS))
  • content/docs/releases/v17/17-7.mdx (via updated_at (literal, a string literal in AUDIT_TIMESTAMP_COLUMNS))

content/docs/releases/ is RELEASE-OWNED (AGENTS.md "Documentation Guardrails"): release
notes are written centrally at release time, and a code PR that edits them is the exact PR
that guardrail exists to stop. They are still audited — read-only. If one of them is actually
wrong, file an issue or open a dedicated docs-only PR; do not edit it here.

What this run could not see
  • 1 anchor(s) matched too much of the corpus to be a work list: created_at (literal, 39 pages)
  • the SDK route bridge reached 54 of 206 client-bound route-ledger rows — the other 152 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run. Of those 152: 0 are remediable by widening that discovery convention (an in-repo file declares the path; the convention did not scan it); 55 are structural — on a ledger where NOT ONE row is declared in-repo, so no discovery change reaches them at any price; 97 are undecided (no in-repo declaration, on a ledger that has other in-repo registrars — absence and an unreadable spelling are not distinguishable here). The rows themselves: node scripts/docs-audit/affected-docs.mjs --bridge-coverage
  • a page that states a rule by its inputs shares no identifier with the emitter that implements the rule, so an emitter-only diff cannot list it — not on this run and not on any run. Measured on fix(driver-sql): emit varchar(maxLength) for a text field a declared index keys on #11430: content/docs/protocol/objectql/types.mdx documents the text-family column mapping by the ObjectQL type names it maps FROM (text / textarea / html) while the diff changed createColumn; it went unlisted, and it was the page that diff falsified, in four places. No shared token exists to detect this on, so a rule your change carries has to be re-read by hand in the pages that restate it.
  • a key NAME is not a key, so the hand re-read the line above prescribes can land on the wrong schema. The same spelling is authorable on one governed type and a [REMOVED] tombstone on another for each of active, aria, joins, objects, template, tools and version (censused on [finding] tools is a key on BOTH AgentSchema (tombstoned, dead) and SkillSchema (live, cloud-attested), so a name-based search attributes skill examples to the agent key — it produced a false stop-the-line alarm on PR #19059 #19093 over the liveness ledger's governed types, top-level keys); nothing in a search result distinguishes the two, so a grep hit on a LIVE example reads as evidence about the DEAD key. Measured on fix(spec): the agent.tools liveness row says dead — it claimed live on a key the schema tombstoned #19059: content/docs/ai/agents.mdx was reported as contradicting the agent.tools tombstone over its tools: example at :161, which is inside the defineSkill({ block opened at :155 — the page was already correct. Settle ownership by PARSING the value against both schemas, never by the name: that literal PASSES SkillSchema, and as an AgentSchema it FAILS at tools with the tombstone prescription. ⛔ These names are not the whole class — a key retired through a .strict() guidance map leaves no tombstone in the walked shape and none of them here (tool.category, live as AIToolDefinition.category).

Coarse fallback — 13 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json 66d5cd6897fee4a5c8a8eaf468f82e0c91ed60a9 → packageMentionDocs.

Which tree this was computed on

This run read content/docs from 624542f8455b05f843355e9c7128e1dfe2239d13 — the merge of head 274ac85416d191eec6c844531e34dd9609fb5e5a into base 66d5cd6897fee4a5c8a8eaf468f82e0c91ed60a9, which is what actions/checkout gives a pull_request run. Not the PR head.

A worktree cut from an older main holds a different content/docs, so re-deriving there can legitimately return a different list — that is a different tree, not a wrong row. To answer on the same tree:

# while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 624542f8455b05f843355e9c7128e1dfe2239d13 && git checkout 624542f8455b05f843355e9c7128e1dfe2239d13
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 66d5cd6897fee4a5c8a8eaf468f82e0c91ed60a9 274ac85416d191eec6c844531e34dd9609fb5e5a && git checkout -B drift-repro 66d5cd6897fee4a5c8a8eaf468f82e0c91ed60a9 && git merge --no-ff 274ac85416d191eec6c844531e34dd9609fb5e5a

node scripts/docs-audit/affected-docs.mjs --json 66d5cd6897fee4a5c8a8eaf468f82e0c91ed60a9

⚠️ That checkout carried uncommitted changes, so the commit above does not fully identify what was read.

Advisory only, and a precision-first one (#9192): a page is listed because it names a
symbol, wire route or SDK method this diff touched — not because it mentions a changed
package. Each row says which anchor put it there, so a wrong row is reportable rather than
merely annoying. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs 66d5cd6897fee4a5c8a8eaf468f82e0c91ed60a9 → pass the list as
args.docs, on the commit named under Which tree this was computed on.

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

席位核对

ACCEPT · domain:server seat 2 · os-project-manager · session_0186vfjHHMsRXC9EUT9hTFMo · 2026-10-26T23:41Z · head 274ac85416 · #25521 cut 12

The maintainer's standing authorisation for landing reviewed PRs, verbatim: 「按照 pm-dispatch 规则复核过的都授权」.

  1. Shape: a draft against main. The body opens with Part of #25521 (⛔ not Fixes; the card stays open for later cuts). Clause-②: no and Metadata-protocol: none are line-initial. skip-changeset applies: the change is move-only. check-governed-merges --pr 26357: NOT governed, 1,038 changed lines.

  2. Premise measured: the claim 6427355170 asked for a fresh seam table after cut 11, and it is in the PR body, taken on cut 11's merge 83e9d7187. The pick is the canonical-encoding convergence that schema sync runs at init. Eight bodies move into the existing sql-driver-schema-sync.ts through its existing SchemaSyncHost: the SQLite backfills (backfillCanonicalDatetimes, backfillCanonicalTimes, backfillCanonicalJsonEncoding, with countNonTemporalTextRows and convergeJsonColumn) and the MySQL widening reads and runner (legacyMysqlTimestampColumns, legacyMysqlTimeColumns, runWideningAlters). Four module-level declarations that only these bodies read move with them. Each method stays on the class as a delegate.

    The seat compared every line removed from sql-driver.ts with the lines added to the module after stripping indentation. All 423 non-blank lines are present there except fourteen:

    • the schema-sync import line, which gains the eight function names;
    • five declarations that members staying in the class still read (AUDIT_TIMESTAMP_COLUMNS, SQLITE_TIME_EXPR_REFS, SQLITE_NON_TEMPORAL_TEXT_REFS, LOCK_WAIT_REFUSAL, isMultiValuedColumn), which gain export in place (cut 2's shape). index.ts re-exports sql-driver.js by name only, so none of them reaches the published surface;
    • the eight method signatures, which lose async now that each returns its module function's promise. The dev's AST proof shows them otherwise identical.

    The module's only removed line is its import type from sql-driver.js, which widens to the new names. sql-driver.ts goes from 20,827 to 20,492 (-335). The dev also reports the public .d.ts identical with a positive control, exports 59 of 59, the late-binding probe identical at 45 of 45 readings, and the driver-sql, turso and sqlite-wasm suites at 0 lost, 0 changed and 0 added per test.

  3. Gates: at this stamp the head's 52 check-runs read 36 success and 16 skipped (path-filtered UI, docs-build, dogfood matrix placeholders, opt-in harnesses and the skipped changeset check), with none failing or in progress, Test Core 1–8 included; mergeable, clean. Locally the dev reports 52 of 53 gates exit 0, with dual-build-cjs-loads left to CI's full build, which is green.

  4. Accepted deviations: the net is -335 against the table's -415 receipt. The two migrate bodies stay in the class because check:durability-log-level resolves logDurabilityFailure only within the same file, and it went red when they moved. The dev measured that, reverted it, and disclosed it; moving them would need a gate edit, which the card's ask rules out. check:adr-anchors reads 1,879 at both base and head.

  5. Out of scope: none filed.

  6. Landing: this seat runs pr_ready and then automerge_enable through the relay, confirms MERGED on both readings, and records the landing on [maintainer] refactor(driver-sql): split src/sql-driver.ts (22,096 lines, +2,533 in 30 days) along its SqlDriver seams, one move-only slice per PR #25521.


Generated by Claude Code

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review October 26, 2026 23:43
@objectstack-fleet
objectstack-fleet Bot enabled auto-merge October 26, 2026 23:43
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Oct 26, 2026
Merged via the queue into main with commit 8b39b77 Oct 27, 2026
53 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/project-thread-c22v4e branch October 27, 2026 00:03
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size/xl skip-changeset PR has no user-facing published change; bypasses the changeset gate

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants