You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
⚠️PM state line, domain:engine seat. The former Blocked-by: #18116 is DISCHARGED — #18116 closed completed and its measurement is comment 5711775596 below. Per #18116's own next-step clause, this card returns to the decision box with the numbers. ⛔ The path citations below were measured WRONG by that tranche and are corrected in-line.
IDataDriver.beginTransaction(): Promise<unknown>(packages/spec/src/contracts/data-driver.ts:322 — ⚠️ the path was corrected; packages/spec/src/data/data-driver.ts does not exist)
原卡 #17878(已隐藏)· 源头 #17690 · 落地轮 #17876 · packages/drivers/driver-sql/src/sql-driver.ts:9010 (⚠️ was cited as :8752, rotted by +258) · packages/spec/src/contracts/data-driver.ts:322 (⚠️ was cited under src/data/, which resolves to nothing)
Triage: needs-user-decision; domain:engine; Task; priority:p2. Grade matches the original card's (p2 / domain:engine / needs-user-decision, as the rebuild records) — re-derived, ⛔ not inherited on trust. 四棱块与速读在此补齐.
Routing: packages/drivers/driver-sql + driver-turso ⇒ domain:engine. ⚠️ The card also reads packages/spec/src/data/data-driver.ts:322, but that is the contract it is measured against, ⛔ not the fix's landing site — 域 = 修复落地的包.
⛔ Correctly in the decision box: both arms change a published declaration on a released driver surface, which is the 人工地板 mechanical test (扩大/改变公开面). ⛔ Triage does not pick.
四棱分析
① 实际业务需求 —— ⚠️零实测拉动,并且这一轴不是陪衬
⛔ Nobody is blocked. The card is explicit that the gate is not hidden today: it still publishes Promise<any>, but the site now carries a comment naming it an LSP violation, the TS2416 receipt, and both exits priced. ⛔ 未 re-mask、⛔ 未加 !、⛔ 未 cast ⇒ 「今天没有隐瞒,只有未决」.
⇒ ⭐ that matters for sequencing: this is a card the project can carry indefinitely without accruing silent debt, because the debt is written on the wall it sits behind. ⚠️ An honest 「decide later」 is therefore a real option here in a way it usually is not.
② 项目长远合理性 —— ⭐ 源头自相矛盾,所以「照既有规范办」这条路是关死的
⚠️⚠️ The decisive structural fact, and the reason no seat can rule this: #17690 contradicts itself on this exact cell. Its 「Not findings」 section verbatim protectsSqlDriver.beginTransaction publishes Promise<Knex.Transaction> … the honest direction, while the same document requires this override to take the contract type. ⛔ Both cannot hold.
⇒ 「按既有规范定」在这里不可用 —— 既有规范自己是分歧的来源。That is precisely the escalation criterion 「既有规范定不了」, and it is met by reading, ⛔ not by preference.
Long-term, A (widen the base to Promise<unknown>) reverses a narrowing the project deliberately made and pushes every driver-sql consumer back to an untyped handle; C (make the implementation answer the base type) keeps the narrowing and pays once in runtime shape. ⇒ 长远轴倾向 C — but see ④.
③ 防 AI 写错 —— ⭐ 两条出路在这一轴上分向相反,这是本卡最值得看的一点
A hands every consumer unknown at a transaction boundary ⇒ an agent author writing driver code gets no type to be wrong against, and the compiler stops helping at exactly the place transactions are easiest to misuse. ⚠️ 消费端站点 11 → 25(+14) is the measured size of that loss.
C keeps the narrow, honest declaration ⇒ the structure keeps refusing wrong uses.
⇒ 本轴明确指向 C。⭐ And it names the real cost of A in this project's own terms: A is 「消费端宽容」 bought with a base-class widening — the shape this project treats as the temptation, ⛔ not the fix.
④ 创业阶段不扩散 —— ⚠️本轴与 ②③ 相反,这是真冲突,⛔ 不调和
C is a runtime refactor of the remote transaction handle, and its cost is ⛔ NOT MEASURED. 创业阶段聚焦原则 says a project at this stage does not open an unmeasured runtime refactor to buy a declaration's correctness when the declaration is already annotated and honest on the wall.
⭐ 但卡面提出的第三条路是可执行的,并且本席明确背书它作为一个选项:先把 C 的价量出来。⚠️ That is a dispatchable measurement task — it needs no ruling, produces a number, and turns a two-unmeasured-costs choice into a comparable one. ⛔ 本席未派发它(它要动 driver-turso 的运行期形状去估价,该由 domain:engine 席判断范围),但若维护者选这条,它可以立即开卡。
Director seat (objectstack #12708, summon #23, session_01WCEaPsmKY4UyoivKkkaUHt), 2026-09-14T00:44Z. Triage's analysis (5655283127) is adopted as read. One reading added from origin/main @ 57343f76 (⛔ not from the card): Knex.Transaction appears outside sql-driver.ts in exactly two files, both in driver-turso (turso-driver.ts, its declared-types test); every .beginTransaction( caller outside packages/drivers (objectql/src/engine.ts:14840/15704/15860) goes through IDataDriver, i.e. already sees Promise<unknown>. ⇒ the card's 「11 → 25 (+14)」 widening cost is internal to the three driver packages; application code loses nothing under A. That lowers A's price; it does not move axis ①.
Prior rulings read: beginTransaction|Knex\.Transaction|Liskov → AGENTS.md docs/adr 11 hits,全部为 ADR-0034 / ADR-0119 对 beginTransaction 作为必需成员的描述(ADR-0119 编者注:「Turso extends SqlDriver and inherits the same beginTransaction」),无一裁定本格声明该服从谁;#17690「Not findings」对本格自相矛盾(卡面已引)
推荐 C,首批为测量(M)(只看①选 C;②③④ 是否翻转:否 —— ④ 只把 C 的落地推到 M 之后)。回退项 A(若 M 量出 C 的半径不可接受,A 的真实代价按上面读数只在三个驱动包内部)。置信缺口:未复现 11→25 与 TS2416(卡面自陈实测且带对照);未量 C 的半径 —— 那正是 M 的产出。
Ruling: batch #133 item 5 · letter C · maintainer 「决裁批 #133 同意」 2026-09-14T03:33Z (to 1D·2a·3A·4C·5C)
Ruling recorded — C, first tranche M: SqlDriver.beginTransaction(): Promise<Knex.Transaction> keeps its honest narrowing; TursoDriver remote mode is to answer that type. Because C's cost is unmeasured, the first tranche is a measurement card that prices the two routes; C is dispatched on the numbers, and A (widen the base to Promise<unknown>) is the fallback only if the measurement comes back XL or infeasible (director seat, decision batch #133 item 5, 2026-09-14)
Derived first from the long-term axis: a base-class declaration must be honest for every subclass, and the honest fix is the subclass keeping the promise, not the whole SQL family dropping it — the layering IDataDriver (unknown) → SqlDriver (Knex.Transaction) is already the mainstream shape; the defect is one subclass inheriting a promise it does not keep. ②③④ do not overturn ①: ③ agrees (A hands every driver-sql consumer unknown at the transaction boundary), ④ only sequences — no unmeasured runtime refactor is opened before it is priced. One reading added by this seat on origin/main @ 57343f76: Knex.Transaction occurs outside sql-driver.ts only in driver-turso (2 files) and every .beginTransaction( caller outside packages/drivers goes through IDataDriver — so A's measured 「+14」 is internal to the driver packages; it lowers A's price and does not change the letter.
The #17690 remainder comment at turso-driver.ts:1685-1699 and the turso-driver-doors-declared-types.test.ts receipt stay as they are until C lands; ⛔ no re-mask, no !, no cast in the meantime.
State
needs-user-decision → pm:blocked; body line Blocked-by: #18116 prepended. domain:engine / priority:p2 / bug kept. When #18116 closes, the unlock scan re-derives per item 2 above (⛔ not a reflex return to pm:queue: the report's size class decides queue vs box).
Measurement report — tranche M of ruling C (decision batch #133 item 5), from #18116
Filed by the domain:engine dev seat on #18116, branch claude/issue-18116-turso-begintransaction-radius, session session_01CqmCgU5RGDoJYhHUMVp2af. Measurement only — no code changed, nothing under packages/ touched, no PR. The card's four fences held.
Notation. This report writes generic types bracket-free: Promise[Knex.Transaction] means the declaration the base class publishes. That is a deliberate spelling choice against body sanitization, not a different type.
0. Reading pin, controls, and what the reader cannot see
Sha. The whole enumeration in (1) was taken at origin/main = d93400f42ea69215b3de22bfe3ca057385ff0eea (one tree, one pass).
origin/main advanced to d0b8ec2aafb47bb7eff4fc847f7d67efb93e6d7d while this card ran. The targeted diff d93400f4..d0b8ec2a -- packages/drivers/driver-sql/src/sql-driver.ts packages/drivers/driver-turso/src/ packages/spec/src/data/driver.zod.ts packages/objectql/src/engine.ts is empty — the only change between the two shas is the addition of scripts/ablation-replace.mjs (+779). So no re-take was needed and the count is not assembled from two trees. #18172 has no PR as of this writing and has not landed.
The reader. A TypeScript compiler-API instrument (not a grep): it builds each driver package's real tsc program from its own tsconfig.json and reports every member access whose receiver is typed as knex's Transaction.
Lit control — commit, a member sql-driver.ts is known to call on the handle: 1 hit, sql-driver.ts:9059, in every run. Control fired.
Dark control — a fabricated member zzzNotARealTransactionMember: 0 hits in the authoritative run. To prove that 0 is a reading and not a blind reader, the same member was written into a control-probe file (scratchpad only, never in the repo) on a genuinely Knex.Transaction-typed receiver and the reader re-run: 1 hit at PROBE:13. The reader therefore distinguishes "not called" from "cannot see". A real-but-uncalled member, isCompleted, behaves the same way: 0 in the repo, 1 in the probe.
What this reader would miss, measured rather than asserted — one blind spot, and it is load-bearing here:
A receiver widened to any is invisible. The probe writes zzzInvisibleThroughAny on a receiver cast to any: 0 hits in both runs although the call is written. This matters directly, because TursoDriver.beginTransaction() publishes Promise[any], so every member call on that door is invisible to this reader by construction. driver-turso accordingly contributes 0 typed hits; its actual use of the handle (transaction.commit() / transaction.rollback() inside RemoteTransport) was read by hand instead and is reported below.
A union reduced to its supertype is invisible to a naive flow-type test. The first version of the instrument keyed on the receiver's flow type and silently missed runner.transaction(...) at sql-driver.ts:7028 and :8607, because TypeScript reduces the declared Knex | Knex.Transaction to Knex at the use site. The gap was caught by cross-checking against a grep reader, and the instrument was repaired to consult the declared type of the receiver's symbol as well. The numbers below are from the repaired reader. The grep cross-check now agrees with it on code (schema 13, raw 2) and over-reports transaction by exactly 2 — both of those are prose mentions inside doc comments (sql-driver.ts:7704, :8562). A grep alone would also have missed commit/rollback entirely, since their receiver is written (transaction as Knex.Transaction) and not a bare identifier.
1. The knex surface SqlDriver actually depends on
5 distinct members over 20 property-access sites, plus 11 sites that invoke the handle as a function, plus 2 sites that hand it to knex's own binder.
⭐ The count that actually prices the routes is the sub-count reachable by an EXTERNALLY supplied handle — i.e. by the value beginTransaction() returned and that comes back as options.transaction. The rest are knex transactions SqlDriver opens for itself via this.knex.transaction() and would stay knex under every route.
External-handle-reachable — 19 sites. This is the surface any remote answer must satisfy:
commit() ×1 (9059) and rollback() ×1 (9068) — SqlDriver.commit / rollback cast the caller's handle and call these.
transaction(cb) ×3 — 7028 and 8607 via the (caller's transaction) ?? this.knex runner idiom; 6935 via attemptWithoutPoisoning(parentTrx, …) reached from 7593 with the caller's handle. Semantically this is savepoint-on-an-existing-transaction.
.schema ×9 (6663, 6671, 6711, 6739, 6743, 6747, 6762, 6763, 6769) — the sequences-table bootstrap, reached with the caller's handle through getNextSequenceValue → ensureSequencesTable(parentTrx) on the SQLite arm.
callable trx(table) ×3 (6746, 6761, 7196) — the handle used as a knex query-builder factory.
.transacting(handle) ×2 (8953, 13528) — the handle passed into knex itself. 13528 is getBuilder, through which every SqlDriver query is built.
Internal-only — 14 sites, not part of the remote surface:raw ×2 (11447 withPinnedSession, 12687), .schema ×4 (12657, 12658, 12688, 12689 — the SQLite table-rebuild), callable ×8 (6853, 7039, 7045, 7060, 7066, 7071, 7278, 7280).
Outside the driver packages: zero dereferences.Knex.Transaction occurs in exactly three files repo-wide (sql-driver.ts, turso-driver.ts, turso-driver-doors-declared-types.test.ts) — the director seat's 57343f76 reading reproduces at this sha. All three beginTransaction() callers in objectql/src/engine.ts (14853, 15717, 15873) treat the handle opaquely: they put it in a context / the ambient txStore and hand it back to driver.commit / driver.rollback. No member is called on it.
⚠️And the reading that reframes the whole pricing: in Turso REMOTE mode, that 19-site surface is reached ZERO times today.
TursoDriver.connect() remote arm, by its own comment, "Skips Knex initialization (not needed)" — in remote mode there is no this.knex at all.
All 26 this.isRemote guards in turso-driver.ts short-circuit to RemoteTransportbefore any SqlDriver body runs, and every one of them drops options, e.g. find at 970 and create at 1106 forward only (object, query) / (object, data).
No RemoteTransport method accepts a transaction handle. All 21 data methods are declared (object, …) with no options parameter at all (remote-transport.ts 1317–2049).
So the only members ever invoked on a remote handle today are libsql's own commit() and rollback(), called inside RemoteTransport.commit / rollback (1845, 1849). A remote transaction currently carries no statements.
2. Route 1 — knex over a libsql-remote dialect
Does such a dialect exist? Not in tree and not in the dependency graph. knex 3.3.0 ships 12 dialects (better-sqlite3, cockroachdb, mariadb, mssql, mysql, mysql2, oracle, oracledb, pgnative, postgres, redshift, sqlite3) — no libsql. There is no custom knex client registered anywhere in tree; SqlDriver constructs knex once, sql-driver.ts:5555, from the caller's Knex.Config.
1(a) — the published dialect. ⛔ NAMED, NOT ADDED (fence 2).
@libsql/knex-libsql@0.1.0 exists on the registry ("Knex dialect for libSQL", MIT, libsql/knex-libsql). Admitting it is four new runtime dependencies, not one:
New dep
Version
Note
@libsql/knex-libsql
0.1.0
last published 2023-06-15; only ever three releases (0.1.0-pre, 0.1.0-pre.1, 0.1.0)
@libsql/sqlite3
^0.3.1
last published 2023-06-12
@libsql/hrana-client
^0.4.1
⚠️ the tree already carries hrana-client 0.10.0 — this pins a second, six-minor-older protocol client
sqlite3
^5.1.5
⚠️ node-sqlite3, a native build, alongside this repo's existing better-sqlite3@^13 peer
⚠️ It also does not answer the card's question as asked: it drives @libsql/sqlite3 → @libsql/hrana-client, i.e. a second remote client stack, not the @libsql/client HTTP transport RemoteTransport already holds. Two independently-authenticated remote clients would be live in one driver.
1(b) — an in-tree dialect over the @libsql/client the driver already has. Sizing anchors, all measured from the installed knex 3.3.0: the sqlite3 dialect is 263 LOC, better-sqlite3 is 101 LOC, knex's shared Client base is 659 LOC and its transaction machinery is 418 LOC. An in-tree client would need acquireRawConnection / destroyRawConnection / validateConnection / _query / processResponse, plus a knex Transaction subclass, because knex's default transaction issues BEGIN / COMMIT / ROLLBACK as raw statements on a held connection and libsql's HTTP face has no such connection — the lifecycle has to be mapped onto client.transaction()'s stateful handle instead.
Estimate, route 1(b): ~300–500 LOC of new dialect + Transaction subclass (1 new file), ~20–40 LOC in TursoDriver.connect() to build knex on the remote arm it currently skips, plus a test suite for the dialect. turso-driver.tsbeginTransaction / commit / rollback overrides could then be DELETED — super would be correct — which is the only route that removes the LSP violation instead of re-dressing it.
⚠️ But note what 1(b) does not buy: the 26 remote guards still bypass knex, so the handle would be a genuine Knex.Transaction that still collects no statements. Making it collect them means routing remote reads/writes through the new dialect, which means retiring or bypassing RemoteTransport's 21 data methods / 4052 LOC.
3. Route 2 — an adapter typed at the door
The structural target, measured from knex 3.3.0's own types:Knex.Transaction has 175 properties and 2 call signatures — 7 from Transaction itself (commit, executionPromise, isCompleted, parentTransaction, query, rollback, savepoint), 18 inherited from Knex (raw, schema, transaction, client, destroy, fn, ref, batchInsert, migrate, seed, queryBuilder, transactionProvider, isTransaction, initialize, userParams, withUserParams, VERSION, __knex__), and 150 from QueryInterface (select, where…, insert, update, del, join…, aggregates, …).
What the source material can honestly supply. libsql's Transaction (@libsql/core api.d.ts) has 7 members: execute, batch, executeMultiple, commit, rollback, close, closed.
Knex.Transaction member
Honest?
From
commit()
✅
libsql commit()
rollback()
✅
libsql rollback()
isCompleted()
✅
libsql closed
executionPromise
◻ borderline
synthesizable, but its knex semantics (resolves with the transactor's result) have no libsql counterpart
raw(sql, bindings)
❌
libsql can run the SQL, but knex's raw must return a knex Raw — itself thenable and carrying toSQL, wrap, transacting, …
callable trx(table) (2 call signatures)
❌
must return a full Knex.QueryBuilder; implementing it honestly is writing a dialect, i.e. route 1 under another name
schema
❌
must return a Knex.SchemaBuilder
transaction(cb) / savepoint
❌
libsql's Transaction exposes no savepoint API
remaining ~168 (QueryInterface + the rest of Knex)
❌
no counterpart
Cost line: at most 3 (charitably 4) of 175 members can be honestly implemented; ≥171 would have to throw NOT_IMPLEMENTED, and both call signatures would throw. Per the card, throwing members are cost, not solution.
Two shapes, and they price very differently:
2(i) structurally typed, no cast — the object must actually satisfy 175 properties + 2 generic call signatures under strict, many of them heavily overloaded generics (DeferredKeySelection, TableNames…). This is ~400–800 LOC of stubs that are, by (1)'s reachability measurement, never called in remote mode. XL, and almost all of it is dead weight.
2(ii) small object plus as unknown as Knex.Transaction at the door — ~30–60 LOC. But this is precisely the shape ruling C's item 3 forbade (⛔ no re-mask, no !, no cast): the declaration would be satisfied on paper while the same libsql object is handed back. It is the current any with more ceremony, and it does not honour the base class. Not a route; listed so the box can see it was priced and rejected.
4. Override fan-out and the existing tests
Bookkeeping.SqlDriver tracks open transactions in two members (sql-driver.ts:4983-4985): activeTransactions (a counter) and openTransactions (a WeakSet). beginTransaction registers (9012–9013); releaseTransaction (9018) de-registers, and is called only from commit (9061) and rollback (9070). The single reader is assertBareKnexSafe (9046), the SQLite single-connection dead-lock guard.
⚠️The remote arm registers nothing today.TursoDriver.beginTransaction returns remoteTransport.beginTransaction() directly, and TursoDriver.commit / rollback route to remoteTransport without ever calling releaseTransaction. That is self-consistent (nothing registered, nothing released) and it is also why the counter is honest today. Both routes must make a decision here, and it is a real cost line either way:
Route 1(b) deletes the overrides ⇒ registration happens in super ⇒ activeTransactions becomes non-zero in remote mode ⇒ assertBareKnexSafe starts firing on a path where it is meaningless (no SQLite, no single connection). The guard is isSqlite-gated (9045), so this is checkable rather than speculative, but it must be checked.
Route 2 keeps the overrides and must add releaseTransaction calls by hand, or knowingly leave the remote handle unregistered.
turso-driver.test.ts:212 / :221 — both cited lines are still exact at this sha. Both build the driver as new TursoDriver({ url: ':memory:' }), i.e. LOCAL mode, so they exercise super.beginTransaction() and never the remote branch. ⇒ They keep passing unchanged under both routes.⚠️ The corollary is a coverage reading: the remote branch of beginTransaction has no runtime test at all — the only remote-side pin is type-level.
turso-driver-doors-declared-types.test.ts — it pins RemoteTransport['beginTransaction'] as the contract type (line 207) and deliberately leaves TursoDriver.beginTransaction()unasserted (its comment block says so in as many words, ~line 91). ⇒ Neither route breaks a pin. Route 1(b), which deletes the override, additionally makes that ~35-line comment block stale, and the pin-count case at line 336 must be re-read in the same PR.
5. Size class per route, and the recommendation
The lane has no written S/M/L/XL rubric beyond the dispatch-backend split (S+M ⇒ one subagent round; L/XL ⇒ reserved surface). Sizes below use exactly that: S/M = one dev round; L = more than one round but bounded; XL = a rewrite of a subsystem.
Route
Size
Basis
1(a) published dialect
⛔ human floor, not sized
4 new runtime deps, newest published 2023-06-15, a duplicate hrana-client major and a second native SQLite build; and it drives a different client stack than the one in place
1(b) in-tree libsql-remote knex dialect
L
~300–500 LOC new dialect + knex Transaction subclass, ~20–40 LOC boot wiring, one new test suite; deletes 3 overrides and the LSP violation with them
1(b+) 1(b) and remote statements actually joining the transaction
XL
requires retiring or bypassing RemoteTransport's 21 data methods / 4052 LOC and the 26 isRemote guards
2(i) structurally typed adapter
XL
175 properties + 2 call signatures to satisfy; ≥171 throw; by measurement none is ever called in remote mode
2(ii) adapter + cast at the door
S
⛔ excluded by ruling item 3 — satisfies the declaration without honouring it
Recommendation: neither route as the card scopes them — return to the box once with these numbers.
The honest reason is not the size of either route. It is that the cell the ruling set out to fix is smaller than the defect sitting next to it. Route 1(b) at L would make TursoDriver remote genuinely answer Promise[Knex.Transaction], and it is the only priced route that removes the LSP violation rather than re-dressing it — but the measurement in (1) says the handle it returns would still collect no statements, because no remote path consults options.transaction. Spending L to publish a correct type over a transaction that is not one is the shape axis ④ exists to refuse. The version that is worth having is 1(b+), and that is XL.
⭐ Per this card's own next-step clause, XL ⇒ #18063 returns to the decision box once, with the numbers — which is what this comment is. Two inputs the box did not have when it ruled, offered as data and not as a re-litigation of letter C:
A third option is now visible and is cheaper than both: make remote mode stop claiming a transaction it cannot deliver (refuse, or degrade loudly through the existing warnTransactionUnsupported path), instead of manufacturing a handle to satisfy a type. That touches the capability/contract layer, which this card is fenced out of (⛔ packages/spec untouched), so it is named here rather than priced.
A is cheaper than the card recorded, and C is dearer. A's measured +14 is confirmed internal to the driver packages at this sha (zero dereferences outside them). Meanwhile C's real price is L for the type alone and XL for the behaviour — the gap the ruling correctly declined to guess at.
Card-reference corrections (line rot, as this lane's charter predicts)
Every cited site was located by symbol, never by line. Of the seven citations across #18116 and #18063, two line numbers have rotted and one path is wrong; the other four are still exact:
⚠ wrong directory, right line. The file is packages/spec/src/contracts/data-driver.ts, and line 322 is exact — beginTransaction(options?: { isolationLevel?: string }): Promise[unknown]. The path as written resolves to nothing. (The Zod half of the same contract is packages/spec/src/data/driver.zod.ts:638, output(z.promise(z.unknown())).) The contract itself is unchanged and not in question.
⚠️ One rotted line number is recorded inside a committed receipt: turso-driver-doors-declared-types.test.ts:77 quotes the TS2416 error as src/turso-driver.ts(1662,18), and the door is now at 1700. The receipt's substance is intact — the error text still reproduces the declarations verbatim — but the coordinate in it is three landings stale.
No sanitizer truncation found in #18116's body. All four generic spellings survive intact (Promise of Knex.Transaction, of any, and of unknown twice), backticks balance (106), and the body terminates on its own footer.
domain:engine 执行席,session_01CqmCgU5RGDoJYhHUMVp2af,R1,2026-09-17T09:10Z。本卡按 #18116 自己的下一步条款返回:「XL or infeasible ⇒ #18063 returns to the decision box once, with the numbers」。测量报告 = 本卡评论 5711775596;#18116 已 completed 关闭,验收记录 5711846621。⛔ 本席不代裁。
③ 防 AI 写错 —— 最尖的一棱,指向 B。 今天的失败形态是静默:拿到句柄、写入、回滚、数据还在。AI(和人)读到 beginTransaction() 存在就会当事务可用,而错误不会响。B 把它变成响亮的拒绝(引擎按声明拒绝),正是「契约收紧优于消费端宽容」。⛔ A 不消解这一棱 —— 类型正确的装饰性事务,静默程度和今天一样。
⇒ 四棱一致指向 B,①额外排除 A。C 是诚实的下限,但它留下的语义缺陷已另行立卡,所以 C 不等于什么都不做。
Governing text: 维护者决裁批 #133 item 5(路线 C + 先测量),以及 packages/spec/src/data/driver.zod.ts:636 的事务句柄契约原文。⚠️ B 要改的 driver.zod.ts:266「Transaction use is gated on METHOD PRESENCE」正是使「无事务」今天不可声明的那一行。
Ruling: batch #148 item 3 · letter B (lift the 「do not touch packages/spec」 fence: a transport can declare it has no transactions and the engine refuses transactional use loudly; supersedes batch #133's route C for this cell) · maintainer 「同意」 2026-09-17T14:26Z
Director seat, summon #24, session_01Wj1HUjzyeiBQ8atRf1ZhaL. Presented in detail with the recommendation B; the maintainer agreed. Facts (#18116's measurement, engine seat re-check): Turso remote mode has no transactions — remote-transport.ts carries only beginTransaction / commit / rollback and no data method receives options (control: 9 data methods), so a handle is issued, writes land, and rollback undoes nothing; batch #133's route C (honest base-class type for the remote handle) prices at L for a decorative transaction; knex-over-libsql remote dialects do not exist in-tree (the published one is 2023, four new deps); an adapter would throw on 171+ of 175 Knex.Transaction members; the cheap honest answer (S) is blocked only by driver.zod.ts:266 gating transaction use on method presence, which a class inheriting the method cannot opt out of.
Ruling — B
packages/spec: the driver contract gains a way for a transport to declare 「no transactions」 (the dev picks the smallest spelling the existing capability/contract surface already has — a capability bit is preferred over a new key), and the engine's transaction gating reads the declaration instead of method presence (driver.zod.ts:266 re-keyed).
driver-turso remote transport declares it; the engine refuses loudly any options.transaction / beginTransaction against such a transport — the 「rollback does not roll back」 semantic defect becomes unreachable, so that separately noted card closes with this one.
Published contract change ⇒ Clause-②: yes, contract review at tier; changesets on @objectstack/spec, the driver-sql base and driver-turso (minor; the remote transport never honoured transactions, so no working behaviour is withdrawn).
⛔ A — L for an honest type over a false behaviour; ⛔ C — leaves the silent rollback lie.
Four-facet reading: ① transports declare what they have; ② zero consumers reach the remote transaction path today; ③ a silent lie becomes a loud refusal; ④ S.
Execution
needs-user-decision → pm:queue; lane domain:engine → domain:spec (the change touches packages/spec; standing routing rule), p2 stands. When the hidden original #17878 becomes readable again, this card closes in its favour with a pointer.
{
"issue": 18063,
"status": "done",
"branch": "claude/issue-18063-declare-no-transactions",
"pr": "#18890",
"session": "session_01LvwGppdonww4zGLWZo5rho",
"premise_still_valid": true,
"summary": "Patch round. The FAIL is fixed: engineCanRollBack (packages/core/src/utils/migration-journal.ts) now asks driverSupportsTransactions() instead of reading beginTransaction method presence, so the fourth gate answers what the three objectql gates answer. The !defaultDriver and typeof e.transaction !== 'function' arms are untouched, as instructed. FakeEngine in migration-journal.test.ts gained the shape the pin could not previously express (its driver published beginTransaction and carried no supports record at all), and two new cases pin the declaration: a direct predicate case with two lit controls (bit absent, bit explicitly false), and a runner case asserting MigrationJournalRefusal with code NOT_IMPLEMENTED and zero journal rows, against a lit control that runs to completion. The changeset's false sentence is corrected to name all four readers by name, @objectstack/core is added at minor, and a new @objectstack/core paragraph states what a caller now sees on a declaring datasource. The require:true belt-and-braces was MEASURED and NOT taken: a currently-degrading path would start throwing (details in tests). Nothing the review passed moved: this round's delta vs the reviewed head 0c6eeea is exactly three files, and all 17 other branch paths are byte-identical blobs (verified by git rev-parse per path, printed in the round log).",
"tests": "REPRODUCTION of the two-path defect, on the real chain (real ObjectQL, real engineCanRollBack resolved through core's dist, real ObjectStackProtocolImplementation.batchData, real runMigrationJournal; only the driver is a double, differing from its lit control by one bit; throwaway file, deleted before the push). BEFORE, at the reviewed head 0c6eeea: engineCanRollBack(declaring engine) = true. PATH A, batchData with options.atomic true over two records where the second fails: NO THROW, response success=false succeeded=0 failed=2 total=2 (a reported rollback), driver begins=0, and 1 record PERSISTED on disk. LIT CONTROL, same double with the bit removed: begins=1 and 0 rows persisted, because the control transport really rolls back (snapshot/restore). PATH B, runMigrationJournal: NOT refused, ran to status=completed with begins=0 and journal kinds run_started, chunk_started, chunk_done, run_done, i.e. the chunk_done marker its own header says would not mean committed. AFTER, at e3ad06e with core rebuilt (core/dist/index.js:5740 now reads 'return !defaultDriver || driverSupportsTransactions('): engineCanRollBack(declaring engine) = false; PATH A throws NOT_IMPLEMENTED status 501 carrying the retry-without-atomic prescription, begins=0, 0 rows persisted; PATH B refuses with MigrationJournalRefusal NOT_IMPLEMENTED and writes zero journal rows; both lit controls unchanged in both directions. ABLATION of the new pin (presence gate put back into migration-journal.ts; source-resolved, so no dist leg, but the on-disk proof was done anyway): HEAD blob df8d8d5009b4a94b0a9db080876420a8cb594495, mutated blob 1807d102b7448e5e1bc77f53931f112a3cb80b28, anchor counts fixed-gate 1 to 0 and presence-gate 0 to 1; run went RED, 2 failed | 21 passed (23), the two failures being exactly the two new [#18063] cases. STAR: under that same ablation the pre-existing pin 'engineCanRollBack is two-level' stayed GREEN, which independently confirms the seat's 'green and blind' reading. Restore proved byte-identical (git hash-object back to df8d8d50, git diff HEAD empty, git status clean); the script carried trap RESTORE on EXIT INT TERM with absolute paths resolved from git rev-parse --show-toplevel. REQUIRE:TRUE MEASUREMENT, see open_questions for the verdict: measured on a real ObjectQL with no default driver registered. engineCanRollBack = true (the !defaultDriver arm, which the fix deliberately leaves alone), engine.transaction() TODAY degrades with no throw, engine.transaction(cb, undefined, { require: true }) throws TransactionUnsupportedError, and that engine DOES reach runAtomicBatch today: batchData atomic on it returns success=false succeeded=0 failed=1 with no throw. So yes, a currently-degrading path would start throwing; require:true is NOT taken. Separately measured and worth recording: the class the review named, a driver with no beginTransaction at all, is NOT the affected class, because engineCanRollBack already answers false for it both before and after this fix (pinned at migration-journal.test.ts:180), so it never reaches those engine.transaction calls; it is refused one layer earlier with 501 / MigrationJournalRefusal. GREEN RUNS, all exit codes captured by redirect and read from the gate's own verdict line, heavy runs through scripts/pm/os-verify-lock.sh with OS_VERIFY_LOCK_SLOT=dev-18063 (no queue-timeout, every VERDICT line read command-exit 0): pnpm --filter @objectstack/core test 51 files / 1318 tests passed; pnpm --filter @objectstack/core typecheck OK including check:test-typecheck; pnpm --filter @objectstack/metadata-protocol test 179 passed / 3 skipped, 2578 tests passed (the consumer of the gate); pnpm --filter @objectstack/objectql test 299 files / 5003 tests passed; pnpm --filter '@objectstack/objectql^...' build exit 0 (dependency closure, so the after-leg reads a rebuilt dist). REPO-WIDE LINT, run as the union rather than a narrowing: pnpm exec eslint . --no-inline-config --format json over 6855 files, 0 errors 0 warnings, at git rev-parse --short HEAD = e3ad06e with a clean working tree. DERIVED GATES, from node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack, all exit 0: check-adr-0087-registration (--base origin/main and --self-test), check-changeset-no-major, check-empty-changeset, check-changeset-fixed, check-undeclared-dep-imports, check-comment-mask-adoption, check:nul-bytes, check:durability-log-level, check:engine-double-contract, check:type-check-coverage, check:cross-package-test-inputs, check:test-source-alias, check:changeset-gate-self-tests, check:published-files, check:dts-closure, check:type-source-resolution, check:tier-file-adoption, check:error-code-casing, check:merge-driver. NOT MEASURED: pnpm check:type-check-debt exited 3 with 'PREREQUISITE NOT MET' (18 workspace dependencies have no built type entry point on disk; its own text says this is neither a pass nor a finding and that the whole packages closure must be built first) - not re-run, because building every package is CI's run, and this round's delta adds no typecheck surface beyond core, whose own typecheck is green above. ALSO NOT MEASURED, declared: origin/main was NOT merged this round, so dispatch-gates printed its STALE TREE warning (at least 24 commits behind origin/main, 7 of the files it derives families from changed in that range); the family list above is therefore derived from this tree, not from current main, and the current farm is CI's to run. The remaining 96 of the 115 derived commands, the 47 artifact-roster families, the 11 declared-wide families and the 6 path-scheduled CI jobs are likewise CI's - this round's delta is three files and the previous head's full set already passed.",
"mcp_calls": "0 - no MCP GitHub tool was called, read or write, in this round.",
"api_writes": "1 - POST /repos//issues/18063/comments (this report). Reads only otherwise: GET /repos/objectstack-ai/objectstack/pulls/18890. No labels written (fenced), no PR-body PATCH (fenced), no POST /issues. Git pushes are not REST writes: 1 push to claude/issue-18063-declare-no-transactions, exit 0, pre-push check:commit-card-trailers green.",
"open_questions": [
{
"question": "The belt-and-braces require:true on runAtomicBatch and the runner's two engine.transaction calls - take it or not? Measured, not assumed.",
"options": [
"A. Do NOT take it. Measured: a real ObjectQL with no default driver registered passes engineCanRollBack through its !defaultDriver arm, degrades today with no throw, reaches runAtomicBatch today (batchData atomic returns succeeded=0 failed=1, no throw), and under require:true throws TransactionUnsupportedError instead. That is a behaviour change on a currently-degrading path, beyond this card's ruling.",
"B. Take it anyway and accept the change on that arm.",
"C. Take it and additionally tighten engineCanRollBack's !defaultDriver arm - a separate decision, since that arm is what keeps narrow host surfaces (MetadataHostEngine, the doubles pinned at migration-journal.test.ts:184) answering true at all."
],
"recommendation": "A, per the dispatch's own rule: a currently-degrading path would start throwing, so it is not taken here and the seat files it if it wants it. Worth recording for that card: the class the review named as the example - a driver with no beginTransaction at all - is NOT affected, because engineCanRollBack already answers false for it in both trees and it is refused one layer earlier."
},
{
"question": "Two pieces of prose now under-count the gates, and both are fenced away from me (no PR-body edit, no touching what the review passed). Which does the seat want to write?",
"options": [
"A. PR body, 'What landed' item 2, currently 'all three transaction gates read that predicate' - now four. Suggested replacement sentence: 'packages/objectql and packages/core - all four transaction gates read that predicate instead of typeof driver.beginTransaction: ObjectQL.transaction(), ScopedContext.transaction, the ScopedContext begin/commit/rollback trio, and engineCanRollBack, the ADR-0119 D4 gate metadata-protocol's atomic batchData and runMigrationJournal share.' The PR title may want the same widening ('the engine gates on the declaration' reads as objectql only).",
"B. Leave the body and let the changeset carry it - the changeset is already corrected and names all four by name, and it is the text that ships to consumers."
],
"recommendation": "A for the body, because the body is what the reviewer reads and the three/four count is exactly what this round changed; B is already done regardless."
}
],
"out_of_scope_findings": [
"noted, not filed: the retired-key tombstone for DriverCapabilities.transactions (packages/spec/src/data/driver.zod.ts:312) still opens 'Transaction use is gated on METHOD PRESENCE - driver.beginTransaction'. After this PR that opening clause is the wrong half-sentence, although the paragraph this PR appended to the same tombstone carries the correct distinction and the refusal still prints the right prescription. Not filed: it is documentation prose, not a reproducible defect, not a contract violation, and it does not lead an author to write metadata the runtime rejects - and the published 17.0.0 CHANGELOG text an upgrading author greps is unchanged either way. Fenced away from me this round (the review passed the tombstones and the dispatch says do not touch them). Successor: the domain:spec seat, on this same PR, which is the one PR that will touch this file again.",
"noted, not filed: packages/runtime/src/sandbox/quickjs-runner.ts:621 reads apiTx?.beginTransaction, which looks like a fifth method-presence gate and is not - that is the ScopedContext trio's beginTransaction, not a driver's, and the trio already returns null for a declaring driver, which the runner degrades on. Recorded because the next person to grep beginTransaction will land on it; nothing to change. Successor: whoever greps that symbol next; no PR owes it anything."
]
}
{
"issue": 18063,
"status": "done",
"branch": "claude/issue-18063-declare-no-transactions",
"pr": "#18890",
"session": "session_01LvwGppdonww4zGLWZo5rho",
"premise_still_valid": true,
"summary": "Merge round. (1) origin/main merged into the branch through scripts/pm/os-regen-merge.sh (base origin/main 873e0e8, merge commit 3384406): CLEAN, zero conflicts, so neither packages/spec/dropped-refinements.baseline.json nor packages/spec/src/migrations/registry.ts needed hand resolution and neither was regenerated. GIT_DIR/os-regen-pending was never written, which is the positive evidence the os-regen driver was never invoked and therefore could not have silently dropped a side. Step 2 kept the branch's bytes of all six routed paths it owns because main moved none of them; step 3 had nothing extra to commit. (2) The tombstone prose fix the seat took in scope: packages/spec/src/data/driver.zod.ts, the transactions retired-key prescription, opening clause only, plus its generated projection in the three content/docs/references/data/driver*.mdx pages (regenerated by check:generated --fix, never hand-edited). savepoints, isolationLevels, the Discovery transactionalBatch sentence, the 'NOT this key restored' refusal text and the 31-tombstone roster are untouched. ONE INTERACTION WORTH THE SEAT'S EYE: packages/spec/src/data/driver.test.ts:124-127 pins this exact message with /DriverCapabilities\.transactions.*removed.*METHOD PRESENCE.beginTransaction.Delete the key/s. The dispatch fences the pins, so rather than edit it I wrote the new clause to keep the literal METHOD PRESENCE in a role that is TRUE of the runtime (one clause of the gate, no longer the whole test) and in an order the regex still reads. The pin ran by name and passed, unmodified.",
"tombstone_wording": {
"before": "Transaction use is gated on METHOD PRESENCE - driver.beginTransaction (engine.transaction(), ADR-0034 ambient transactions): a driver without the method gets the non-transactional fallback, whatever this bit claimed.",
"after": "Transaction use is gated on the DRIVER'S DECLARATION, no longer on METHOD PRESENCE alone: engine.transaction() asks driverSupportsTransactions(driver) - driver.beginTransaction present AND transactionsUnsupported not set (ADR-0034 ambient transactions, ADR-0119 D1). A driver without the method - or a transport that declares that live bit - gets the non-transactional fallback, whatever this bit claimed.",
"unchanged_tail": "Discovery's transactionalBatch capability is likewise derived ... never from this bit. The live transactionsUnsupported bit is NOT this key restored and is not its opposite spelled differently: ... A driver with real transactions declares nothing. [+ capRemoved's own prefix and 'Delete the key.']"
},
"merge_conflict_set": "EMPTY. git merge origin/main reported 'Merge made by the ort strategy', 95 files changed, no conflict lines and no CONFLICT paths in any of the three classes os-regen-merge.sh partitions (hand-written / routed-and-conflicted / NOT_DRIVER_MANAGED). Nothing was hand-resolved and nothing was resolved by regeneration. GitHub agrees the head is conflict-free: PR 18890 reads mergeable=true.",
"generated_artifact_drift": "NONE from the merge. pnpm --filter @objectstack/spec check:generated on the merged tree reported exactly one stale artifact - content/docs/references/ - and that one was caused by MY prose edit, not by the merge: check:docs regenerates the whole references tree and the resulting diff is 4 lines in 3 files, every one of them the transactions row (driver.mdx has two, driver-sql and driver-nosql one each). Every other reference page came back byte-identical. After --fix: 'All 15 generated artifacts are up to date' (exit 0), including check:migration-registry and check:authorable-surface with its .base.json anchor - so the two MIXED files the dispatch named are current without either having been regenerated by hand.",
"nothing_else_moved": "Proved by comparing the branch's delta against its base before and after the merge. git diff --numstat 0b31d90fb37 e3ad06ec35b (pre-merge) and git diff --numstat origin/main...HEAD (post-merge) name the SAME 20 files with IDENTICAL insertion/deletion counts on 19 of them; the single line that differs is packages/spec/src/data/driver.zod.ts, 97/3 becomes 103/6, which is exactly this round's 6-insertion 3-deletion prose hunk. The three mdx files keep their line counts because the regeneration replaced the same rows. Second leg: git diff --numstat 0b31d90fb37 origin/main over each of the four files I touched returns ZERO entries, so main never edited any of them in the merge window and every byte of them on my side is the branch's own.",
"tests": "All heavy runs through scripts/pm/os-verify-lock.sh with OS_VERIFY_LOCK_SLOT=issue-18063-merge; every verdict below is the lock's own 'VERDICT command-exit N' line, and every exit code was captured by redirecting both streams into a log file and reading the status variable on the next statement, never through a pipe. No queue-timeout and no exit 99 occurred. (1) pnpm --filter @objectstack/spec build - exit 0 (172s). (2) pnpm --filter @objectstack/spec check:generated - exit 1 first (1 stale: gen:docs), then --fix exit 0, then a clean re-run exit 0: 'All 15 generated artifacts are up to date'. (3) THE PIN, run by name: pnpm --filter @objectstack/spec exec vitest run --reporter=verbose src/data/driver.test.ts -t 'the transactions prescription' - exit 0, output line: 'the transactions prescription points at method presence, and says the live bit is not it restored 9ms', 1 passed | 57 skipped. Whole file: 58 passed (58). The pin file is UNMODIFIED this round. (4) pnpm --filter @objectstack/spec test - exit 0, 489 files / 14213 tests passed. (5) pnpm --filter @objectstack/spec typecheck - exit 0 (includes check:test-typecheck, 54 files / 259 errors / 144 pinned signatures held). (6) The four gates the review passed, re-run on the merged tree after building their 14-package dependency closure (pnpm --workspace-concurrency=2 --filter 'PKG^...' build, exit 0): objectql engine-transaction-declared-unsupported + protocol-batch-atomic 15 passed; core migration-journal 23 passed; driver-turso transactions-unsupported-declaration + doors-declared-types + turso-driver 112 passed. All exit 0. (7) 13 cheap gate families this round's paths implicate, all command-exit 0: check:merge-driver, check-spec-docblock-symbol-anchors, check:doc-anchors, check:docs-single-h1, check-doc-frontmatter, check-docs-section-name, check-adr-0087-registration --base origin/main, check-empty-changeset --base origin/main, check-changeset-no-major --base origin/main, check:role-word, check-keyed-text-bounds, check:docs-spec-enumerations, check:quick-reference-counts. (8) check:nul-bytes exit 0 (8895 files, no raw control bytes) plus a direct grep -naP control-byte scan of the edited file: no match. (9) pnpm lint (eslint . --no-inline-config) run WHOLE, not narrowed - exit 0. No narrowing to declare. DECLARED NARROWING, the one thing I did not run: node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack derives 114 commands from the 20-path change set; I ran the subset above rather than all 114, because this round's own delta is one 6-line prose hunk plus its generated projection plus a clean merge, and the other families were green on the pre-merge head the review passed. The remainder is CI's run: NOT MEASURED locally - dispatch-gates additionally reports 11 wide-population families, 47 artifact-roster families, 5 workflow-valued commands and 6 path-scheduled CI jobs as outside any local invocation. No ablation this round: nothing new was asserted.",
"mcp_calls": "0 - no MCP GitHub tool was called, read or write.",
"api_writes": "1 - POST /repos//issues/18063/comments (this report). No label write, no PR-body PATCH, no POST /pulls. Separately, 2 git pushes to refs/heads/claude/issue-18063-declare-no-transactions (the merge commit, then the prose commit).",
"open_questions": [],
"out_of_scope_findings": [
"noted, not filed: packages/spec/src/data/driver.test.ts:124 - the pin's TITLE still reads 'the transactions prescription points at method presence', which after this PR is narrow rather than wrong (the prescription points at the declaration, of which method presence is one clause). A test title, not a defect, a contract breach or an authoring trap, so none of the three filing classes. Successor: the domain:spec seat reviewing this PR - it is already inside this file's blast radius, having taken the tombstone prose itself for exactly that reason, and the fence on the pins is why I left the title alone."
]
}
os-dev-report
{
"issue": 18063,
"status": "done",
"branch": "claude/issue-18063-declare-no-transactions",
"pr": "#18890",
"session": "session_01LvwGppdonww4zGLWZo5rho",
"premise_still_valid": true,
"summary": "Base-catch-up round only: no scope change, no new PR, no PR-body edit, draft flag and labels untouched. New head 4d11817, pushed fast-forward over ddaffcc (no rebase, no amend, no force). BASE read BEFORE step 1 and kept: 873e0e8. os-regen-merge.sh merged origin/main at 2767af8 (which contains #18971d8b12fc) with zero conflicts; merge commit 6ec2519. Step 2 took main's side of NOTHING and printed the KEEPING notice for all six routed paths the branch owns (content/docs/references/data/driver.mdx, driver-sql.mdx, driver-nosql.mdx plus packages/spec/api-surface/data.json, authorable-surface/data.json, export-origins/data.json) because main changed none of them; step 3 had no extra changes to commit and no os-regen-pending marker was left behind. Routing read from the MERGED tree, not from memory: grep for api-surface in .gitattributes returns exactly two rows, 146 packages/spec/api-surface/** merge=os-regen and 147 packages/spec/api-surface-declarations/** merge=os-regen; the api-surface-signatures.json row is gone. That matches what the sibling PR saw. packages/spec/src/migrations/registry.ts and packages/spec/dropped-refinements.baseline.json never conflicted, so no hand resolution was needed or done. Remedy run as dispatched: build, then gen:api-surface-declarations, then commit (4d11817). Two shards moved and BOTH moves are additive: data.txt gains the optional transactionsUnsupported boolean on the five schemas that embed the driver capabilities object plus the driverSupportsTransactions declaration (entry count 832 to 833 exported names, 845 to 846 declarations), and contracts.txt gains the beginTransaction TSDoc paragraph on IDataDriver. No shard was hand-edited. Branch delta vs the merged main is 22 files: the 20 the contract review saw at ddaffcc plus the 2 regenerated shards. PR read back after the push: head 4d11817, draft true, labels documentation / size-l / tests / tooling / needs:contract-review / protocol:data, all unchanged.",
"generated_artifact_count": "16 — Checking 16 generated artifacts and, after the regeneration, All 16 generated artifacts are up to date. The review record's All 15 is stale, as the dispatch warned.",
"tests": "Every exit code below was captured into a variable BEFORE any pipe (cmd redirected to a file, then EXIT=$?), never read through a pipe. | PRE-REGEN check:generated exit 1 — 1 of 16 stale, api-surface-declarations/, remedy pnpm --filter @objectstack/spec gen:api-surface-declarations. Every other gate green including check:api-surface. | gen:api-surface-declarations exit 0 — Wrote api-surface-declarations/ (17 entry points, 5337 declarations, 12.08 MiB). touched: contracts.txt, data.txt. | POST-REGEN check:generated exit 0 — All 16 generated artifacts are up to date. Converged in one pass. | check:api-surface-declarations (standalone, post) exit 0 — @objectstack/spec declaration text unchanged (17 entry points, 5337 declarations). | check:api-surface (standalone) exit 0 BOTH before and after the regeneration — @objectstack/spec public API surface unchanged. It never reported REMOVED or RESHAPED, so the dispatch's STOP clause did not fire on the gate it names. See open_questions for the gate-name reading the seat should take. | pnpm --filter @objectstack/spec build exit 0 (under os-verify-lock, VERDICT command-exit 0). Dependency-closure build for core / objectql / driver-turso / driver-sql exit 0 (VERDICT command-exit 0). | pnpm --filter @objectstack/spec test exit 0 — 489 test files, 14213 tests passed. | pnpm --filter @objectstack/spec typecheck exit 0 (tsc --noEmit, check:scripts-typecheck, check:test-typecheck). | core migration-journal exit 0 — 1 file, 23 tests passed. | objectql engine-transaction-declared-unsupported + protocol-batch-atomic exit 0 — 2 files, 15 tests passed. | driver-turso transactions-unsupported-declaration + doors-declared-types + turso-driver exit 0 — 3 files, 112 tests passed. | git push exit 0; pre-push printed check:commit-card-trailers: 2 commit message(s) on this push carry no card relation and no model identifier in the trailer pair. | ONE-SHOT PROBE (mutation, restored under trap, not left in the tree): to test whether the aggregate's shard list is a complete staleness inventory, contracts.txt alone was reverted to its merged-from-main bytes (on-disk hash moved 5362717e751 from cccfda3cc7f, and the added-paragraph grep counted 0, so the mutation is proven landed on disk), and the standalone gate exited 1 naming contracts.txt ~ IDataDriver (interface) (declaration text changed). Restore was git checkout HEAD -- path against an absolute REPO_ROOT, proven by hash equality back to cccfda3cc7f and by an empty git diff HEAD for that path. Final git status --porcelain is empty. | NOT MEASURED: repo-wide CI. dispatch-gates.mjs --commands --repo objectstack-ai/objectstack derives 115 commands over this 22-path change set (three-dot against merge base 2767af8, so no sibling PR is attributed to this branch), and that is the farm CI owns. DECLARED NARROWING: this round ran the dispatch-named set plus the whole check:generated aggregate, not those 115.",
"mcp_calls": "0 — no MCP GitHub tool was called, read or write.",
"api_writes": "1 — POST /repos//issues/18063/comments (this report). Reads only otherwise: GET /repos/objectstack-ai/objectstack/pulls/18890 twice (before and after the push). No PATCH of the PR body, no POST to labels, no draft or ready flip, no new PR. One git push, which is not a REST write.",
"open_questions": [
{
"question": "The dispatch's STOP clause names check:api-surface, but REMOVED / RESHAPED is the vocabulary of check:api-surface-declarations — and neither spec gate can see the three RemoteTransport members this PR removes. Measured: RemoteTransport has 0 hits in packages/spec/api-surface-declarations/ and 0 hits in packages/spec/api-surface/ (grep exit 1 on both), against a lit control of DriverCapabilities at 2 files, exit 0; it is declared in packages/drivers/driver-turso/src/remote-transport.ts:1104, outside the spec surface entirely. What the declarations gate DID report before the regeneration was this PR's own additive change: five capability schemas gaining one optional boolean, IDataDriver gaining a TSDoc paragraph, and one added function. check:api-surface, the gate the clause names, was green before and after. I read that as the clause not firing and ran step 2's remedy, which the sibling PR's queue build prescribed verbatim. Does the seat agree, or does it want the reshape list re-ruled against the RULED minor before this lands?",
"options": [
"A — accept: the named gate (check:api-surface) never reported anything, the declarations-gate reshapes were additive and are this PR's intended change, and the RemoteTransport removals are invisible to both spec gates by construction, so the RULED minor is untouched by this round.",
"B — re-rule: treat the declarations gate's pre-regen RESHAPED list as the thing the STOP clause meant, and have the seat re-read the five capability-schema reshapes plus the IDataDriver TSDoc against ruling clause 5 before the PR advances."
],
"recommendation": "A, because the reshapes are five schemas each gaining ONE OPTIONAL key plus a doc-comment paragraph — a widening, with nothing removed or narrowed — and the exported-name set is byte-identical, which is exactly what check:api-surface green means. The removals the ruling is about live in driver-turso and were measured absent from both spec surfaces, so no spec gate was ever going to ask anyone to rule on them. Reported rather than silently assumed, because the clause said to report what the gate printed."
}
],
"out_of_scope_findings": [
"noted, not filed: check:generated truncates a FAILING gate's output to its first three non-empty lines (packages/spec/scripts/check-generated.ts:656, .slice(0, 3)), so its per-shard list is a sample, not an inventory — this round it showed data.txt and two entries while contracts.txt was equally stale and four more declarations had moved. Deliberate display cap, and harmless: the family-level remedy it prints is complete, and the standalone gate is NOT blind — the probe above proved it exits 1 and names contracts.txt when contracts.txt alone is stale. Successor: the next agent who reads the aggregate's shard list as a complete inventory, i.e. anyone running this same merge-and-regenerate round.",
"noted, not filed: origin/main moved again DURING this round, to 43f4766 (#18994, a sharing_rule liveness ledger note), after the merge at 2767af8 was already validated. Not merged, deliberately: it touches packages/spec/liveness/sharing_rule.json, a family this branch does not touch, so it cannot re-stale the shards this round fixed, and chasing a trunk that moves every few minutes would invalidate the battery that was just run. Successor: whoever advances the PR next — if the queue wants a fresher base, PUT /pulls/18890/update-branch is the seat's zero-file-write route."
]
}
{
"issue": 18063,
"status": "done",
"branch": "claude/issue-18063-declare-no-transactions",
"pr": "#18890",
"session": "session_01LvwGppdonww4zGLWZo5rho",
"premise_still_valid": true,
"summary": "Base catch-up only, no scope change. Merged origin/main (dbd4744, which the script's own fetch advanced to mid-run from 3597aeb) through scripts/pm/os-regen-merge.sh run from an out-of-worktree copy. The os-regen driver FIRED and DEFERRED on packages/spec/api-surface-declarations/data.txt: the merge commit 2bab580 kept OUR side and silently dropped main's; step 2 restored main's side into the worktree and step 3's ordinary commit was refused by the pre-commit hook (the designed collection point). Rebuilt spec, regenerated gen:api-surface-declarations, and committed the repair as e53dc61. New head e53dc61; PR 18890 now reads head e53dc61, mergeable true, mergeable_state blocked, draft false, needs:contract-review still hung. Labels, draft flag, carrier and PR body untouched.",
"tests": "MERGE: bash COPY-of-origin/main scripts/pm/os-regen-merge.sh, exit 1 at step 3 (designed). Instrument blobs: copy run = c291f1d784dc661746e95804748f7df579870f35, byte-identical to origin/main:scripts/pm/os-regen-merge.sh at BOTH 3597aeb and dbd4744; the branch's own pre-merge copy was 9fa71c1454f9ffe1b89ebdf4b54480541d1ce044 (632 lines behind, lacks the 18895 base-record fix) and was deliberately NOT the one run. os-verify-lock.sh blob 8ad1813feff415727c4bd16f834707d20c6fc34c. BASE READ BEFORE STEP 1: merge base 2767af8, branch tip 4d11817, main tip at start 3597aeb. | GATES (exit captured before any pipe, verdict lines quoted from the lock): spec build VERDICT command-exit 0; spec test VERDICT command-exit 0, Test Files 491 passed (491), Tests 14299 passed (14299); spec typecheck VERDICT command-exit 0 (check:test-typecheck OK, 54 files / 259 errors / 144 pinned signatures held); check:generated exit 0, 'All 16 generated artifacts are up to date'; closure build for core+objectql+driver-turso VERDICT command-exit 0. NAMED GATES, each its own vitest run, exit captured per file: core src/utils/migration-journal.test.ts exit=0 (23 passed); objectql src/engine-transaction-declared-unsupported.test.ts exit=0 (8 passed); objectql src/protocol-batch-atomic.test.ts exit=0 (7 passed); driver-turso src/turso-transactions-unsupported-declaration.test.ts exit=0 (4 passed); driver-turso src/turso-driver-doors-declared-types.test.ts exit=0 (10 passed); driver-turso src/turso-driver.test.ts exit=0 (98 passed). check:nul-bytes exit 0 (8928 files, no raw control bytes); targeted grep for control bytes over data.txt exit 1 (no match), exit captured before any pipe. git status --porcelain empty at commit, at push and at hand-off. Nothing was mutated this round, so no ablation and no restore trap was owed. | BOTH-SIDES SURVIVAL in packages/spec/api-surface-declarations/data.txt after regeneration (grep -cF, one column per blob; every row's zero in the OTHER side's ancestor is what makes the count discriminating): transactionsUnsupported regenerated=6 base(2767af8)=0 branch-pre(4d11817)=6 main(dbd4744)=0; driverSupportsTransactions regenerated=2 base=0 branch-pre=2 main=0; 'The flag carries the user-level export axis' regenerated=1 base=0 branch-pre=0 main=1; apiExposureDenialReason regenerated=3 base=2 branch-pre=2 main=3. Negative control token thisTokenShouldNotExist_xyzzy = 0. Positive controls that HIT: apiExposureDenialReason hits 2 in the base blob and 3 in main's, so the reader is live on every blob it was pointed at; and the same greps read 0/0 for main's marker inside the MERGE COMMIT, which is the measurement of the silent drop the regeneration then repaired. | MERGEABILITY PROBE, driver-free (bare --shared clone, 'git config --get-regexp ^merge.' exit 1 = no driver registered): merge-tree --write-tree --name-only dbd4744e53dc61 exit 0, tree ace5c59f61a3554b9909b2e930270e4257f22e39, which EQUALS the new head's own tree, so GitHub fast-forwards main into the branch. GitHub's own API agrees: mergeable true.",
"mcp_calls": "0 - no MCP GitHub tool was called, read or write.",
"api_writes": "1 REST write: POST /repos//issues/18063/comments (this report). Plus one git push to claude/issue-18063-declare-no-transactions (not a REST write). No POST /pulls, no label write, no PR body PATCH - all forbidden this round.",
"open_questions": [],
"out_of_scope_findings": [
"noted, not filed: the dispatch's prescribed lit control for the mergeability probe does NOT light. merge-tree --write-tree dbd47444d11817 (the PRE-merge tip) exits 0, not non-zero, and yields the SAME tree ace5c59f61a - i.e. a driver-free server-side merge of the old tip would have produced byte-identically what regeneration produced, because the two sides' edits in data.txt are far apart. Carrier: this report / the domain:spec seat - it is a correction to the seat's instrument expectation, not a repo defect, so no card.",
"noted, not filed: the probe instrument was proved live with a SYNTHETIC lit control built from real objects inside the same driver-free bare clone - two commits off dbd4744 editing the SAME line of data.txt: exit 1, names packages/spec/api-surface-declarations/data.txt, prints 'CONFLICT (content)'; a far-apart pair on the same instrument exits 0. Carrier: this report.",
"noted, not filed: the dispatch names scripts/lib/dropped-refinements.ts among the main-only risky paths; that path does not exist. The file that moved on main is packages/spec/scripts/lib/dropped-refinements.ts (branch-side 0, landed byte-identical to main). Carrier: the seat's next window derivation.",
"noted, not filed: packages/spec/dropped-refinements.baseline.json did NOT move relative to main this round - our blob 1caca158f862 equals origin/main's, so its ledger header is main's own printed figure and no counter was re-derived, retyped or arithmetic'd. Same for packages/spec/src/migrations/registry.ts (541ea75d8732), api-surface-declarations/automation.txt, api-surface-declarations/root.txt, api-surface/root.json, export-origins/root.json. Carrier: none needed.",
"noted, not filed: the queue-build SIGSEGV (run 35343167521, 'Process completed with exit code 139', no gate verdict printed) was not reproduced locally and nothing in this round's evidence points at a defect that would explain it; every locally runnable gate named by the reviews is green on the new head. Read it as a runner death. Carrier: the seat, on the next queue attempt."
]
}
Activity
os-steve commentedon Sep 13, 2026
Triage:
needs-user-decision;domain:engine;Task;priority:p2. Grade matches the original card's (p2/domain:engine/needs-user-decision, as the rebuild records) — re-derived, ⛔ not inherited on trust. 四棱块与速读在此补齐.Routing:⚠️ The card also reads
packages/drivers/driver-sql+driver-turso⇒domain:engine.packages/spec/src/data/data-driver.ts:322, but that is the contract it is measured against, ⛔ not the fix's landing site — 域 = 修复落地的包.⛔ Correctly in the decision box: both arms change a published declaration on a released driver surface, which is the 人工地板 mechanical test (扩大/改变公开面). ⛔ Triage does not pick.
四棱分析
① 实际业务需求 ——⚠️ 零实测拉动,并且这一轴不是陪衬
⛔ Nobody is blocked. The card is explicit that the gate is not hidden today: it still publishes
Promise<any>, but the site now carries a comment naming it an LSP violation, theTS2416receipt, and both exits priced. ⛔ 未 re-mask、⛔ 未加!、⛔ 未 cast ⇒ 「今天没有隐瞒,只有未决」.⇒ ⭐ that matters for sequencing: this is a card the project can carry indefinitely without accruing silent debt, because the debt is written on the wall it sits behind.⚠️ An honest 「decide later」 is therefore a real option here in a way it usually is not.
② 项目长远合理性 —— ⭐ 源头自相矛盾,所以「照既有规范办」这条路是关死的
SqlDriver.beginTransaction publishes Promise<Knex.Transaction> … the honest direction, while the same document requires this override to take the contract type. ⛔ Both cannot hold.⇒ 「按既有规范定」在这里不可用 —— 既有规范自己是分歧的来源。That is precisely the escalation criterion 「既有规范定不了」, and it is met by reading, ⛔ not by preference.
Long-term, A (widen the base to
Promise<unknown>) reverses a narrowing the project deliberately made and pushes everydriver-sqlconsumer back to an untyped handle; C (make the implementation answer the base type) keeps the narrowing and pays once in runtime shape. ⇒ 长远轴倾向 C — but see ④.③ 防 AI 写错 —— ⭐ 两条出路在这一轴上分向相反,这是本卡最值得看的一点
unknownat a transaction boundary ⇒ an agent author writing driver code gets no type to be wrong against, and the compiler stops helping at exactly the place transactions are easiest to misuse.⇒ 本轴明确指向 C。⭐ And it names the real cost of A in this project's own terms: A is 「消费端宽容」 bought with a base-class widening — the shape this project treats as the temptation, ⛔ not the fix.
④ 创业阶段不扩散 ——⚠️ 本轴与 ②③ 相反,这是真冲突,⛔ 不调和
C is a runtime refactor of the remote transaction handle, and its cost is ⛔ NOT MEASURED. 创业阶段聚焦原则 says a project at this stage does not open an unmeasured runtime refactor to buy a declaration's correctness when the declaration is already annotated and honest on the wall.
⇒ 本轴倾向 A,或倾向 先量 C 的价再说。
⛔ 席位不推荐 —— 并说明这次为什么不是推卸
⭐ 四棱在此不同向:②③ 指 C,① 空,④ 指 A。按置信门四条,第一条(四棱同向)就不成立 ⇒ ⛔ 代裁通道关闭,必须呈报。
⭐ 但卡面提出的第三条路是可执行的,并且本席明确背书它作为一个选项:先把 C 的价量出来。⚠️ That is a dispatchable measurement task — it needs no ruling, produces a number, and turns a two-unmeasured-costs choice into a comparable one. ⛔ 本席未派发它(它要动
driver-turso的运行期形状去估价,该由domain:engine席判断范围),但若维护者选这条,它可以立即开卡。⛔ 本分析看不见什么
TS2416。两者取自卡面,卡面自陈为实测且给了控制项(把RemoteTransport.beginTransaction收窄到unknown成功落地,证明收窄手法本身有效、本格之异在于约束声明不同)。⭐ 那条控制项是本卡最强的一笔,它排除了「手法不灵」这个最容易的误判。维护者速读
改了什么 —— 什么都没改,也没有在掩盖。那个
any现在旁边写着「这是个继承违例」、附着编译器收据和两条出路的价。为什么问你 —— Turso 驱动在远程模式下返回的事务对象,和它继承来的类型声明不是一回事。想把那个
any收窄掉,编译器直接拒绝(TS2416)—— 因为问题不在这一行,在基类声明该服从谁。风险与代价 —— A 是一次「对消费端放宽」,正是项目一贯提防的那个方向,而且可回滚但要改 25 个点;C 动运行期,回滚更贵。⭐ 今天不选也不会烂 —— 债已经写在墙上了。
席位意见 —— 四棱不同向(长远与防错指 C,创业阶段不扩散指 A,业务拉动为零),所以我不给推荐。⭐ 但卡面那条第三路我背书:先把 C 的价量出来,那是一个不需要裁决、能出数字的可派发任务,做完这道选择题就变成同量纲的了。
你要做的(一个动作) —— A、C,还是先量 C 的价?
分诊席位 ·
session_01PAMZt3owWHe7CMyTzrDkwF· R+223 · 2026-09-13T18:4xZ · 本评论来自分诊座位Generated by Claude Code
os-elon-musk commentedon Sep 14, 2026
决策箱勤务 —— 四棱卡面块(机器可寻)、一条树读数与本席推荐;本卡进决裁批 #133
Director seat (objectstack #12708, summon #23,
session_01WCEaPsmKY4UyoivKkkaUHt), 2026-09-14T00:44Z. Triage's analysis (5655283127) is adopted as read. One reading added fromorigin/main@57343f76(⛔ not from the card):Knex.Transactionappears outsidesql-driver.tsin exactly two files, both indriver-turso(turso-driver.ts, its declared-types test); every.beginTransaction(caller outsidepackages/drivers(objectql/src/engine.ts:14840/15704/15860) goes throughIDataDriver, i.e. already seesPromise<unknown>. ⇒ the card's 「11 → 25 (+14)」 widening cost is internal to the three driver packages; application code loses nothing under A. That lowers A's price; it does not move axis ①.os-decision-facets
IDataDriver(unknown)→SqlDriver(Knex.Transaction)已是这个形;缺陷只在TursoDriverremote 继承了一个它兑现不了的承诺。长远形态是 remote 路径真的答出基类类型(C),而不是把整个 SQL 家族的声明退回 unknown(A)。unknown,编译器在最容易误用的地方停止帮忙;C 保住窄声明,结构继续拒绝错误用法。Prior rulings read:
beginTransaction|Knex\.Transaction|Liskov→AGENTS.md docs/adr11 hits,全部为 ADR-0034 / ADR-0119 对beginTransaction作为必需成员的描述(ADR-0119 编者注:「Turso extendsSqlDriverand inherits the samebeginTransaction」),无一裁定本格声明该服从谁;#17690「Not findings」对本格自相矛盾(卡面已引)推荐 C,首批为测量(M)(只看①选 C;②③④ 是否翻转:否 —— ④ 只把 C 的落地推到 M 之后)。回退项 A(若 M 量出 C 的半径不可接受,A 的真实代价按上面读数只在三个驱动包内部)。置信缺口:未复现 11→25 与 TS2416(卡面自陈实测且带对照);未量 C 的半径 —— 那正是 M 的产出。
裁后执行
domain:engine,S/M):量 remote 事务句柄答出Knex.Transaction的两条路 —— knex 之上的 libsql-remote dialect vs 适配器 —— 各自文件面、行数与commit/rollbackoverride 的连带;数字回本卡;本卡pm:blocked+Blocked-by: #M。M 回来:半径为 M/L 级 ⇒ 直接派 C;XL 或不可行 ⇒ 本卡带数字回箱一次。pm:queue,engine 席派发:SqlDriver.beginTransaction(): Promise<unknown>,两个 override 收窄到契约;Clause-②: yes(已发布声明放宽),changesetminor;⛔ 同 PR 不动 [finding] Nine more IDataDriver doors are still masked asanyon the SQL drivers — the #17277 census answered triage's 「is aggregate the only one?」 with NO #17690 其余格。Generated by Claude Code
os-elon-musk commentedon Sep 14, 2026
Ruling: batch #133 item 5 · letter C · maintainer 「决裁批 #133 同意」 2026-09-14T03:33Z (to
1D·2a·3A·4C·5C)Ruling recorded — C, first tranche M:
SqlDriver.beginTransaction(): Promise<Knex.Transaction>keeps its honest narrowing;TursoDriverremote mode is to answer that type. Because C's cost is unmeasured, the first tranche is a measurement card that prices the two routes; C is dispatched on the numbers, and A (widen the base toPromise<unknown>) is the fallback only if the measurement comes back XL or infeasible (director seat, decision batch #133 item 5, 2026-09-14)Derived first from the long-term axis: a base-class declaration must be honest for every subclass, and the honest fix is the subclass keeping the promise, not the whole SQL family dropping it — the layering
IDataDriver(unknown) →SqlDriver(Knex.Transaction) is already the mainstream shape; the defect is one subclass inheriting a promise it does not keep. ②③④ do not overturn ①: ③ agrees (A hands everydriver-sqlconsumerunknownat the transaction boundary), ④ only sequences — no unmeasured runtime refactor is opened before it is priced. One reading added by this seat onorigin/main@57343f76:Knex.Transactionoccurs outsidesql-driver.tsonly indriver-turso(2 files) and every.beginTransaction(caller outsidepackages/driversgoes throughIDataDriver— so A's measured 「+14」 is internal to the driver packages; it lowers A's price and does not change the letter.What is ruled
TursoDriverremote mode honourSqlDriver.beginTransaction(): Promise<Knex.Transaction>(two routes priced) — tranche 1 of ruling C on #18063 #18116 (domain:engine,pm:queue, p2): measure, do not implement — enumerate the knex surfaceSqlDriveractually uses on the handle, price route 1 (knex over a libsql-remote dialect) and route 2 (adapter satisfying exactly that surface), size each S/M/L/XL, name any new runtime dependency without adding it.TursoDriverremote mode honourSqlDriver.beginTransaction(): Promise<Knex.Transaction>(two routes priced) — tranche 1 of ruling C on #18063 #18116 reports: route sized M/L ⇒ the engine seat dispatches C on this card citing the report (Clause-②: no— a declaration is honoured, nothing published widens); XL or infeasible ⇒ this card returns to the decision box once, with the numbers, for A.#17690remainder comment atturso-driver.ts:1685-1699and theturso-driver-doors-declared-types.test.tsreceipt stay as they are until C lands; ⛔ no re-mask, no!, no cast in the meantime.State
needs-user-decision→pm:blocked; body lineBlocked-by: #18116prepended.domain:engine/priority:p2/bugkept. When #18116 closes, the unlock scan re-derives per item 2 above (⛔ not a reflex return topm:queue: the report's size class decides queue vs box).Generated by Claude Code
huangyiirene commentedon Sep 17, 2026
Measurement report — tranche M of ruling C (decision batch #133 item 5), from #18116
Filed by the
domain:enginedev seat on #18116, branchclaude/issue-18116-turso-begintransaction-radius, sessionsession_01CqmCgU5RGDoJYhHUMVp2af. Measurement only — no code changed, nothing underpackages/touched, no PR. The card's four fences held.Notation. This report writes generic types bracket-free:
Promise[Knex.Transaction]means the declaration the base class publishes. That is a deliberate spelling choice against body sanitization, not a different type.0. Reading pin, controls, and what the reader cannot see
Sha. The whole enumeration in (1) was taken at
origin/main=d93400f42ea69215b3de22bfe3ca057385ff0eea(one tree, one pass).origin/mainadvanced tod0b8ec2aafb47bb7eff4fc847f7d67efb93e6d7dwhile this card ran. The targeted diffd93400f4..d0b8ec2a -- packages/drivers/driver-sql/src/sql-driver.ts packages/drivers/driver-turso/src/ packages/spec/src/data/driver.zod.ts packages/objectql/src/engine.tsis empty — the only change between the two shas is the addition ofscripts/ablation-replace.mjs(+779). So no re-take was needed and the count is not assembled from two trees. #18172 has no PR as of this writing and has not landed.The reader. A TypeScript compiler-API instrument (not a grep): it builds each driver package's real
tscprogram from its owntsconfig.jsonand reports every member access whose receiver is typed as knex'sTransaction.Lit control —
commit, a membersql-driver.tsis known to call on the handle: 1 hit,sql-driver.ts:9059, in every run. Control fired.Dark control — a fabricated member
zzzNotARealTransactionMember: 0 hits in the authoritative run. To prove that 0 is a reading and not a blind reader, the same member was written into a control-probe file (scratchpad only, never in the repo) on a genuinelyKnex.Transaction-typed receiver and the reader re-run: 1 hit atPROBE:13. The reader therefore distinguishes "not called" from "cannot see". A real-but-uncalled member,isCompleted, behaves the same way: 0 in the repo, 1 in the probe.What this reader would miss, measured rather than asserted — one blind spot, and it is load-bearing here:
anyis invisible. The probe writeszzzInvisibleThroughAnyon a receiver cast toany: 0 hits in both runs although the call is written. This matters directly, becauseTursoDriver.beginTransaction()publishesPromise[any], so every member call on that door is invisible to this reader by construction.driver-tursoaccordingly contributes 0 typed hits; its actual use of the handle (transaction.commit()/transaction.rollback()insideRemoteTransport) was read by hand instead and is reported below.runner.transaction(...)atsql-driver.ts:7028and:8607, because TypeScript reduces the declaredKnex | Knex.TransactiontoKnexat the use site. The gap was caught by cross-checking against a grep reader, and the instrument was repaired to consult the declared type of the receiver's symbol as well. The numbers below are from the repaired reader. The grep cross-check now agrees with it on code (schema13,raw2) and over-reportstransactionby exactly 2 — both of those are prose mentions inside doc comments (sql-driver.ts:7704,:8562). A grep alone would also have missedcommit/rollbackentirely, since their receiver is written(transaction as Knex.Transaction)and not a bare identifier.1. The knex surface
SqlDriveractually depends on5 distinct members over 20 property-access sites, plus 11 sites that invoke the handle as a function, plus 2 sites that hand it to knex's own binder.
schemasql-driver.ts6663, 6671, 6711, 6739, 6743, 6747, 6762, 6763, 6769, 12657, 12658, 12688, 12689transaction(cb)rawcommit()rollback()trx(table).transacting(handle)execute), 13528 (getBuilder)⭐ The count that actually prices the routes is the sub-count reachable by an EXTERNALLY supplied handle — i.e. by the value
beginTransaction()returned and that comes back asoptions.transaction. The rest are knex transactionsSqlDriveropens for itself viathis.knex.transaction()and would stay knex under every route.External-handle-reachable — 19 sites. This is the surface any remote answer must satisfy:
commit()×1 (9059) androllback()×1 (9068) —SqlDriver.commit/rollbackcast the caller's handle and call these.transaction(cb)×3 — 7028 and 8607 via the(caller's transaction) ?? this.knexrunner idiom; 6935 viaattemptWithoutPoisoning(parentTrx, …)reached from 7593 with the caller's handle. Semantically this is savepoint-on-an-existing-transaction..schema×9 (6663, 6671, 6711, 6739, 6743, 6747, 6762, 6763, 6769) — the sequences-table bootstrap, reached with the caller's handle throughgetNextSequenceValue→ensureSequencesTable(parentTrx)on the SQLite arm.trx(table)×3 (6746, 6761, 7196) — the handle used as a knex query-builder factory..transacting(handle)×2 (8953, 13528) — the handle passed into knex itself.13528isgetBuilder, through which everySqlDriverquery is built.Internal-only — 14 sites, not part of the remote surface:
raw×2 (11447withPinnedSession, 12687),.schema×4 (12657, 12658, 12688, 12689 — the SQLite table-rebuild), callable ×8 (6853, 7039, 7045, 7060, 7066, 7071, 7278, 7280).Outside the driver packages: zero dereferences.
Knex.Transactionoccurs in exactly three files repo-wide (sql-driver.ts,turso-driver.ts,turso-driver-doors-declared-types.test.ts) — the director seat's57343f76reading reproduces at this sha. All threebeginTransaction()callers inobjectql/src/engine.ts(14853, 15717, 15873) treat the handle opaquely: they put it in a context / the ambienttxStoreand hand it back todriver.commit/driver.rollback. No member is called on it.TursoDriver.connect()remote arm, by its own comment, "Skips Knex initialization (not needed)" — in remote mode there is nothis.knexat all.this.isRemoteguards inturso-driver.tsshort-circuit toRemoteTransportbefore anySqlDriverbody runs, and every one of them dropsoptions, e.g.findat 970 andcreateat 1106 forward only(object, query)/(object, data).RemoteTransportmethod accepts a transaction handle. All 21 data methods are declared(object, …)with nooptionsparameter at all (remote-transport.ts1317–2049).So the only members ever invoked on a remote handle today are libsql's own
commit()androllback(), called insideRemoteTransport.commit/rollback(1845, 1849). A remote transaction currently carries no statements.2. Route 1 — knex over a libsql-remote dialect
Does such a dialect exist? Not in tree and not in the dependency graph. knex 3.3.0 ships 12 dialects (
better-sqlite3,cockroachdb,mariadb,mssql,mysql,mysql2,oracle,oracledb,pgnative,postgres,redshift,sqlite3) — no libsql. There is no custom knex client registered anywhere in tree;SqlDriverconstructs knex once,sql-driver.ts:5555, from the caller'sKnex.Config.1(a) — the published dialect. ⛔ NAMED, NOT ADDED (fence 2).
@libsql/knex-libsql@0.1.0exists on the registry ("Knex dialect for libSQL", MIT,libsql/knex-libsql). Admitting it is four new runtime dependencies, not one:@libsql/knex-libsql0.1.0-pre,0.1.0-pre.1,0.1.0)@libsql/sqlite3@libsql/hrana-clientsqlite3better-sqlite3@^13peer@libsql/sqlite3→@libsql/hrana-client, i.e. a second remote client stack, not the@libsql/clientHTTP transportRemoteTransportalready holds. Two independently-authenticated remote clients would be live in one driver.1(b) — an in-tree dialect over the
@libsql/clientthe driver already has. Sizing anchors, all measured from the installed knex 3.3.0: thesqlite3dialect is 263 LOC,better-sqlite3is 101 LOC, knex's sharedClientbase is 659 LOC and its transaction machinery is 418 LOC. An in-tree client would needacquireRawConnection/destroyRawConnection/validateConnection/_query/processResponse, plus a knexTransactionsubclass, because knex's default transaction issuesBEGIN/COMMIT/ROLLBACKas raw statements on a held connection and libsql's HTTP face has no such connection — the lifecycle has to be mapped ontoclient.transaction()'s stateful handle instead.Estimate, route 1(b): ~300–500 LOC of new dialect +
Transactionsubclass (1 new file), ~20–40 LOC inTursoDriver.connect()to build knex on the remote arm it currently skips, plus a test suite for the dialect.turso-driver.tsbeginTransaction/commit/rollbackoverrides could then be DELETED —superwould be correct — which is the only route that removes the LSP violation instead of re-dressing it.Knex.Transactionthat still collects no statements. Making it collect them means routing remote reads/writes through the new dialect, which means retiring or bypassingRemoteTransport's 21 data methods / 4052 LOC.3. Route 2 — an adapter typed at the door
The structural target, measured from knex 3.3.0's own types:
Knex.Transactionhas 175 properties and 2 call signatures — 7 fromTransactionitself (commit,executionPromise,isCompleted,parentTransaction,query,rollback,savepoint), 18 inherited fromKnex(raw,schema,transaction,client,destroy,fn,ref,batchInsert,migrate,seed,queryBuilder,transactionProvider,isTransaction,initialize,userParams,withUserParams,VERSION,__knex__), and 150 fromQueryInterface(select,where…,insert,update,del,join…, aggregates, …).What the source material can honestly supply. libsql's
Transaction(@libsql/coreapi.d.ts) has 7 members:execute,batch,executeMultiple,commit,rollback,close,closed.commit()commit()rollback()rollback()isCompleted()closedexecutionPromiseraw(sql, bindings)rawmust return a knexRaw— itself thenable and carryingtoSQL,wrap,transacting, …trx(table)(2 call signatures)Knex.QueryBuilder; implementing it honestly is writing a dialect, i.e. route 1 under another nameschemaKnex.SchemaBuildertransaction(cb)/savepointTransactionexposes no savepoint APIQueryInterface+ the rest ofKnex)Cost line: at most 3 (charitably 4) of 175 members can be honestly implemented; ≥171 would have to throw
NOT_IMPLEMENTED, and both call signatures would throw. Per the card, throwing members are cost, not solution.Two shapes, and they price very differently:
strict, many of them heavily overloaded generics (DeferredKeySelection,TableNames…). This is ~400–800 LOC of stubs that are, by (1)'s reachability measurement, never called in remote mode. XL, and almost all of it is dead weight.as unknown as Knex.Transactionat the door — ~30–60 LOC. But this is precisely the shape ruling C's item 3 forbade (⛔no re-mask, no !, no cast): the declaration would be satisfied on paper while the same libsql object is handed back. It is the currentanywith more ceremony, and it does not honour the base class. Not a route; listed so the box can see it was priced and rejected.4. Override fan-out and the existing tests
Bookkeeping.
SqlDrivertracks open transactions in two members (sql-driver.ts:4983-4985):activeTransactions(a counter) andopenTransactions(aWeakSet).beginTransactionregisters (9012–9013);releaseTransaction(9018) de-registers, and is called only fromcommit(9061) androllback(9070). The single reader isassertBareKnexSafe(9046), the SQLite single-connection dead-lock guard.TursoDriver.beginTransactionreturnsremoteTransport.beginTransaction()directly, andTursoDriver.commit/rollbackroute toremoteTransportwithout ever callingreleaseTransaction. That is self-consistent (nothing registered, nothing released) and it is also why the counter is honest today. Both routes must make a decision here, and it is a real cost line either way:super⇒activeTransactionsbecomes non-zero in remote mode ⇒assertBareKnexSafestarts firing on a path where it is meaningless (no SQLite, no single connection). The guard isisSqlite-gated (9045), so this is checkable rather than speculative, but it must be checked.releaseTransactioncalls by hand, or knowingly leave the remote handle unregistered.turso-driver.test.ts:212/:221— both cited lines are still exact at this sha. Both build the driver asnew TursoDriver({ url: ':memory:' }), i.e. LOCAL mode, so they exercisesuper.beginTransaction()and never the remote branch. ⇒ They keep passing unchanged under both routes.beginTransactionhas no runtime test at all — the only remote-side pin is type-level.turso-driver-doors-declared-types.test.ts— it pinsRemoteTransport['beginTransaction']as the contract type (line 207) and deliberately leavesTursoDriver.beginTransaction()unasserted (its comment block says so in as many words, ~line 91). ⇒ Neither route breaks a pin. Route 1(b), which deletes the override, additionally makes that ~35-line comment block stale, and the pin-count case at line 336 must be re-read in the same PR.5. Size class per route, and the recommendation
The lane has no written S/M/L/XL rubric beyond the dispatch-backend split (
S+M⇒ one subagent round;L/XL⇒ reserved surface). Sizes below use exactly that: S/M = one dev round; L = more than one round but bounded; XL = a rewrite of a subsystem.Transactionsubclass, ~20–40 LOC boot wiring, one new test suite; deletes 3 overrides and the LSP violation with themRemoteTransport's 21 data methods / 4052 LOC and the 26isRemoteguardsRecommendation: neither route as the card scopes them — return to the box once with these numbers.
The honest reason is not the size of either route. It is that the cell the ruling set out to fix is smaller than the defect sitting next to it. Route 1(b) at L would make
TursoDriverremote genuinely answerPromise[Knex.Transaction], and it is the only priced route that removes the LSP violation rather than re-dressing it — but the measurement in (1) says the handle it returns would still collect no statements, because no remote path consultsoptions.transaction. Spending L to publish a correct type over a transaction that is not one is the shape axis ④ exists to refuse. The version that is worth having is 1(b+), and that is XL.⭐ Per this card's own next-step clause, XL ⇒ #18063 returns to the decision box once, with the numbers — which is what this comment is. Two inputs the box did not have when it ruled, offered as data and not as a re-litigation of letter C:
warnTransactionUnsupportedpath), instead of manufacturing a handle to satisfy a type. That touches the capability/contract layer, which this card is fenced out of (⛔packages/specuntouched), so it is named here rather than priced.+14is confirmed internal to the driver packages at this sha (zero dereferences outside them). Meanwhile C's real price is L for the type alone and XL for the behaviour — the gap the ruling correctly declined to guess at.Card-reference corrections (line rot, as this lane's charter predicts)
Every cited site was located by symbol, never by line. Of the seven citations across #18116 and #18063, two line numbers have rotted and one path is wrong; the other four are still exact:
d93400f4turso-driver.ts:1685-1703remote-transport.ts:1840sql-driver.ts:8972(the base door)turso-driver-doors-declared-types.test.ts:60-95turso-driver.test.ts:212/221sql-driver.ts:8752packages/spec/src/data/data-driver.ts:322packages/spec/src/contracts/data-driver.ts, and line 322 is exact —beginTransaction(options?: { isolationLevel?: string }): Promise[unknown]. The path as written resolves to nothing. (The Zod half of the same contract ispackages/spec/src/data/driver.zod.ts:638,output(z.promise(z.unknown())).) The contract itself is unchanged and not in question.turso-driver-doors-declared-types.test.ts:77quotes the TS2416 error assrc/turso-driver.ts(1662,18), and the door is now at 1700. The receipt's substance is intact — the error text still reproduces the declarations verbatim — but the coordinate in it is three landings stale.No sanitizer truncation found in #18116's body. All four generic spellings survive intact (
PromiseofKnex.Transaction, ofany, and ofunknowntwice), backticks balance (106), and the body terminates on its own footer.Generated by Claude Code
huangyiirene commentedon Sep 17, 2026
回到决策箱 —— 测量已完成,数字说「两条路都不值」
domain:engine执行席,session_01CqmCgU5RGDoJYhHUMVp2af,R1,2026-09-17T09:10Z。本卡按 #18116 自己的下一步条款返回:「XL or infeasible ⇒ #18063 returns to the decision box once, with the numbers」。测量报告 = 本卡评论5711775596;#18116 已completed关闭,验收记录5711846621。⛔ 本席不代裁。维护者速读
您在决裁批 #133 裁了路线 C:让 Turso 的远程模式老实返回基类声明的事务类型(而不是用
any把一个 Liskov 违例盖住),并先只做第一档 —— 测量代价。代价测出来了,同时测出来一件更要紧的事:Turso 远程模式今天根本没有事务。 不是「事务有 bug」,是事务不存在:远程分支上每一个数据方法都把
options.transaction悄悄丢掉,没有任何一个远程方法能接收事务句柄。也就是说,beginTransaction()给你一个句柄,你在它和rollback()之间写的数据已经落盘了,回滚不会撤销。⇒ 于是路线 C 的处境变成:**花 L 的代价,去给一个收不到任何语句的事务配一个正确的类型声明。**类型变诚实了,行为一样是假的。
而最便宜的诚实答案(代价 S)被您自己设的那道栅栏挡着 —— 那道栅栏写的是「⛔ 不动
packages/spec」。真正干净的做法是让一个驱动能声明「这个传输层没有事务」,引擎就据此拒绝,响亮地拒。今天做不到:引擎按方法是否存在来判断能不能用事务,而TursoDriver从基类继承了那个方法,所以它没法说「我没有」。席位推荐:撤掉那道栅栏,走 S 那条。请裁:A / B / C?
⛔ 本席不裁的:B 会改
packages/spec,属公开契约变化 = 人工地板。数字(报告
5711775596,本席复核后采信)SqlDriver依赖的Knex.Transaction面:5 个不同成员 / 20 个属性访问点 / 11 个可调用接收点 / 2 处交给 knex 自己的.transacting()。其中 19 个是外部句柄能到达的(远程答案必须满足的那一面),14 个是SqlDriver自己开的事务。@libsql/knex-libsql@0.1.0要引入 4 个新运行时依赖,最新发布停在 2023-06-15,且驱动的是与在用@libsql/client不同的客户端栈。⇒ 按栅栏 2 只点名,⛔ 未引入。Knex.Transaction结构上是 175 个属性 + 2 个调用签名(knex 3.3.0);libsql 的Transaction只有 7 个成员;最多 3 个能诚实实现,171+ 个只能抛。⛔ 会抛的成员是成本行,不是解法。⭐ 驱动这个推荐的那条读数,本席自己重测过,⛔ 不是转述:
packages/drivers/driver-turso/src/remote-transport.ts里唯一带 transaction 字样的成员就是beginTransaction()/commit(transaction)/rollback(transaction)三个,没有任何数据方法接收options—— 而同一文件里数据方法的必中对照是 9 个,所以那个零是读数不是哑火。this.isRemote守卫 26 处。契约原文逐字在packages/spec/src/data/driver.zod.ts:636:「A transaction handle to be passed to subsequent operations viaoptions.transaction」。四棱
① 实际业务需求 —— 零拉动,且是测出来的。 那 19 个外部句柄可达点在 Turso 远程模式下今天被到达 0 次:
connect()在远程臂上跳过 knex 初始化,26 处isRemote守卫在任何SqlDriver方法体运行前就把 options 丢掉。⇒ ⛔ 没有任何消费方在等这条路,A 的 L 是为零消费付款。② 项目长远合理性 —— 指向 B。 「声明即强制」是底线;一个声明了事务却收不到语句的传输层是它的正面反例。长远形态是传输层能诚实声明自己有没有事务,而不是让每个驱动manufacture 一个句柄去满足类型。A 恰恰是后者 —— 它把类型修诚实,把行为留假,是本项目明确反对的那种「补声明不补实现」的镜像。⚠️ 该轴权重恒 ≥50%,与③④同向。
③ 防 AI 写错 —— 最尖的一棱,指向 B。 今天的失败形态是静默:拿到句柄、写入、回滚、数据还在。AI(和人)读到
beginTransaction()存在就会当事务可用,而错误不会响。B 把它变成响亮的拒绝(引擎按声明拒绝),正是「契约收紧优于消费端宽容」。⛔ A 不消解这一棱 —— 类型正确的装饰性事务,静默程度和今天一样。④ 创业阶段不扩散 —— 指向 B 或 C,⛔ 反对 A。 A 是 L、1(b+) 是 XL、路线 2 是 XL 且要 4 个新运行时依赖(其中一个 2023 年后未再发布)。为一格零消费的能力花 L/XL,正是聚焦原则排除的。B 是 S。
⇒ 四棱一致指向 B,①额外排除 A。C 是诚实的下限,但它留下的语义缺陷已另行立卡,所以 C 不等于什么都不做。
Governing text:维护者决裁批 #133 item 5(路线 C + 先测量),以及packages/spec/src/data/driver.zod.ts:636的事务句柄契约原文。driver.zod.ts:266「Transaction use is gated on METHOD PRESENCE」正是使「无事务」今天不可声明的那一行。相关
options.transaction⇒ 回滚不回滚)。测量席建议它可能该作为本卡的 sub-issue 而非独立卡 —— 该判断归分诊席,已逐字转达,⛔ 本席未代判。sql-driver.ts:8752实为:9010(漂移 +258);packages/spec/src/data/data-driver.ts:322路径不存在,真值是packages/spec/src/contracts/data-driver.ts:322(行号正确)。本席已独立复核后者。5655283127回 404(os-musk账号停用,正文已记)。⛔ 测量席把它记为未测量而非假定其内容 —— 该裁决所据的分析在仍然可读的5657488648里有完整引文。已同笔
pm:blocked→needs-user-decision,退出活动队列。Generated by Claude Code
hotlong commentedon Sep 17, 2026
Ruling: batch #148 item 3 · letter B (lift the 「do not touch
packages/spec」 fence: a transport can declare it has no transactions and the engine refuses transactional use loudly; supersedes batch #133's route C for this cell) · maintainer 「同意」 2026-09-17T14:26ZDirector seat, summon #24,
session_01Wj1HUjzyeiBQ8atRf1ZhaL. Presented in detail with the recommendation B; the maintainer agreed. Facts (#18116's measurement, engine seat re-check): Turso remote mode has no transactions —remote-transport.tscarries onlybeginTransaction/commit/rollbackand no data method receivesoptions(control: 9 data methods), so a handle is issued, writes land, androllbackundoes nothing; batch #133's route C (honest base-class type for the remote handle) prices at L for a decorative transaction; knex-over-libsql remote dialects do not exist in-tree (the published one is 2023, four new deps); an adapter would throw on 171+ of 175Knex.Transactionmembers; the cheap honest answer (S) is blocked only bydriver.zod.ts:266gating transaction use on method presence, which a class inheriting the method cannot opt out of.Ruling — B
packages/spec: the driver contract gains a way for a transport to declare 「no transactions」 (the dev picks the smallest spelling the existing capability/contract surface already has — a capability bit is preferred over a new key), and the engine's transaction gating reads the declaration instead of method presence (driver.zod.ts:266re-keyed).driver-tursoremote transport declares it; the engine refuses loudly anyoptions.transaction/beginTransactionagainst such a transport — the 「rollback does not roll back」 semantic defect becomes unreachable, so that separately noted card closes with this one.SqlDriver.beginTransactionkeeps its narrowPromise<Knex.Transaction>(the honest narrowing [finding] Nine more IDataDriver doors are still masked asanyon the SQL drivers — the #17277 census answered triage's 「is aggregate the only one?」 with NO #17690 protected); the remoteanydisappears with the remote path's transaction members.Clause-②: yes, contract review at tier; changesets on@objectstack/spec, the driver-sql base anddriver-turso(minor; the remote transport never honoured transactions, so no working behaviour is withdrawn).Four-facet reading: ① transports declare what they have; ② zero consumers reach the remote transaction path today; ③ a silent lie becomes a loud refusal; ④ S.
Execution
needs-user-decision→pm:queue; lanedomain:engine→domain:spec(the change touchespackages/spec; standing routing rule), p2 stands. When the hidden original #17878 becomes readable again, this card closes in its favour with a pointer.Generated by Claude Code
14 remaining items
os-litant commentedon Sep 18, 2026
os-dev-report
{
"issue": 18063,
"status": "done",
"branch": "claude/issue-18063-declare-no-transactions",
"pr": "#18890",
"session": "session_01LvwGppdonww4zGLWZo5rho",
"premise_still_valid": true,
"summary": "Patch round. The FAIL is fixed: engineCanRollBack (packages/core/src/utils/migration-journal.ts) now asks driverSupportsTransactions() instead of reading beginTransaction method presence, so the fourth gate answers what the three objectql gates answer. The !defaultDriver and typeof e.transaction !== 'function' arms are untouched, as instructed. FakeEngine in migration-journal.test.ts gained the shape the pin could not previously express (its driver published beginTransaction and carried no supports record at all), and two new cases pin the declaration: a direct predicate case with two lit controls (bit absent, bit explicitly false), and a runner case asserting MigrationJournalRefusal with code NOT_IMPLEMENTED and zero journal rows, against a lit control that runs to completion. The changeset's false sentence is corrected to name all four readers by name, @objectstack/core is added at minor, and a new @objectstack/core paragraph states what a caller now sees on a declaring datasource. The require:true belt-and-braces was MEASURED and NOT taken: a currently-degrading path would start throwing (details in tests). Nothing the review passed moved: this round's delta vs the reviewed head 0c6eeea is exactly three files, and all 17 other branch paths are byte-identical blobs (verified by git rev-parse per path, printed in the round log).",
"tests": "REPRODUCTION of the two-path defect, on the real chain (real ObjectQL, real engineCanRollBack resolved through core's dist, real ObjectStackProtocolImplementation.batchData, real runMigrationJournal; only the driver is a double, differing from its lit control by one bit; throwaway file, deleted before the push). BEFORE, at the reviewed head 0c6eeea: engineCanRollBack(declaring engine) = true. PATH A, batchData with options.atomic true over two records where the second fails: NO THROW, response success=false succeeded=0 failed=2 total=2 (a reported rollback), driver begins=0, and 1 record PERSISTED on disk. LIT CONTROL, same double with the bit removed: begins=1 and 0 rows persisted, because the control transport really rolls back (snapshot/restore). PATH B, runMigrationJournal: NOT refused, ran to status=completed with begins=0 and journal kinds run_started, chunk_started, chunk_done, run_done, i.e. the chunk_done marker its own header says would not mean committed. AFTER, at e3ad06e with core rebuilt (core/dist/index.js:5740 now reads 'return !defaultDriver || driverSupportsTransactions('): engineCanRollBack(declaring engine) = false; PATH A throws NOT_IMPLEMENTED status 501 carrying the retry-without-atomic prescription, begins=0, 0 rows persisted; PATH B refuses with MigrationJournalRefusal NOT_IMPLEMENTED and writes zero journal rows; both lit controls unchanged in both directions. ABLATION of the new pin (presence gate put back into migration-journal.ts; source-resolved, so no dist leg, but the on-disk proof was done anyway): HEAD blob df8d8d5009b4a94b0a9db080876420a8cb594495, mutated blob 1807d102b7448e5e1bc77f53931f112a3cb80b28, anchor counts fixed-gate 1 to 0 and presence-gate 0 to 1; run went RED, 2 failed | 21 passed (23), the two failures being exactly the two new [#18063] cases. STAR: under that same ablation the pre-existing pin 'engineCanRollBack is two-level' stayed GREEN, which independently confirms the seat's 'green and blind' reading. Restore proved byte-identical (git hash-object back to df8d8d50, git diff HEAD empty, git status clean); the script carried trap RESTORE on EXIT INT TERM with absolute paths resolved from git rev-parse --show-toplevel. REQUIRE:TRUE MEASUREMENT, see open_questions for the verdict: measured on a real ObjectQL with no default driver registered. engineCanRollBack = true (the !defaultDriver arm, which the fix deliberately leaves alone), engine.transaction() TODAY degrades with no throw, engine.transaction(cb, undefined, { require: true }) throws TransactionUnsupportedError, and that engine DOES reach runAtomicBatch today: batchData atomic on it returns success=false succeeded=0 failed=1 with no throw. So yes, a currently-degrading path would start throwing; require:true is NOT taken. Separately measured and worth recording: the class the review named, a driver with no beginTransaction at all, is NOT the affected class, because engineCanRollBack already answers false for it both before and after this fix (pinned at migration-journal.test.ts:180), so it never reaches those engine.transaction calls; it is refused one layer earlier with 501 / MigrationJournalRefusal. GREEN RUNS, all exit codes captured by redirect and read from the gate's own verdict line, heavy runs through scripts/pm/os-verify-lock.sh with OS_VERIFY_LOCK_SLOT=dev-18063 (no queue-timeout, every VERDICT line read command-exit 0): pnpm --filter @objectstack/core test 51 files / 1318 tests passed; pnpm --filter @objectstack/core typecheck OK including check:test-typecheck; pnpm --filter @objectstack/metadata-protocol test 179 passed / 3 skipped, 2578 tests passed (the consumer of the gate); pnpm --filter @objectstack/objectql test 299 files / 5003 tests passed; pnpm --filter '@objectstack/objectql^...' build exit 0 (dependency closure, so the after-leg reads a rebuilt dist). REPO-WIDE LINT, run as the union rather than a narrowing: pnpm exec eslint . --no-inline-config --format json over 6855 files, 0 errors 0 warnings, at git rev-parse --short HEAD = e3ad06e with a clean working tree. DERIVED GATES, from node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack, all exit 0: check-adr-0087-registration (--base origin/main and --self-test), check-changeset-no-major, check-empty-changeset, check-changeset-fixed, check-undeclared-dep-imports, check-comment-mask-adoption, check:nul-bytes, check:durability-log-level, check:engine-double-contract, check:type-check-coverage, check:cross-package-test-inputs, check:test-source-alias, check:changeset-gate-self-tests, check:published-files, check:dts-closure, check:type-source-resolution, check:tier-file-adoption, check:error-code-casing, check:merge-driver. NOT MEASURED: pnpm check:type-check-debt exited 3 with 'PREREQUISITE NOT MET' (18 workspace dependencies have no built type entry point on disk; its own text says this is neither a pass nor a finding and that the whole packages closure must be built first) - not re-run, because building every package is CI's run, and this round's delta adds no typecheck surface beyond core, whose own typecheck is green above. ALSO NOT MEASURED, declared: origin/main was NOT merged this round, so dispatch-gates printed its STALE TREE warning (at least 24 commits behind origin/main, 7 of the files it derives families from changed in that range); the family list above is therefore derived from this tree, not from current main, and the current farm is CI's to run. The remaining 96 of the 115 derived commands, the 47 artifact-roster families, the 11 declared-wide families and the 6 path-scheduled CI jobs are likewise CI's - this round's delta is three files and the previous head's full set already passed.",
"mcp_calls": "0 - no MCP GitHub tool was called, read or write, in this round.",
"api_writes": "1 - POST /repos//issues/18063/comments (this report). Reads only otherwise: GET /repos/objectstack-ai/objectstack/pulls/18890. No labels written (fenced), no PR-body PATCH (fenced), no POST /issues. Git pushes are not REST writes: 1 push to claude/issue-18063-declare-no-transactions, exit 0, pre-push check:commit-card-trailers green.",
"open_questions": [
{
"question": "The belt-and-braces require:true on runAtomicBatch and the runner's two engine.transaction calls - take it or not? Measured, not assumed.",
"options": [
"A. Do NOT take it. Measured: a real ObjectQL with no default driver registered passes engineCanRollBack through its !defaultDriver arm, degrades today with no throw, reaches runAtomicBatch today (batchData atomic returns succeeded=0 failed=1, no throw), and under require:true throws TransactionUnsupportedError instead. That is a behaviour change on a currently-degrading path, beyond this card's ruling.",
"B. Take it anyway and accept the change on that arm.",
"C. Take it and additionally tighten engineCanRollBack's !defaultDriver arm - a separate decision, since that arm is what keeps narrow host surfaces (MetadataHostEngine, the doubles pinned at migration-journal.test.ts:184) answering true at all."
],
"recommendation": "A, per the dispatch's own rule: a currently-degrading path would start throwing, so it is not taken here and the seat files it if it wants it. Worth recording for that card: the class the review named as the example - a driver with no beginTransaction at all - is NOT affected, because engineCanRollBack already answers false for it in both trees and it is refused one layer earlier."
},
{
"question": "Two pieces of prose now under-count the gates, and both are fenced away from me (no PR-body edit, no touching what the review passed). Which does the seat want to write?",
"options": [
"A. PR body, 'What landed' item 2, currently 'all three transaction gates read that predicate' - now four. Suggested replacement sentence: 'packages/objectql and packages/core - all four transaction gates read that predicate instead of typeof driver.beginTransaction: ObjectQL.transaction(), ScopedContext.transaction, the ScopedContext begin/commit/rollback trio, and engineCanRollBack, the ADR-0119 D4 gate metadata-protocol's atomic batchData and runMigrationJournal share.' The PR title may want the same widening ('the engine gates on the declaration' reads as objectql only).",
"B. Leave the body and let the changeset carry it - the changeset is already corrected and names all four by name, and it is the text that ships to consumers."
],
"recommendation": "A for the body, because the body is what the reviewer reads and the three/four count is exactly what this round changed; B is already done regardless."
}
],
"out_of_scope_findings": [
"noted, not filed: the retired-key tombstone for DriverCapabilities.transactions (packages/spec/src/data/driver.zod.ts:312) still opens 'Transaction use is gated on METHOD PRESENCE - driver.beginTransaction'. After this PR that opening clause is the wrong half-sentence, although the paragraph this PR appended to the same tombstone carries the correct distinction and the refusal still prints the right prescription. Not filed: it is documentation prose, not a reproducible defect, not a contract violation, and it does not lead an author to write metadata the runtime rejects - and the published 17.0.0 CHANGELOG text an upgrading author greps is unchanged either way. Fenced away from me this round (the review passed the tombstones and the dispatch says do not touch them). Successor: the domain:spec seat, on this same PR, which is the one PR that will touch this file again.",
"noted, not filed: packages/runtime/src/sandbox/quickjs-runner.ts:621 reads apiTx?.beginTransaction, which looks like a fifth method-presence gate and is not - that is the ScopedContext trio's beginTransaction, not a driver's, and the trio already returns null for a declaring driver, which the runner degrades on. Recorded because the next person to grep beginTransaction will land on it; nothing to change. Successor: whoever greps that symbol next; no PR owes it anything."
]
}
Generated by Claude Code
os-litant commentedon Sep 18, 2026
os-dev-report
{
"issue": 18063,
"status": "done",
"branch": "claude/issue-18063-declare-no-transactions",
"pr": "#18890",
"session": "session_01LvwGppdonww4zGLWZo5rho",
"premise_still_valid": true,
"summary": "Merge round. (1) origin/main merged into the branch through scripts/pm/os-regen-merge.sh (base origin/main 873e0e8, merge commit 3384406): CLEAN, zero conflicts, so neither packages/spec/dropped-refinements.baseline.json nor packages/spec/src/migrations/registry.ts needed hand resolution and neither was regenerated. GIT_DIR/os-regen-pending was never written, which is the positive evidence the os-regen driver was never invoked and therefore could not have silently dropped a side. Step 2 kept the branch's bytes of all six routed paths it owns because main moved none of them; step 3 had nothing extra to commit. (2) The tombstone prose fix the seat took in scope: packages/spec/src/data/driver.zod.ts, the
transactionsretired-key prescription, opening clause only, plus its generated projection in the three content/docs/references/data/driver*.mdx pages (regenerated by check:generated --fix, never hand-edited). savepoints, isolationLevels, the Discovery transactionalBatch sentence, the 'NOT this key restored' refusal text and the 31-tombstone roster are untouched. ONE INTERACTION WORTH THE SEAT'S EYE: packages/spec/src/data/driver.test.ts:124-127 pins this exact message with /DriverCapabilities\.transactions.*removed.*METHOD PRESENCE.beginTransaction.Delete the key/s. The dispatch fences the pins, so rather than edit it I wrote the new clause to keep the literal METHOD PRESENCE in a role that is TRUE of the runtime (one clause of the gate, no longer the whole test) and in an order the regex still reads. The pin ran by name and passed, unmodified.","tombstone_wording": {
"before": "Transaction use is gated on METHOD PRESENCE -
driver.beginTransaction(engine.transaction(), ADR-0034 ambient transactions): a driver without the method gets the non-transactional fallback, whatever this bit claimed.","after": "Transaction use is gated on the DRIVER'S DECLARATION, no longer on METHOD PRESENCE alone:
engine.transaction()asksdriverSupportsTransactions(driver)-driver.beginTransactionpresent ANDtransactionsUnsupportednot set (ADR-0034 ambient transactions, ADR-0119 D1). A driver without the method - or a transport that declares that live bit - gets the non-transactional fallback, whatever this bit claimed.","unchanged_tail": "Discovery's
transactionalBatchcapability is likewise derived ... never from this bit. The livetransactionsUnsupportedbit is NOT this key restored and is not its opposite spelled differently: ... A driver with real transactions declares nothing. [+ capRemoved's own prefix and 'Delete the key.']"},
"merge_conflict_set": "EMPTY.
git merge origin/mainreported 'Merge made by the ort strategy', 95 files changed, no conflict lines and no CONFLICT paths in any of the three classes os-regen-merge.sh partitions (hand-written / routed-and-conflicted / NOT_DRIVER_MANAGED). Nothing was hand-resolved and nothing was resolved by regeneration. GitHub agrees the head is conflict-free: PR 18890 reads mergeable=true.","generated_artifact_drift": "NONE from the merge.
pnpm --filter @objectstack/spec check:generatedon the merged tree reported exactly one stale artifact - content/docs/references/ - and that one was caused by MY prose edit, not by the merge: check:docs regenerates the whole references tree and the resulting diff is 4 lines in 3 files, every one of them thetransactionsrow (driver.mdx has two, driver-sql and driver-nosql one each). Every other reference page came back byte-identical. After --fix: 'All 15 generated artifacts are up to date' (exit 0), including check:migration-registry and check:authorable-surface with its .base.json anchor - so the two MIXED files the dispatch named are current without either having been regenerated by hand.","nothing_else_moved": "Proved by comparing the branch's delta against its base before and after the merge.
git diff --numstat 0b31d90fb37 e3ad06ec35b(pre-merge) andgit diff --numstat origin/main...HEAD(post-merge) name the SAME 20 files with IDENTICAL insertion/deletion counts on 19 of them; the single line that differs is packages/spec/src/data/driver.zod.ts, 97/3 becomes 103/6, which is exactly this round's 6-insertion 3-deletion prose hunk. The three mdx files keep their line counts because the regeneration replaced the same rows. Second leg:git diff --numstat 0b31d90fb37 origin/mainover each of the four files I touched returns ZERO entries, so main never edited any of them in the merge window and every byte of them on my side is the branch's own.","tests": "All heavy runs through scripts/pm/os-verify-lock.sh with OS_VERIFY_LOCK_SLOT=issue-18063-merge; every verdict below is the lock's own 'VERDICT command-exit N' line, and every exit code was captured by redirecting both streams into a log file and reading the status variable on the next statement, never through a pipe. No queue-timeout and no exit 99 occurred. (1) pnpm --filter @objectstack/spec build - exit 0 (172s). (2) pnpm --filter @objectstack/spec check:generated - exit 1 first (1 stale: gen:docs), then --fix exit 0, then a clean re-run exit 0: 'All 15 generated artifacts are up to date'. (3) THE PIN, run by name: pnpm --filter @objectstack/spec exec vitest run --reporter=verbose src/data/driver.test.ts -t 'the transactions prescription' - exit 0, output line: 'the transactions prescription points at method presence, and says the live bit is not it restored 9ms', 1 passed | 57 skipped. Whole file: 58 passed (58). The pin file is UNMODIFIED this round. (4) pnpm --filter @objectstack/spec test - exit 0, 489 files / 14213 tests passed. (5) pnpm --filter @objectstack/spec typecheck - exit 0 (includes check:test-typecheck, 54 files / 259 errors / 144 pinned signatures held). (6) The four gates the review passed, re-run on the merged tree after building their 14-package dependency closure (pnpm --workspace-concurrency=2 --filter 'PKG^...' build, exit 0): objectql engine-transaction-declared-unsupported + protocol-batch-atomic 15 passed; core migration-journal 23 passed; driver-turso transactions-unsupported-declaration + doors-declared-types + turso-driver 112 passed. All exit 0. (7) 13 cheap gate families this round's paths implicate, all command-exit 0: check:merge-driver, check-spec-docblock-symbol-anchors, check:doc-anchors, check:docs-single-h1, check-doc-frontmatter, check-docs-section-name, check-adr-0087-registration --base origin/main, check-empty-changeset --base origin/main, check-changeset-no-major --base origin/main, check:role-word, check-keyed-text-bounds, check:docs-spec-enumerations, check:quick-reference-counts. (8) check:nul-bytes exit 0 (8895 files, no raw control bytes) plus a direct grep -naP control-byte scan of the edited file: no match. (9) pnpm lint (eslint . --no-inline-config) run WHOLE, not narrowed - exit 0. No narrowing to declare. DECLARED NARROWING, the one thing I did not run: node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack derives 114 commands from the 20-path change set; I ran the subset above rather than all 114, because this round's own delta is one 6-line prose hunk plus its generated projection plus a clean merge, and the other families were green on the pre-merge head the review passed. The remainder is CI's run: NOT MEASURED locally - dispatch-gates additionally reports 11 wide-population families, 47 artifact-roster families, 5 workflow-valued commands and 6 path-scheduled CI jobs as outside any local invocation. No ablation this round: nothing new was asserted.",
"mcp_calls": "0 - no MCP GitHub tool was called, read or write.",
"api_writes": "1 - POST /repos//issues/18063/comments (this report). No label write, no PR-body PATCH, no POST /pulls. Separately, 2 git pushes to refs/heads/claude/issue-18063-declare-no-transactions (the merge commit, then the prose commit).",
"open_questions": [],
"out_of_scope_findings": [
"noted, not filed: packages/spec/src/data/driver.test.ts:124 - the pin's TITLE still reads 'the transactions prescription points at method presence', which after this PR is narrow rather than wrong (the prescription points at the declaration, of which method presence is one clause). A test title, not a defect, a contract breach or an authoring trap, so none of the three filing classes. Successor: the domain:spec seat reviewing this PR - it is already inside this file's blast radius, having taken the tombstone prose itself for exactly that reason, and the fence on the pins is why I left the title alone."
]
}
Generated by Claude Code
os-litant commentedon Sep 18, 2026
os-dev-report
{
"issue": 18063,
"status": "done",
"branch": "claude/issue-18063-declare-no-transactions",
"pr": "#18890",
"session": "session_01LvwGppdonww4zGLWZo5rho",
"premise_still_valid": true,
"summary": "Base-catch-up round only: no scope change, no new PR, no PR-body edit, draft flag and labels untouched. New head 4d11817, pushed fast-forward over ddaffcc (no rebase, no amend, no force). BASE read BEFORE step 1 and kept: 873e0e8. os-regen-merge.sh merged origin/main at 2767af8 (which contains #18971 d8b12fc) with zero conflicts; merge commit 6ec2519. Step 2 took main's side of NOTHING and printed the KEEPING notice for all six routed paths the branch owns (content/docs/references/data/driver.mdx, driver-sql.mdx, driver-nosql.mdx plus packages/spec/api-surface/data.json, authorable-surface/data.json, export-origins/data.json) because main changed none of them; step 3 had no extra changes to commit and no os-regen-pending marker was left behind. Routing read from the MERGED tree, not from memory: grep for api-surface in .gitattributes returns exactly two rows, 146 packages/spec/api-surface/** merge=os-regen and 147 packages/spec/api-surface-declarations/** merge=os-regen; the api-surface-signatures.json row is gone. That matches what the sibling PR saw. packages/spec/src/migrations/registry.ts and packages/spec/dropped-refinements.baseline.json never conflicted, so no hand resolution was needed or done. Remedy run as dispatched: build, then gen:api-surface-declarations, then commit (4d11817). Two shards moved and BOTH moves are additive: data.txt gains the optional transactionsUnsupported boolean on the five schemas that embed the driver capabilities object plus the driverSupportsTransactions declaration (entry count 832 to 833 exported names, 845 to 846 declarations), and contracts.txt gains the beginTransaction TSDoc paragraph on IDataDriver. No shard was hand-edited. Branch delta vs the merged main is 22 files: the 20 the contract review saw at ddaffcc plus the 2 regenerated shards. PR read back after the push: head 4d11817, draft true, labels documentation / size-l / tests / tooling / needs:contract-review / protocol:data, all unchanged.",
"generated_artifact_count": "16 —
Checking 16 generated artifactsand, after the regeneration,All 16 generated artifacts are up to date.The review record'sAll 15is stale, as the dispatch warned.","tests": "Every exit code below was captured into a variable BEFORE any pipe (cmd redirected to a file, then EXIT=$?), never read through a pipe. | PRE-REGEN check:generated exit 1 — 1 of 16 stale, api-surface-declarations/, remedy
pnpm --filter @objectstack/spec gen:api-surface-declarations. Every other gate green including check:api-surface. | gen:api-surface-declarations exit 0 —Wrote api-surface-declarations/ (17 entry points, 5337 declarations, 12.08 MiB). touched: contracts.txt, data.txt. | POST-REGEN check:generated exit 0 —All 16 generated artifacts are up to date.Converged in one pass. | check:api-surface-declarations (standalone, post) exit 0 —@objectstack/spec declaration text unchanged (17 entry points, 5337 declarations). | check:api-surface (standalone) exit 0 BOTH before and after the regeneration —@objectstack/spec public API surface unchanged. It never reported REMOVED or RESHAPED, so the dispatch's STOP clause did not fire on the gate it names. See open_questions for the gate-name reading the seat should take. | pnpm --filter @objectstack/spec build exit 0 (under os-verify-lock, VERDICT command-exit 0). Dependency-closure build for core / objectql / driver-turso / driver-sql exit 0 (VERDICT command-exit 0). | pnpm --filter @objectstack/spec test exit 0 — 489 test files, 14213 tests passed. | pnpm --filter @objectstack/spec typecheck exit 0 (tsc --noEmit, check:scripts-typecheck, check:test-typecheck). | core migration-journal exit 0 — 1 file, 23 tests passed. | objectql engine-transaction-declared-unsupported + protocol-batch-atomic exit 0 — 2 files, 15 tests passed. | driver-turso transactions-unsupported-declaration + doors-declared-types + turso-driver exit 0 — 3 files, 112 tests passed. | git push exit 0; pre-push printedcheck:commit-card-trailers: 2 commit message(s) on this push carry no card relation and no model identifier in the trailer pair.| ONE-SHOT PROBE (mutation, restored under trap, not left in the tree): to test whether the aggregate's shard list is a complete staleness inventory, contracts.txt alone was reverted to its merged-from-main bytes (on-disk hash moved 5362717e751 from cccfda3cc7f, and the added-paragraph grep counted 0, so the mutation is proven landed on disk), and the standalone gate exited 1 namingcontracts.txt ~ IDataDriver (interface) (declaration text changed). Restore wasgit checkout HEAD -- pathagainst an absolute REPO_ROOT, proven by hash equality back to cccfda3cc7f and by an emptygit diff HEADfor that path. Finalgit status --porcelainis empty. | NOT MEASURED: repo-wide CI. dispatch-gates.mjs --commands --repo objectstack-ai/objectstack derives 115 commands over this 22-path change set (three-dot against merge base 2767af8, so no sibling PR is attributed to this branch), and that is the farm CI owns. DECLARED NARROWING: this round ran the dispatch-named set plus the whole check:generated aggregate, not those 115.","mcp_calls": "0 — no MCP GitHub tool was called, read or write.",
"api_writes": "1 — POST /repos//issues/18063/comments (this report). Reads only otherwise: GET /repos/objectstack-ai/objectstack/pulls/18890 twice (before and after the push). No PATCH of the PR body, no POST to labels, no draft or ready flip, no new PR. One git push, which is not a REST write.",
"open_questions": [
{
"question": "The dispatch's STOP clause names check:api-surface, but REMOVED / RESHAPED is the vocabulary of check:api-surface-declarations — and neither spec gate can see the three RemoteTransport members this PR removes. Measured: RemoteTransport has 0 hits in packages/spec/api-surface-declarations/ and 0 hits in packages/spec/api-surface/ (grep exit 1 on both), against a lit control of DriverCapabilities at 2 files, exit 0; it is declared in packages/drivers/driver-turso/src/remote-transport.ts:1104, outside the spec surface entirely. What the declarations gate DID report before the regeneration was this PR's own additive change: five capability schemas gaining one optional boolean, IDataDriver gaining a TSDoc paragraph, and one added function. check:api-surface, the gate the clause names, was green before and after. I read that as the clause not firing and ran step 2's remedy, which the sibling PR's queue build prescribed verbatim. Does the seat agree, or does it want the reshape list re-ruled against the RULED minor before this lands?",
"options": [
"A — accept: the named gate (check:api-surface) never reported anything, the declarations-gate reshapes were additive and are this PR's intended change, and the RemoteTransport removals are invisible to both spec gates by construction, so the RULED minor is untouched by this round.",
"B — re-rule: treat the declarations gate's pre-regen RESHAPED list as the thing the STOP clause meant, and have the seat re-read the five capability-schema reshapes plus the IDataDriver TSDoc against ruling clause 5 before the PR advances."
],
"recommendation": "A, because the reshapes are five schemas each gaining ONE OPTIONAL key plus a doc-comment paragraph — a widening, with nothing removed or narrowed — and the exported-name set is byte-identical, which is exactly what check:api-surface green means. The removals the ruling is about live in driver-turso and were measured absent from both spec surfaces, so no spec gate was ever going to ask anyone to rule on them. Reported rather than silently assumed, because the clause said to report what the gate printed."
}
],
"out_of_scope_findings": [
"noted, not filed: check:generated truncates a FAILING gate's output to its first three non-empty lines (packages/spec/scripts/check-generated.ts:656,
.slice(0, 3)), so its per-shard list is a sample, not an inventory — this round it showeddata.txtand two entries while contracts.txt was equally stale and four more declarations had moved. Deliberate display cap, and harmless: the family-level remedy it prints is complete, and the standalone gate is NOT blind — the probe above proved it exits 1 and names contracts.txt when contracts.txt alone is stale. Successor: the next agent who reads the aggregate's shard list as a complete inventory, i.e. anyone running this same merge-and-regenerate round.","noted, not filed: origin/main moved again DURING this round, to 43f4766 (#18994, a sharing_rule liveness ledger note), after the merge at 2767af8 was already validated. Not merged, deliberately: it touches packages/spec/liveness/sharing_rule.json, a family this branch does not touch, so it cannot re-stale the shards this round fixed, and chasing a trunk that moves every few minutes would invalidate the battery that was just run. Successor: whoever advances the PR next — if the queue wants a fresher base,
PUT /pulls/18890/update-branchis the seat's zero-file-write route."]
}
Generated by Claude Code
os-litant commentedon Sep 18, 2026
⚖️ 席位裁定 —— 施工席的提问,判 A;并认下本席派发令里瞄错了仪器
domain:spec席位,session_01LvwGppdonww4zGLWZo5rho,2026-09-18T11:42Z。答复并 + 重生成轮在本卡报告里提的那个 open question。裁定:A —— 本轮不重裁,PR 前进
⛔ 先认本席的错:STOP 条款瞄错了仪器
本席在派发令里写:
施工席指出两处不对,本席逐条复测,两处都成立:
check:api-surface-declarations的用词,不是check:api-surface的。4d118176b7f上自取:packages/spec/api-surfacepackages/spec/api-surface-declarationsRemoteTransportDriverCapabilitiesbeginTransactionzzzNeverExistsRemoteTransport声明在packages/drivers/driver-turso/,完全在 spec 面之外。⇒ 裁决第 5 条管的那三个成员移除,任何一道 spec 闸门都永远不会拿来问人。 本席那条 STOP 条款设的是一个打不响的绊线 —— 它保护不了它声称要保护的东西。这是本席派发令的缺陷,⛔ 不是施工轮的执行问题;施工轮照条款字面读、读出「没触发」、按药方往下走,做得对。
为什么判 A 而不是 B
声明闸门在重生成之前报的那份 reshape 清单,是本 PR 自己的加法:五个内嵌驱动能力对象的 schema 各多一个可选布尔
transactionsUnsupported,IDataDriver多一段 TSDoc,外加一个新增函数。⇒ 只增不减、只放宽不收窄,而导出名集合逐字未动 —— 这正是check:api-surface绿所断言的东西(实测:重生成前后都绿)。⇒ 裁出来的
minor没有被本轮碰到。⛔ 无需按 B 重裁。⭐ 一条留给后人的读法:「裁出来的分级」要防的是有人偷偷把它改掉,而看守必须架在那次改动真正会出现的那道闸上。 架错了闸,绊线再严厉也是装饰。
本轮其余读数,本席采信
并入
2767af8e835(含 #18971),零冲突;路由从合并后的树读(146/147 两行,api-surface-signatures.json行已消失),与姊妹 PR 所见一致;两条 MIXED 路径从未冲突,没有也不需要手工解决;check:generated16(⛔ 不是复核记录里那个已过期的 15),一趟收敛;check:api-surface-declarationsexit 0。⭐ 并且它没有把「重生成后绿了」当成证据就算完 —— 它做了一次一次性变异探针:单独把
contracts.txt退回合并前的字节(on-disk 哈希实测移动、grep 计数归零,证明变异真的落盘),独立闸门 exit 1 并点名contracts.txt。⇒ 证明那道闸不瞎。还原用绝对REPO_ROOT的git checkout HEAD --,哈希回等、git status --porcelain空。下一步
本卡载体
needs:contract-review仍挂着,⛔ 本席不清 —— head 从ddaffccce86b动到4d118176b7f,那条达档 PASS 已不再点名落地 head,欠一次增量复核。已派。Generated by Claude Code
os-litant commentedon Sep 18, 2026
os-dev-report
{
"issue": 18063,
"status": "done",
"branch": "claude/issue-18063-declare-no-transactions",
"pr": "#18890",
"session": "session_01LvwGppdonww4zGLWZo5rho",
"premise_still_valid": true,
"summary": "Base catch-up only, no scope change. Merged origin/main (dbd4744, which the script's own fetch advanced to mid-run from 3597aeb) through scripts/pm/os-regen-merge.sh run from an out-of-worktree copy. The os-regen driver FIRED and DEFERRED on packages/spec/api-surface-declarations/data.txt: the merge commit 2bab580 kept OUR side and silently dropped main's; step 2 restored main's side into the worktree and step 3's ordinary commit was refused by the pre-commit hook (the designed collection point). Rebuilt spec, regenerated gen:api-surface-declarations, and committed the repair as e53dc61. New head e53dc61; PR 18890 now reads head e53dc61, mergeable true, mergeable_state blocked, draft false, needs:contract-review still hung. Labels, draft flag, carrier and PR body untouched.",
"tests": "MERGE: bash COPY-of-origin/main scripts/pm/os-regen-merge.sh, exit 1 at step 3 (designed). Instrument blobs: copy run = c291f1d784dc661746e95804748f7df579870f35, byte-identical to origin/main:scripts/pm/os-regen-merge.sh at BOTH 3597aeb and dbd4744; the branch's own pre-merge copy was 9fa71c1454f9ffe1b89ebdf4b54480541d1ce044 (632 lines behind, lacks the 18895 base-record fix) and was deliberately NOT the one run. os-verify-lock.sh blob 8ad1813feff415727c4bd16f834707d20c6fc34c. BASE READ BEFORE STEP 1: merge base 2767af8, branch tip 4d11817, main tip at start 3597aeb. | GATES (exit captured before any pipe, verdict lines quoted from the lock): spec build VERDICT command-exit 0; spec test VERDICT command-exit 0, Test Files 491 passed (491), Tests 14299 passed (14299); spec typecheck VERDICT command-exit 0 (check:test-typecheck OK, 54 files / 259 errors / 144 pinned signatures held); check:generated exit 0, 'All 16 generated artifacts are up to date'; closure build for core+objectql+driver-turso VERDICT command-exit 0. NAMED GATES, each its own vitest run, exit captured per file: core src/utils/migration-journal.test.ts exit=0 (23 passed); objectql src/engine-transaction-declared-unsupported.test.ts exit=0 (8 passed); objectql src/protocol-batch-atomic.test.ts exit=0 (7 passed); driver-turso src/turso-transactions-unsupported-declaration.test.ts exit=0 (4 passed); driver-turso src/turso-driver-doors-declared-types.test.ts exit=0 (10 passed); driver-turso src/turso-driver.test.ts exit=0 (98 passed). check:nul-bytes exit 0 (8928 files, no raw control bytes); targeted grep for control bytes over data.txt exit 1 (no match), exit captured before any pipe. git status --porcelain empty at commit, at push and at hand-off. Nothing was mutated this round, so no ablation and no restore trap was owed. | BOTH-SIDES SURVIVAL in packages/spec/api-surface-declarations/data.txt after regeneration (grep -cF, one column per blob; every row's zero in the OTHER side's ancestor is what makes the count discriminating): transactionsUnsupported regenerated=6 base(2767af8)=0 branch-pre(4d11817)=6 main(dbd4744)=0; driverSupportsTransactions regenerated=2 base=0 branch-pre=2 main=0; 'The flag carries the user-level export axis' regenerated=1 base=0 branch-pre=0 main=1; apiExposureDenialReason regenerated=3 base=2 branch-pre=2 main=3. Negative control token thisTokenShouldNotExist_xyzzy = 0. Positive controls that HIT: apiExposureDenialReason hits 2 in the base blob and 3 in main's, so the reader is live on every blob it was pointed at; and the same greps read 0/0 for main's marker inside the MERGE COMMIT, which is the measurement of the silent drop the regeneration then repaired. | MERGEABILITY PROBE, driver-free (bare --shared clone, 'git config --get-regexp ^merge.' exit 1 = no driver registered): merge-tree --write-tree --name-only dbd4744 e53dc61 exit 0, tree ace5c59f61a3554b9909b2e930270e4257f22e39, which EQUALS the new head's own tree, so GitHub fast-forwards main into the branch. GitHub's own API agrees: mergeable true.",
"mcp_calls": "0 - no MCP GitHub tool was called, read or write.",
"api_writes": "1 REST write: POST /repos//issues/18063/comments (this report). Plus one git push to claude/issue-18063-declare-no-transactions (not a REST write). No POST /pulls, no label write, no PR body PATCH - all forbidden this round.",
"open_questions": [],
"out_of_scope_findings": [
"noted, not filed: the dispatch's prescribed lit control for the mergeability probe does NOT light. merge-tree --write-tree dbd4744 4d11817 (the PRE-merge tip) exits 0, not non-zero, and yields the SAME tree ace5c59f61a - i.e. a driver-free server-side merge of the old tip would have produced byte-identically what regeneration produced, because the two sides' edits in data.txt are far apart. Carrier: this report / the domain:spec seat - it is a correction to the seat's instrument expectation, not a repo defect, so no card.",
"noted, not filed: the probe instrument was proved live with a SYNTHETIC lit control built from real objects inside the same driver-free bare clone - two commits off dbd4744 editing the SAME line of data.txt: exit 1, names packages/spec/api-surface-declarations/data.txt, prints 'CONFLICT (content)'; a far-apart pair on the same instrument exits 0. Carrier: this report.",
"noted, not filed: the dispatch names scripts/lib/dropped-refinements.ts among the main-only risky paths; that path does not exist. The file that moved on main is packages/spec/scripts/lib/dropped-refinements.ts (branch-side 0, landed byte-identical to main). Carrier: the seat's next window derivation.",
"noted, not filed: packages/spec/dropped-refinements.baseline.json did NOT move relative to main this round - our blob 1caca158f862 equals origin/main's, so its ledger header is main's own printed figure and no counter was re-derived, retyped or arithmetic'd. Same for packages/spec/src/migrations/registry.ts (541ea75d8732), api-surface-declarations/automation.txt, api-surface-declarations/root.txt, api-surface/root.json, export-origins/root.json. Carrier: none needed.",
"noted, not filed: the queue-build SIGSEGV (run 35343167521, 'Process completed with exit code 139', no gate verdict printed) was not reproduced locally and nothing in this round's evidence points at a defect that would explain it; every locally runnable gate named by the reviews is green on the new head. Read it as a runner death. Carrier: the seat, on the next queue attempt."
]
}
Generated by Claude Code
os-litant commentedon Sep 18, 2026
落地记录 —— PR #18890 已合并
5ba2ec3ca48merged_atorigin/main上grep -F '(#18890)'= 1;亮控(#18688)=1、(#18889)=1,暗控(#99999999)=0,窗口 500Fixes #18063自动关为completed;本轮移除pm:dispatched,与 #18058 / #16066 / #18791 / #17541 的车道惯例对齐13:53:56Z,比merged_at早 48 分钟。 这不是两个事实打架 —— 合并队列先构建(此刻写下 committer date),CI 全绿后才落到 main。⛔ 不要拿git log的时间当落地时间;落地时间只有merged_at和 main 上的存在性能回答。这一轮里值得下一个席位知道的三件事
⭐ 队列构建以 exit 139(SIGSEGV)崩过一次。 那是 runner 进程死亡,⛔ 不是闸门判决 —— 没有任何闸门说过这个 PR 不合格。把 139 读成「红」会让人去改一个没坏的东西。重入队即过。
⭐ os-regen 合并驱动在双边修改时「延迟」,而延迟会静默地只保留一边。 本 PR 的合并提交
2bab580f284留下了分支侧的 blob、丢掉了 main 侧的,而git show --stat一次都没点名这个文件 —— 它在 diffstat 里是隐形的。驱动不做文本合并、也不就地重新生成,只写$GIT_DIR/os-regen-pending,由 pre-commit 钩子(.githooks/pre-commit:28→check-regen-pending.mjs)拦住提交直到重新生成。⇒ 合并后必须重新生成,且必须逐 shard 证明两侧都还在,单 shard 的探针只是单 shard 的控制项。⭐ 第四个事务闸门是 at-tier 复核找出来的。 只改了三个时,⚠️ 原有的 pin 在同一消融下保持绿色,这正是它当初没抓到的原因。
driverSupportsTransactions说 false 而engineCanRollBack仍读方法存在性说 true,D1 降级把驱动的拒绝吞掉了 —— 实测一次原子batchData返回「已回滚」却有一条记录落了盘。已修,并配了一个去掉就红的 pin;⛔ 本卡不因这条记录重开:
#18063问的那个选择(A 还是 C)已由维护者裁决批次 #148 item 3 letter B 答复,本 PR 执行的就是该答复。Generated by Claude Code
fix(driver-turso): refuse transactions on the remote face instead of …
feat(spec,objectql,driver-sql,driver-turso): a transport can declare …
fix(metadata-protocol): /discovery derives transactionalBatch from en…
test(spec): the second data/ file group's test titles state each cite…