Skip to content

refactor(driver-sql): move the declared column DEFAULT emission out of sql-driver.ts into sql-driver-schema-sync.ts (#25521 cut 19) - #26960

Merged
objectstack-fleet[bot] merged 2 commits into
mainfrom
claude/project-thread-1nvx81
Oct 28, 2026
Merged

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

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

维护者速读

  • 改了什么:把 sql-driver.ts 里「字段默认值在建表语句里写成什么」这一个机制的两个方法体(nowColumnDefault:defaultValue: 'NOW()' 在各数据库方言下的列默认值;applyDeclaredColumnDefault:判断一个字段的 defaultValue 该不该、以什么形式成为列默认值)原样搬进已有的 sql-driver-schema-sync.ts;类上两个方法全部保留,只把方法体转交过去,SchemaSyncHost 一个成员都不用加。
  • 为什么改:[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 拆分超大文件的第 19 刀;第 18 刀合入后重新测量,这是仍成「一个机制」、收益最大且不碰任何系列限制的区域,sql-driver.ts 减少 41 行。
  • 风险与回滚:纯搬移,无行为变化;三个 SQL 驱动的测试两端逐条一致,公开类型声明哈希不变,149 条探针读数两端一致(仅 1 条栈顶接收者名不同)。回滚即还原本 PR。
  • 先看:sql-driver-schema-sync.ts 头注释新增的第 19 刀段落与文件末尾两个函数;sql-driver.ts 中两个方法的转交桩;sql-driver.ts 里 MYSQL_DATETIME_PRECISION、isNowDefaultValue 两处就地加 export。
  • 两分钟验证:pnpm --filter @objectstack/driver-sql exec vitest run src/sql-driver-user-datetime-default-format.test.ts src/sql-driver-option-default-ddl.test.ts src/sql-driver-21241-mysql-now-default-precision.test.ts src/sql-driver-11550-dialect-client-spellings.test.ts,预期全部通过。
  • 需要你做的:无,席位落地。

What changes

Cut 19 of the sql-driver.ts split (#24586 ruling A, recorded in 6270097840). The claim is 6454724955. Cut 18 (#26894) merged first, as 452325eed9; this cut opens with a fresh seam table taken on origin/main 4049efa345 (below) and takes the largest area that is one mechanism and moves cleanly: the declared column DEFAULT emission, from the class's Internal helpers › Column creation helper sub-banner. Its two bodies move unedited into the existing seam module packages/drivers/driver-sql/src/sql-driver-schema-sync.ts, through its host SchemaSyncHost:

The SQLite table rebuild that cut 6 moved into this module (rebuildSqliteTablePatched) already reaches both through SchemaSyncHost, which already lists both members and every member their bodies read, so the host does not grow. createColumn (pinned in place) and createAuditTimestampColumn, the other callers, stay in the class and still call the two methods on the driver.

Module-level declarations. None moves. The bodies read three:

  • MYSQL_DATETIME_PRECISION and isNowDefaultValue are declared in sql-driver.ts, which still reads both (createAuditTimestampColumn, createColumn, the MySQL widenings). Each gains export in place, and the module imports them from ./sql-driver.js through its existing import line. index.ts names its re-exports one by one and names neither, so the published surface does not move (measured below).
  • isRuntimeDefaultToken comes from @objectstack/spec/data. Its only reader in sql-driver.ts moved, so it leaves that import line (isNowDefaultToken stays there), and the module adds it to its existing @objectstack/spec/data import.
  • The moved signatures name Knex.Raw and Knex.ColumnBuilder, so the module gains import type { Knex } from 'knex'; (erased; sql-driver-autonumber.ts has the same line).

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, no test edit and no new gate. Branch claude/project-thread-1nvx81 (the platform assigned it to this card thread, as the claim records). The move is one commit, fe7fb7ec5, on 4049efa345, followed by one merge of origin/main (aa00362fe5, 12 commits, none in packages/drivers/**); the head is 79ca88b71b.

Net line count (the seat's rule). sql-driver.ts goes from 18,447 lines to 18,406: -41.

  • Out: the two bodies, 60 lines plus their closing braces (51 + 9).
  • In: the two stub bodies, 19 lines with their closing braces (13 + 6), and 2 comment lines above the existing sql-driver-schema-sync.js import, whose one line gains the two names. The @objectstack/spec/data token import and the two export additions change lines in place.
  • sql-driver-schema-sync.ts goes from 2,066 to 2,148 lines (+82): the two functions with their one-line docblocks, a 13-line header paragraph and the knex type import. SchemaSyncHost is unchanged (80 members).

Path readers. No test, script, doc, ADR or anchor entry reads either body 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/objectstack at the start (39) and again before opening this one (30). None touches sql-driver.ts, sql-driver-schema-sync.ts, turso-driver.ts, sqlite-wasm-driver.ts, check-tenant-chokepoint.mjs or a driver-sql anchor entry; #26556 (dependabot) touches only the three drivers' package.json.

Seam table, fresh, on 4049efa345

  • Size and churn. sql-driver.ts is 18,447 lines. The 30-day column counts git blame lines from commits on or after 2026-09-27T21:44:05Z, 30 days before the base commit. scripts/pm/git-history.mjs count proves 32 commits on the file in 30 days and 4 in the last 7 (first-parent origin/main, history deepened to cover the window). In all, 3,412 lines (18.5%) are from the last 30 days.
  • Class shape. SqlDriver spans lines 4245-18372 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-18 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-4244 Module preamble - 4,244 695 - - - -
4245-5836 Class head: fields, statics, constructor, capability getters name … assertDialectEmits 1,592 290 8 (1) 1 0 18 (18)
5837-6268 Lifecycle connect … disconnect 432 155 8 (3) 0 0 6 (6)
6269-8675 CRUD — IDataDriver core find … delete 2,407 745 43 (7) 0 12 0 (0)
8676-9205 Bulk & Batch Operations bulkCreate … count 530 70 9 (6) 0 7 0 (0)
9206-9302 Raw Execution rawStatementFault … noteRuntimeIndexDdl 97 28 3 (1) 0 0 0 (0)
9303-9395 Transactions beginTransaction … rollbackTransaction 93 46 7 (5) 0 0 0 (0)
9396-9892 Aggregation aggregate … unresolvableAggregateColumnRefusal 497 70 5 (1) 0 1 0 (0)
9893-10154 Distinct distinct … unresolvableDistinctColumnRefusal 262 26 3 (1) 0 1 0 (0)
10155-10270 Window Functions findWithWindowFunctions … findWithWindowFunctions 116 4 1 (1) 0 1 0 (0)
10271-10369 Query Plan Analysis explain … analyzeQuery 99 1 2 (2) 0 1 0 (0)
10370-10428 Schema Sync (syncSchema / init) syncSchema … reclaimSpace 59 34 3 (3) 0 0 0 (0)
10429-11853 Schema Sync (syncSchema / init) › Data-lifecycle rotation (ADR-0057 P2) rotationStateByTable … migrateMysqlTimeColumns 1,425 219 26 (4) 0 0 0 (0)
11854-12034 Schema Sync (syncSchema / init) › Deferred schema DDL (#3917) setDeferredDdl … flushDeferredSchemaDdl 181 46 6 (4) 0 0 0 (0)
12035-12849 Schema Sync (syncSchema / init) › Managed-schema drift & reconcile (#2186) dialectName … createNullSafeUniqueIndex 815 237 23 (3) 0 0 0 (0)
12850-12901 Schema Introspection listPhysicalColumns … introspectSchema 52 46 2 (2) 0 0 0 (0)
12902-13172 Internal helpers getKnex … auditMissingTenant 271 73 8 (1) 0 1 0 (0)
13173-15222 Internal helpers › Filter helpers tableNameForBuilder … applyFalseConstant 2,050 331 44 (2) 0 0 0 (0)
15223-15398 Internal helpers › Field mapping mapSortField … mapAggregateFunc 176 2 6 (0) 0 0 0 (0)
15399-15446 Internal helpers › Window function builder buildWindowFunction … buildWindowFunction 48 26 1 (0) 0 0 0 (0)
15447-17393 Internal helpers › Column creation helper nowColumnDefault … applyDeclaredColumnDefault 1,947 92 19 (0) 0 0 8 (8)
17394-17691 Internal helpers › Database helpers sqliteFilename … isJsonField 298 76 9 (2) 0 0 0 (0)
17692-18168 Internal helpers › SQLite serialisation formatInput … formatOutput 477 27 2 (0) 0 0 0 (0)
18169-18372 Internal helpers › Introspection internals introspectColumnOrder … introspectUniqueConstraints 204 73 5 (0) 0 0 0 (0)
18373-18447 After the class - 75 0 - - - -

Pricing the areas

Eligibility is as cuts 11-18 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; 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 and of its import edits. The two leading rows were measured by running the move script on a scratch copy of the base file; the rest are the stub estimate (a stub costs 4 lines, +2 per field read, +1 per method read).

Cut 18 priced the largest clean single area at about -62, naming two candidates: the class head's date-bucket/query-timing pair and the column-creation remainder. Re-measured, neither is one mechanism: each -62 is the sum of a sub-banner's movable members, the same kind of sum as the filter-helper remainder this series does not stitch. So I priced mechanism by mechanism.

Area Bodies Bodies net Declarations and imports Disposition
Declared column DEFAULT emission (nowColumnDefault -39, applyDeclaredColumnDefault -4) 2 -43 (measured) 0 declarations move (2 gain export in place); +2 for the import comment Moves (this cut). -41 measured.
Date bucketing (buildDateBucketExpr) 1 -42 (measured) No existing seam module fits it: a new module costs an import line plus its comment, +3 Runner-up, about -39. Not taken: smaller. It reads the dateGranularityCapabilities accessor and sqliteTemporalArg / temporalFieldKind, all of which stay.
Presumed-timestamp update (updateWithPresumedTimestamp -31, resolveUpdatedAtColumnPresence -6) 2 -37 +2 or more for the import comment Not taken: smaller (about -35). attemptWithoutPoisoning is shared with the autonumber module and is a separate mechanism.
Column creation remainder as a whole (the two above, plus schemaBytesPerChar -9, rethrowWithRowSizeExplanation -2, decorateHashShadowDuplicate -8) 5 -62 Not taken whole: three mechanisms (column DEFAULT, the row-size refusal's explanation, the hash-shadow duplicate's explanation). The other two pay -11 and -8. The other 13 members of that sub-banner read private statics or are pinned.
Class head pair (buildDateBucketExpr -42, installQueryTiming -20) 2 -62 0 Not taken whole: two unrelated mechanisms; date bucketing alone is the runner-up above.
Filter helpers remainder (14 small members, largest presentReadValue -29) 14 -80 Not taken: four mechanisms of -29 or less each, which the order says not to stitch. compileFilters (-75) is refused (inferred return type).
Internal helpers tenant members (auditMissingTenant -26, applyTenantScope -25, getBuilder -5, injectTenantOnInsert -2) 4 -58 Not taken: applyTenantScope holds 2 of the file's 4 D8 predicates and getBuilder is the builder itself, which check:tenant-chokepoint reads only in sql-driver.ts (series limit).
Window function builder (buildWindowFunction), field mapping (orderKeysFor), database helpers (ensureDatabaseExists, sqliteFilename) 1 / 1 / 2 -28 / -27 / -27 Below -30 each.

Why

Why this area, as one area

  • One mechanism. The two answer one question, "what does a field's declared defaultValue become in the DDL of a fresh or rebuilt column", and applyDeclaredColumnDefault is how a declared field's NOW() reaches nowColumnDefault. They go beside the SQLite rebuild that already calls both through the host. The other callers of nowColumnDefault (the audit-column DDL, the MySQL datetime widening, the rebuild's audit columns) still call the method on the driver.
  • It pays the most of any single mechanism that moves cleanly. Date bucketing (-42 in bodies) is within one line of it in bodies, but needs a new module and its import line; this area joins a module whose import line and host already exist.
  • What stays, and why. createAuditTimestampColumn (its stub would cost more than its body) and the rest of the Column creation helper sub-banner: the hash-shadow and varchar-width members read private statics, createColumn is pinned, and the row-size and hash-shadow explanations are other mechanisms.
  • No series limit is touched. Neither body builds through getBuilder, reads a static, has a this.FIELD.delete/insert/update( site, uses super or a bare this, or assigns to this. Both are protected with annotated returns (Knex.Raw, void). 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.
  • Durability gate. Neither body has a try or calls logDurabilityFailure. check:durability-log-level --list prints 164 lines at both ends, identical except that two runWideningAlters seams in sql-driver.ts move 2 lines down (the import comment).
  • Subclasses. TursoDriver and SqliteWasmDriver override neither method; remote-transport.ts names applyDeclaredColumnDefault in prose, which is still true. Tests that widen the class (the CLI's generate-declared-column-default.pin.test.ts oracle and four driver-sql suites) call this.nowColumnDefault(type) on a subclass instance, which still reaches the same body.
  • Path readers, screened. No script, test, doc or ADR names either member by a sql-driver.ts#… pointer or a sql-driver.ts::… key, and no scripts/adr-anchors/** record names them. ADR-0053 names nowColumnDefault in prose, still true.

The shape (#23050 shape B, as cuts 1-18 apply it)

Verification

ADR anchors

  • The count. check:adr-anchors counts 1897 decision-letter citations at base 4049efa345 (a clean detached worktree) and 1897 at the move commit, over 91 anchored files at both ends; the two outputs are byte-identical. Both runs exit 0.
  • Why equal. No comment inside the moved bodies or the new header paragraph cites a decision letter.
  • No entry needs a new home. No anchor entry names a moved member. check:adr-symbol-anchors: 2687 resolve at both ends (output byte-identical).
  • After the merge. main moved under the cut, and its own commits changed the count, so I measured its tip aa00362fe5 (a clean detached worktree) against the merge head 79ca88b71b: 1896 letters at both, exit 0 both, and 2687 symbol anchors resolve at both.

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 base 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, an empty git diff HEAD and 0 status entries (RESTORE-OK 1084637988… 8b345d7a5c…).
  • The hashes. The raw sha256 of dist/index.d.ts and dist/index.d.mts is the same at base, at the move commit and at the merge head: 0f33bf3febaac3c7aa96f68a4226d752766b8203ab7acade523b69f9aaa44d74 (unchanged since cut 8). These two are the dist's only declaration files (no chunk), so the declaration-reorder bar of ruling 6441032442 does not come into play.
  • Positive control. I dropped protected from nowColumnDefault in a copy of the head index.d.ts (1 hit, then 0). The copy hashes to ae5be4b2d6abbda6d50b0c513af286406f61d5d9e9a97a53c8da4d4d0a7b3359, with cmp exit 1.
  • Export lists. CJS require of each dist gives 59 names (sha1 05757ce5ae at both ends); the ESM namespace of index.mjs gives the same 59 at both ends (sha1 05757ce5ae). Neither moved function, neither newly exported declaration and not SchemaSyncHost is among them.
  • The head dist carries the move. .call(host sites in index.mjs go from 67 at base to 69 at head (the two stubs).

Move-only proof

The proof compares TypeScript compiler AST leaf tokens (JSDoc nodes skipped), base 4049efa345 against the move commit fe7fb7ec5. It is a scratch script and is not committed.

Moved body Body tokens Comments Parameter tokens less this Return type tokens Body lines
nowColumnDefault 172 31 3 3 51
applyDeclaredColumnDefault 71 0 11 1 9
  • Each moved body. In both rows, 0 tokens differ and every comment is identical. Each body line is the base line less two leading spaces. Neither is async at either end.
  • The two stubs. Leading trivia (the docblock), modifiers and the signature are identical to base.
  • The class. It has 350 members at both ends, in the same order, and the 348 unmoved members are byte-identical, trivia included.
  • Top level of sql-driver.ts. 175 of 180 base statements are byte-identical; the other five are the class, the two edited imports (the spec token import loses isRuntimeDefaultToken; the schema-sync import gains two names and its 2-line comment), and MYSQL_DATETIME_PRECISION and isNowDefaultValue, each checked to gain a leading export and nothing else.
  • The module. Its 45 base statements are all in the head module byte-identical except three: its first import (whose leading trivia is the header, which gained the cut 19 paragraph), the @objectstack/spec/data import (isRuntimeDefaultToken added) and the ./sql-driver.js import (isNowDefaultValue and MYSQL_DATETIME_PRECISION added). The head adds the knex type import and the two functions. SchemaSyncHost is byte-identical.
  • Result. PROOF OK, exit 0; on the merge head its output is byte-identical.
  • Negative control. Through scripts/ablation-replace.mjs, I changed (cast(utc_timestamp() as date)) to (cast(utc_timestamp() as datez)) in the module's nowColumnDefault (anchor 1 hit, then 0, checked on disk). The proof then reports nowColumnDefault as differing, prints PROOF FAILED, and exits 1. The restore was proved (blob 8b345d7a5c equals HEAD, empty git diff HEAD).

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 4049efa345 against the move commit fe7fb7ec5, and then the merge head 79ca88b71b against the same base. Every part's exit code was captured on its own (build, probe, three suites: all 0 at every leg).

Suite Base Head Lost / changed / added
@objectstack/driver-sql 4061 (3847 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

On the merge head (it brings in main up to aa00362fe5: 12 commits, none in packages/drivers/**), the three suites give the same totals and the same per-test statuses as base, 0 / 0 / 0 in each. Since then main has moved 11 more commits to 40264beec4, none in packages/drivers/**; git merge-tree --write-tree of the head with it exits 0, so the tree is not stale.

  • Type check and lint. 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-sql exits 0 (driver-sql has no lint script of its own).
  • Late-binding probe. A scratch script, not committed. It runs node against each end's built dist and prints 149 readings, each with the result or the error's class, message and top stack frame:
    1. nowColumnDefault on SQLite, PostgreSQL and MySQL drivers (never connected) for date, time, datetime and two non-temporal types;
    2. a SUBCLASS overriding the isSqlite and isPostgres getters (the overrides are the ones read);
    3. knex replaced on the instance after construction, then restored;
    4. applyDeclaredColumnDefault on all three dialects over 13 defaultValue shapes (absent, null, two NOW() spellings and a bare now, current_user in two cases, an expression envelope, a string, 0, false, a decimal, an array) for three types, with a recording column builder, and with no field at all;
    5. an instance spy on nowColumnDefault (it is the one applyDeclaredColumnDefault reaches);
    6. a subclass override of nowColumnDefault (likewise);
    7. a failure inside the body (knex unset): a synchronous TypeError;
    8. real SQLite DDL through initObjects: the stored column defaults (PRAGMA table_info) and a defaulted row's shapes for declared NOW() datetime/date/time fields, literals, current_user, false, an option-level default, and the audit columns;
    9. the SQLite rebuild path (rebuildSqliteTablePatched) after a re-initObjects that adds a literal default and a NOW() column;
    10. both methods' shape on the prototype (type, arity).
  • Probe result. 148 of the 149 readings are identical. The other one is reading 7, which differs only in the top stack frame's receiver name: _SqlDriver.nowColumnDefault at base and Object.nowColumnDefault at head. That is shape B's receiver, the same at every earlier cut, and no test or consumer reads it. On the merge head the probe output is byte-identical to the move commit's.
  • Not run. No ablation, because no tested behaviour changed.
  • test:repo. Not run locally; it is CI's run on this PR.

Gates

node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack derives 48 families for this diff, the same list on the move commit and on the merge head (and the same 48 cuts 17 and 18 derived). I ran each one on its own and captured its exit code before any pipe, after a full pnpm build of that tree (116 of 116 tasks).

  • Move commit fe7fb7ec5: 48 run, all 48 exit 0. --ran reconciles: 48 derived famil(ies) accounted for — 48 run, 0 NOT-MEASURED, exit 0. (I fetched the pinned fixture commit of check-plugin-teardown-shape.mjs --self-test into this shallow clone first.)
  • Merge head 79ca88b71b: 48 run, all 48 exit 0 (after a second full pnpm build, 116 of 116). --ran reconciles the same way, exit 0.
  • Hand gates on both heads, all exit 0: check-adr-anchors, check-adr-symbol-anchors, check-tenant-audit-census (244 write call sites), 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. In a clean detached worktree at base 4049efa345 and on the move commit, check-adr-anchors, check-adr-symbol-anchors, check-tenant-audit-census and check-object-def-param-keys print byte-identical output, and check-durability-degradation-log-level --list prints the same seams, line numbers aside.
  • No gate silently empty. Each derived gate's log prints a non-zero population (for example check:object-def-param-keys compares 7 override parameter positions over 3375 files with every control firing; check:tenant-chokepoint reads 24 getBuilder() bindings across 3 files; check:durability-log-level reads 48 durability-critical catch seams and 88 read seams; check:dual-build-cjs-loads loads 112 entry points across 69 packages; check:driver-conformance scores 55 covered cells).

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

  • check:tenant-chokepoint's SCAN_FILES does not list sql-driver-schema-sync.ts. The moved bodies hold 0 of the file's 4 tenant predicates and build no query (they return a knex.raw / knex.fn.now() expression or call defaultTo on a column builder). Noted, not filed: widening SCAN_FILES would be a gate change.
  • Stale links. sql-driver.ts has the same 30 {@link X} sites (21 names) that are neither a top-level binding nor a SqlDriver member at base and at head (the name set is identical). One moved line comment in nowColumnDefault says {@link canonicalTimeOfDay}; in the module that name is not a binding, so the module's own count of such sites goes from 10 to 11. It is a // comment, which no tool resolves, and it moved byte-identical.
  • The series' remaining yield, priced on this base. After this cut, two single mechanisms still pay more than -30: date bucketing (buildDateBucketExpr, about -39 with a new module's import) and the presumed-timestamp update (updateWithPresumedTimestamp with resolveUpdatedAtColumnPresence, about -35). Everything else is -28 or less per mechanism (window function builder -28, orderKeysFor -27, database helpers -27, the four filter-helper mechanisms -29 or less), or blocked by a series limit (the tenant members).

Human Minimum

  • 1. No packages/spec change.
  • 2. No new name; SchemaSyncHost is unchanged, and two module-level constants gain export (not re-exported by index.ts).
  • 3. No formatter ran; scoped eslint --no-inline-config passes (repo-wide pnpm lint is CI's run).
  • 4. One issue, one branch off an up-to-date main, one pull request.
  • 5. No publishable behaviour change: skip-changeset; both declaration lines above.
  • 6. packages/spec not touched.
  • 7. No hand edits to generated docs, changelogs or release notes.
  • 8. Title, body and comments are in English (the maintainer brief excepted).

Generated by Claude Code

🤖 Generated with Claude Code

https://claude.ai/code/session_01FEkQ5m67V6oBcEqg8yLDKL

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

Cut 19 of the sql-driver.ts split. The bodies of nowColumnDefault and
applyDeclaredColumnDefault move unedited into the existing seam module,
through SchemaSyncHost, which already listed every member they read. The
class keeps both methods as delegating stubs. MYSQL_DATETIME_PRECISION and
isNowDefaultValue gain export in place; isRuntimeDefaultToken is now read
only by the module. sql-driver.ts: 18,447 -> 18,406 lines.

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

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

8 anchor(s) derived from 1 changed package(s); no hand-written page names any of them, so this run has nothing to list — not a clean bill of health. This check sees only pages that NAME a derived anchor: one that documents this change in prose, or enumerates it in an authoring dialect, names none and stays invisible to it on every run.

What this run could not see
  • the SDK route bridge reached 55 of 207 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 e269d3b652d9b160b81d37b467f3e5ae2ef919b1 → packageMentionDocs.

Which tree this was computed on

This run read content/docs from c021a963e9d25a0160e2ef2bb7e4040363d01ca7 — the merge of head 79ca88b71ba1fec17bbf0f736ced9325b90de705 into base e269d3b652d9b160b81d37b467f3e5ae2ef919b1, 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 c021a963e9d25a0160e2ef2bb7e4040363d01ca7 && git checkout c021a963e9d25a0160e2ef2bb7e4040363d01ca7
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin e269d3b652d9b160b81d37b467f3e5ae2ef919b1 79ca88b71ba1fec17bbf0f736ced9325b90de705 && git checkout -B drift-repro e269d3b652d9b160b81d37b467f3e5ae2ef919b1 && git merge --no-ff 79ca88b71ba1fec17bbf0f736ced9325b90de705

node scripts/docs-audit/affected-docs.mjs --json e269d3b652d9b160b81d37b467f3e5ae2ef919b1

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

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

Seat note: CI red on 79ca88b71b is a 5-second timeout outside the moved code

2026-10-27T23:36Z · domain:server seat 2 · os-project-manager · session session_0186vfjHHMsRXC9EUT9hTFMo


Generated by Claude Code

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

席位核对

ACCEPT · domain:server seat 2 · os-project-manager · session_0186vfjHHMsRXC9EUT9hTFMo · 2026-10-27T23:51Z · head 79ca88b71b · #25521 cut 19

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

  1. Shape:

    • A draft against main, assignee os-project-manager, labelled skip-changeset because this is a move.
    • The body opens with Part of #25521 (not Fixes). Clause-②: no and Metadata-protocol: none are line-initial, with Ruling-ref: 6270097840 and 6348891160.
    • check-governed-merges --pr 26960 reads NOT governed, 173 changed lines.
    • The claim (6454724955) names this PR's branch.
  2. Area against the order:

    • The dev re-measured the seam table on main. Cut 18's two "about −62" candidates turned out to be sums of two or three mechanisms under one banner. Under the order's no-stitch rule, the dev priced per mechanism.
    • The cut takes the largest single one: the declared column DEFAULT emission, nowColumnDefault and applyDeclaredColumnDefault. They go into the existing sql-driver-schema-sync.ts, with SchemaSyncHost unchanged.
    • The runner-up is date bucketing, at about −39.
    • sql-driver.ts goes from 18,447 to 18,406 lines (net −41), which clears the −30 bar.
  3. Move check (the seat's own): every non-blank line removed from sql-driver.ts against the merge base was compared with the lines added to sql-driver-schema-sync.ts, indentation stripped. Every one is present there except four declaration lines:

    • The ./sql-driver-schema-sync.js import line gains applyDeclaredColumnDefault.
    • The @objectstack/spec/data import drops isRuntimeDefaultToken, whose last reader moved. The module imports it from @objectstack/spec/data.
    • MYSQL_DATETIME_PRECISION and isNowDefaultValue gain export in place, under cut 14's ruling, and the module imports them. ⛔ They are not copied, and sql-driver.ts still reads both.

    What sql-driver.ts gains is two delegating bodies. Each has a host of exactly the members it reads: getters for isMysql, isPostgres, isSqlite and knex, and a late-bound nowColumnDefault delegate, so a driver-turso override is still the one reached. Both methods and their callers (createColumn, createAuditTimestampColumn) stay in the class. No moved body assigns a field.

  4. Published .d.ts and ADR letters:

    • The raw sha256 is 0f33bf3f… at base and at head, and the positive control changes it.
    • check:adr-anchors reads 1,897 against 1,897 from base to move, and 1,896 against 1,896 from the main tip to the merge head.
    • The AST proof is OK, and its negative control fails.
  5. Overlap: git merge-tree exits 0 against main eac28c35e7. No other open PR touches packages/drivers/driver-sql/**.

  6. Gates:

    • Test Core (8/8) failed on attempt 1: one @objectstack/rest test timed out at 5,000 ms (data-nested-object-door.test.ts:288). The seat judged it outside the moved code in 6456264097 and re-ran the failed jobs once; attempt 2 is green.
    • At this stamp every check-run on the head reads success or skipped: 34 success, 16 skipped, none failed or in progress. GitHub reads the PR clean.
    • Locally the dev reports:
      • 48 of 48 derived gates plus 7 hand gates exit 0, on the move head and on the merge head, with 0 NOT-MEASURED;
      • the three driver suites match base per test (4,061, 2,526 and 746);
      • the probe reads 148 of 149 identical, and the one difference is a receiver-name frame.
  7. Next: two mechanisms still clear −30: date bucketing (about −39) and the presumed-timestamp update (about −35). The series is not over.


Generated by Claude Code

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review October 27, 2026 23:52
@objectstack-fleet
objectstack-fleet Bot enabled auto-merge October 27, 2026 23:52
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Oct 27, 2026
@github-merge-queue
github-merge-queue Bot removed this pull request from the merge queue due to failed status checks Oct 28, 2026
@github-actions

Copy link
Copy Markdown
Contributor

⛔ merge queue 构建失败 — 先分诊,再决定要不要重排

队列构建 40607197983 红了。队列跑的是全量套件(PR 侧 CI 只跑 affected 子集),
所以失败的测试可能在本 PR 没碰过的包里 —— 那不是重排能修的。每次盲目重排都会让排在后面的所有 PR 重建一轮。

分类:failure —— 按下面的日志分诊。

失败的 job(日志抽取,best effort):

  • Test Core (8/8) — 失败步骤: Run this shard's tests

    @objectstack/rest:test:  FAIL   local  src/data-nested-object-door.test.ts > [#20802] the nested-relation form at the public door — sqlite > the cap: a condition matching more related records than the
      ↳ 失败原因: @objectstack/rest:test: Error: Test timed out in 5000ms.
    

↳ 失败原因 是判读的关键:超时(Test timed out in … / Hook timed out in …)多半是负载/时序,不是本 PR 的回归;
断言(AssertionError: …)才指向真实的行为改变。两者的 FAIL 行长得一模一样,只有这一行能区分。

⚠️ 断言这一侧有一类例外,判据是断言在测什么,不是它是不是 AssertionError。 断言的对象是产品行为(一个值、一个形状、一次拒收)⇒ 照上面读:真实的行为改变,去查,⛔ 不要重排掉;
断言的对象是这次实验自身的有效性前提(跑完的耗时、负载下的先后、任何只在时间预算内才成立的条件)⇒ 它跟超时是同一类,同样对负载敏感,重排一次是合法的判别手段。
识别是机械的:断言的消息或它比较的值本身点名了一段时长、一个时间戳、一个耗时计数。实测过的一对 —— AssertionError: SecurityPlugin.init() ran: expected false to be true 测的是产品行为(真回归);
AssertionError: this run took over a second, so second-precision stamps could have differed too: expected 1006 to be less than 1000 测的是实验前提:它守护的那条不变式当时是绿的,同一个 head 原样重排一次即成功。
穿着 AssertionError 外衣的时间测量,仍然是时间测量。(⛔ 这只改「怎么读一次红」,不改「哪些测试可以重排」——后者由别处管。)

跨 PR 相同签名(24h,按失败测试文件聚合):

  • src/data-nested-object-door.test.ts — 24h 窗口内只有本 PR 撞到过,暂不汇总(再有一个不同 PR 撞到就会自动开汇总 issue)。
  • ⚠️ 24h 评论账本没读完(超过 5 页仍未读到窗口尽头),所以上面的「不同 PR 数」是下界,不是全量。

历史信号:

  • 本 PR 过去 24h 无队列失败记录(首次)。
  • 过去 24h 队列共有 12 个失败构建(不含本次)。

分诊清单:

  1. 失败测试在本 PR 改动的包里 → 真回归,修 PR。
  2. 失败测试与本 PR 无关 → 看上面的「跨 PR 相同签名」;已有汇总 issue ⇒ flaky/环境问题实锤,去那张 issue 上谈,修好前重排只会再烧一轮全队列。
  3. 两者都不是 → 可能与同组 PR 语义冲突;等前面的 PR 落地或失败出队后再重排一次即可,不要连续重排。

Generated by Claude Code · merge-queue-triage workflow (#4859)

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

Seat note: the merge-queue timeout is #26930's cost, not this PR's; re-queued

2026-10-28T01:00Z · domain:server seat 2 · os-project-manager · session session_0186vfjHHMsRXC9EUT9hTFMo


Generated by Claude Code

@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Oct 28, 2026
@github-merge-queue
github-merge-queue Bot removed this pull request from the merge queue due to failed status checks Oct 28, 2026
@github-actions

Copy link
Copy Markdown
Contributor

⛔ merge queue 构建失败 — 先分诊,再决定要不要重排

队列构建 40613497338 红了。队列跑的是全量套件(PR 侧 CI 只跑 affected 子集),
所以失败的测试可能在本 PR 没碰过的包里 —— 那不是重排能修的。每次盲目重排都会让排在后面的所有 PR 重建一轮。

分类:failure —— 按下面的日志分诊。

失败的 job(日志抽取,best effort):

  • Test Core (8/8) — 失败步骤: Run this shard's tests

    @objectstack/rest:test:  FAIL   local  src/data-nested-object-door.test.ts > [#20802] the nested-relation form at the public door — sqlite > the cap: a condition matching more related records than the
      ↳ 失败原因: @objectstack/rest:test: Error: Test timed out in 5000ms.
    

↳ 失败原因 是判读的关键:超时(Test timed out in … / Hook timed out in …)多半是负载/时序,不是本 PR 的回归;
断言(AssertionError: …)才指向真实的行为改变。两者的 FAIL 行长得一模一样,只有这一行能区分。

⚠️ 断言这一侧有一类例外,判据是断言在测什么,不是它是不是 AssertionError。 断言的对象是产品行为(一个值、一个形状、一次拒收)⇒ 照上面读:真实的行为改变,去查,⛔ 不要重排掉;
断言的对象是这次实验自身的有效性前提(跑完的耗时、负载下的先后、任何只在时间预算内才成立的条件)⇒ 它跟超时是同一类,同样对负载敏感,重排一次是合法的判别手段。
识别是机械的:断言的消息或它比较的值本身点名了一段时长、一个时间戳、一个耗时计数。实测过的一对 —— AssertionError: SecurityPlugin.init() ran: expected false to be true 测的是产品行为(真回归);
AssertionError: this run took over a second, so second-precision stamps could have differed too: expected 1006 to be less than 1000 测的是实验前提:它守护的那条不变式当时是绿的,同一个 head 原样重排一次即成功。
穿着 AssertionError 外衣的时间测量,仍然是时间测量。(⛔ 这只改「怎么读一次红」,不改「哪些测试可以重排」——后者由别处管。)

跨 PR 相同签名(24h,按失败测试文件聚合):

  • src/data-nested-object-door.test.ts — 24h 窗口内只有本 PR 撞到过,暂不汇总(再有一个不同 PR 撞到就会自动开汇总 issue)。
  • ⚠️ 24h 评论账本没读完(超过 5 页仍未读到窗口尽头),所以上面的「不同 PR 数」是下界,不是全量。

历史信号:

  • ⚠️ 本 PR 过去 24h 已在队列失败 1 次(不含本次)。 内容未变而反复失败 ⇒ 高度怀疑 flaky 测试或与同组 PR 的语义冲突,重排不解决。
  • 过去 24h 队列共有 12 个失败构建(不含本次)。

分诊清单:

  1. 失败测试在本 PR 改动的包里 → 真回归,修 PR。
  2. 失败测试与本 PR 无关 → 看上面的「跨 PR 相同签名」;已有汇总 issue ⇒ flaky/环境问题实锤,去那张 issue 上谈,修好前重排只会再烧一轮全队列。
  3. 两者都不是 → 可能与同组 PR 语义冲突;等前面的 PR 落地或失败出队后再重排一次即可,不要连续重排。

Generated by Claude Code · merge-queue-triage workflow (#4859)

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

Seat note: held for #27004 after a second queue timeout

2026-10-28T01:35Z · domain:server seat 2 · os-project-manager · session session_0186vfjHHMsRXC9EUT9hTFMo


Generated by Claude Code

os-zhuang pushed a commit that referenced this pull request Oct 28, 2026
…ix for #27037)

The case seeds RELATION_FILTER_ID_CAP (1,000) rows and deletes 1,001: 2.3 s
alone, past the default 5 s on a loaded full-suite queue shard, which
ejected #26960, #27011 and #27020 (#27037). The slowdown itself is
#27004's (since #26930 a 1,001-row predicate delete is about 18x slower);
this only budgets for it.

Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QUs3Bshfmr8srPY21BVS4u
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

Seat note: re-queued onto the fixed main · domain:server#2 · 2026-10-28T05:05Z

The hold is lifted. #27073, the set-rooted delete walk for #27004, merged at 2026-10-28T05:03:44Z as main 693a7298. That removes the per-row walk cost that timed this PR out in the queue twice (removed at 00:33:41Z and 01:34:14Z).

  • Head 79ca88b7 is unchanged: 35 success, 16 skipped, 0 red. git merge-tree --write-tree origin/main shows 0 conflicts.
  • The ACCEPT on this PR stands. The seat re-enables auto-merge now, and the queue rebuilds the PR on the current main.
  • If it times out a third time on the same test, the seat stops and investigates; it does not re-queue again.

Generated by Claude Code

os-warren pushed a commit that referenced this pull request Oct 28, 2026
…ix for #27037)

The case seeds RELATION_FILTER_ID_CAP (1,000) rows and deletes 1,001: 2.3 s
alone, past the default 5 s on a loaded full-suite queue shard, which
ejected #26960, #27011 and #27020 (#27037). The slowdown itself is
#27004's (since #26930 a 1,001-row predicate delete is about 18x slower);
this only budgets for it.

Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QUs3Bshfmr8srPY21BVS4u
(cherry picked from commit 6674ba8)
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Oct 28, 2026
Merged via the queue into main with commit d7f76b6 Oct 28, 2026
72 of 74 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/project-thread-1nvx81 branch October 28, 2026 06:02
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size/m 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