Repository navigation
Commit d1a2e71
<!-- 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
0 commit comments