Repository navigation
refactor(driver-sql): move the data-lifecycle rotation sub-banner's movable members out of sql-driver.ts into sql-driver-schema-sync.ts (#25521 cut 17) - #26808
Conversation
…into sql-driver-schema-sync.ts ensureRotation, rotationShardKey, rotationColumnList, warnBuiltinColumnCollisions and registerExternalObject move unedited into sql-driver-schema-sync.ts; the class keeps each method as a shape-B stub through SchemaSyncHost. declaredValueShapes gains export in place. Move-only. Claude-Session: https://claude.ai/code/session_01FEkQ5o3WH4EyHws8N24jPx Co-authored-by: Claude <noreply@anthropic.com>
…-0gietk Claude-Session: https://claude.ai/code/session_01FEkQ5o3WH4EyHws8N24jPx Co-authored-by: Claude <noreply@anthropic.com>
📓 Docs Drift CheckThis PR changes 1 package(s): 14 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:
⛔ 6 release-owned page(s) also name something this change touched. These are read-only:
What this run could not see
Coarse fallback — 13 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): Which tree this was computed onThis run read A worktree cut from an older # while this PR is open — GitHub drops the merge commit once it closes
git fetch origin ce14351315b781a05770389257b1c97a987a734b && git checkout ce14351315b781a05770389257b1c97a987a734b
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin c8583f80ac47177421c9c1fb71e5e33e431ad9cf 3dcd74655b93e5ce15bfb88e1bf58a874e582cc1 && git checkout -B drift-repro c8583f80ac47177421c9c1fb71e5e33e431ad9cf && git merge --no-ff 3dcd74655b93e5ce15bfb88e1bf58a874e582cc1
node scripts/docs-audit/affected-docs.mjs --json c8583f80ac47177421c9c1fb71e5e33e431ad9cf
|
席位核对ACCEPT · The maintainer's standing authorisation for landing reviewed PRs, verbatim: 「按照 pm-dispatch 规则复核过的都授权」.
Generated by Claude Code |
Requested by Leehom · project thread
Part of #25521
Clause-②: no
Metadata-protocol: none
Ruling-ref: 6270097840
Ruling-ref: 6348891160
维护者速读
sql-driver.ts里「数据生命周期轮转」小节中能搬的五个方法体(轮转器ensureRotation及它调用的分片键rotationShardKey、视图列清单rotationColumnList,三条建表路径共用的内置列冲突告警warnBuiltinColumnCollisions,联邦外部对象的免 DDL 注册registerExternalObject)原样搬进已有的sql-driver-schema-sync.ts;类上五个方法全部保留,只把方法体转交过去,SchemaSyncHost按需增补成员。src/sql-driver.ts(22,096 lines, +2,533 in 30 days) along itsSqlDriverseams, one move-only slice per PR #25521 拆分超大文件的第 17 刀;重新测量后这是收益最大且不碰任何系列限制的区域,sql-driver.ts减少 125 行。sql-driver-schema-sync.ts头注释新增的第 17 刀段落与SchemaSyncHost新增成员;sql-driver.ts中五个方法的转交桩;sql-driver.ts顶部两处 import 的改动。pnpm --filter @objectstack/driver-sql exec vitest run src/sql-driver-rotation.test.ts src/sql-driver-ddl-gate.test.ts src/sql-driver-external-columnmap.test.ts src/sql-driver-shard-tenancy-opt-out.test.ts,预期全部通过。What changes
Cut 17 of the
sql-driver.tssplit (#24586 ruling A, recorded in6270097840). The claim is6446054910. The cut opens with a fresh seam table (below), taken onorigin/main785a7c3b3e, and takes the area cut 16 priced next and that still pays best without breaking a series limit: the movable members of the data-lifecycle rotation sub-banner (Schema Sync › Data-lifecycle rotation). Their bodies move unedited into the existing seam modulepackages/drivers/driver-sql/src/sql-driver-schema-sync.ts, the module cut 12 already moved six members of this same sub-banner into, through its hostSchemaSyncHost:ensureRotation, the rotator: it inventories a rotation-managed table's shards, adopts a legacy base table as the current shard (and recovers when both exist), column-syncs the retained shards, drops the expired ones and rebuilds theUNION ALLread view;rotationShardKeyandrotationColumnList, which it calls (the shard key of an instant, and the view's column list);warnBuiltinColumnCollisions, the driver-sql: a declared field namedid/created_at/updated_atis silently discarded by initObjects — declared type, length and constraints all ignored with no diagnostic #12015 warning the create, alter and shard paths share when a declared field collides with a builtin column;registerExternalObject, the DDL-free registration of a federated object.No module-level declaration moves: none is read by these bodies alone.
declaredValueShapesis also read byregisterManagedObjectMetadata, which stays, so it gains a leadingexportin place and the module imports it. The three names of./builtin-column-collision.js(undeliveredStorageAttributes,formatAttribute,type UndeliveredAttribute) lose their last value reader insql-driver.ts; the import there shrinks totype undeliveredStorageAttributes, which the staying docblock ofwarnBuiltinColumnCollisionslinks (a link-only import is a type import, erased from the build).The change is move-only, and the card stays open for later cuts. There is no
docs/adr/**edit, nopackages/spec/**edit, no anchor entry change, no gate edit, no test edit and no new gate. Branchclaude/project-thread-0gietk(the platform assigned it to this card thread, as the claim records). The move is one commit,6b6ca4b01, on785a7c3b3e, followed by one merge oforigin/main(83c4bd29f7); the head is3dcd74655b.Net line count (the seat's rule).
sql-driver.tsgoes from 18,646 lines to 18,521: -125.builtin-column-collision.jsimport becomes one line).sql-driver-schema-sync.jsimport, whose one line gains the five names.sql-driver-schema-sync.tsgoes from 1,653 to 1,914 lines (+261): the five functions, 20 newSchemaSyncHostmembers, three imports and a header paragraph.Path readers. No test, script, doc, ADR or anchor entry reads any of the five bodies by path, so none is migrated (screen below).
Serial with the file's in-flight PRs. I read the file list of every open PR on
objectstack-ai/objectstackat the start (27) and again before opening this one (26). None touchessql-driver.ts,sql-driver-schema-sync.ts,turso-driver.ts,sqlite-wasm-driver.ts,check-tenant-chokepoint.mjs,check-object-def-param-keys.mjsor a driver-sql anchor entry. #26556 (dependabot) touches onlydriver-sql/package.json.Seam table, fresh, on
785a7c3b3esql-driver.tsis 18,646 lines. The 30-day column countsgit blamelines from commits on or after 2026-09-27T15:00:18Z, 30 days before the base commit.scripts/pm/git-history.mjs countproves 76 commits on the file in 30 days and 31 in the last 7. In all, 3,323 lines (17.8%) are from the last 30 days.SqlDriverspans lines 4244-18571 with 350 members: 243 instance methods, 13 accessors and 32 statics, all 32 private or protected.!andsatisfiesbefore it readsthis. Sections are the class's own banner comments.name…assertDialectEmitsconnect…disconnectfind…deletebulkCreate…countrawStatementFault…noteRuntimeIndexDdlbeginTransaction…rollbackTransactionaggregate…unresolvableAggregateColumnRefusaldistinct…unresolvableDistinctColumnRefusalfindWithWindowFunctions…findWithWindowFunctionsexplain…analyzeQuerysyncSchema…reclaimSpacerotationStateByTable…migrateMysqlTimeColumnssetDeferredDdl…flushDeferredSchemaDdldialectName…createNullSafeUniqueIndexlistPhysicalColumns…introspectSchemagetKnex…auditMissingTenanttableNameForBuilder…applyFalseConstantmapSortField…mapAggregateFuncbuildWindowFunction…buildWindowFunctionnowColumnDefault…applyDeclaredColumnDefaultsqliteFilename…isJsonFieldformatInput…formatOutputintrospectColumnOrder…introspectUniqueConstraintsPricing the areas
Eligibility is as cuts 11-16 defined it: an annotated return type or not public; no
getBuilder,this.FIELD.delete/insert/update(, private-static,super, bare-thisorthis-write site; notinsertOnlyUpsertColumns; not a member pinned in place (createColumn,applyFilterCondition,formatInput/formatOutput,initObjects,ensureShardTable,computeTenantField,migrateMysqlDatetimeColumns/migrateMysqlTimeColumns,compileFilters). Each area is priced as its stubs minus its bodies, then net of the module-level declarations only it reads (they would move along) and of its import edits.ensureRotation-53,registerExternalObject-43,warnBuiltinColumnCollisions-22,rotationShardKey-5,rotationColumnList-1)previewDeferredSchemaWork-32,previewDatetimeConvergence-22,previewTimeConvergence-22)PendingSchemaWork,fieldHasColumnand the twoSQLITE_*constants are shared)presentReadValue-29)compileFilters(-75) is refused (inferred return type).buildDateBucketExpr-42,installQueryTiming-20)nowColumnDefault-39 and four small members)Why
Why this area, as one area
Data-lifecycle rotationsub-banner that can move. Cut 12 moved six other members of the same sub-banner (the SQLite backfills and the MySQL widening's reads and runner) intosql-driver-schema-sync.ts, so this cut puts them beside those, behind the host that already exists, instead of opening a new module.rotateShards(its stub would cost as many lines as its body),aliasShardBookkeeping(its stub would cost more than its body),ensureShardTable(named bycheck:object-def-param-keys),computeTenantField(named bypackages/spec/liveness/object.json),computeAndRecordTenantFieldandregisterManagedObjectMetadata(each has athis.FIELD.delete(site, the tenant-audit series limit),initObjects(pinned), andmigrateMysqlDatetimeColumns/migrateMysqlTimeColumns(each catch around a vocabulary callee reports throughlogDurabilityFailure, whichcheck:durability-log-levelresolves only within one file).rotationStateByTablestay; each stub would cost more than its body.getBuilder, reads a static, has athis.FIELD.delete/insert/update(site, usessuperor a barethis, or assigns tothis.registerExternalObjectis public with an annotatedvoidreturn; the other four are protected with annotated returns.check:tenant-chokepoint's D8 predicates, measured with its ownnullArmsOf: 4 at base and 4 at head, all insql-driver.ts, 0 in the moved bodies (registerExternalObjectnamestenantFieldfour times, as a value it stores, never as a predicate).catchclause.check:durability-log-level --listgives the same seams at both ends, line numbers aside (164 lines of output at each end).ensureRotationandwarnBuiltinColumnCollisionsandregisterExternalObjectwrite to the logger throughthis.logger, which the host passes as a getter, so each call keeps its receiver.check:logger-receiver-detachis green (below).TursoDriverandSqliteWasmDriverextendSqlDriverand override none of the five (TursoDrivercalls the inheritedregisterExternalObject). Every stub keeps its signature and modifier, and every member a body calls is a late-bound closure on the instance (probe below).sql-driver.ts#…pointer or asql-driver.ts::…key, and noscripts/adr-anchors/**record names them.check-object-def-param-keys.mjscites#ensureRotationas a symbol anchor in prose; the method keeps its name and signature insql-driver.ts, and the gate's output is byte-identical at both ends. The tests that readsql-driver.tsas text look forcreateColumn, the width constants, the INVALID_FILTER builders and log-call shapes, none of which moved. Prose mentions elsewhere (turso-driver.ts,remote-transport.ts, ADR-0015 and ADR-0062, the objectql federation tests) nameSqlDriver.registerExternalObject, which is still true.The shape (#23050 shape B, as cuts 1-16 apply it)
NAME.call(host, ...), marked[#25521 cut 17].rotationShardKeyandrotationColumnListread no member of the driver, so their methods call the function directly.ensureRotationlosesasyncand returns the moved function's own promise, as cut 12's methods do.knex,logger,rotationStateByTable,isSqliteand the seventeen per-object bookkeeping maps) go as GETTERS that read the driver at call time; the methods (ensureShardTable,aliasShardBookkeeping,rotationShardKey,rotationColumnList,computeAndRecordTenantField,isJsonField) 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.SchemaSyncHostgrows by exactly the 20 members these bodies read that it did not list yet, each typed as the class member's indexed access, in the interface's sorted order (54 to 74 members). There is no cast, and no member becomes public.aggregandColumnClass(@objectstack/core);FILE_REFERENCE_TYPES,NUMERIC_VALUE_TYPES,parseAutonumberFormat,resolveAutonumberFormat,type AutonumberToken(@objectstack/spec/data);formatAttribute,undeliveredStorageAttributes,type UndeliveredAttribute(./builtin-column-collision.js); anddeclaredValueShapes,NUMERIC_SCALAR_TYPESadded to the existing./sql-driver.jsimport. The module reads them only inside function bodies, so the import cycle stays evaluation-safe.index.tsnames its exports one by one; the export lists below prove none of them leaks.[#25521 cut 17]paragraph saying what moved, what stayed and where each read declaration comes from. Thesql-driver.tsimport comment names cut 17 in 3 lines. Neither cites anADR-NNNNletter. The line comments insideregisterExternalObject's body that link{@link mediaFields}and its siblings now nameSchemaSyncHostmembers of the same names.registerExternalObject's one multi-line parameter type is re-indented the same way.Verification
ADR anchors
check:adr-anchorscounts 1898 decision-letter citations at base785a7c3b3e(a clean detached worktree) and 1898 at head, over 91 anchored files at both ends. Both runs exit 0.ADR-…mention in them stays insql-driver.ts.check:adr-symbol-anchors: 2687 resolve at both ends.mainmoved under the cut, so I measured its tip83c4bd29f7against the merge head3dcd74655b: 1898 letters at both (91 anchored files, exit 0), and 2687 symbol anchors resolve at both.The published
.d.tsdoes not changeThis is measured under the series bar (ruling
6348891160, item 1).pnpm turbo run build --filter=@objectstack/driver-sql --forceat each end, against the same dependency tree. The base leg writes basesql-driver.tsand basesql-driver-schema-sync.tsinto this worktree under an EXIT/INT/TERM trap that restores HEAD. The restore is proved by the HEAD blob hashes, an emptygit diff HEADand 0 status entries.dist/index.d.tsanddist/index.d.mtsis the same at base and at head,0f33bf3febaac3c7aa96f68a4226d752766b8203ab7acade523b69f9aaa44d74(unchanged since cut 8), on the move commit and again on the merge head. These two are the dist's only declaration files (no chunk), so nothing was re-ordered and the declaration-reorder bar of ruling6441032442does not come into play.protectedfromensureRotationin the headindex.d.ts. The result hashes to7d72d5f631e8e134fe0cfc67e9fcaa78bc3cddd2b5702c2754502ec0bcff8987, withcmpexit 1.requireof each dist gives 59 names (sha105757ce5aeat both ends); the ESM export statement of the base and headindex.mjslists the same 59 names (sha105757ce5aeboth). None of the five moved functions,declaredValueShapesorSchemaSyncHostis among them..call(hostsites inindex.mjsgo from 61 at base to 64 at head (three host stubs; the two direct-call stubs have none).Move-only proof
The proof compares TypeScript compiler AST leaf tokens (JSDoc nodes skipped), base
785a7c3b3eagainst head6b6ca4b01. It is a scratch script and is not committed.thisregisterExternalObjectensureRotationwarnBuiltinColumnCollisionsrotationShardKeyrotationColumnListensureRotationisasyncat both ends; the other four are not.ensureRotationlessasync).sql-driver.ts. 176 of 180 base statements are byte-identical; the other four are the class,declaredValueShapes(it differs from base by a leadingexportand nothing else, checked), and the two edited imports../sql-driver.jsimport (two names added) andSchemaSyncHost(20 members added). The head adds three imports and the five functions.PROOF OK, exit 0.scripts/ablation-replace.mjs, I changedweek: 7 * 86_400_000toweek: 8 * 86_400_000in the module'sensureRotation(1 hit, checked on disk). The proof then reportsensureRotationas differing, printsPROOF FAILED, and exits 1. The restore was proved (blob equals HEAD, emptygit diff HEAD).Tests
Each end ran under one
os-verify-lockhold, after building that end's driver-sql dist. I compared the vitest JSON per test name and status, base785a7c3b3eagainst head6b6ca4b01, and then again on the merge head3dcd74655b. Every part's exit code was captured on its own (build, probe, three suites: all 0 at every leg).@objectstack/driver-sql@objectstack/driver-turso, against that end's built driver-sql dist@objectstack/driver-sqlite-wasmOn the merge head (it brings in
mainup to83c4bd29f7: 14 commits, none inpackages/drivers/**), the three suites give the same totals and the same per-test statuses as base, 0 / 0 / 0 in each.tsc --noEmit -p .in driver-sql exits 0 on the move commit and on the merge head.pnpm exec eslint --no-inline-config packages/drivers/driver-sqlexits 0.code,status, own keys and top stack frame, the tablessqlite_masterholds, the bookkeeping maps written, and the log lines:rotationShardKeyoverday,week,monthand an unknown unit, across Sunday/Monday, month, year and leap-day edges, and aNaNinstant;rotationColumnListwith no fields, builtin names, an odd column name, andnull;warnBuiltinColumnCollisionsover the three phases and an unknown one, with no fields, a presentation-only collision, undelivered storage attributes and anulldeclaration;ensureRotationthroughrotateShardson SQLite across seven sweeps (new shards, the window, drops, rows written and counted through the view), weekly and monthly policies, the legacy base-table adoption, the partial-failure recovery (base table and current shard both present), a direct call, and a builtin-collision field on the shard path;registerExternalObjecton SQLite, Postgres and MySQL drivers: every field class,remoteName/remoteSchema(warned and ignored on SQLite, stored elsewhere), acolumnMapwith a non-string entry, autonumber formats, a tenancy opt-out and its sticky re-registration, a declared tenant field,sys_organization, and bad input;rotationShardKey,rotationColumnList,ensureShardTable,aliasShardBookkeeping,computeAndRecordTenantField,isJsonFieldandwarnBuiltinColumnCollisions; instance spies on six of them; the logger, four bookkeeping maps androtationStateByTablereplaced after construction, andknexbehind a getter (the new ones are the ones reached);_SqlDriver.Xat base andObject.X(host stubs) orX(direct-call stubs) at head. That is shape B's receiver, the same at every earlier cut, and no test or consumer reads it. One reading depends on the calendar day (the boot shardinitObjectscreates on the real clock is dropped by the first pinned sweep); every leg ran on 2026-10-27. On the merge head the probe output is byte-identical to the move commit's.test:repo. Not run locally; it is CI's run on this PR.Gates
node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstackderives 48 families for this diff, the same list on the move commit and on the merge head. I ran each one on its own and captured its exit code before any pipe.Merge head
3dcd74655b: 48 run, all 48 exit 0.--ranreconciles:48 derived famil(ies) accounted for — 48 run, 0 NOT-MEASURED, exit 0.Move commit
6b6ca4b01: 46 exited 0 on the first pass. Two exited 3, a missing prerequisite and not a verdict:node scripts/check-plugin-teardown-shape.mjs --self-testcould not read its pinned fixture commit in this shallow clone, and passed (48 cases) once that commit was fetched;check:dual-build-cjs-loadsneeded a full workspace build, and passed (112 require entry points across 69 packages load) afterpnpm build.Hand gates on both heads, all exit 0:
check-adr-anchors,check-adr-symbol-anchors,check-tenant-audit-census(244 write call sites at both ends),check-platform-checklist --changed-from origin/main,check-commit-card-trailers --range origin/main..HEAD,eslint --no-inline-config packages/drivers/driver-sql,check-durability-degradation-log-level --list(same seams both ends).check-object-def-param-keysandcheck-tenant-audit-censusprint byte-identical output at base and head.No gate silently empty. Each derived gate's log prints a non-zero population (for example
check:object-def-param-keyscompares 7 override parameter positions over 3362 files with every control firing;check:tenant-chokepointreads thegetBuilder()bindings of its scan files;check:durability-log-levelreads 88 read seams).The 48 derived commands:
node scripts/check-closing-keyword-parity.mjs,node scripts/check-closing-keyword-parity.mjs --self-test,node scripts/check-comment-mask-adoption.mjs,node scripts/check-comment-mask-adoption.mjs --self-test,node scripts/check-comment-mask-corpus.mjs,node scripts/check-dts-emitted.mjs --self-test,node scripts/check-issue-citations.mjs,node scripts/check-plugin-teardown-shape.mjs,node scripts/check-plugin-teardown-shape.mjs --self-test,node scripts/ci/assert-turbo-remote-cache.mjs --self-test,node scripts/ci/plan-test-core-shards.mjs --self-test,node scripts/ci/prune-turbo-cache.mjs --self-test,node scripts/docs-audit/check-affected-docs.mjs,node scripts/docs-audit/check-drift-comment.mjs,pnpm --filter @objectstack/spec run check:duration-unit-keys,pnpm check:ci-filter-parity,pnpm check:closed-ceremony-text,pnpm check:cross-package-test-inputs,pnpm check:dispatcher-error-vocabulary,pnpm check:doc-authoring,pnpm check:driver-conformance,pnpm check:driver-memory-census,pnpm check:dts-closure,pnpm check:dual-build-cjs-loads,pnpm check:durability-log-level,pnpm check:error-status-conformance,pnpm check:gitlink-declared,pnpm check:issue-citations,pnpm check:keyed-text-bounds,pnpm check:lean-entry-closure,pnpm check:logger-receiver-detach,pnpm check:nul-bytes,pnpm check:object-def-param-keys,pnpm check:org-identifier,pnpm check:page-declaration-shape,pnpm check:platform-object-tenancy-census,pnpm check:published-files,pnpm check:refd-timer-probe,pnpm check:registry-log-declared,pnpm check:rest-log-spy-declared,pnpm check:slot-lookup,pnpm check:sourcemap-no-sources-content,pnpm check:system-context-census,pnpm check:tenant-chokepoint,pnpm check:test-source-alias,pnpm check:tier-file-adoption,pnpm check:undeclared-dep-imports,pnpm check:watch-hint-literal.Acceptance notes
logger-receiver-detach.test.ts's structural pin readssql-driver.tsonly. Three log calls moved tosql-driver-schema-sync.ts(as cut 12's did); the repo-widecheck:logger-receiver-detachgate still reads them there and is green. Noted, not filed.check:tenant-chokepoint'sSCAN_FILESdoes not listsql-driver-schema-sync.ts. The moved bodies hold 0 of the file's 4 tenant predicates and build no query. Noted, not filed: wideningSCAN_FILESwould be a gate change.sql-driver.tsstill has the same 30{@link X}names that are neither a top-level binding nor aSqlDrivermember at base and at head (the set is identical); this cut adds none, becauseundeliveredStorageAttributesstays linkable through its type import.previewDeferredSchemaWork,previewDatetimeConvergence,previewTimeConvergence, about -76) are now the largest clean area. After them the series' remaining yield outside the pinned methods is small, scattered bodies of about -60 or less per area.Human Minimum
packages/specchange.SchemaSyncHostgains members only.eslint --no-inline-configpasses (repo-widepnpm lintis CI's run).main, one pull request.skip-changeset; both declaration lines above.packages/specnot touched.Generated by Claude Code
🤖 Generated with Claude Code
https://claude.ai/code/session_01FEkQ5o3WH4EyHws8N24jPx