Skip to content

Commit d1a2e71

Browse files
refactor(driver-sql): move the upsert conflict-target path out of sql-driver.ts into sql-driver-upsert-conflict-target.ts (#25521 cut 14) (#26408)
<!-- claude-projects-attribution: {"github_login":"os-project-manager"} --> _Requested by **Leehom** · [project thread](https://claude.ai/code/project/chan_01TenzsRopZj4jMbvjX8uz1X?thread=cmsg_01TenzsRopZj4jMbvjX8uz1XJTT8niknX7SW4y6iBvugEZ)_ Part of #25521 Clause-②: no Metadata-protocol: none Ruling-ref: 6270097840 Ruling-ref: 6348891160 ## 维护者速读 - **改了什么**:把 `sql-driver.ts` 里 upsert 的「冲突键预检」代码(判断表上是否真有能承接 `conflictKeys` 的唯一索引、MySQL 下是否会落到别的唯一键上,以及多组织写入的组织栅栏两个小助手)原样搬进新文件 `sql-driver-upsert-conflict-target.ts`;类上的方法全部保留,只把方法体转交过去。 - **为什么改**:#25521 拆分超大文件的第 14 刀;这块在剩余可搬区域里收益最大,`sql-driver.ts` 减少 228 行。 - **风险与回滚**:纯搬移,无行为变化;三个 SQL 驱动的测试两端逐条一致,公开类型声明哈希不变。回滚即还原本 PR。 - **先看**:`sql-driver-upsert-conflict-target.ts` 的头注释与 host 接口;`sql-driver.ts` 中五个方法的转交桩;`refuseUnbackedConflictTarget` 上方那段文档注释如今重新紧贴它自己的函数。 - **两分钟验证**:`pnpm --filter @objectstack/driver-sql exec vitest run src/sql-driver-upsert-conflict-target-dialects.test.ts src/sql-driver-upsert-conflict-target-envelope.test.ts`,预期全部通过。 - **需要你做的**:无,席位落地。 ## What changes Cut 14 of the `sql-driver.ts` split (#24586 ruling A, recorded in `6270097840`). The claim is `6431182919`. The cut opens with a fresh seam table (below), taken on `origin/main` `abb904605` (cut 13's merge), and takes the area that pays best without breaking a series limit: the **conflict-target path of `upsert`**. It moves unedited into a **new** seam module, `packages/drivers/driver-sql/src/sql-driver-upsert-conflict-target.ts`, through a new host `UpsertConflictTargetHost`: - `assertConflictTargetHonoured`, the MySQL pre-flight (#8621 unbacked, #8755 ambiguous, and #8807's identity-at-risk verdict); - `assertConflictTargetBackedWithoutClause`, its #8740 twin for a statement with no conflict clause; - `introspectKeyIndexes`, the cached PRIMARY KEY / UNIQUE read both consult; - `upsertTenantGuard` and `upsertConflictRefusal`, the #21185 organization-fence helpers `driver-turso` also calls on its own instance. 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 test change, no gate edit and no new gate. Branch `claude/project-thread-h28rw8` (the platform assigned it to this card thread, as the claim records). The move is one commit, `84d0e45bd`, on `abb904605`. **Net line count (the seat's rule).** `sql-driver.ts` goes from 20,022 lines to 19,794: **-228**. - **Out:** the five bodies, 134 lines (9 + 4 + 17 + 97 + 7). Three module-level declarations that only these bodies read, 125 lines with their own docblocks plus 3 separator lines: `indexCoversConflictTarget` (11), `unbackedConflictTargetCause` (11) and `refuseAmbiguousConflictTarget` (103). - **In:** the five stub bodies, 30 lines (6 + 2 + 7 + 8 + 7), and a 4-line import of the module with its comment. - The new module is 359 lines. **Why -228 and not the -320 cut 13 priced.** Two corrections, both measured on this base: - `indexCoversConflictTarget`'s leading trivia holds two earlier comments that are not its own: the `[#8445 → #8567]` note on `isUnbackedConflictTargetError` and the `[#8445]` docblock of `refuseUnbackedConflictTarget` (80 lines; the #26015 insertion of `indexCoversConflictTarget` and `unbackedConflictTargetCause` had separated that docblock from its function). They describe code that stays, so they stay. With the two functions gone, the `[#8445]` docblock again sits directly on `refuseUnbackedConflictTarget`. - `ConflictTargetVerdict` (15 lines) is the declared return type of the class method `assertConflictTargetHonoured`, whose signature stays in the class (and in the published `.d.ts`). It stays, exported in place. **Serial with the file's in-flight PRs.** I read the full file list of every open PR at start (23 PRs, every page of each). Before cut 13 merged only #26377 touched `sql-driver.ts`. No other open PR touches `sql-driver.ts`, `turso-driver.ts`, `sqlite-wasm-driver.ts`, `check-tenant-chokepoint.mjs` or the driver-sql anchor entries. #26138 touches `driver-sql/src/builtin-column-collision.ts` only; #26337 adds `driver-sql/src/sql-driver-external-unprovisioned-sort-anchor.test.ts` only. ## Seam table, fresh, on `abb904605` - **Size and churn.** `sql-driver.ts` is 20,022 lines. The 30-day column counts `git blame` lines from commits on or after 2026-09-27T01:27:21Z, 30 days before the base commit. History reaches 2026-09-20. `scripts/pm/git-history.mjs count` proves 74 commits on the file in 30 days and 32 in the last 7. In all, 3,490 lines (17.4%) are from the last 30 days. - **Class shape.** `SqlDriver` spans lines 5273-19947 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-13 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-5272 | Module preamble | - | 5,272 | 811 | - | - | - | - | | 5273-6864 | Class head: fields, statics, constructor, capability getters | `name` … `assertDialectEmits` | 1,592 | 290 | 8 (1) | 1 | 0 | 18 (18) | | 6865-7296 | Lifecycle | `connect` … `disconnect` | 432 | 155 | 8 (3) | 0 | 0 | 6 (6) | | 7297-9807 | CRUD — IDataDriver core | `find` … `delete` | 2,511 | 736 | 43 (7) | 0 | 12 | 0 (0) | | 9808-10419 | Bulk & Batch Operations | `bulkCreate` … `count` | 612 | 119 | 9 (6) | 0 | 7 | 0 (0) | | 10420-10516 | Raw Execution | `rawStatementFault` … `noteRuntimeIndexDdl` | 97 | 28 | 3 (1) | 0 | 0 | 0 (0) | | 10517-10609 | Transactions | `beginTransaction` … `rollbackTransaction` | 93 | 46 | 7 (5) | 0 | 0 | 0 (0) | | 10610-11141 | Aggregation | `aggregate` … `unresolvableAggregateColumnRefusal` | 532 | 60 | 5 (1) | 0 | 1 | 0 (0) | | 11142-11429 | Distinct | `distinct` … `unresolvableDistinctColumnRefusal` | 288 | 17 | 3 (1) | 0 | 1 | 0 (0) | | 11430-11545 | Window Functions | `findWithWindowFunctions` … `findWithWindowFunctions` | 116 | 4 | 1 (1) | 0 | 1 | 0 (0) | | 11546-11644 | Query Plan Analysis | `explain` … `analyzeQuery` | 99 | 1 | 2 (2) | 0 | 1 | 0 (0) | | 11645-11703 | Schema Sync (syncSchema / init) | `syncSchema` … `reclaimSpace` | 59 | 34 | 3 (3) | 0 | 0 | 0 (0) | | 11704-13252 | Schema Sync (syncSchema / init) › Data-lifecycle rotation (ADR-0057 P2) | `rotationStateByTable` … `migrateMysqlTimeColumns` | 1,549 | 158 | 26 (4) | 0 | 0 | 0 (0) | | 13253-13509 | Schema Sync (syncSchema / init) › Deferred schema DDL (#3917) | `setDeferredDdl` … `flushDeferredSchemaDdl` | 257 | 25 | 6 (4) | 0 | 0 | 0 (0) | | 13510-14324 | Schema Sync (syncSchema / init) › Managed-schema drift & reconcile (#2186) | `dialectName` … `createNullSafeUniqueIndex` | 815 | 237 | 23 (3) | 0 | 0 | 0 (0) | | 14325-14376 | Schema Introspection | `listPhysicalColumns` … `introspectSchema` | 52 | 46 | 2 (2) | 0 | 0 | 0 (0) | | 14377-14647 | Internal helpers | `getKnex` … `auditMissingTenant` | 271 | 73 | 8 (1) | 0 | 1 | 0 (0) | | 14648-16797 | Internal helpers › Filter helpers | `tableNameForBuilder` … `applyFalseConstant` | 2,150 | 354 | 44 (2) | 0 | 0 | 0 (0) | | 16798-16973 | Internal helpers › Field mapping | `mapSortField` … `mapAggregateFunc` | 176 | 2 | 6 (0) | 0 | 0 | 0 (0) | | 16974-17021 | Internal helpers › Window function builder | `buildWindowFunction` … `buildWindowFunction` | 48 | 26 | 1 (0) | 0 | 0 | 0 (0) | | 17022-18968 | Internal helpers › Column creation helper | `nowColumnDefault` … `applyDeclaredColumnDefault` | 1,947 | 92 | 19 (0) | 0 | 0 | 8 (8) | | 18969-19266 | Internal helpers › Database helpers | `sqliteFilename` … `isJsonField` | 298 | 76 | 9 (2) | 0 | 0 | 0 (0) | | 19267-19743 | Internal helpers › SQLite serialisation | `formatInput` … `formatOutput` | 477 | 27 | 2 (0) | 0 | 0 | 0 (0) | | 19744-19947 | Internal helpers › Introspection internals | `introspectColumnOrder` … `introspectUniqueConstraints` | 204 | 73 | 5 (0) | 0 | 0 | 0 (0) | | 19948-20022 | After the class | - | 75 | 0 | - | - | - | - | ### Pricing the areas Eligibility is as cuts 11-13 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`. 129 members are eligible on this base. Each candidate area is priced as its stubs minus its bodies, then **net of the module-level declarations it shares with members that stay** (they stay in place, exported; ⛔ no second copy of a helper), counting only a declaration's own docblock. | Area (section) | Bodies | Bodies net | Declarations that move along | Disposition | |:--|--:|--:|--:|:--| | **CRUD upsert conflict-target path** (`assertConflictTargetHonoured`, `assertConflictTargetBackedWithoutClause`, `introspectKeyIndexes`, `upsertTenantGuard`, `upsertConflictRefusal`) | 5 | **-104** | **-128** (`indexCoversConflictTarget`, `unbackedConflictTargetCause`, `refuseAmbiguousConflictTarget`), +4 for the module import | **Moves (this cut).** -228 measured. | | Backend statement fault (`backendStatementFault`) | 1 | -75 | about -145 (`backendStatementFaultError` with its docblock) | Not taken: about -216, less, and a single body. Next candidate. | | Rotation sub-banner (`ensureRotation`, `registerExternalObject`, `warnBuiltinColumnCollisions`, …) | 7 | -195 gross | 0 | Not taken: `migrateMysqlDatetimeColumns` and `migrateMysqlTimeColumns` (-71) stay under the cut 12 durability-gate trap, leaving about -124. | | The rest of the Filter helpers (`compileFilters` -75, `applyNormalizedComparison` -60, …) | - | - | `filterArrayReachedDriverError` (-44) | Not priced as one area: small bodies spread around `applyFilterCondition`, which an ADR-0139 pointer pins in place. | ## Why this area - **It pays the most of any area that moves cleanly.** The five bodies are one mechanism: what `upsert` asks of the table's physical keys before it compiles, and the fence it applies afterwards. - **What stays, and why.** - `upsert` builds through `getBuilder` (2 sites) and stays, with `assertMergeLandedInWrittenOrganization`, `assertMergeLandedOnSuppliedIdentity` and `mergeLandsInWrittenOrganization`. - `insertOnlyUpsertColumns` stays (#8740). - `refuseUnbackedConflictTarget` (also thrown by `upsert`'s reactive catch), `refuseUpsertConflictOutsideWrittenOrganization` (also thrown by `upsert` and `assertMergeLandedInWrittenOrganization`) and `ConflictTargetVerdict` (the class method's return type) are shared. Each gains a leading `export` and nothing else, and the module imports them back. The module reads the two functions only inside function bodies, so the import cycle is evaluation-safe. `index.ts` names its exports one by one; the export lists below prove none of the three leaks. - `rotationWriteTarget`, `introspectIndexes`, `resolveTenantField` and `remoteColumn` are read by members that stay too. The moved bodies reach them through the host. - **No series limit is touched.** None of the five builds through `getBuilder`, reads a static, has a `this.FIELD.delete/insert/update(` site, uses `super` or a bare `this`, or writes to `this` (`introspectKeyIndexes` calls `.get`/`.set` on the `physicalKeyIndexes` map it reads through a getter, as before). Every one has an annotated return type; three are protected and two 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 new module. `upsertTenantGuard` reads the tenant field to build the fence descriptor and holds no predicate; disclosed because the new module is outside the gate's `SCAN_FILES`. - **Durability gate.** `introspectKeyIndexes` has the one `catch` in the moved code; it returns `null` and reaches no vocabulary callee. `check:durability-log-level` reads 48 seams (7 propagating) at both ends. - **Subclasses.** `TursoDriver` calls `upsertTenantGuard` and `upsertConflictRefusal` on its own instance; both stubs keep their signatures and stay `protected`. `TursoDriver` and `SqliteWasmDriver` override none of the five. Every member the bodies call is a late-bound closure on the instance. - **Path readers, screened.** No script, test, doc or ADR names any of the five, or the three moved declarations, by a `sql-driver.ts#…` pointer or a `sql-driver.ts::…` key. Prose mentions (`live-dialect-matrix.isolation.test.ts`, `sql-driver-introspection.ts`, `sql-driver-upsert-conflict-target-envelope.test.ts`, the turso parity tests, the driver-sql CHANGELOG) name the functions or `sql-driver.ts` as a file, and each is still true. No test reads these bodies' text by path, so no test changes. ## The shape (#23050 shape B, as cuts 1-13 apply it) - Each class method keeps its docblock and signature, byte-identical, except that the three `async` methods drop `async` (each stub returns the moved function's promise; the `.d.ts` does not carry `async`). Its body is now one host literal plus `NAME.call(host, ...)`, marked `[#25521 cut 14]`. `upsertConflictRefusal` reads no member, so its stub calls the function directly (the cut 11 shape). - Fields go as GETTERS that read the driver at call time (`physicalTableByObject`, `physicalKeyIndexes`). Methods go as closures that call the method on the driver at call time (`introspectIndexes`, `introspectKeyIndexes`, `remoteColumn`, `resolveTenantField`, `rotationWriteTarget`). So a subclass override, an instance spy, or a field replaced after construction is still the one the body reaches (probe below). - `UpsertConflictTargetHost` lists exactly those seven members, each typed as the class member's indexed access. There is no cast, and no member becomes public. - **Module imports.** `StandardErrorCode` (`@objectstack/spec/api`, read by the moved `refuseAmbiguousConflictTarget`), `type DriverOptions` (`@objectstack/spec/data`), `type PhysicalIndex` (`./schema-drift.js`), and from `./sql-driver.js` the three shared declarations plus `type SqlDriver`. No `sql-driver.ts` import lost its last reader. - **Comments.** The module header says what moved, what stayed and why. The `sql-driver.ts` import comment names cut 14 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. ## ADR anchors - **The count.** `check:adr-anchors` counts **1877** decision-letter citations at base `abb904605` (a clean detached worktree) and **1877** at head, over 91 anchored files at both ends. Both runs exit 0. - **Why equal.** No comment inside the moved bodies or declarations cites a decision letter. The one `ADR-0120 D1` near this code is in `upsertTenantGuard`'s docblock, which stays on the class method. - **No entry needs a new home.** The `sql-driver.ts` entry's one paragraph (ADR-0053) describes `formatOutput` and `presentReadValue`, neither of which moved. No `scripts/adr-anchors/**` file changed. ## The published `.d.ts` does not change This is measured under the series bar (ruling `6348891160`, item 1). - **How each end was built.** `pnpm turbo run build --filter=@objectstack/driver-sql --force` at each end, against the same dependency tree. The base leg writes base `sql-driver.ts` 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 (`25041557`, `bcb95569`), 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). - **Positive control.** I dropped `protected` from `assertConflictTargetHonoured` in the head `index.d.ts`. The result hashes to `0f7f0de877d0becf5acec215a618fa3922518b60690f91898bec355659e1e21a`, with a 1-line diff and `cmp` exit 1. - **Export lists.** CJS `require` of the head dist gives 59 names, the same set as its ESM `import` (sha1 `05757ce5ae`); the ESM export statement of the base and head `index.mjs` lists the same 59 names (sha1 `05757ce5ae` both). None of the three 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 47 at base to 51 at head (four stubs; the fifth calls directly). ## Move-only proof The proof compares TypeScript compiler AST leaf tokens (JSDoc nodes skipped), base `abb904605` against head `84d0e45bd`. It is a scratch script and is not committed. | Moved body | Body tokens | Comments | Parameter tokens less `this` | Return type tokens | Body lines | |:--|--:|--:|--:|--:|--:| | `upsertTenantGuard` | 89 | 2 | 15 | 15 | 9 | | `upsertConflictRefusal` | 23 | 0 | 9 | 1 | 4 | | `introspectKeyIndexes` | 111 | 2 | 15 | 8 | 17 | | `assertConflictTargetHonoured` | 423 | 54 | 11 | 4 | 97 | | `assertConflictTargetBackedWithoutClause` | 114 | 0 | 11 | 4 | 7 | - **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. `async` matches the base method on all five functions. - **The five stubs.** Leading trivia (the docblock), modifiers and the signature are identical to base, less `async` on the three async methods. - **The class.** It has 350 members at both ends, in the same order, and the 345 unmoved members are byte-identical, trivia included. - **Module-level declarations.** The three that moved are byte-identical in the module from their own docblock on, and gone from `sql-driver.ts`. The three shared ones differ from base by a leading `export` and nothing else. `refuseUnbackedConflictTarget`'s leading trivia is now exactly the 80 lines of earlier comments that stayed from `indexCoversConflictTarget`'s trivia (checked byte for byte). At `sql-driver.ts`'s top level, 194 of 201 base statements are byte-identical. The rest are the three moved declarations, the three exports 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 `introspectKeyIndexes(tableName, { fresh: true })` to `{ fresh: false }` in the module (1 hit, checked on disk). The proof then reports `assertConflictTargetHonoured` with its body tokens 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 `abb904605` against head `84d0e45bd`. Every part's exit code was captured on its own (build, probe, three suites: all 0 at both ends). | Suite | Base | Head | Lost / changed / added | |:--|:--|:--|:--| | `@objectstack/driver-sql` | 4060 (3846 passed, 214 skipped; 248 files) | same | 0 / 0 / 0 | | `@objectstack/driver-turso`, against that end's built driver-sql dist | 2526 (2492 passed, 34 skipped; 95 files) | same | 0 / 0 / 0 | | `@objectstack/driver-sqlite-wasm` | 746 (746 passed; 39 files) | same | 0 / 0 / 0 | - **Type check and lint.** `tsc --noEmit -p .` in driver-sql exits 0. `pnpm exec eslint --no-inline-config packages/drivers/driver-sql` exits 0. - **Late-binding probe.** A scratch script, not committed. It runs node against each end's built dist on in-memory SQLite and prints 50 readings: 1. `upsertTenantGuard` on six rows (no options, empty `tenantId`, the tenant row, another organization's row, a row without the value, a non-tenant object) and `upsertConflictRefusal` on one, two, empty and absent keys, with instance spies on all five host methods showing the calls in order; 2. `introspectKeyIndexes` cold, cached, `fresh`, on a keyless table and a missing one, with `introspectIndexes` replaced on the instance by a throwing read (`null`), and with the `physicalKeyIndexes` map replaced on the instance after construction (the new map is the one read); 3. `assertConflictTargetHonoured` on every verdict: ambiguous named and unnamed, identity at risk (`id` with rivals) on two tables, honoured, unbacked named and unnamed, a composite unbacked target, a missing table, and a stale cache re-read before a refusal; 4. `assertConflictTargetBackedWithoutClause` backed, unbacked, on `id`, and on a missing table; 5. a SUBCLASS overriding `rotationWriteTarget`, `resolveTenantField`, `remoteColumn` and `introspectIndexes`, each reached, and `physicalTableByObject` shadowed on the instance after construction (the refusal's `cause` names the shadowed table); 6. nine `upsert()`/`find()` calls end to end on SQLite: a seed and a merge, two unbacked refusals through the reactive catch (one tenant-scoped), and tenant-scoped composite upserts in two organizations, read back; 7. bad arguments on four of the methods. - **Probe result.** 49 of the 50 readings are identical, refusal `code`, `status`, message and `cause` included. The other one is the bad-argument row of `upsertTenantGuard`: it throws the same `TypeError` with the same message, and only the top stack frame's receiver name differs, `_SqlDriver.upsertTenantGuard` at base and `Object.upsertTenantGuard` at head. That is the host-literal receiver of shape B, the same at every earlier cut, and no test or consumer reads it. - **Not run.** No ablation, because no tested behaviour changed. - **`test:repo`.** The `repo` projects of types (11), core (83), objectql (5), spec (1279) and rest (280 passed, 18 skipped) pass locally after building rest's closure. Runtime's is NOT MEASURED locally; CI's Test Core runs all of them. - **After the merge of `origin/main`** (`8adeb4ba47`, 11 commits, no `packages/drivers/**` file): `tsc` exits 0, the two upsert conflict-target test files pass (22 passed, 3 skipped), and `check:adr-anchors` still reads 1877. ## Gates - **Derived list.** `node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack` derives 53 commands for the 2 changed paths (merge base `abb904605`). On `84d0e45bd`, 52 exit 0. `check:dual-build-cjs-loads` exits 3, `PREREQUISITE NOT MET` (no full build here): **NOT MEASURED** locally, CI runs it. The teardown self-test's pinned commit was fetched first, and objectql was built for `check:lean-entry-closure`. After the merge of `origin/main` the list gains one family, `node scripts/ci/plan-test-core-shards.mjs --self-test`, which exits 0 on `8adeb4ba47`. - **Reconciliation.** `dispatch-gates --ran`: 54 derived families accounted for, 53 run, 1 NOT-MEASURED (derived from the recorded exit 3), exit 0. - **Card-named gates, read at base (a clean detached worktree at `abb904605`) and at head, all exit 0:** | Gate | Base | Head | |:--|:--|:--| | `check:adr-anchors` | 91 files, 1877 decision-letter citations | 91 files, 1877 | | `check:adr-symbol-anchors` | 2664 resolve | 2664 resolve | | `check:tenant-chokepoint` | 24 bindings / 3 files, D8 clean; 4 tenant predicates | same; 4 tenant predicates | | `check:durability-log-level` | 48 seams, all loud (7 propagating) | 48 seams, all loud (7 propagating) | | `check:driver-conformance` | 55 covered / 0 DEBT | same | | `check:keyed-text-bounds` | 112 files / 238 index entries | same | | `check:object-def-param-keys` | 3321 files / 544 classes / 7 positions | 3322 files (the new module) / 544 / 7 | | `check:system-context-census` | 153 sites / 132 symbols | same | | `check-tenant-audit-census` | 244 sites | same | | `check:logger-receiver-detach` | 0 detaches, 3762 files | 0 detaches, 3763 files (the new module) | - `check:nul-bytes` passes. The pre-push `check:commit-card-trailers` passed on push. ## Acceptance notes - **`check:tenant-chokepoint`'s D8 rule does not scan the new module.** The moved bodies hold 0 of the file's 4 tenant predicates (measured with the gate's own `nullArmsOf`), and none builds a query. Noted, not filed: widening `SCAN_FILES` would be a gate change. - **A docblock returns to its function.** The `[#8445]` docblock of `refuseUnbackedConflictTarget` had been separated from it by the two helpers #26015 inserted; with them moved it again sits on its function. Its bytes are unchanged, and the published `.d.ts` does not carry it (the function is not exported from `index.ts`). - **Next slice, priced on this base.** The backend statement fault classifier (`backendStatementFault` with `backendStatementFaultError`) pays about -216; it reads `logger`, so it needs a `check:logger-receiver-detach` and `check:durability-log-level` screen first. ## Human Minimum - [x] 1. No `packages/spec` change. - [x] 2. The one new name, `UpsertConflictTargetHost`, follows the interface casing. - [x] 3. No formatter ran; scoped `eslint --no-inline-config` passes (repo-wide `pnpm lint` is CI's run). - [x] 4. One issue, one branch off an up-to-date `main`, one pull request. - [x] 5. No publishable behaviour change: `skip-changeset`; both declaration lines above. - [x] 6. `packages/spec` not touched. - [x] 7. No hand edits to generated docs, changelogs or release notes. - [x] 8. Title, body and comments are in English (the maintainer brief excepted). --- _Generated by [Claude Code](https://claude.ai/code/session_01Cyh2bm4otUm5Vnb5Zw2KDT)_ 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_01Cyh2bm4otUm5Vnb5Zw2KDT Co-authored-by: Claude <noreply@anthropic.com>
1 parent 9a0108c commit d1a2e71

2 files changed

Lines changed: 398 additions & 267 deletions

File tree

0 commit comments

Comments
 (0)