Skip to content

refactor(driver-sql): move the filter compiler's leaf predicate emitters out of sql-driver.ts into sql-driver-predicate-emitters.ts (#25521 cut 16) - #26669

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

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

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 里过滤编译器「把一个已校验的比较写成 SQL」的那一层(规范化列比较、$ne/$nin 的空值安全包装、$empty、JSON 列上的 $contains 成员判断、$contains/$startsWith/$endsWith/$icontains 与 $like/$ilike),连同只被它们用到的十六个模块级声明(各方言的文本匹配构造、转义、ASCII 大小写表、$like 模式计划、$empty 的空列表判断与两个拒绝),原样搬进新文件 sql-driver-predicate-emitters.ts;类上的六个方法全部保留,只把方法体转交过去。顺带修好第 15 刀留下的四处 {@link unresolvableColumnNameOf} 失效链接(只加一个仅类型导入)。
  • 为什么改:[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 拆分超大文件的第 16 刀(18.2.0 发布后重启);这一层是重新测量后收益最大的可搬区域,sql-driver.ts 减少 703 行。
  • 风险与回滚:纯搬移,无行为变化;三个 SQL 驱动的测试两端逐条一致,公开类型声明哈希不变,676 条探针读数两端一致(仅 2 条栈顶接收者名不同)。回滚即还原本 PR。
  • 先看:sql-driver-predicate-emitters.ts 的头注释与 PredicateEmitterHost;sql-driver.ts 中六个方法的转交桩和新的 import 行;sql-driver-compile-refusal-seam.test.ts 新增的扫描文件。
  • 两分钟验证:pnpm --filter @objectstack/driver-sql exec vitest run src/sql-driver-compile-refusal-seam.test.ts src/sql-driver-like-pattern.test.ts src/sql-driver-20446-empty-flip.test.ts src/sql-driver-17590-json-column-membership.test.ts,预期全部通过。
  • 需要你做的:无,席位落地。

What changes

Cut 16 of the sql-driver.ts split (#24586 ruling A, recorded in 6270097840), the first after the 18.2.0 restart (6439210093). The claim is 6439272472. The cut opens with a fresh seam table (below), taken on origin/main f9867020c6, and takes the area that pays best without breaking a series limit: the leaf predicate emitters of the filter compiler, the methods that turn one comparison the compiler has already validated into SQL on the builder. They move unedited into a new seam module, packages/drivers/driver-sql/src/sql-driver-predicate-emitters.ts, through a new host PredicateEmitterHost:

With them go the sixteen module-level declarations only they read:

  • the text-match constructs textMatchPredicate, sqliteLengthAwareTextMatch and mysqlAsciiLowerBinary, the comparand escapes escapeLikeComparand, escapeGlobComparand and wrapTextMatchShape, the ASCII case map ASCII_UPPER_LETTERS / ASCII_LOWER_LETTERS, and NUL_CHARACTER;
  • the $like pattern plan: likePatternPredicate, sqliteLikePatternPlan, sqliteLikePatternMatch and the SqliteLikePatternPlan shape;
  • the $empty construct emptyJsonListPredicate and its two refusals, undeclaredEmptyOperatorFieldError and emptyListUnsupportedDialectError.

The likePatternToGlobPattern import loses its last reader in sql-driver.ts, so that one name leaves the import there (the line and its comment stay for the two $like gates beside it) and the module imports it.

Cut 15's follow-up, folded in. Four links to {@link unresolvableColumnNameOf}, in two staying docblocks (aggregateBackendFault's and distinctBackendFault's), named a function that cut 15 moved to sql-driver-read-exit-fault.ts. The existing import of that module gains type unresolvableColumnNameOf, so all four resolve again. No comment changes. The same spelling keeps this cut from adding new broken links: five moved declarations that staying docblocks link (textMatchPredicate, likePatternPredicate, emptyJsonListPredicate, undeclaredEmptyOperatorFieldError, emptyListUnsupportedDialectError) are exported from the module and imported back as types. 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, no packages/spec/** edit, no anchor entry change, no gate edit and no new gate. One test changes, and only as a path reader (below). Branch claude/project-thread-ulif5z (the platform assigned it to this card thread, as the claim records). The move is one commit, 5ceec116b, on f9867020c6, followed by one merge of origin/main.

Net line count (the seat's rule). sql-driver.ts goes from 19,349 lines to 18,646: -703.

  • Out: the six bodies, 131 lines (62 + 35 + 24 + 4 + 3 + 3). The sixteen declarations, 592 lines with their own docblocks (ASCII map 13, NUL_CHARACTER 2, escapeLikeComparand 11, escapeGlobComparand 21, wrapTextMatchShape 6, sqliteLengthAwareTextMatch 85, mysqlAsciiLowerBinary 27, textMatchPredicate 138, SqliteLikePatternPlan 27, sqliteLikePatternPlan 32, sqliteLikePatternMatch 62, likePatternPredicate 73, undeclaredEmptyOperatorFieldError 29, emptyListUnsupportedDialectError 19, emptyJsonListPredicate 47), plus 15 separator lines.
  • In: the six stub bodies, 31 lines (2 + 7 + 8 + 2 + 6 + 6), and a 4-line import of the module with its comment.
  • The new module is 850 lines.

Path readers migrated in the same commit. sql-driver-compile-refusal-seam.test.ts enumerates every INVALID_FILTER builder by parsing a fixed list of source files, and drives every caller of withheldFilterError through its table. The two $empty refusals now live in the new module, so the list gains it (one line, plus its comment and the two docblocks that name the list). No assertion and no table row changes.

Serial with the file's in-flight PRs. I read the file list of every open PR on objectstack-ai/objectstack at the start (19) and again before opening this one (14). None touches sql-driver.ts, turso-driver.ts, sqlite-wasm-driver.ts, check-tenant-chokepoint.mjs or a driver-sql anchor entry. #26337 adds driver-sql/src/sql-driver-external-unprovisioned-sort-anchor.test.ts, which reads none of the moved members; #26556 and #26517 touch only package manifests and changelogs. #26599 (it touched builtin-column-collision.ts) merged into main during the cut and is in this branch through the merge.

Seam table, fresh, on f9867020c6

  • Size and churn. sql-driver.ts is 19,349 lines. The 30-day column counts git blame lines from commits on or after 2026-09-27T09:51:51Z, 30 days before the base commit. scripts/pm/git-history.mjs count proves 75 commits on the file in 30 days and 32 in the last 7. In all, 3,432 lines (17.7%) are from the last 30 days.
  • Class shape. SqlDriver spans lines 4847-19274 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-15 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-4846 Module preamble - 4,846 779 - - - -
4847-6438 Class head: fields, statics, constructor, capability getters name … assertDialectEmits 1,592 290 8 (1) 1 0 18 (18)
6439-6870 Lifecycle connect … disconnect 432 155 8 (3) 0 0 6 (6)
6871-9277 CRUD — IDataDriver core find … delete 2,407 745 43 (7) 0 12 0 (0)
9278-9807 Bulk & Batch Operations bulkCreate … count 530 70 9 (6) 0 7 0 (0)
9808-9904 Raw Execution rawStatementFault … noteRuntimeIndexDdl 97 28 3 (1) 0 0 0 (0)
9905-9997 Transactions beginTransaction … rollbackTransaction 93 46 7 (5) 0 0 0 (0)
9998-10494 Aggregation aggregate … unresolvableAggregateColumnRefusal 497 70 5 (1) 0 1 0 (0)
10495-10756 Distinct distinct … unresolvableDistinctColumnRefusal 262 26 3 (1) 0 1 0 (0)
10757-10872 Window Functions findWithWindowFunctions … findWithWindowFunctions 116 4 1 (1) 0 1 0 (0)
10873-10971 Query Plan Analysis explain … analyzeQuery 99 1 2 (2) 0 1 0 (0)
10972-11030 Schema Sync (syncSchema / init) syncSchema … reclaimSpace 59 34 3 (3) 0 0 0 (0)
11031-12579 Schema Sync (syncSchema / init) › Data-lifecycle rotation (ADR-0057 P2) rotationStateByTable … migrateMysqlTimeColumns 1,549 158 26 (4) 0 0 0 (0)
12580-12836 Schema Sync (syncSchema / init) › Deferred schema DDL (#3917) setDeferredDdl … flushDeferredSchemaDdl 257 25 6 (4) 0 0 0 (0)
12837-13651 Schema Sync (syncSchema / init) › Managed-schema drift & reconcile (#2186) dialectName … createNullSafeUniqueIndex 815 237 23 (3) 0 0 0 (0)
13652-13703 Schema Introspection listPhysicalColumns … introspectSchema 52 46 2 (2) 0 0 0 (0)
13704-13974 Internal helpers getKnex … auditMissingTenant 271 73 8 (1) 0 1 0 (0)
13975-16124 Internal helpers › Filter helpers tableNameForBuilder … applyFalseConstant 2,150 349 44 (2) 0 0 0 (0)
16125-16300 Internal helpers › Field mapping mapSortField … mapAggregateFunc 176 2 6 (0) 0 0 0 (0)
16301-16348 Internal helpers › Window function builder buildWindowFunction … buildWindowFunction 48 26 1 (0) 0 0 0 (0)
16349-18295 Internal helpers › Column creation helper nowColumnDefault … applyDeclaredColumnDefault 1,947 92 19 (0) 0 0 8 (8)
18296-18593 Internal helpers › Database helpers sqliteFilename … isJsonField 298 76 9 (2) 0 0 0 (0)
18594-19070 Internal helpers › SQLite serialisation formatInput … formatOutput 477 27 2 (0) 0 0 0 (0)
19071-19274 Internal helpers › Introspection internals introspectColumnOrder … introspectUniqueConstraints 204 73 5 (0) 0 0 0 (0)
19275-19349 After the class - 75 0 - - - -

Pricing the areas

Eligibility is as cuts 11-15 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. Each area is priced as its stubs minus its bodies, then net of the module-level declarations only it reads (they move along, byte-identical) and the ones it shares with members that stay (they stay in place, exported; ⛔ no second copy of a helper).

Area Bodies Bodies net Declarations that move along Disposition
Leaf predicate emitters (applyNormalizedComparison, applyNullSafeNegative, applyEmptyOperator, applyJsonMembership, applyLike, applyLikePattern) 6 -100 -607 (sixteen declarations with their docblocks and separators), +4 for the import Moves (this cut). -703 measured.
Rotation sub-banner (ensureRotation, registerExternalObject, warnBuiltinColumnCollisions, rotationShardKey, rotationColumnList) 5 -124 (dry run) 0 Not taken: about -120 with its import, a sixth of this cut. Priced as the next slice (below).
compileFilters with filterArrayReachedDriverError 1 about -115 included Not taken: compileFilters has an inferred return type, so the move script refuses it (series limit).
Deferred-DDL previews - about -80 0 Not taken: smaller.
Withheld-filter log family - about -40 0 Not taken: smaller.

Why

Why this area, as one area

  • One mechanism. The filter compiler (applyFilters → compileFilters → applyFilterCondition) validates a condition, resolves its column, and then hands ONE comparison to a leaf emitter, which only writes the predicate onto the builder. The six emitters run no statement and read no schema beyond the two predicates the host carries. They sat inside the 2,150-line Filter helpers sub-banner only because the compiler calls them.
  • It pays the most of any area that moves cleanly, nearly six times the next one, because sixteen module-level declarations (the dialect text-match constructs, the $like pattern plan, the $empty construct and its two refusals) have no other reader and move with them.
  • What stays, and why.
    • The compiler itself stays; applyFilterCondition is pinned in place by its ADR-0139 pointer, and compileFilters has an inferred return type.
    • applyContainsLike, applyTextOperatorOverNonTextColumn and applyFalseConstant are leaf emitters too, but each reads none of the moving declarations and its stub would cost as many lines as its body.
    • LIKE_ESCAPE_CHARACTER is also bound by scanMaxNumericTail's prefix scan, and TextMatchShape is named by applyLike's signature on the class. Each gains a leading export and nothing else, and the module imports both back. withheldFilterError, the finding: after the #7598 Q1=B ruling, a read scope with a driver-refused field reference answers 400 from the driver — which cuts across #5367's attribution argument on that one path #7929 seam both $empty refusals build through, was already exported. The module reads these 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 them leaks.
    • declaredValueShape, isJsonColumn and applyNullSafeNegative are read by members that stay too; the moved bodies reach them through the host.
  • No series limit is touched. None of the six builds through getBuilder, reads a static, has a this.FIELD.delete/insert/update( site, uses super or a bare this, or writes to this. Every one has an annotated return type and is private. 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 code (none reads a tenant field).
  • Durability gate. The moved code has no catch clause. check:durability-log-level --list gives the same seams at both ends, line numbers aside; the gate reads 48 seams (7 propagating) and 82 read seams at head.
  • Logger. No moved body writes to a logger. check:logger-receiver-detach: 0 detaches (3779 non-test files at head).
  • Subclasses. TursoDriver and SqliteWasmDriver extend SqlDriver and override none of the six. Every stub keeps its signature and modifier, and every member a body calls is a late-bound closure on the instance (probe below).
  • Path readers, screened. No script, test, doc or ADR names any of the six, or the sixteen moved declarations, by a sql-driver.ts#… pointer or a sql-driver.ts::… key, and no scripts/adr-anchors/** record names them. Of the tests that read sql-driver.ts as text, only the compile-refusal seam test reads a moved declaration (migrated above). Prose mentions elsewhere (service-analytics's text-match and $like mirrors, @objectstack/core's JSON membership helper, the spec's text conformance table, the driver tests) name the functions, and each is still true.

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

  • Each class method keeps its docblock and signature, byte-identical. Its body is now one host literal plus NAME.call(host, ...), marked [#25521 cut 16]. applyNormalizedComparison and applyNullSafeNegative read no member of the driver, so their methods call the function directly.
  • dialectName goes as a GETTER that reads the driver at call time. declaredValueShape, isJsonColumn and applyNullSafeNegative go as closures that call the method on the driver at call time. So a subclass override, an instance spy, or a dialectName replaced after construction is still the one the body reaches.
  • PredicateEmitterHost lists exactly those four members, each typed as the class member's indexed access. There is no cast, and no member becomes public.
  • Module imports. jsonMembershipPredicate (@objectstack/core), expandEmptyOperator and likePatternToGlobPattern (@objectstack/spec/data), type SqlDialectName (./schema-drift.js), and from ./sql-driver.js LIKE_ESCAPE_CHARACTER, withheldFilterError, type TextMatchShape, type isMultiValuedColumn (named by a moved docblock) and type SqlDriver.
  • Comments. The module header says what moved, what stayed and why. The sql-driver.ts import comment names cut 16 in 3 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; no body line starts inside a template literal.

Verification

ADR anchors

  • The count. check:adr-anchors counts 1881 decision-letter citations at base f9867020c6 (a clean detached worktree) and 1881 at head, over 91 anchored files at both ends. Both runs exit 0.
  • Why equal. No comment inside the moved bodies, declarations or import cites a decision letter. The stubs keep their docblocks, so every ADR-… mention in them stays in sql-driver.ts.
  • No entry needs a new home. No anchor entry names a moved member or declaration. check:adr-symbol-anchors: 2683 resolve at both ends.
  • After the merge. main moved under the cut, so I measured its tip dbf7e04adf against the merge head 048467ad98: 1872 letters at both (91 anchored files, exit 0), and 2687 symbol anchors resolve at both. The drop from 1881 comes from main's own commits, not from this diff.

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 the base seam test into this worktree and deletes the new module, 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.
  • 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), 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 ruling 6441032442 does not come into play.
  • Positive control. I dropped private from applyLike in the head index.d.ts. The result hashes to 4450f3699c74b340a3c946f037a353be89d3c21fe80163eed2040a9a86dcb1ca, with cmp exit 1.
  • Export lists. CJS require of the head dist gives 59 names (sha1 05757ce5ae); the ESM export statement of the base and head index.mjs lists the same 59 names (sha1 05757ce5ae both). None of the seven newly exported declarations, and nothing from the new module, is among them.
  • The head dist carries the move. .call(host sites in index.mjs go from 57 at base to 61 at head (four host stubs; the two direct-call stubs have none).

Move-only proof

The proof compares TypeScript compiler AST leaf tokens (JSDoc nodes skipped), base f9867020c6 against head 5ceec116b. It is a scratch script and is not committed.

Moved body Body tokens Comments Parameter tokens less this Return type tokens Body lines
applyNormalizedComparison 432 5 27 1 62
applyEmptyOperator 259 3 27 1 35
applyJsonMembership 172 3 25 1 24
applyNullSafeNegative 33 0 18 1 4
applyLike 50 0 21 1 3
applyLikePattern 46 0 15 1 3
  • Each moved body. In every row, 0 tokens differ and every comment is identical. Each body line is the base line less two leading spaces. None is async, at either end.
  • The six 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 344 unmoved members are byte-identical, trivia included.
  • Module-level declarations. The sixteen that moved are byte-identical in the module from their own docblock on, and gone from sql-driver.ts; five of them carry a leading export and nothing else. LIKE_ESCAPE_CHARACTER and TextMatchShape differ from base by a leading export and nothing else. At sql-driver.ts's top level, 174 of 195 base statements are byte-identical; the rest are the sixteen moved declarations, the two exports, the two edited imports and the class. The one head-only statement besides those is the new import.
  • Result. PROOF OK, exit 0.
  • Negative control. Through scripts/ablation-replace.mjs, I changed '[$&]' to '[$&]]' in the module's escapeGlobComparand (1 hit, checked on disk). The proof then reports escapeGlobComparand as differing, prints PROOF FAILED, and exits 1. The restore was proved (blob 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 f9867020c6 against head 5ceec116b, and then again on the merge head 048467ad98. 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 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

On the merge head (it brings in main up to dbf7e04adf), the three suites give the same totals and the same per-test statuses as base, 0 / 0 / 0 in each.

  • Type check and lint. tsc --noEmit -p . in driver-sql exits 0 on both heads. 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 and prints 676 readings, each with the result or the envelope's message, code, status, own keys and symbol-keyed members, the SQL and bindings left on the builder, and the log lines written:
    1. applyNormalizedComparison over every operator on both joins, with null comparands and empty or malformed lists, on SQLite, Postgres, MySQL and an unknown dialect;
    2. applyNullSafeNegative on both methods;
    3. applyEmptyOperator on three row shapes, both polarities and both joins, an undeclared field and an unknown dialect (both refusals);
    4. applyJsonMembership on a JSON and a scalar column, both polarities, scalar and list values;
    5. applyLike on the three shapes, negated, folded, with metacharacters and a NUL;
    6. applyLikePattern plain, with wildcards, escapes, a NUL, a GLOB metacharacter, and folded;
    7. a SUBCLASS overriding declaredValueShape, isJsonColumn and applyNullSafeNegative, instance spies on each, and dialectName replaced on the instance after construction (the new ones are the ones reached);
    8. find/count end to end on SQLite through the compiler, reaching each emitter;
    9. bad arguments.
  • Probe result. 674 of the 676 readings are identical, SQL, bindings and envelopes included. The other 2 differ only in the top stack frame's receiver name, where a null builder throws inside a moved body: _SqlDriver.X at base and Object.X at head. That is the host-literal receiver of shape B, 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 54 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 048467ad98: 54 run, all 54 exit 0. --ran reconciles: 54 derived famil(ies) accounted for — 54 run, 0 NOT-MEASURED, exit 0.
  • Move commit 5ceec116b: 53 exited 0; check:dual-build-cjs-loads exited 3 there (its prerequisite, a full workspace build, was missing). After building the driver-sql and objectql closures for the merge head it ran and passed (112 require entry points across 69 packages load).
  • Hand gates on the merge head, all exit 0: check-adr-anchors, check-adr-symbol-anchors, check-tenant-audit-census, 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.
  • No gate silently empty. Each derived gate's log prints a non-zero population (for example check:logger-receiver-detach walks 3779 files and fires on its control corpus; check:tenant-chokepoint reads 24 getBuilder() bindings across 3 files).

The 54 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:engine-double-contract, 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:objectql-double-limit, pnpm check:org-identifier, pnpm check:page-declaration-shape, pnpm check:platform-object-tenancy-census, pnpm check:published-files, pnpm check:query-options-erasure, 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:type-check-coverage, pnpm check:type-check-debt, pnpm check:undeclared-dep-imports, pnpm check:watch-hint-literal, pnpm check:where-matcher.

Acceptance notes

  • Other stale links from cut 15 are left. Cut 15 also moved unresolvableFilterColumnError (3 links), backendStatementFaultError (3) and unresolvableColumnProvenance (2), which staying docblocks in sql-driver.ts still name. The order folded in only the unresolvableColumnNameOf links, so those stay as they were. On a scan of {@link X} names that are neither a top-level binding nor a SqlDriver member, sql-driver.ts goes from 34 at base to 30 at head (the four fixed); this cut adds none. In the new module, one comment inside the moved applyNormalizedComparison body names the class member filterColumnExpr, byte-identical to base. Noted, not filed: the same type-import repair would close them in one later line.
  • check:tenant-chokepoint's SCAN_FILES does not list the new module. The moved bodies hold 0 of the file's 4 tenant predicates and build no query. Noted, not filed: widening SCAN_FILES would be a gate change.
  • Next slice, priced on this base. The rotation sub-banner is now the largest clean area (ensureRotation, registerExternalObject, warnBuiltinColumnCollisions, rotationShardKey, rotationColumnList, about -120), with migrateMysqlDatetimeColumns/migrateMysqlTimeColumns (durability trap), ensureShardTable (named by check-object-def-param-keys.mjs) and computeTenantField (named by packages/spec/liveness/object.json) staying. The series' remaining yield outside the pinned methods (createColumn, applyFilterCondition, formatInput/formatOutput, initObjects) is small bodies of under -120 each.

Human Minimum

  • 1. No packages/spec change.
  • 2. The one new name, PredicateEmitterHost, follows the interface casing.
  • 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_01FEkQ5ncyQ2HYfpr45hwrPU

…ers out of sql-driver.ts into sql-driver-predicate-emitters.ts (#25521 cut 16)

applyNormalizedComparison, applyNullSafeNegative, applyEmptyOperator,
applyJsonMembership, applyLike and applyLikePattern, with the module-level
dialect constructs only they read, move unedited into a new seam module
reached through PredicateEmitterHost. The class keeps every method as a stub.
The compile-refusal seam test reads the new module. The four staying links to
unresolvableColumnNameOf resolve again through a type-only import.

Claude-Session: https://claude.ai/code/session_01FEkQ5ncyQ2HYfpr45hwrPU
Co-authored-by: Claude <noreply@anthropic.com>
@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

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

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

  • content/docs/data-modeling/drivers.mdx (via SqlDriver (symbol, a top-level type))
  • content/docs/data-modeling/index.mdx (via SqlDriver (symbol, a top-level type))
  • content/docs/data-modeling/queries.mdx (via SqlDriver (symbol, a top-level type))
  • content/docs/permissions/tenant-audit-census.mdx (via SqlDriver (symbol, a top-level type))
  • content/docs/plugins/packages.mdx (via SqlDriver (symbol, a top-level type))
  • content/docs/protocol/kernel/index.mdx (via SqlDriver (symbol, a top-level type))
  • content/docs/protocol/kernel/lifecycle.mdx (via SqlDriver (symbol, a top-level type))
  • content/docs/protocol/objectql/query-syntax.mdx (via SqlDriver (symbol, a top-level type))
  • content/docs/protocol/objectql/types.mdx (via SqlDriver (symbol, a top-level type))

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

  • content/docs/releases/v17/17-0.mdx (via SqlDriver (symbol, a top-level type))
  • content/docs/releases/v17/17-5.mdx (via SqlDriver (symbol, a top-level type))
  • content/docs/releases/v17/17-6.mdx (via SqlDriver (symbol, a top-level type), declaredValueShape (symbol, a field of interface PredicateEmitterHost), declaredValueShape (literal, a string literal in applyEmptyOperator; a string literal in declaredValueShape))
  • content/docs/releases/v18/18-1.mdx (via SqlDriver (symbol, a top-level type))

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
  • 2 name(s) were too generic to anchor anything (single lowercase words)
  • 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 86ae47e13acd862527b23ee7e40be3a58a212a3e → packageMentionDocs.

Which tree this was computed on

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

node scripts/docs-audit/affected-docs.mjs --json 86ae47e13acd862527b23ee7e40be3a58a212a3e

⚠️ 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 86ae47e13acd862527b23ee7e40be3a58a212a3e → 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

Seat note: Test Core (6/8) red is not this PR's

2026-10-27T12:48Z · domain:server seat 2 · os-project-manager · head 048467ad98

  • Failing check: Test Core (6/8), job 122182885154. Its only annotation is packages/plugins/plugin-security pnpm run test exited (1). The aggregate Test Core job is red for the same reason.
  • Why it is not this PR's:
    • The same leg failed with the identical annotation on main at b49b752d17 (push run 40499879396, job 122175491737), with no part of this PR present.
    • Later main runs went green.
    • The dev ran the full plugin-security suite on this head after the closure build: 5,036 tests, 0 failed, slowest 2.8 s.
  • Fix: none exists yet. The test name sits in a log the seat's proxy cannot fetch.
  • Next: one re-run of the failed jobs. If it goes red again, the seat reads that run's summary for the test name and files it.

Generated by Claude Code

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

席位核对

ACCEPT · domain:server seat 2 · os-project-manager · session_0186vfjHHMsRXC9EUT9hTFMo · 2026-10-27T13:05Z · head 048467ad98 · #25521 cut 16

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

  1. Shape:

  2. Premise: the seam table is re-measured fresh on f9867020c6 and is in the body. The cut takes the filter compiler's leaf predicate emitters: six method bodies plus the sixteen module-level declarations only they read. They go to sql-driver-predicate-emitters.ts, and sql-driver.ts goes from 19,349 to 18,646 lines on this head.

  3. Move check (the seat's own): every line removed from sql-driver.ts was compared with the new module after stripping indentation. All 718 non-blank lines are present there except nine:

    • Two import lines lose the names only the moved bodies read; the module imports those itself.
    • TextMatchShape and LIKE_ESCAPE_CHARACTER, shared with staying code, gain export in place under cut 14's shared-helper ruling.
    • Five moved helpers gain export in the new module, so the four stale {@link unresolvableColumnNameOf} docblocks and their siblings resolve through a type-only import. index.ts names its exports one by one, so none of them leaks.

    Everything added to sql-driver.ts is the six delegating methods with their hosts. Each host carries a read-only dialectName getter and arrow delegates, and no moved body assigns a field. The module's value imports from ./sql-driver.js (LIKE_ESCAPE_CHARACTER, withheldFilterError) are read only inside function bodies (lines 382, 580, 619 and 641), so the cycle is evaluation-safe. Cuts 2, 11 and 12 set the same precedent.

  4. Path readers: the compile-refusal seam test's enumeration now also reads the new module, which holds the two $empty refusals. The body records the sweep for every other test, doc and anchor, and none names a moved body by path.

  5. Published .d.ts: dist/index.d.ts and dist/index.d.mts are the only declaration files, and their raw sha256 is unchanged at base and head (0f33bf3f…). The positive control (private dropped from applyLike) changes the hash. Nothing re-orders, so 6441032442's bar does not come into play. check:adr-anchors counts 1,881 at base and 1,881 at head.

  6. Overlap: the open feat!: ownership queues and assignment rules (ADR-0142 phase 2) #26337 touches packages/drivers/driver-sql/src/sql-driver*. git merge-tree against this head is clean (seat's read at this stamp).

  7. Gates: at this stamp every check-run on the head reads success or skipped; none reads failed or in progress. The first run's Test Core (6/8) plugin-security red was the main flake recorded in 6442280472; it went green on the one re-run. Locally the dev reports:

    • 54 of 54 derived gates exit 0, with 0 NOT-MEASURED;
    • the three suites match base on the move commit and on the merge head;
    • the AST proof is OK, and its negative control fails as it should.
  8. Next: rotation, priced at about −120, is the next slice.


Generated by Claude Code

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review October 27, 2026 13:06
@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 27, 2026
@github-actions

Copy link
Copy Markdown
Contributor

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

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

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

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

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

    @objectstack/plugin-security:test:  FAIL  src/delegated-organization-membership.test.ts > [#25107 · ADR-0090 D10] parity: each probe agrees with the engine operation it describes > `group`: refused by
      ↳ 失败原因: @objectstack/plugin-security: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/delegated-organization-membership.test.ts — 24h 窗口内只有本 PR 撞到过,暂不汇总(再有一个不同 PR 撞到就会自动开汇总 issue)。
  • ⚠️ 24h 评论账本没读完(超过 5 页仍未读到窗口尽头),所以上面的「不同 PR 数」是下界,不是全量。

历史信号:

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

分诊清单:

  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: re-queued after the merge-group red

2026-10-27T14:37Z · domain:server seat 2 · head 048467ad98

The merge-group red (run 40509204270) is the same plugin-security leg as 6442280472. The triage bot names the test: delegated-organization-membership.test.ts › parity › "group: refused by …", which failed with "Test timed out in 5000ms".


Generated by Claude Code

Merged via the queue into main with commit a485b5a Oct 27, 2026
72 of 74 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/project-thread-ulif5z branch October 27, 2026 14:55
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 tests

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants