Skip to content

feat(spec,objectql,driver-sql,driver-turso): a transport can declare it has no transactions, and the engine gates on the declaration - #18890

Merged
os-litant merged 12 commits into
mainfrom
claude/issue-18063-declare-no-transactions
Sep 18, 2026
Merged

os-litant merged 12 commits into
mainfrom
claude/issue-18063-declare-no-transactions

Conversation

@os-litant

@os-litant os-litant commented Sep 18, 2026

Copy link
Copy Markdown
Collaborator

Fixes #18063

Clause-②: yes

Governing text: maintainer decision batch #148 item 3, letter B, 「同意」 2026-09-17T14:26Z (issue comment 5716042163). It supersedes batch #133's route C. Quoted verbatim and untranslated, because the spelling delegation is the part this PR had to execute:

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).

Notation. This body writes generic types bracket-free — Promise[Knex.Transaction] means the declaration SqlDriver publishes. That is a spelling choice against body sanitization, not a different type.


What landed

  1. packages/specDriverCapabilities gains one live bit, transactionsUnsupported, plus the predicate that reads it, driverSupportsTransactions(), exported from @objectstack/spec/data.
  2. packages/objectql and packages/core⚠️ corrected by the seat: all FOUR transaction gates, not three. They read that predicate instead of typeof driver.beginTransaction: ObjectQL.transaction, ScopedContext.transaction, ScopedContext.txDriver() behind the discrete begin/commit/rollback trio, and engineCanRollBack — the ADR-0119 D4 gate that metadata-protocol's atomic batchData / updateManyData / deleteManyData and runMigrationJournal share. The degrade warning now says WHICH of the two reasons it fired for.

The fourth gate was found by the at-tier review and is why this count changed. With only three re-keyed, the gates DISAGREED: driverSupportsTransactions said false while engineCanRollBack still read method presence and said true — and the D1 degrade swallowed the driver's refusal. Measured on the real chain: an atomic batchData returned a 「rollback」 with one record persisted and begins = 0, against a lit control (same double, bit removed) that threw 501 with 0 rows; the migration runner ran to completed and wrote the chunk_done marker its own header says 「would not mean committed」. ⇒ this PR briefly re-opened the 「rollback does not roll back」 defect one layer up. Fixed, with a pin that fails without it — and ⚠️ the pre-existing pin stayed GREEN under that ablation, which is why it never caught this.
3. driver-sqlSqlDriver.supports spells transactionsUnsupported: false. SqlDriver.beginTransaction() keeps its narrow Promise[Knex.Transaction]; nothing in the base was widened.
4. driver-turso — the remote face declares the bit; TursoDriver.beginTransaction() publishes the inherited declaration instead of Promise[any]; RemoteTransport loses its three decorative transaction members.


The spelling, priced — because the ruling's preferred spelling points at a tombstone

driver.zod.ts:266 is the prescription line of a retired-key tombstone: transactions was removed in @objectstack/spec 17.0.0 under ADR-0049 enforce-or-remove, and savepoints / isolationLevels beside it are the same retired family. Three spellings were priced before one was chosen.

(a) Revive the name transactions. Rejected, and the cost is measurable rather than aesthetic:

  • It needs packages/spec/src/migrations/registry.ts edited — the D3 entry driver-capabilities-inert-bits-removed names data.DriverCapabilities.transactions in its surface list and states the count ("of 34 declared bits, THREE have a decision-making reader … THIRTY-ONE were written by every driver and read by nothing") in its reason and acceptanceCriteria. That file is MIXED and deliberately not routed to the os-regen merge driver, so it is the one file in this area that a merge cannot resolve mechanically. The chosen spelling touches it zero times (verified: git diff origin/main..HEAD does not name it).
  • It inverts the record's own convention. Every optional bit here means false when absent; a revived transactions must mean "yes, transactions" when absent, or every existing driver silently loses them on upgrade. That is a tri-state boolean in a record where nothing else is one.
  • It converts a documented refusal into silent acceptance of a value whose meaning changed underneath it. The old bit claimed "I support transactions" and nothing read it; the new declaration must express "I have none", and it is load-bearing. An old inert value becoming load-bearing with the opposite sense is the ADR-0104 silent-strip class one level up — the class the tombstones exist to prevent.
  • It makes the tombstone's own published text false. That text ("no code in any repository ever read it, so its value never changed which code path ran") is what an upgrading author actually reads.
  • It also costs the pins that hold the retired set: RETIRED_BITS in driver.test.ts, the prescription case, the 31-tombstone counts in two docblocks.

(b) A new key outside supports — dispreferred by the ruling by name, and larger: a second place to look for one fact.

(c) A new inverse-polarity bit on the live DriverCapabilities record. Chosen. It keeps absence = false, leaves the tombstone true and refusing, touches neither MIXED file, and costs one key plus its reader.

The ruling's stated preference and the tombstone do not conflict, and that is the finding worth stating plainly. "A capability bit is preferred over a new key" asks for a bit on the existing DriverCapabilities record — it does not ask for the retired NAME back. Spelling (c) satisfies the preference in full while the tombstone stays exactly as published. No seat question is escalated here because there is no fork to escalate.

Why adding a bit SATISFIES enforce-or-remove rather than reversing it

The 17.0.0 audit removed thirty-one bits for one stated reason: no code anywhere read them. It kept the three where method presence provably cannot carry the signal. Ruling B's entire content is the creation of the missing reader. The bit arrives with the engine dispatch that consumes it, in the same change — the honest order the ADR asks for — and the record's own docblock now states that bar for the next author.

Why method presence could not carry this

TursoDriver extends SqlDriver, whose beginTransaction() opens a real knex transaction, so the inherited method reported the libSQL REMOTE transport as transactional. It is not: RemoteTransport's data methods take no options argument at all, so a handle cannot reach the statement that would have to join it. A subclass cannot opt out of a door it did not open. This is the exact mirror of batchSchemaSync, which exists because a subclass can inherit syncSchemasBatch from a base whose transport batches while its own cannot — and which the engine likewise ANDs with method presence.


Premise check: part of this card was consumed while it sat in the box

Reported rather than quietly absorbed, because it changes what clause 3 still owed.

62bce5c297d — "refuse transactions on the remote face instead of silently dropping them (PR #18717)", 2026-09-17T17:13:06Z, four hours after the ruling — landed the driver-level loud refusal for card #18616: TursoDriver refuses beginTransaction() / commit() / rollback() and any options.transaction on the remote arm. That card is already completed; this PR does not re-open or re-decide it, and deliberately does not add a second, engine-level options.transaction refusal beside the driver's — a second mechanism for zero additional drivers is the shape this whole card is about.

What that leaves for this PR is the half nothing had built, and it is load-bearing:

The refusal's own remedy was unreachable. refuseRemoteTransaction's message tells callers to "take the non-transactional path deliberately: engine.transaction() without require: true on a datasource whose driver has no transactions runs the callback with no rollback and says so (ADR-0119 D1)". With the gate reading method presence, that path could never be taken for this driver — the method is there, so the engine opened a transaction and the callback got a 501 out of beginTransaction() instead of the declared degrade. The message made a promise only this change can keep.

Two more premise readings, both against origin/main:

  • RemoteTransport's three transaction members were unreachable once the driver refused: remoteTransport.beginTransaction|commit|rollback0 call sites repo-wide, against a lit control of 7 lines calling other members of the same field. They are deleted here.
  • TursoDriver.beginTransaction()'s any dissolves without paying either price [finding] Nine more IDataDriver doors are still masked as any on the SQL drivers — the #17277 census answered triage's 「is aggregate the only one?」 with NO #17690 priced. refuseRemoteTransaction returns never, so the remote branch is assignable to any declared return type and the only arm that still returns is super.beginTransaction(). The override republishes the base's type. It is spelled ReturnType[SqlDriver['beginTransaction']] and not the knex type directly, because knex is not a dependency of driver-turso (check:undeclared-dep-imports; the same constraint the doors suite's KnexSlice works around) — and deriving it from the base is the stronger pin.

⚠️ #17690 could not be read — it, #17876 and #17878 all answer 404 (the os-musk account is deactivated). Lit control: #18063 and #18116 read fine through the same instrument in the same round, so the 404s are a reading. Clause 4 of the ruling — "SqlDriver.beginTransaction keeps its narrow Promise[Knex.Transaction] (the honest narrowing #17690 protected)" — is therefore treated as the governing restatement. No claim is made here about #17690's original text.


Behaviour change for a caller

On a datasource whose driver declares the bit, engine.transaction() takes the DECLARED non-transactional path (ADR-0119 D1) instead of opening a transaction it cannot honour:

  • without require: the callback runs with no transaction, owned: false, and the degrade warns once per datasource — naming the declaration, not a missing method, because sending an operator to look for a method this class publishes wastes the report;
  • with require: true: TransactionUnsupportedError before the callback writes anything;
  • ScopedContext.transaction answers identically, and the discrete trio's begin returns null.

Every one of those is the answer a driver with no beginTransaction already received. Nothing that worked stops working — which is why the changesets are minor: the remote transport never honoured a transaction, so no working behaviour is withdrawn (the ruling's own stated ground, and the ground on which RemoteTransport's three published members are removed at minor).


Verification

All readings on the merged tree, 0c6eeea0f6f, against origin/main 0b31d90fb37. Every exit code captured by redirect, never through a pipe.

run result
build closure (turbo run build, driver-turso + objectql closures) exit 0 — 15/15 tasks
typecheck — spec, objectql, driver-sql, driver-turso exit 0 — 17/17 tasks
@objectstack/spec check:generated exit 0 — all 15 artifacts current
spec src/data/driver.test.ts 58 passed
objectql — 6 transaction suites 66 passed
driver-turso pnpm test (whole package) 1282 passed / 55 files
driver-sql pnpm test (whole package) 2627 passed, 168 skipped
pnpm lint (eslint . --no-inline-config, whole repo) exit 0 — complete population, no narrowing claimed
24 further gate families run locally all exit 0

pnpm check:type-check-debt returned exit 3, PREREQUISITE NOT MET — it refuses to measure without the whole workspace built, which is a farm-scale build. Recorded as NOT MEASURED, ⛔ not as a pass and ⛔ not as a red. The rest of the derived gate roster is CI's run.

Reverse verification — two legs, both dist-aware

Leg A — the spec predicate (objectql resolves @objectstack/spec through exports, i.e. dist/, per the KNOWN_UNALIASED_TEST_IMPORTS ledger, so the mutation had to reach dist/ to mean anything):

reading
driver.zod.ts blob at HEAD 63ab873394848c025325ac234e8bd62b361c9ce0
blob after mutation (declaration clause deleted) 481e373c37ec774e03b6c127b6ceb6d8527c03d1 — differs, so it reached disk
rebuild, then ablation-dist-preflight @objectstack/spec … --absent exit 0 — the guard is gone from dist/
engine-transaction-declared-unsupported.test.ts 8 failed / 8
spec driver.test.ts 1 failed / 58 — only the predicate case, as predicted
restore (git checkout HEAD -- …), git diff HEAD empty; blob back to 63ab873…
rebuild, preflight (present) exit 0
re-run 8 passed / 8

Leg B — the driver declaration (same-package source resolution, no build in the path):

reading
turso-driver.ts blob at HEAD bb55757e6025d55d58babbbe0c090eefa4afa001
blob after mutation (transactionsUnsupported: false) 2bfb64a95287d3145dea4d36fd75cad23868f3e1 — reached disk; injected marker observed on disk
declaration suite + capability census pin 2 failed / 102
restore blob identical to HEAD, git diff HEAD empty
re-run 102 passed / 102

Predicted direction was RED, and RED is what both legs produced. Both scripts carried trap … EXIT INT TERM with absolute paths; both restores are proven by blob identity and an empty git diff HEAD, not by an exit code.

⚠️ One instrument error, reported rather than dropped: leg B's two *_SRC_COUNT echo lines were mis-quoted inside a quoted heredoc, so grep read the pattern's tail as extra filenames and printed a prefixed 0. Those two lines are void, not readings. The on-disk proof does not rest on them — it rests on the anchor assertion (the pre-mutation text had to occur exactly once or the script aborted), the injected marker counted on disk, and the two blob hashes.

Merge

origin/main was merged after PR #18704 landed, through scripts/pm/os-regen-merge.sh — merge committed first, regeneration afterwards, never during (a gen:schema run in MERGE state rolls the authorable-surface anchor back to the old fork point). Three os-regen artifacts were regenerated from the merged tree. Asserted afterwards: zero lines present in origin/main's api-surface/data.json, authorable-surface/data.json or export-origins/data.json are absent from the regenerated files, with the lit control firing (the single addition is driverSupportsTransactions (function)). Neither MIXED file — dropped-refinements.baseline.json, packages/spec/src/migrations/registry.ts — is touched by this branch at all.


Acceptance notes

Out-of-scope observations, noted and deliberately not acted on here:

  • packages/spec/src/data/driver.zod.ts still carries the retired savepoints and isolationLevels beside transactions; both remain correctly retired under this change and neither gained a reader. Noted, not filed.
  • packages/objectql/src/engine.ts has a third comment (near the batchData observability path) that cites warnTransactionUnsupported as its model; it is prose, still accurate, and left alone. Noted, not filed.
  • The turso-driver-doors-declared-types.test.ts header still quotes a TS2416 coordinate (src/turso-driver.ts(1662,18)) that has drifted by landings. The receipt's substance reproduces; only the coordinate is stale, and it is kept verbatim as the historical error text. Noted, not filed.

Generated by Claude Code


Generated by Claude Code

…it has no transactions, and the engine gates on the declaration

WIP — implementation in place, verification pending.

Claude-Session: https://claude.ai/code/session_01LvwGppdonww4zGLWZo5rho
Co-authored-by: Claude <noreply@anthropic.com>
…ee layers, plus the changeset

Claude-Session: https://claude.ai/code/session_01LvwGppdonww4zGLWZo5rho
Co-authored-by: Claude <noreply@anthropic.com>
…te the three stale spec artifacts

Claude-Session: https://claude.ai/code/session_01LvwGppdonww4zGLWZo5rho
Co-authored-by: Claude <noreply@anthropic.com>
…igins from the merged tree

Merge of origin/main routed three os-regen artifacts without a text merge.
Regenerated after the merge commit, never during it. Asserted: zero lines
present in origin/main are absent from the regenerated files, and the only
addition is this branch's own new export.

Claude-Session: https://claude.ai/code/session_01LvwGppdonww4zGLWZo5rho
Co-authored-by: Claude <noreply@anthropic.com>
@github-actions github-actions Bot added size/l documentation Improvements or additions to documentation protocol:data tests tooling labels Sep 18, 2026
@github-actions

github-actions Bot commented Sep 18, 2026

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 5 package(s): @objectstack/core, @objectstack/driver-sql, @objectstack/driver-turso, @objectstack/objectql, @objectstack/spec, touching 11 documentable anchor(s). ⚠️ 5 changed file(s) yielded no anchor (packages/spec/api-surface-declarations/contracts.txt, packages/spec/api-surface-declarations/data.txt, packages/spec/api-surface/data.json, …), so the pages documenting them are NOT COVERED by this run — this is not a clean bill of health for those files.

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

  • content/docs/data-modeling/drivers.mdx (via TursoDriver (symbol, a top-level class))
  • content/docs/data-modeling/index.mdx (via IDataDriver (symbol, a top-level interface))
  • content/docs/data-modeling/queries.mdx (via IDataDriver (symbol, a top-level interface))
  • content/docs/kernel/contracts/data-engine.mdx (via IDataDriver (symbol, a top-level interface))
  • content/docs/kernel/contracts/index.mdx (via IDataDriver (symbol, a top-level interface))
  • content/docs/kernel/services.mdx (via IDataDriver (symbol, a top-level interface))
  • content/docs/plugins/packages.mdx (via TursoDriver (symbol, a top-level class))
  • content/docs/protocol/objectql/query-syntax.mdx (via IDataDriver (symbol, a top-level interface))

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

  • content/docs/releases/v17/17-3.mdx (via IDataDriver (symbol, a top-level interface))

content/docs/releases/ is RELEASE-OWNED (AGENTS.md "Documentation Guardrails"): release
notes are written centrally at release time, and a code PR that edits them is the exact PR
that guardrail exists to stop. They are still audited — read-only. If one of them is actually
wrong, file an issue or open a dedicated docs-only PR; do not edit it here.

What this run could not see
  • 5 changed file(s) yielded no anchor (packages/spec/api-surface-declarations/contracts.txt, packages/spec/api-surface-declarations/data.txt, packages/spec/api-surface/data.json, …) — pages documenting those are invisible to this run
  • 1 anchor(s) matched too much of the corpus to be a work list: ObjectQL (symbol, 69 pages)
  • 4 name(s) were too generic to anchor anything (single lowercase words)
  • the SDK route bridge reached 60 of 215 client-bound route-ledger rows — the other 155 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run. Of those 155: 0 are remediable by widening that discovery convention (an in-repo file declares the path; the convention did not scan it); 55 are structural — on a ledger where NOT ONE row is declared in-repo, so no discovery change reaches them at any price; 100 are undecided (no in-repo declaration, on a ledger that has other in-repo registrars — absence and an unreadable spelling are not distinguishable here). The rows themselves: node scripts/docs-audit/affected-docs.mjs --bridge-coverage
  • a page that states a rule by its inputs shares no identifier with the emitter that implements the rule, so an emitter-only diff cannot list it — not on this run and not on any run. Measured on fix(driver-sql): emit varchar(maxLength) for a text field a declared index keys on #11430: content/docs/protocol/objectql/types.mdx documents the text-family column mapping by the ObjectQL type names it maps FROM (text / textarea / html) while the diff changed createColumn; it went unlisted, and it was the page that diff falsified, in four places. No shared token exists to detect this on, so a rule your change carries has to be re-read by hand in the pages that restate it.

Coarse fallback — 143 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json dbd474431fdf39a2a3d31f3f6f0475eb51fa70f7packageMentionDocs.

Which tree this was computed on

This run read content/docs from de136d65ba30f396dbf6d43bc176fb9ea28aac89 — the merge of head e53dc612b4822691edd263655ecb264312bd55f0 into base dbd474431fdf39a2a3d31f3f6f0475eb51fa70f7, which is what actions/checkout gives a pull_request run. Not the PR head.

A worktree cut from an older main holds a different content/docs, so re-deriving there can legitimately return a different list — that is a different tree, not a wrong row. To answer on the same tree:

# while this PR is open — GitHub drops the merge commit once it closes
git fetch origin de136d65ba30f396dbf6d43bc176fb9ea28aac89 && git checkout de136d65ba30f396dbf6d43bc176fb9ea28aac89
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin dbd474431fdf39a2a3d31f3f6f0475eb51fa70f7 e53dc612b4822691edd263655ecb264312bd55f0 && git checkout -B drift-repro dbd474431fdf39a2a3d31f3f6f0475eb51fa70f7 && git merge --no-ff e53dc612b4822691edd263655ecb264312bd55f0

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

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

Advisory only, and a precision-first one (#9192): a page is listed because it names a
symbol, wire route or SDK method this diff touched — not because it mentions a changed
package. Each row says which anchor put it there, so a wrong row is reportable rather than
merely annoying. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs dbd474431fdf39a2a3d31f3f6f0475eb51fa70f7 → pass the list as
args.docs, on the commit named under Which tree this was computed on.

Copy link
Copy Markdown
Collaborator Author

Contract review

Served-tier: 109/109 CONTRACT_REVIEW_TIER
Head-sha: 0c6eeea0f6f62df058212ce24a902ac5baf1b79d

⭐ Tier verified by the seat from the reviewing round's harness-stamped per-message served-model field: 109 of 109 at tier, 0 off-tier.

① Derived judgments

What is sound, and is ⛔ not reopened. Clause 3 landed: SqlDriver.beginTransaction is byte-identical at base and head, Promise[any] in turso-driver.ts 6 → 3 (the three remaining are other doors), the three RemoteTransport transaction members deleted with 0 call sites (lit control: 6 calls to other members of the same class). Clause 5 landed. Both named deviations upheld — the round verified the driver-level floor 62bce5c297d really is an ancestor of base, head and origin/main, and that the D1 path its message names really was unreachable before this change (beginTransaction() sits OUTSIDE the try at base, so the 501 propagated).

Seat decision 1 (spelling (c)) — upheld, and independently re-derived. registry.ts diff is 0 bytes (lit control: driver.zod.ts 142 diff lines); the D3 entry lists the retired key in surface and states 「of 34 declared bits, THREE … THIRTY-ONE」 in both reason and acceptanceCriteria ⇒ reviving the name falsifies all three. The tombstone still refuses {transactions: false} with its 「NOT this key restored」 prescription.

absence = false is honest, and the round reasoned it out rather than accepting it. The bit is an opt-OUT for the one shape presence cannot express. The alternative — absence = unsupported — would drop every out-of-tree driver that declares nothing into the D1 degrade on upgrade: silent ecosystem-wide loss of atomicity, and a major, contradicting clause 5. Measured: {beginTransaction, supports:{}} → true, {transactionsUnsupported: undefined} → true; only a literal true denies.

Tombstones undisturbedsavepoints and isolationLevels lines unchanged, 31 tombstones still (RETIRED_BITS length 31), the docblock's historical counts stay true.

⛔ The finding this round FAILS on

Clause 1 is short by ONE gate, and that re-opens the defect this card exists to close.

engineCanRollBackpackages/core/src/utils/migration-journal.ts:99 — still reads method presence:

return !defaultDriver || typeof (defaultDriver as { beginTransaction?: unknown }).beginTransaction === 'function';

The seat re-read that line itself and confirms it. Its own docblock calls it 「the ADR-0119 D4 gate, shared」; it is exported from @objectstack/core and consumed by metadata-protocol's atomic batchData / updateManyData / deleteManyData (protocol.ts:12315) and by runMigrationJournal. A grep of method-presence transaction reads across packages/*/src, excluding tests and driver packages, finds exactly this one (lit control: the same grep hits the three objectql sites and spec's predicate).

the re-keying made the two gates disagree. Before this PR both read presence, they agreed, and the driver's 501 surfaced. Now driverSupportsTransactions says false while engineCanRollBack says true, and the D1 degrade swallows the refusal.

Measured, with a lit control — resolution path established first (neither objectql's nor driver-turso's vitest config aliases spec or core, so both resolve through dist; core's dist/index.js:5739 carries the method-presence read):

leg result
declared-unsupported driver at this head, runAtomicBatch shape transcribed from protocol.ts 12315–12352 「rollback」 response, no throw, begins = 0, rows.size = 1 — record 0 PERSISTED
LIT control, same double with the bit removed (= the base tree) threw 501, begins = 1, rows = 0

Also measured against the real TursoDriver remote instance: transactionsUnsupported true, driverSupportsTransactions false, engineCanRollBack true.

⇒ on two reachable paths — the per-object REST batch with options.atomic: true, and the migration-journal runner, which then writes chunk_done rows its own docblock says 「would not mean committed」 — this PR converts main's loud 501 into a silent non-atomic run reported as rolled back. That is exactly the 「rollback does not roll back」 defect ruling clause 2 declares unreachable.

⚠️ The existing pin migration-journal.test.ts:178 is green and blind to this shape: its FakeEngine driver carries no supports at all.

⚠️ And the changeset's sentence 「every transaction entrance in the engine calls it」 is not true of the runtime as shipped — that ships to consumers as CHANGELOG prose.

② Semver level

minor is right for what the ruling priced. Through the driver's data methods a remote handle never carried a statement (0 RemoteTransport data methods take options, against 13 present). On origin/main today the path answers 501; at the last release it was a decorative handle. ⇒ nothing working is withdrawn. @objectstack/core joins at minor once the gate is completed.

⚠️ Caveat recorded: the deleted RemoteTransport.beginTransaction() returned a live @libsql/client Transaction usable by anyone holding the exported class — in-repo 0 callers, objectui 0 (lit control: 1574 @objectstack/spec references), cloud ⛔ unreadable from here. 「Never honoured」 holds for the ObjectStack contract.

③ Boundary flags

One the seat must own. The diff removes three public members of the exported RemoteTransport — the shape check-clause2-carriers.mjs calls yes (narrowing), i.e. BREAKING — while ruling clause 5 orders that removal and sets minor by name. ⇒ the level here is RULED, not derived; strict semver reads the removal as major. The declaration follows the ruling, and this flag records that it does so knowingly.

Clause ② yes is right: a new authorable key, a new public export, and engine dispatch on both = 扩大公开面 and 放宽接受集. No arm declared; the carrier gate reads an absent arm as no direction. Governed surfaces: 0 of 18 files. MIXED files untouched. ADR-0049 enforce-or-remove is satisfied in the bit-arrives-with-a-reader sense — but the reader set is short by one, which is the finding above.

⚠️ A shallow-clone trap the round hit and named, recorded for the next reader: on a 50-commit clone git merge-base --is-ancestor answered 「not an ancestor」 for a commit whose content was plainly in the tree. Deepen before trusting a negative.

NOT MEASURED

The dev's two ablation legs were not re-run (the resolution-path premise was confirmed instead and the declared-vs-undeclared columns exercise the dist predicate end to end, but no source mutation was performed). Local pnpm typecheck and the type-level Equals pins — closure built OS_SKIP_DTS=1; vouched by CI's required TypeScript Type Check. check:type-check-debt. The D4 finding through a booted REST server on a live Turso remote datasource (measured at the transcribed runAtomicBatch shape against the real ObjectQL and a real TursoDriver instance instead). objectstack-ai/cloud consumers of the deleted members — ⛔ outside the permitted repositories. #17690 / #17876 / #17878 original text — 404, deactivated author; the ruling's restatement governs. Whole-package suites — CI's.

Implemented-by: claude/issue-18063-declare-no-transactions
Reviewed-by: session_01LvwGppdonww4zGLWZo5rho

VERDICT: FAIL


Generated by Claude Code

…ethod presence (#18063)

The fourth gate. `ObjectQL.transaction()`, `ScopedContext.transaction` and the
`ScopedContext` begin/commit/rollback trio were re-keyed onto
`driverSupportsTransactions()`; `engineCanRollBack` was not, so the two
disagreed for exactly the driver this card exists for — a transport that
inherits `beginTransaction` from a base class it cannot honour and declares
`supports.transactionsUnsupported`.

Measured on the real chain (real `ObjectQL`, real `engineCanRollBack` through
core's `dist`, real `ObjectStackProtocolImplementation.batchData`, real
`runMigrationJournal`), on a declaring driver, before this commit:

  - `batchData({ atomic: true })` answered `succeeded: 0, failed: 2` — a
    rollback — with `begins = 0` and record 0 still on disk. The lit control,
    the same double with the bit removed, opened one transaction and left zero
    rows behind.
  - `runMigrationJournal` did not refuse: it ran to `completed` with
    `begins = 0` and wrote `chunk_started`, `chunk_done`, `run_done` — the
    `done` marker its own header says would not mean committed.

So the degrade swallowed the refusal the base tree produced, one layer up from
the engine. `engineCanRollBack`'s driver clause now asks the same predicate the
engine dispatches on, which is what "shared so the two cannot drift" was for.

`FakeEngine` gains the shape that makes the existing pin able to fail: its
driver published `beginTransaction` and carried no `supports` record at all, so
a gate reading presence and a gate reading the declaration were
indistinguishable against it.

Claude-Session: https://claude.ai/code/session_01LvwGppdonww4zGLWZo5rho
Co-authored-by: Claude <noreply@anthropic.com>
…e now runs

The retired-key prescription still opened with "Transaction use is gated on
METHOD PRESENCE", which this branch made false: `engine.transaction()` asks
`driverSupportsTransactions(driver)` — the method present AND
`transactionsUnsupported` not declared. The paragraph appended further down
already drew the distinction correctly, so the opening clause was the one
sentence in the prescription that contradicted the code around it, and an
author who writes the retired key reads that sentence first.

The opening clause now names the real gate and keeps method presence as the
clause it still is rather than the whole test. Nothing else in the
prescription moves: the Discovery `transactionalBatch` sentence, the
"NOT this key restored" refusal text and "A driver with real transactions
declares nothing" are untouched, `savepoints` and `isolationLevels` are
untouched, and the retired-bit roster is unchanged.

`content/docs/references/data/driver{,-sql,-nosql}.mdx` are the generated
projection of that string — regenerated by `check:generated --fix`, which
proved `gen:docs` the only stale artifact in the tree and reproduced every
other reference page byte-identically.

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

Copy link
Copy Markdown
Collaborator Author

✅ 达档合约复核 PASS —— head ddaffccce86b5deb8fe075f5f50df8e6088120fc

domain:spec 席位,session_01LvwGppdonww4zGLWZo5rho,2026-09-18T10:27Z。

⚠️ 先说清楚:这条 PASS 还不是落地许可

它点名的 ddaffccce86b 不会是落地 headmain 上已落 #18971(d8b12fca97c),带来一族新产物 packages/spec/api-surface-declarations/*.txt;本 PR 的树按构造不含它们。本席实测:transactionsUnsupporteddriverSupportsTransactions 在 main 的分片里命中 0(亮对照 DriverCapabilities=4、beginTransaction=1;暗对照 0)⇒ 并 main 后必须重生成分片,而那一并会移动 head,于是欠一次增量复核

⇒ 本席现在不清载体、不 undraft、不入队。姊妹 PR #18889 正是在这道闸上被踢出队列的,原因已实测到闸门原话。

档位核验(本席自取,⛔ 不采信复核自述)

复核轮 JSONL 抄本逐条消息的 harness 盖章 message.model:claude-fable-5-1 171 / 171,off-tier 0,抄本 367 行。⇒ 达档。

本席点名要它回答的两件事,它都答了,而且答得比问的多

① 那句墓碑新话是「真」,还是「照着正则捏的」?

复核逐条拆开对着 head 上的代码验:driverSupportsTransactions(driver.zod.ts:846-857)确实是 typeof beginTransaction === 'function' supports.transactionsUnsupported !== true;engine.transaction()engine.ts:14901 调它。⇒ 「不再只看方法存在」是对一个合取式的正确描述。

但它反过来把正则也测了,测出本席没想到的一层:那条正则同时匹配旧的那句假话。⇒ driver.test.ts:124-127 这个钉子已经分辨不出真假两句了。这不是行文问题,是钉子瞎了 —— 它记为 ③.1,并判「本轮不动,另立卡」。本席采信:钉子少断言一句真话是覆盖面欠债,不是契约缺陷。

② 上一次 FAIL 的第四道闸,现在认不认声明?

认了,而且是三种取法交叉验的,⛔ 不是读一个绿文件就算:

取法 读数
源码 migration-journal.ts:105-121 的 driver 分支已改成 driverSupportsTransactions(...),从 @objectstack/spec/data 导入
消融 把该分支改回 typeof ….beginTransaction === 'function' ⇒ core 的 migration-journal.test.ts 2 失败 / 21 通过,失败的恰是两条 [#18063];还原后 blob 与 HEAD 相同、porcelain 空;未消融时 23/23
真链路 TursoDriver 远端实例 → transactionsUnsupported=trueengineCanRollBack=false;engine.transaction(cb) 跑了回调、没抛、告警一次;{require:true}TransactionUnsupportedError亮对照:同一探针对 :memory: 本地实例 ⇒ 位 false、谓词 trueengineCanRollBack=true

⇒ 声明式驱动不再从这道闸漏过去

⭐ 它自己避开了本席刚踩到的那个坑

条款②读数取的是被复核那棵树上的匹配器(check-widening-tells.mjs blob 31cb9de0…),并明说 origin/main 上的 032bd9db… 没有使用。⇒ 这正是本席在 #18638 上刚栽过的「旧匹配器读数」陷阱,它没栽。

分级:minor裁出来的,不是推出来的 —— 记录里明写

RemoteTransport 去掉三个公开成员,严格 semver 读作 major。裁决第 5 条按名点了这次移除并定 minor,理由是远端 transport 从来没有兑现过事务 —— 复核证了这条理由:main 上该驱动自 62bce5c297d 起就以 501 拒绝。复核原话:「The level is RULED, not derived, and this record says so knowingly.

⭐ 本席采信这种写法,并要它成为惯例:裁出来的分级必须自报是裁出来的,⛔ 不许伪装成推导结论。

条款②:yes (widening) —— 新增可写拼法 + 新公开导出。卡上载体是无臂的 Clause-②: yes,一致,⛔ 无需重新声明。(narrowing) 不得声明:它会读作 BREAKING,与裁出来的 minor 打架。

本席欠的两张卡(③.1 / ③.2),另开,⛔ 不挡本卡

  1. 墓碑钉子对自己的主题已瞎(test-only,一行正则 + 标题)。
  2. Discovery 的 transactionalBatch 仍只从引擎推(protocol.ts:6333),于是声明了该位的组合照样广告 transactionalBatch: true,而 batchData({atomic:true}) 答 501 —— 而那条 501 的补救文案偏偏叫人「先探 capabilities.transactionalBatch」。⭐ 无方法那一半在 main 上本就如此,本 PR 只是给同一处盲点添了第二个人群,且墓碑行文如实写了。归 domain:engine

以下为达档复核记录原文,本席逐字采纳,⛔ 未编辑一字

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: ddaffccce86b5deb8fe075f5f50df8e6088120fc

Isolated contract-review subagent for the domain:spec seat. PR #18890, card #18063, branch claude/issue-18063-declare-no-transactions. Every reading below was taken by this review against the head above (worktree detached at it at /tmp/claude-0/-home-user/d31c56ec-952d-5a16-945f-6e430b9a6d34/scratchpad/pr-18890/wt, deps installed, spec/core/objectql/driver-turso closures built with .d.ts under the shared lock: 15/15 tasks, exit 0), or against origin/main at the sha named in the line. Exit codes captured before any pipe. Clone deepened (--deepen=400) before any ancestry negative was trusted; all five cited commits resolve and 873e0e8e270 is an ancestor of both head and origin/main.

① Derived judgments

Claim 1 — the merge (33844063be4) was clean and neither MIXED file needed hand resolution: CONFIRMED from the tree, not the report. Parents e3ad06ec35b (branch) + 873e0e8e270 (main); first-parent diff 95 files. packages/spec/dropped-refinements.baseline.json = blob 6622c96… and packages/spec/src/migrations/registry.ts = blob b2d7a0d… at head, at the main-side parent and at today's origin/main alike; git diff 873e0e8e270...HEAD -- (both paths) empty (control: driver.zod.ts on the same command shows 103/6). Independent of any driver: a bare --shared probe clone with no merge.os-regen.driver registered ran merge-tree --write-tree 873e0e8e270 e3ad06ec35b → exit 0 (clean) and its tree 2d66651333e1cf22915bb2cb81da049b95071fae is byte-identical to the merge commit's tree. The routed artifacts that moved on main between the two merge bases were api-surface/api.json, authorable-surface/{api,ui}.json, export-origins/api.json (8 insertions); the branch touched only the data.json shards — textually disjoint, so the driver had nothing to route. The os-regen-pending argument is not needed and not relied on.

Claim 2 — nothing else moved: CONFIRMED. git diff --numstat 0b31d90fb37 e3ad06ec35b and git diff --numstat 873e0e8e270...HEAD name the same 20 files; diff of the two listings is exactly one line, driver.zod.ts 97/3 → 103/6. The same 20-file listing is also identical against origin/main...HEAD at the time of reading (three-dot; origin/main had moved to d8b12fca97c, merge base still 873e0e8e270).

Claim 3 — the tombstone prose fix and its projection: CONFIRMED. The final commit ddaffccce touches exactly four files: driver.zod.ts (+6/−3 inside the transactions retiredKey string) and the three content/docs/references/data/driver{,-sql,-nosql}.mdx rows that carry that string (4 hunks, the transactions row only). Generated, not hand-edited: at this head check:generated (no --fix) reports check:docs content/docs/references/** ✓, so the three rows are byte-identical to what gen:docs produces from the edited string.

Claim 4 — is the new prose TRUE, or shaped for the regex? TRUE, and it is the truth the runtime now has; the regex is the part that no longer proves it.
The new opening reads: 「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.」
Checked clause by clause against the code at head: driverSupportsTransactions (driver.zod.ts:846-857) is exactly typeof beginTransaction === 'function' AND supports.transactionsUnsupported !== true; engine.transaction() calls it at engine.ts:14901; ScopedContext.transaction at :15791; txDriver() at :15885. "No longer on METHOD PRESENCE alone" is a correct description of a conjunction whose first conjunct is still presence — it is not a sentence one would write differently without the regex, and it is not shaped around it: the fact it states is the fact the pin would need to assert. What the regex adds is nothing: it matches the new text (true) AND, by my control, it also matches the OLD false sentence (true) — so driver.test.ts:124-127 no longer discriminates the true opening from the false one. That is a pin-narrowness finding (③ below), not a prose finding. The two ADR citations hold: ADR-0119 D1 is where the "callback runs without a transaction and without rollback" degrade is declared (docs/adr/0119…:90).

The fourth gate — engineCanRollBack, packages/core/src/utils/migration-journal.ts — NOW HONOURS THE DECLARATION. Measured three ways, not read off a green file.

  1. Source at head (:105-121): the driver clause is !defaultDriver || driverSupportsTransactions(defaultDriver as {...}), imported from @objectstack/spec/data (:58). Repo-wide grep for presence-shaped reads (typeof …beginTransaction, ?.beginTransaction gates, 'beginTransaction' in) across all of packages/, non-test, non-dist: the predicate's own body, two comments, and quickjs-runner.ts:621 — which reads the engine-API trio's beginTransaction (whose begin now returns null for a declaring driver, and the runner already degrades on null). Control lit: driverSupportsTransactions has 5 non-test call/import sites.
  2. Ablation at head: with the driver clause mutated back to typeof (defaultDriver …).beginTransaction === 'function' (anchor unique, marker seen on disk, blob df8d8d5…e670282…), core's migration-journal.test.ts goes 2 failed / 21 passed — exactly the two [#18063] cases; restored blob identical to HEAD, porcelain 0; unmutated run 23/23.
  3. Real chain through built dist: a real TursoDriver({url:'libsql://…'}) (remote) → supports.transactionsUnsupported = true, driverSupportsTransactions = false, and a real ObjectQL with it registered as default → engineCanRollBack = false; engine.transaction(cb) ran the callback, threw nothing, warned once naming the declaration; {require:true} threw TransactionUnsupportedError. Lit control, same probe on TursoDriver({url:':memory:'}) (local): bit false, predicate true, engineCanRollBack = true. Consumers of the gate at head: metadata-protocol/protocol.ts:12315 (atomic batch → 501 NOT_IMPLEMENTED on false) and runMigrationJournal (:473, refuses NOT_IMPLEMENTED before any journal row). A declaring driver no longer slips through.

Claim 5 — check:generated on the merged tree: CONFIRMED at this head. pnpm --filter @objectstack/spec check:generated → all 15 gates ✓, 「All 15 generated artifacts are up to date」, exit 0 (lock wrapper VERDICT command-exit 0). 15 is this head's count: PR #18971 (origin/main d8b12fca97c, after this head's base) adds a new artifact family this tree does not carry — ③.4. The 「exactly one stale artifact before --fix」 half is not re-measurable from a committed head; what is measurable is that the final commit's footprint is exactly the string plus its three projections, which is the footprint of gen:docs alone being stale.

Claim 6 — tests: CONFIRMED where re-run. core migration-journal 23 passed; objectql engine-transaction-declared-unsupported + protocol-batch-atomic 15 passed / 2 files; driver-turso turso-transactions-unsupported-declaration + turso-driver-doors-declared-types + turso-driver 112 passed / 3 files; spec pin by name (-t "transactions prescription") 1 passed | 57 skipped, and the pin file is untouched by the prose commit (git diff 33844063be4 ddaffccce -- driver.test.ts empty). spec test 489 files / 14213 tests, exit 0; spec typecheck exit 0 (tsc --noEmit + scripts + test-layer typecheck) — both under the shared lock on this worktree, exits captured per part before any pipe. CI at this head, read from the check runs: all 33 completed runs success, 2 skipped (Console Pin Gate, Packed-tarball smoke — expected skips); Lint & Repo Gates completed success at 10:08:00Z.

Other derived judgments on the diff. DriverCapabilities gains one optional key, absence = false (parse test asserts absence; my control {transactionsUnsupported: undefined} → true). Retired roster unchanged: RETIRED_BITS 31, LIVE_BITS 4; the transactions/savepoints/isolationLevels tombstones still refuse. IDataDriver.beginTransaction TSDoc gains a paragraph, signature unchanged. SqlDriver.supports spells transactionsUnsupported: false; SqlDriver.beginTransaction untouched. TursoDriver.beginTransaction() republishes ReturnType[SqlDriver['beginTransaction']] (bracket-free spelling here; the source uses the generic form) — the any is gone. RemoteTransport loses beginTransaction/commit/rollback — it IS publicly exported (driver-turso/src/index.ts:38); in-repo callers 0 (control lit), pinned objectui 53ded82bf: RemoteTransport 0, driver-turso 0, beginTransaction 0 (control: @objectstack/spec 2252 lines / 825 files). The driver-level refusal 62bce5c297d is an ancestor of head and of origin/main; refuseRemoteTransaction is live at head (8 occurrences vs 6 at base). ADR anchors exist for engine.ts and sql-driver.ts; check:adr-anchors is inside the green Lint & Repo Gates.

② Semver level

minor on all five (spec, core, objectql, driver-sql, driver-turso), and the changeset at head declares exactly that. New optional key + new export + engine dispatch = additive; absence = false keeps every existing driver's behaviour. One hunk is narrowing-shaped: three public members removed from the exported RemoteTransport. Strict semver reads that as major; ruling clause 5 orders the removal by name and sets minor on the stated ground (the remote transport never honoured a transaction, so no working behaviour is withdrawn — confirmed: on main the driver already refuses them with 501 since 62bce5c297d). The level is RULED, not derived, and this record says so knowingly. The changeset is not breaking, so the ADR-0087 disposition marker is not required (check-adr-0087-registration keys off a declared breaking/major changeset); it carries the migration-shaped prose anyway. Check Changeset green. No CHANGELOG.md, no content/docs/releases/ touched.

Clause ②: yes (widening). New authorable spelling on a published record (T1 at driver.zod.ts:270) and a new public export (T3 at api-surface/data.json:735) — read with the widening-tells matcher from the reviewed tree (scripts/pm/check-widening-tells.mjs blob 31cb9de0… at ddaffccce; the shared checkout's 025f8e51… gave the same two tells; origin/main's 032bd9db… was not used). The card's carrier reads Clause-②: yes with no arm (check-clause2-carriers --pair 18890 → exit 0, both carriers agree, correction comment 5725140321 governs) — consistent; no re-declaration needed. (narrowing) is not declared and must not be: it would read BREAKING against the ruled minor; the narrowing-shaped hunk is the ruled exception above. This is 已发布契约面 widened, not 拉回已声明契约.

③ Boundary flags — each its own card, none blocks

  1. The tombstone pin is now blind to its own subject (test-only pin tightening). driver.test.ts:124-127's regex …removed.*METHOD PRESENCE.*beginTransaction.*Delete the key matches the new true prose AND the old false one (measured). Its title 「points at method presence」 is narrow, not wrong: presence is still the first conjunct. Verdict on the seat's question: nothing in this PR; a follow-up card. The dispatch fenced the pins, the prose is true independent of the pin, and the fix is a one-line regex + title rename (…removed.*DECLARATION.*driverSupportsTransactions.*beginTransaction.*Delete the key). Does not block: a pin that under-asserts a true sentence is coverage debt, not a contract defect; test-only pins are pm:queue-eligible.
  2. Discovery's transactionalBatch still derives from the engine alone (metadata-protocol/protocol.ts:6333: typeof this.engine?.transaction === 'function'), so a composition whose default driver declares the bit — or has no beginTransaction at all — advertises transactionalBatch: true while batchData({atomic:true}) answers 501, and the 501's own remedy text says 「probe capabilities.transactionalBatch on /discovery first」. Pre-existing for the no-method case on main; this PR adds a second population to the same blindness and its tombstone prose says so truthfully (「never from this bit」). Remedy is one line: read engineCanRollBack(this.engine) there. Does not block: the ruling's scope is the engine's gating, discovery was already blind before this head, and Route & surface ownership §4 makes it a finding class (b) card, filed in domain:engine/metadata-protocol.
  3. DriverConfig.capabilities as an AUTHORED record has no runtime reader in objectql/core/runtime/metadata-protocol (grep, control: 4 driver.supports readers). The runtime reads the driver INSTANCE's supports. Pre-existing and shared with autonumber/batchSchemaSync (rows present in authorable-surface/data.json at base, lines 233-234/266); the liveness ledger does not cover DriverCapabilities (not a registered metadata type; control: queryDateGranularity absent from every ledger file too). Observation for the seat; not this PR's.
  4. Landing-time gate from PR feat(spec): pin every export by its .d.ts declaration text, and retire the 27 signature hashes #18971 (origin/main d8b12fca97c, after this head's base): the new packages/spec/api-surface-declarations/*.txt shards are absent from this tree by construction; the seat's own reading says this PR will red that gate until regenerated on a merged tree. Not a review finding; the seat's merge-and-regenerate round. Knowing this changes nothing in the verdict: every conclusion above is anchored to 873e0e8e270 as base and says so where it matters (Claims 1, 2, 5).

NOT MEASURED

Implemented-by: claude/issue-18063-declare-no-transactions
Reviewed-by: session_01LvwGppdonww4zGLWZo5rho

VERDICT: PASS — landing head ddaffccce86b5deb8fe075f5f50df8e6088120fc.


Scratch (not part of the record): review worktree left at /tmp/claude-0/-home-user/d31c56ec-952d-5a16-945f-6e430b9a6d34/scratchpad/pr-18890/wt (detached at the head, porcelain clean after the ablation restore); logs and the ablation/probe scripts beside it under …/scratchpad/pr-18890/. No GitHub writes were made. The refs/pm-review/pr-18890-head ref in the shared clone points at the landing head.


Generated by Claude Code

`main` grew `packages/spec/api-surface-declarations/` after this branch's base,
and this branch's two additions to the `./data` entry are absent from those
shards: the optional `supports.transactionsUnsupported` capability bit and the
`driverSupportsTransactions` predicate. So the family read stale the moment the
merge landed, which is what dequeued the sibling PR from the merge queue.

Regenerated from a real build with the command the queue build printed:
`pnpm --filter @objectstack/spec build && pnpm --filter @objectstack/spec
gen:api-surface-declarations`. No shard was hand-edited.

Two shards move and both moves are additive. `data.txt` gains
`transactionsUnsupported: z.ZodOptional<z.ZodBoolean>` on the five schemas that
embed the driver capabilities object and the `driverSupportsTransactions`
declaration, taking the entry from 832 exported names / 845 declarations to
833 / 846. `contracts.txt` gains the `beginTransaction` TSDoc paragraph on
`IDataDriver` that says method presence is not a transaction claim.

Nothing is removed: `check:api-surface` reads "public API surface unchanged"
both before and after the regeneration, so the exported-name set is untouched
and only declaration text moved.

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

Copy link
Copy Markdown
Collaborator Author

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: 4d118176b7f9169f355fb142c01dacb0e977dce0

Isolated contract-review subagent for the domain:spec seat. PR #18890, card #18063, branch claude/issue-18063-declare-no-transactions. MERGE-DELTA review only: scope is ddaffccce86b5deb8fe075f5f50df8e6088120fc4d118176b7f9169f355fb142c01dacb0e977dce0, i.e. merge commit 6ec251922e4 (parents ddaffccce86b + origin/main 2767af8e835, which contains #18971 d8b12fca97c) plus regeneration commit 4d118176b7f (parent 6ec251922e4). Nothing judged by the prior at-tier PASS (seat record 5728707331, head ddaffccce86b) is reopened. Every reading below was taken by this review between 2026-09-18T11:44Z and 11:58Z against the objects in the shared clone or against a fresh detached worktree at the landing head (<scratchpad>/pr-18890/r2/wt, own pnpm install --frozen-lockfile exit 0, real pnpm --filter @objectstack/spec build with .d.ts under os-verify-lock.sh: check-dts-emitted 34/34, VERDICT command-exit 0). Exit codes were captured by redirect before any pipe. ⛔ Nothing from the round's own scratch, transcript or dispatch was read; the seat's ruling comment 5729488117 and the card/PR bodies were. No GitHub writes of any kind.

① Derived judgments — the seven claims, each against my own instrument

Claim 1 — merge via os-regen-merge.sh, zero conflicts, base read before step 1 and kept, step 2 took main's side of NOTHING and KEPT all six branch-owned routed paths, step 3 had nothing extra, no os-regen-pending marker: CONFIRMED from the tree, by a stronger instrument than the script's transcript.

  • Ancestry: git log --parents ddaffccce86b..4d118176b7f = exactly two non-main commits (6ec251922e4 with parents ddaffccce86b 2767af8e835; 4d118176b7f with parent 6ec251922e4) plus the five origin/main commits between 873e0e8e270 and 2767af8e835. git merge-base ddaffccce86b 2767af8e835 = 873e0e8e270996313218738b8a1e97a5ffa16567; d8b12fca97c is an ancestor of 2767af8e835. (Shallow clone: only positive ancestry answers are relied on here, none negative.)
  • Both-sides overlap is EMPTY: comm -12 of git diff --name-only 873e0e8e270 2767af8e835 and git diff --name-only 873e0e8e270 ddaffccce86b prints nothing. Git invokes a merge driver only where both sides changed a path, so the os-regen driver ran on zero paths — which is also why no os-regen-pending marker can exist: scripts/git-merge-regen.mjs (:41) is the marker's only writer, and it was never entered.
  • Driverless probe (AGENTS.md §11's prescribed instrument): git clone --bare --sharedPROBE.git with no merge.os-regen.driver (verified absent), merge-tree --write-tree ddaffccce86b 2767af8e835 → exit 0, tree 968a41341148e8d789a4a52a6fd397d1767a32e5byte-identical to 6ec251922e4^{tree}. The committed merge IS the clean textual merge; no side was dropped anywhere. Control on the same instrument: merge-tree ddaffccce86b 873e0e8e270 = ddaffccce86b^{tree} (252e991f3fa…).
  • The six routed paths the branch owns (content/docs/references/data/driver{,-sql,-nosql}.mdx, packages/spec/{api-surface,authorable-surface,export-origins}/data.json): git diff --stat 873e0e8e270 2767af8e835 -- <six> is EMPTY (main moved none), and at 6ec251922e4 each blob == the branch's blob at ddaffccce86b and != main's. That is precisely the script's KEEPING the branch's bytes of … branch (branch edited, main did not) for all six, and the TAKING main's side branch for none. The 17 api-surface-declarations/*.txt shards at 6ec251922e4 are blob-identical to 2767af8e835's; api-surface-signatures.json is absent at both. Step 3 "nothing extra": consistent with the merge commit carrying git's default Merge remote-tracking branch 'origin/main' … subject rather than the script's step-3 subject (merge origin/main (os-regen artifacts taken from main; regeneration follows)), which is only written when step 2 changed something.

Claim 2 — routing read from the MERGED tree: CONFIRMED. git show 4d118176b7f:.gitattributes | grep -n api-surface → exactly line 146 packages/spec/api-surface/** merge=os-regen and line 147 packages/spec/api-surface-declarations/** merge=os-regen; grep signatures → 0 rows; 19 merge=os-regen rows in total (control). The only .gitattributes movement in the window is main's #18971 hunk (+…api-surface-declarations/**, -…api-surface-signatures.json); the branch never touched the file. git check-attr merge run inside the worktree at the landing head: api-surface/data.json, api-surface-declarations/{data,contracts}.txt, authorable-surface/data.json, export-origins/data.json, content/docs/references/data/driver.mdxos-regen; api-surface-signatures.jsonunspecified.

Claim 3 — the two MIXED files are NOT routed and never conflicted: CONFIRMED. check-attr mergepackages/spec/src/migrations/registry.ts: unspecified, packages/spec/dropped-refinements.baseline.json: unspecified (they appear in .gitattributes only inside comment lines 27–61). Blob ids are identical at 873e0e8e270, ddaffccce86b, 2767af8e835, 6ec251922e4 and 4d118176b7f (b2d7a0d58d1… and 6622c962124… respectively); lit control driver.zod.ts differs between main (6e0ffbb2e03…) and head (46daf837a26…). Neither side moved them, so no resolution — hand or otherwise — was possible or needed.

Claim 4 — two shards moved, BOTH moves additive, no shard hand-edited: CONFIRMED, at the line level, at the declaration level, and by byte-identical regeneration.

  • git show 4d118176b7f -- packages/spec/api-surface-declarations/ is 2 files, +24/−2. The only two - lines are the data.txt header counters # exported names: 832833 and # declarations: 845846. Every other hunk is an insertion: transactionsUnsupported: z.ZodOptional<z.ZodBoolean>; inserted into exactly five schemas (DriverCapabilitiesSchema, DriverConfigSchema, DriverInterfaceSchema, NoSQLDriverConfigSchema, SQLDriverConfigSchema), the new // ── driverSupportsTransactions (function) ── block (8 lines), and in contracts.txt nine TSDoc lines inserted between * Begin a new database transaction. and * @returns … inside IDataDriver.beginTransaction — the signature lines around it are untouched. (Why a TSDoc edit reaches a "shape" artifact whose header says leading TSDoc is excluded: this comment is inside the interface body, so it is part of IDataDriver's own getText(); the shape is unchanged.)
  • Declaration-level accounting from the gate itself (probe B below, both shards at main's bytes = the pre-regeneration tree, since 2767af8e835: and 6ec251922e4: blobs are identical for both): 0 removed, 1 added, 6 reshaped — the 6 "reshapes" are the five optional-key insertions plus the comment insertion; nothing narrowed, nothing removed.
  • No hand edit, my instrument: after the real build, gen:api-surface-declarations (exit 0, Wrote api-surface-declarations/ (17 entry points, 5337 declarations, 12.08 MiB)) reproduced both committed blobs exactly (contracts.txt cccfda3cc7f4…, data.txt 46025c2e6a6a… == HEAD:), git status --porcelain -- packages/spec/api-surface-declarations 0 lines; gen:api-surface likewise left api-surface/ at 0 lines; whole-tree porcelain 0.

Claim 5 — check:generated 16, check:api-surface-declarations exit 0 (17 / 5337), check:api-surface exit 0 before AND after: CONFIRMED. On the landing tree: check:generated exit 0 — Checking 16 generated artifacts✓ All 16 generated artifacts are up to date. (the roster grew from 15 to 16 by #18971's check:api-surface-declarations entry, read from the main-side hunk of check-generated.ts); check:api-surface-declarations exit 0 — declaration text unchanged ✓ (17 entry points, 5337 declarations); check:api-surface exit 0 — public API surface unchanged ✓. "Before": on the pre-regeneration tree (probe B) check:api-surface is also exit 0 with the same line — necessarily, because the regen commit touches nothing under api-surface/ (git diff --stat 6ec251922e4 4d118176b7f -- packages/spec/api-surface/ authorable-surface/ export-origins/ empty) and that gate never reads the declarations directory. "Converged in one pass": the committed regeneration equals one generator run from a fresh build (Claim 4), which is what a one-pass --fix produces; the round's --fix transcript itself was not re-run.

Claim 6 — 22 files = the prior 20 + the 2 shards: CONFIRMED. git diff --name-only 2767af8e835 4d118176b7f = 22 (GitHub changed_files: 22); set-difference against the prior review's 20-file list (873e0e8e270...ddaffccce86b) is exactly api-surface-declarations/contracts.txt and data.txt, nothing the other way. git diff --stat ddaffccce86b 4d118176b7f -- <the 20 files> is EMPTY — none of the PR's own files moved in this delta.

Claim 7 — the mutation probe: REPRODUCED, and it proves what it claims — with one limit the round did not state, which I closed with a second probe. Probe A (as the round did): git restore --source=2767af8e835 -- contracts.txt (tree-only, unstaged) → on-disk hash 5362717e751b… == main's blob, added-paragraph grep 0 (the diff proves it is ≥1 at HEAD), porcelain M …contracts.txt → standalone gate exit 1, naming contracts.txt / ~ IDataDriver (interface) (declaration text changed) / 0 removed, 0 added, 1 reshaped; restore git checkout HEAD -- <absolute path> → hash cccfda3cc7f4… == HEAD:, porcelain 0; gate exit 0 again. What that proves: the on-disk shard is a real input the gate compares against the fresh dist, the committed bytes are the generator's (green with them, red with main's), and the gate discriminates on this shard. What it does NOT prove: anything about data.txt — a one-shard probe is a one-shard control. Probe B (both shards at main's bytes, hashes verified equal to 6ec251922e4:): standalone gate exit 1 naming both shards with the full 7-row inventory (data.txt: 5 ~ …Schema (const) + + driverSupportsTransactions (function); contracts.txt: ~ IDataDriver (interface)), 0 removed, 1 added, 6 reshaped; the aggregate check:generated exit 1 printed ✗ check:api-surface-declarations with only three detail lines (data.txt, ~ DriverCapabilitiesSchema, ~ DriverConfigSchema) — contracts.txt and four data.txt rows cut, exactly the .slice(0, 3) at check-generated.ts:656; check:api-surface exit 0 on that same tree. Restored both via git checkout HEAD -- <abs>; hashes equal HEAD:, whole-tree porcelain 0; post-probe standalone gate exit 0, 5337 declarations.

⭐ The seat's ruling A (comment 5729488117) — independently verified, and it is RIGHT

Re-measured at 4d118176b7f, with hitting controls on the same instrument (git grep -c <token> <sha> -- <dir>):

token packages/spec/api-surface/ packages/spec/api-surface-declarations/
RemoteTransport 0 files 0 files
lit DriverCapabilities 2 files (contracts.json 1, data.json 2) 2 files (contracts.txt 5, data.txt 4)
lit beginTransaction 0 files — lit control on this dir: IDataDriver 1 file; the rows are name (kind) only, so a member name cannot appear here by construction 2 files (contracts.txt 3, data.txt 2)
lit driverSupportsTransactions 1 file 2 files
dark ZzQxNonsenseTokenQxZz 0 0

RemoteTransport is declared once, at packages/drivers/driver-turso/src/remote-transport.ts:1104, exported from driver-turso/src/index.ts:38. Both spec artifacts are built by collectEntries() from packages/spec/package.json's 17 typed exports entries — driver-turso is not an input to either gate by construction, and RemoteTransport's only appearances under packages/spec/ are CHANGELOG, spec-changes.json, migration entries and comments, none under api-surface*/. The three members are present at 873e0e8e270 (remote-transport.ts:1845/1850/1854) and absent at ddaffccce86b and 4d118176b7f; git diff --stat ddaffccce86b 4d118176b7f -- packages/drivers/driver-turso/src/ is EMPTY — the removal predates this delta and the delta does not touch it (main's only driver-turso change in the window is a README fetch typing example, lines 171–175, unrelated). ⇒ The dispatch's STOP clause named an instrument that (a) cannot see a driver-turso removal at all and (b) does not even own the words REMOVED/RESHAPED (those are the declarations gate's). The pre-regeneration red the declarations gate DID report is exactly probe B's seven rows — all insertions of an optional key, a new function, or comment text — this PR's own additive change, already judged 「扩大公开面」 by the prior PASS. Nothing in this delta touches the ruled minor; A (accept, do not re-rule) is the correct ruling. One reading to keep beside it: the declarations gate's advisory 「A REMOVED or RESHAPED declaration is a breaking change … rule minor/major」 fires on ANY text change including a comment insertion — "reshaped" in that gate's vocabulary is a prompt to read the diff, not a narrowing finding.

② Semver level and Clause ② for the delta

The changeset at head (.changeset/18063-transport-declares-no-transactions.md, unchanged by the delta) declares minor on all five packages; the delta adds no package, no export, no key and no closed-set member beyond what the prior PASS judged, so minor stands, and the ruled exception (RemoteTransport removal at minor by ruling clause 5) is untouched — see above.

Clause ② for the delta: yes, by restatement only. SKILL.md:515 「条款②只指已发布契约面,拉回已声明契约不触它;卡面复述仍是条款②」 — the two regenerated shards are the shape-half projection of the same published-contract widening (new optional key on a published record, new public export) that the standing declaration already covers; they widen nothing further and narrow nothing. The declaration therefore needs no change: PR body and card must remain yes, and ⛔ (narrowing) must not be declared (it would read BREAKING against the ruled minor). ⚠️ One correction to the brief: the PR body as read at 2026-09-18T11:47Z carries a bare Clause-②: yes (line 3), not yes (widening); yes (widening) is the direction the prior review record derived, not text on the body. The card is bare yes via correction 5725140321 (which the carriers reader selects, attributed to session_01LvwGppdonww4zGLWZo5rho). node scripts/pm/check-clause2-carriers.mjs --pair 18890 at the landing head, from the reviewed tree (script blob 3a270ef2eb5…), exit 0: both carriers readable and agreeing, thread complete (15 card comments / 3 PR comments, ladder stopped on a short page). Bare yes is a well-formed spelling (AGENTS.md Post-Task §3: yes|no plus at most one arm), so this is consistent, not a defect; adding (widening) to the body would be optional and harmless.

③ Does the delta change any conclusion of the prior PASS? NO.

None of the 20 PR files moved (Claim 6); origin/main in the window touched nothing under packages/spec/src, packages/objectql, packages/core, packages/metadata-protocol or packages/drivers/*/src (only READMEs, plugin-hono-server, packages/lint, docs and #18971's spec tooling); the two MIXED files are byte-stable on every side (Claim 3); every gate the prior record cited is green again on the merged tree; the prior record's boundary flag ③.4 (landing-time gate from #18971) is exactly what this delta discharges, and its check:generated count 15 is superseded by 16 as that flag predicted. The prior PASS's ①/②/③ stand unchanged.

Boundary flags — each with why it does not block

  1. PR body reads bare Clause-②: yes, not yes (widening) as the brief states. Not blocking: valid spelling, both carriers agree, carriers predicate exit 0, Check Changeset success at head; the arm is review-derived.
  2. Declarations-gate vocabulary trap: "reshaped" = any declaration-text change (a comment insertion counts). Not blocking: probe B's seven rows were read one by one and are all widenings; the ruling reads the diff, not the word.
  3. check:generated truncates a failing gate's list to three lines — reproduced in probe B (contracts.txt and four data.txt rows cut). Not blocking: the standalone gate carries the inventory, which is what this review and the round used.
  4. packages/drivers/driver-turso/README.md:25 still lists RemoteTransport: beginTransaction, commit, rollback — present at 873e0e8e270, untouched by branch and by main, so it has been stale since the members were removed at the prior head, ⛔ not a delta change; hand-written (no generator names the file). Not blocking: outside this delta's scope and not a contract artifact; a docs-accuracy follow-up (acceptance note or docs-only PR) for the seat.
  5. A member TSDoc edit reaches the "shape" artifact (contracts.txt +9 comment lines inside IDataDriver) despite the header's "leading TSDoc excluded" — instrument behaviour (getText() of the enclosing interface), not drift. Not blocking; a reader note for the next review that sees a comment-only "reshape".
  6. Shallow clone (.git/shallow present, 423 commits): all six named commits resolve, merge-base answered; no ancestry negative was relied on. Not blocking.
  7. os-regen-pending absence was not read from the round's $GIT_DIR (its worktree is not mine to read); it is established mechanically — empty both-sides overlap plus a byte-identical driverless merge tree — which is stronger than a transcript line. Not blocking.

NOT MEASURED

  • The round's os-regen-merge.sh transcript itself (its KEEPING lines, the marker file) and its check:generated --fix pass — replaced by tree-identity and byte-identical regeneration, not re-run.
  • CI convergence at 4d118176b7f: at 2026-09-18T11:53Z Lint & Repo Gates and Test Core (1/6, 5/6, 6/6) were in_progress; TypeScript Type Check, Type Check · consumer gates (the job that runs check:api-surface and check:api-surface-declarations, success 11:46:42Z), Type Check · workspace, Type Check · debt ledger, Build Core, Governed Surface Queue Guard, Check Changeset, Temporal Conformance, Dogfood Regression Gate were success; Console Pin Gate and Packed-tarball smoke skipped (expected). The seat's landing check ③ (every check success) is the seat's to read after convergence.
  • Whole-package test suites and pnpm typecheck at the landing head — no code file moved in the delta; CI's.
  • The merge queue's server-side rebuild of this head (the only instrument for that question).
  • objectstack-ai/cloud consumers and the objectui pin grep for RemoteTransport — unchanged from the prior record, not re-run.
  • This review's own served tier — not self-attestable; the seat reads it from the harness stamps.

Implemented-by: claude/issue-18063-declare-no-transactions
Reviewed-by: session_01LvwGppdonww4zGLWZo5rho

Scratch (not part of the record): review worktree at /tmp/claude-0/-home-user/d31c56ec-952d-5a16-945f-6e430b9a6d34/scratchpad/pr-18890/r2/wt (detached at the landing head, porcelain 0 after both probe restores); driverless probe clone …/pr-18890/r2/PROBE.git; gate logs …/pr-18890/r2/{gates.out,1-*.log,2-*.log,3-*.log,4-*.log,5-*.log,clause2-pair.log,build.log,install.log}. No GitHub writes were made.

VERDICT: PASS — landing head 4d118176b7f9169f355fb142c01dacb0e977dce0.


Generated by Claude Code

Copy link
Copy Markdown
Collaborator Author

✅ 增量复核 PASS —— 落地 head 4d118176b7f9169f355fb142c01dacb0e977dce0

domain:spec 席位,session_01LvwGppdonww4zGLWZo5rho,2026-09-18T12:03Z。记录原文见紧接本评论之前的那一条(5729696078),⛔ 逐字采纳、未编辑一字。拆两条的原因同前(记录带它自己的裸时间戳,与 {{NOW}} 同体会被判 MIXED)。

档位核验(本席自取): claude-fable-5-1 103 / 103,off-tier 0

它用了比「脚本自述」更强的仪器

⭐ 它没有采信 os-regen-merge.sh 的运行记录,而是从树上证:两侧改动路径集 comm -12 为空 ⇒ git 只在两侧都改的路径上调驱动,驱动跑过 0 条路径,所以 os-regen-pending 标记在机制上不可能存在 —— 这比读一行 transcript 强。再加无驱动裸克隆merge-tree --write-tree,产出的树与已提交合并树字节相同

「没手改分片」也是它自己证的:真构建后重跑生成器,两个分片的 blob 逐字复现,porcelain 0。

⭐ 它还把施工轮那个变异探针的局限点出来并补上:单分片探针只是单分片对照。它另做了一次两个分片同时退回 main 字节的探针,拿到完整的 7 行清单,并顺带复现了 check:generated 只印 3 行的截断(本席已立卡 #19015)。

本席的裁定 A 被独立复验:

RemoteTransport 在两个 spec 产物里都是 0 个文件(亮对照 DriverCapabilities 2/2、driverSupportsTransactions 1/2;暗对照 0),它只声明在 driver-turso。两个产物都由 packages/spec/package.json 的 17 个 typed exports 入口构建 ⇒ driver-turso 按构造就不是它们的输入。三个成员的移除早于本 delta,且本 delta 根本没碰 driver-turso/src

⭐ 它补了一条本席该记住的读法:声明闸门里的 “reshaped” 只是「声明文本变了」,连插一段注释都算。⇒ 那句「REMOVED or RESHAPED is breaking … rule minor/major」是叫人去读 diff 的提示,⛔ 不是「收窄了」的判定。本轮 7 行全是加法。

⛔ 一条对本席派发令的更正

本席在派发令里写 PR 正文的声明是 Clause-②: yes (widening)正文实际是裸的 Clause-②: yes;(widening) 是上一轮复核推导出的方向,不是正文上的字。⇒ 两种拼法都合规(yes|no至多一个臂),双载体一致、--pair 18890 exit 0,不是缺陷;但本席的派发令引错了正文,记在明处。

一条留给后人的 docs 债(⛔ 不挡本卡)

packages/drivers/driver-turso/README.md:25 仍列着 RemoteTransport: beginTransaction, commit, rollback —— 那三个成员在上一个 head 就已移除,所以这条 README 从那时起就是陈的,⛔ 不是本 delta 造成的,也没有生成器管这个文件。归后续 docs 处理。


Generated by Claude Code

@os-litant
os-litant marked this pull request as ready for review September 18, 2026 12:04
@os-litant
os-litant added this pull request to the merge queue Sep 18, 2026
@github-merge-queue
github-merge-queue Bot removed this pull request from the merge queue due to failed status checks Sep 18, 2026
The os-regen driver deferred on packages/spec/api-surface-declarations/data.txt
(both sides changed it), so the merge commit carried this branch's side and
silently dropped main's. Step 2 restored main's side into the worktree and this
regeneration re-derives the branch's declarations on top, so the shard now holds
both: this branch's `transactionsUnsupported` / `driverSupportsTransactions`
declarations and main's `ResolveApiOptions.userExportAllowed` doc block.

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

Copy link
Copy Markdown
Collaborator Author

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: e53dc612b4822691edd263655ecb264312bd55f0

Isolated contract-review subagent for the domain:spec seat. PR #18890, card #18063, branch claude/issue-18063-declare-no-transactions. MERGE-DELTA review only: scope 4d118176b7fe53dc612b48, i.e. merge commit 2bab580f284 (parents 4d118176b7f + origin/main dbd474431fd) plus regeneration commit e53dc612b48 (parent 2bab580f284). The two prior PASSes (5728707331 at ddaffccce86b, 5729709192/5729696078 at 4d118176b7f) are not reopened. Readings taken 2026-09-18T13:32Z–13:46Z against refs/pmrev/main-tip = dbd474431fdf39a2a3d31f3f6f0475eb51fa70f7 and refs/pmrev/pr18890-head = e53dc612b4822691edd263655ecb264312bd55f0 (fetched into refs I own; clone deepened 200 on the PR head; merge-base 4d118176b7f dbd474431fd = 2767af8e835 answered; only positive ancestry answers relied on). Exit codes captured by redirect before any pipe. Nothing from the round's own scratch, transcript or dispatch was read; the round's report (5730687284), the correction (5725140321), the PR body and the prior records were. No GitHub writes of any kind.

Instrument blobs named (⚠️ the shared checkout HEAD 55523fdee7a is 94 commits behind main): scripts/pm/check-clause2-carriers.mjs ccd5ad7c9a00fe703d644be261a24f1ed847915a (self-reported by the run, matches git hash-object); scripts/pm/os-verify-lock.sh 8ad1813feff415727c4bd16f834707d20c6fc34c (identical at shared HEAD and at e53dc612b48); scripts/git-merge-regen.mjs 4506a6d5662f19e7197eaf7fdce22b587f185164 (identical at 4d118176b7f, e53dc612b48, dbd474431fd — the driver my reproduction ran); scripts/check-regen-pending.mjs b0ca371b6967c441b6459a9a76f214431b101e4a (read, not run). scripts/pm/os-regen-merge.sh was NOT run by this review; its header was read from the shared checkout's blob 9fa71c1454f9…, which is stale against main's/head's c291f1d784dc… — no output of it is quoted here.

① Derived judgments — the five claims, each against my own instrument

Claim 1 — the driver fired and deferred on data.txt, the merge commit kept one side, the regeneration restored both: CONFIRMED, reproduced with the driver ON and with the driver OFF.

  • Routing: .gitattributes line 147 at both e53dc612b48 and dbd474431fd reads packages/spec/api-surface-declarations/** merge=os-regen; git check-attr merge with the head's attributes → os-regen.
  • Both-sides set: comm -12 of git diff --name-only 2767af8e835 4d118176b7f (22 paths) and … 2767af8e835 dbd474431fd (70 paths) = exactly one path, packages/spec/api-surface-declarations/data.txt. Git invokes a merge driver only where both sides changed a path, so this delta is the first in the PR's history where the driver had a path to act on (the prior delta's set was empty).
  • Driver-ON reproduction (throwaway linked worktree detached at 4d118176b7f, this clone's registered merge.os-regen.driver): git merge --no-commit --no-ff dbd474431fd → exit 0, driver printed not text-merged — it is generated. Regenerate from the merged tree, the marker $GIT_DIR/os-regen-pending was created holding exactly one line, packages/spec/api-surface-declarations/data.txt; the worktree AND index blob of data.txt = 46025c2e6a6aaa39f523a96e8668a4f5c55dbb47 = the branch-pre blob; main's marker sentence 0, transactionsUnsupported 6. git merge --abort, worktree removed.
  • Blob identity in the real history: data.txt at base a034ec0d68…, branch-pre 46025c2e6a…, main bdf043b1be…, merge commit 2bab580f284 = 46025c2e6a… (identical to branch-pre), regen 04309e5c34…. git show --stat 2bab580f284 names data.txt 0 times; the second-parent diff (dbd474431fd..2bab580f284) names it (32 lines) — the "invisible in the diffstat" symptom exactly as the script header describes.
  • Driver-OFF probe (bare --shared clone, git config --get merge.os-regen.driver exit 1 = unset): merge-tree --write-tree dbd474431fd 4d118176b7f → exit 0, tree ace5c59f61a3554b9909b2e930270e4257f22e39. diff-tree of that tree against 2bab580f284^{tree} (75e5c42846dd…) = exactly one path, M data.txt (the drop); against e53dc612b48^{tree} = empty — the landing head's tree IS the driver-free three-way merge of the two sides. Control on the same instrument: merge-tree 2767af8e835 dbd474431fd = main's own tree.
  • Regen commit e53dc612b48: 1 file, data.txt +13/−2; git diff --name-status 2bab580f284 e53dc612b48 = only data.txt.

Claim 2 — both-sides survival at the head: CONFIRMED, re-measured with my own controls, and the round's numbers reproduce cell for cell. grep -cF per blob (regen e53dc612b48 / base 2767af8e835 / branch-pre 4d118176b7f / main dbd474431fd / MERGE 2bab580f284):

  • transactionsUnsupported 6 / 0 / 6 / 0 / 6
  • driverSupportsTransactions 2 / 0 / 2 / 0 / 2
  • The flag carries the user-level export axis 1 / 0 / 0 / 1 / 0 — the 0 in the merge commit is the silent drop; the 1 at head is the repair
  • apiExposureDenialReason 3 / 2 / 2 / 3 / 2
  • negative control thisTokenShouldNotExist_xyzzy 0 / 0 / 0 / 0 / 0
  • my same-subject positive controls: userExportAllowed 2 / 2 / 2 / 2 / 2; ResolveApiOptions 3 / 3 / 3 / 3 / 3; DriverCapabilities 4 / 4 / 4 / 4 / 4 — the reader hits on every blob it was pointed at, so every zero above is a reading.
  • Line counts 23856 / 23832 / 23845 / 23843 / 23845. A driver-free git merge-file of the three blobs (base/ours/theirs) exits 0 and its output is byte-identical to the regenerated blob (diff 0 lines). Lines of main absent at head: exactly the two header counters (# exported names: 832, # declarations: 845 → 833/846). Lines of branch-pre absent at head: exactly the two lines of the old sentence flag. Always true this phase … (#3391 follow-up) that main's docs(spec): the user-level export slot is a real opt-in grant, not "always true this phase" #19010 (75c0dacffb6, the only main commit touching data.txt in the window) rewrote — main's replacement carried, the superseded sentence not resurrected. Lines at head absent from both parents: 0. Lines of main absent from the MERGE commit: 13 (the two counters + the 11-line doc block) — the drop, measured.

Claim 3 — gates: PARTIALLY verified; the local re-runs did not complete before hand-back and are listed under NOT MEASURED, ⛔ not adopted. What was read: fresh worktree at e53dc612b48, pnpm install --frozen-lockfile exit 0; the locked turbo run build --force of the spec/core/objectql/driver-sql/driver-turso closures was in flight at hand-back (@objectstack/spec build succeeded incl. DTS, check-dts-emitted passing; core/metadata packages building). CI at the landing head (read 13:41Z): run 35350149086 Type Check · consumer gates success 13:25:45–13:33:42Z (the job that runs check:api-surface and check:api-surface-declarations, and the job that segfaulted in the queue build), Type Check · source gates success, Type Check · debt ledger success; run 35350149224 Build Core, Temporal Conformance (live PG + MySQL), Dogfood Regression Gate (+1/3, 2/3, 3/3), Dogfood Verify CLI, Build Docs, Test Core (2/6), Test Core (4/6) success; Check Changeset, Governed Surface Queue Guard, the claim/single-writer checks success; Console Pin Gate and Packed-tarball smoke skipped (expected). Test Core (1/6, 3/6, 5/6, 6/6), Type Check · workspace, Lint & Repo Gates were in_progress. mergeable_state: blocked (not dirty), draft: false, needs:contract-review present.

Claim 4 — six files byte-equal to main at the head: CONFIRMED by blob id, with a lit control. packages/spec/dropped-refinements.baseline.json 1caca158f8…, packages/spec/src/migrations/registry.ts 541ea75d87…, api-surface-declarations/automation.txt 283e0f069a…, api-surface-declarations/root.txt 6c83181913…, api-surface/root.json d0a072af86…, export-origins/root.json aae573e566… — each head == main, and each differs from branch-pre (i.e. main moved them and the delta took main's side). Lit control on the same instrument: data.txt head ≠ main; contracts.txt head == branch-pre ≠ main (branch-only edit, correctly kept). The 22-file diff --name-status dbd474431fd e53dc612b48 equals the PR's 22 get_files rows; 0 governed-surface paths; the changeset (minor on all five packages) is byte-equal across the delta.

Claim 5 — the prescribed lit control did not light, and the synthetic substitute: JUDGED ADEQUATE for what that probe can attest, with one scope caveat the seat should carry forward. Reproduced: the driver-free merge-tree --write-tree dbd474431fd 4d118176b7f exits 0 because the sides' hunks are far apart — branch hunks at data.txt lines 15, 5290, 5357, 5430, 15138, 21564, 22874; main's single hunk 21413–21424; nearest separation ~140 lines, far outside any conflict window — so the prescribed expectation (non-zero) was a wrong premise, not a dead instrument. The round's synthetic control reproduces on my own scratch repo: same-line edits → exit 1, CONFLICT (content); a far-apart pair → exit 0. That is a proper liveness control for the driver-free merge-tree probe. Caveat: that probe measures GitHub-style text mergeability, ⛔ not os-regen deferral — the driver defers on ANY both-sides change regardless of line distance. The deferral has its own lit instrument (the driver-ON reproduction above: marker written, branch-side blob kept), and the silent drop its own (the grep matrix with hitting controls, and the tree diff naming exactly data.txt). Both were run here, so the substitution leaves no gap in this record.

Clause-② carriers: node scripts/pm/check-clause2-carriers.mjs --pair 18890 (blob ccd5ad7c…) → exit 0 at head e53dc612b48: the card's declaration is read from correction 5725140321 (Clause-②-correction: 5725112884, Clause-②: yes), the PR body carries bare Clause-②: yes, both carriers agree; 16 card comments / 5 PR comments, both threads complete.

② Semver level and Clause ② for the delta

minor on all five packages stands — the changeset is unchanged by the delta and the delta adds no export, key, member or code line of the PR's own (the only branch-side change is the regenerated shard). Clause ② for the delta: yes, by restatement only (SKILL.md:515 「条款②只指已发布契约面,拉回已声明契约不触它;卡面复述仍是条款②」): the regenerated data.txt is the shape-half projection of the same published-contract widening the standing declaration already covers, plus main's own already-landed doc block; nothing is widened further and nothing narrowed. The standing bare Clause-②: yes on the PR body and the card is correct and sufficient; ⛔ no (narrowing) arm is to be added.

③ Does the delta change any conclusion of the two prior PASSes? NO.

The both-sides set is exactly data.txt, so main touched none of the PR's other 21 files in the window; the merge commit against branch-pre is main's 18 first-parent commits and nothing else; the regeneration commit touches only data.txt and equals a driver-free text merge byte for byte. The prior delta record's Claims 1–7 and the full record's ①②③ (including the ruled minor for the RemoteTransport removal and the four transaction gates) rest on files this delta did not move. The prior record's boundary flag ③.3 (check:generated truncates to three lines) is not exercised here — nothing was red.

Boundary flags — each with why it does not block

  1. Queue build 35343167521 (merge_group, head 0044b6538c3 = the queue's generation of the PRE-delta tip, not this head): job 105593530486 Type Check · consumer gates@objectstack/spec#build exited 139 mid-way through gen:schema's per-file listing (✓ api/ListInstalledPackagesRequest.json then ELIFECYCLE … 139), Tasks: 2 successful, 3 total, no gate verdict; aggregator 105596471737 failed solely on typecheck-consumers: failure. A runner/process death, not a finding: the same spec build succeeded locally at this head (ESM/CJS/DTS, check-dts-emitted) and the same job is success on the PR build of this head. Not blocking; the seat re-queues.
  2. The seat's dispatch prescribed a lit control that cannot light on this input (see Claim 5). Not blocking; carry the caveat: a merge-tree exit code is evidence about text mergeability, never about os-regen deferral.
  3. Shared checkout is 94 commits behind main and its os-regen-merge.sh blob (9fa71c14…) is stale against main's (c291f1d7…). Not blocking: no output of that script is quoted; the two pm instruments this review ran are blob-named and identical at head.
  4. scripts/pm/os-verify-lock.sh is mode 100644 at head — a direct invocation returns exit 126 (permission denied); the first launch's seven exit codes were discarded as void, not counted, and the runs relaunched through bash. Not blocking; a tooling note (the script's own usage line shows a direct invocation).
  5. CI at the landing head had not converged at read time (Test Core 1/3/5/6, Type Check · workspace, Lint & Repo Gates in progress). Not blocking for this record; the seat's landing pre-check ③ (every check success, contract-review.md) is read after convergence.
  6. Shallow clone: deepened before any ancestry answer; all six named commits resolve, merge-base answered, --is-ancestor used for positive answers only. Not blocking.
  7. Docs Drift Check (5725105588) reports data.txt/contracts.txt yield no doc anchor. Informational; generated artifacts are outside its coverage by design.

NOT MEASURED

  • Local check:generated (the 「All 16 generated artifacts are up to date」 line), spec test 491 files / 14299 tests, typecheck, and the six named gates (core migration-journal 23; objectql engine-transaction-declared-unsupported 8 + protocol-batch-atomic 7; driver-turso transactions-unsupported-declaration 4 + doors-declared-types 10 + turso-driver 98) at e53dc612b48 — my locked runs were still in the build step at hand-back; they are ⛔ not part of this record. Corroboration available from an independent instrument: CI's Type Check · consumer gates success at this head (runs check:api-surface + check:api-surface-declarations), Build Core success, Test Core 2/6 and 4/6 success; the remainder was in progress.
  • The standalone check:api-surface-declarations full inventory at head — not run locally (needs the build); CI's consumer-gates success at head is the reading.
  • The round's os-regen-merge.sh transcript and the step-3 pre-commit refusal — not re-run; the marker-writing half was reproduced, and the refusal path is by reading .githooks/pre-commit:28check-regen-pending.mjs || exit 1.
  • Semantic interaction of main's 69 other window paths with the PR's behaviour — no source overlap; CI Test Core at head is the instrument.
  • The merge queue's server-side rebuild of e53dc612b48 — only the queue can.
  • This review's own served tier — not self-attestable; the seat reads the harness stamps.

Implemented-by: claude/issue-18063-declare-no-transactions
Reviewed-by: session_01LvwGppdonww4zGLWZo5rho

Scratch (not part of the record): review worktree scratchpad/pr-18890/r3/wt (detached at the landing head, porcelain 0); the locked verification driver scratchpad/pr-18890/r3/verify.sh was still running at hand-back (lock holder pid 11640, held since 13:38:33Z; it proceeds build → checkgen → spectest → typecheck → gate-core → gate-objectql → gate-turso and releases the lock itself; exit files land as scratchpad/pr-18890/r3/NAME.exit beside NAME.log, ALL-DONE at the end) — the seat may read those as its own readings or stop only that recorded pid; blob-measurement files scratchpad/pr-18890/data.{base,ours,theirs,merge,regen,textmerge}.txt, pair.out, both-sides.txt, delta-vs-main.txt. The repro worktree, probe clone and synthetic repo were removed. No GitHub writes were made.

VERDICT: PASS — landing head e53dc612b4822691edd263655ecb264312bd55f0.


Generated by Claude Code

Copy link
Copy Markdown
Collaborator Author

✅ 增量复核 PASS —— 落地 head e53dc612b4822691edd263655ecb264312bd55f0

domain:spec 席位,session_01LvwGppdonww4zGLWZo5rho,2026-09-18T13:46Z。记录原文见紧接本评论之前的那一条(5730895948),⛔ 逐字采纳、未编辑一字。

档位核验(本席自取): claude-fable-5-1 89 / 89,off-tier 0

⭐ 这一轮把「静默丢失」证死了,而且是三件独立仪器

上一轮并轮报告说:合并提交把 main 那一侧丢了,钩子挡下、重生成修回。本轮不采信,自己重做:

  1. 驱动开着复现 —— 在一次性工作树里真跑 git merge:驱动打印「not text-merged — it is generated」,$GIT_DIR/os-regen-pending 被写出、内容恰好一行 data.txt,而工作区与索引里的 blob 等于并前分支那一侧
  2. 驱动关掉探针 —— 裸 --shared 克隆(git config --get merge.os-regen.driver exit 1 = 确无驱动):merge-tree 产出的树对 合并提交的树 差异恰好一条 data.txt(那就是丢失),对 落地 head 的树 差异为空 ⇒ ⭐ 落地 head 的树就是两侧的无驱动三方合并。
  3. grep 矩阵,每个 blob 都有命中对照 —— main 的记号在 base/并前/合并提交读 0、在 main 与 head 读 1;而同主题正对照 userExportAllowed 2/2/2/2/2、ResolveApiOptions 3/3/3/3/3、DriverCapabilities 4/4/4/4/4 在每个 blob 上都命中上面每一个 0 都是读数,不是仪器空转。

还有两条收口:git merge-file 三方合并的输出与重生成 blob 逐字节相同;「head 上有、两个父都没有的行 = 0」 ⇒ 重生成没有凭空造出任何东西

⛔ 本席那条亮对照错在哪 —— 它说清了,本席照收

本席让它拿「并前尖端」当亮对照、期望非零。实测 exit 0。它给出原因:两侧的 hunk 相距约 140 行,远在冲突窗口之外 ⇒ 本席的期望是错的前提,不是仪器坏了

⭐ 并且它点出本席混淆了两件事:merge-tree 的退出码衡量的是「文本可合并性」,⛔ 从来不是「os-regen 是否延迟」 —— 驱动在两侧都改时一律延迟,与行距无关。所以它另外跑了两件专门的仪器(上面 1 与 3)来覆盖延迟与丢失,没有留下缺口

⛔ 它自己声明没测完的部分,本席不替它圆

复核的本地门禁重跑在交接时还没跑完,它把整段列进 NOT MEASURED 并明写「⛔ 不属于本记录」。本席采信这种诚实,并补上本席自己的读数:它留下的后台验证器已产出 build.exit=0checkgen.exit=0;其余仍在跑。

落地前置 ③(每一项 check 都 success)由本席在 CI 收敛后自读 —— 复核明写这一条归席位,本席照办,⛔ 不在未收敛时入队。

那次段错误,归档为 runner 死亡

队列构建 35343167521exit 139 复核也查了:是 @objectstack/spec#buildgen:schema 逐文件列表中途 ELIFECYCLE … 139,没有打印任何闸门裁决;而同一个 job 在本 head 上是 success。⇒ 进程死亡,不是发现。本席据此重新入队,⛔ 不去找一个能解释段错误的「缺陷」。


Generated by Claude Code

Copy link
Copy Markdown
Collaborator Author

📌 复核记录升级 —— 同一裁断、同一 head,Claim 3 从「部分」变为「实测」

domain:spec 席位,session_01LvwGppdonww4zGLWZo5rho,2026-09-18T13:50Z。

复核席在交接后把它自己锁下的重活跑完了并重发了记录。裁断行与落地 head 逐字未变(本席 diff 过:**VERDICT: PASS**e53dc612b48 两版相同)。档位重取:claude-fable-5-1 94 / 94,off-tier 0。

本席不贴第二份 16KB 的近似重复 —— 已贴的 5730895948 仍是记录本体。两版的实质差异只有一处,列在这里:

Claim 3:PARTIALLY verifiedCONFIRMED(三项在锁下跑完)

运行 读数
turbo run build --force(spec/core/objectql/driver-sql/driver-turso 闭包) Tasks: 15 successful, 15 total,VERDICT command-exit 0,持锁 271s
check:generated 「Checking 16 generated artifacts … ✓ All 16 generated artifacts are up to date.」,VERDICT command-exit 0,持锁 79s
pnpm --filter @objectstack/spec test Test Files 491 passed (491) / Tests 14299 passed (14299),VERDICT command-exit 0,持锁 189s

⇒ 上一版 NOT MEASURED 里的两条(本地 check:generated 与 spec test)已被它自己测掉。typecheck 与六条点名门禁仍排在同一把锁后面,仍列为 NOT MEASURED —— ⛔ 它没有把没跑的说成跑了,本席也不替它圆。

本席独立读它留下的 .exit 文件,与上表一致:build.exit=0checkgen.exit=0spectest.exit=0

⛔ 仍然归席位的那一条,本席照担

落地前置 ③(每一项 check 都 success)在 CI 收敛后由本席自读 —— 复核明写这条归席位,并在它的读数时点(13:41Z)还有 Test Core 1/3/5/6Type Check · workspaceLint & Repo Gates 在跑。本席已挂「全绿才入队」的守卫,⛔ 未收敛不入队。


Generated by Claude Code

@os-litant
os-litant added this pull request to the merge queue Sep 18, 2026

Copy link
Copy Markdown
Collaborator Author

⛔ 更正本席上一条增补(5730952177)里的一句

domain:spec 席位,session_01LvwGppdonww4zGLWZo5rho,2026-09-18T13:55Z。

本席在增补里写:「typecheck 与六条点名门禁仍排在同一把锁后面,仍列为 NOT MEASURED」。

那句话现在过期了。 复核席把锁下的整条链跑完并第三次重发记录,裁断与落地 head 仍逐字未变(PASS / e53dc612b48),档位重取 103 / 103

本席独立读它留下的退出码文件,不转抄它的叙述

文件 对应日志尾行(本席自读)
build.exit 0
checkgen.exit 0
spectest.exit 0
typecheck.exit 0 Tasks: 18 successful, 18 total
gate-core.exit 0 Test Files 1 passed (1) / Tests 23 passed (23)
gate-objectql.exit 0 Test Files 2 passed (2) / Tests 15 passed (15)
gate-turso.exit 0 Test Files 3 passed (3) / Tests 112 passed (112)

ALL-DONE 存在,验证锁已释放、队列为空。

⇒ 七项全部 exit 0,数字与复核记录相符。本席上一条把它们说成「仍未测」是读得太早,⛔ 不是复核漏测。

⛔ 仍然归席位、仍然没有被这条更正吃掉的一条

落地前置 ③(每一项 check 都 success)是 CI 的读数,不是本地门禁的读数。 本地七项全绿不能替代它 —— 本席的入队守卫仍按 CI 判,在 37 绿 0 红 1 在跑 上拒绝过三轮,直到全绿才放行—— added_to_merge_queue 事件,2026-09-18T13:53:56Z。

⭐ 复核在这一点上也没含糊:它把 NOT MEASURED 一路收窄到只剩真正不可测的几项(队列自己的服务端重建、它自己的服务档位),而不是收窄到零


Generated by Claude Code

Merged via the queue into main with commit 5ba2ec3 Sep 18, 2026
39 checks passed
@os-litant
os-litant deleted the claude/issue-18063-declare-no-transactions branch September 18, 2026 14:42
os-litant pushed a commit that referenced this pull request Sep 18, 2026
…ed on the origin/main merge

The merge of origin/main (b146102) routed four paths to the os-regen
driver, which defers rather than text-merging: the three
api-surface-declarations shards automation.txt, data.txt and ui.txt, and the
generated reference page content/docs/references/ui/component.mdx. On every one
of them the driver kept this branch's side and silently dropped main's, so
step 2 of the sanctioned sequence restored main's side into the worktree and
this commit re-derives all four from the merged tree.

What each side contributed, now both present:

  ui.txt / component.mdx    main's element:text.variant widening to the
                            published nine (#19019), plus this branch's
                            evaluated-slot narrowing
  data.txt                  main's transport no-transactions declaration
                            (#18890) and the $orderby dual declaration
                            (#19018), plus this branch's `source: string`
                            narrowing on ConditionalValidationSchema and
                            CrossFieldValidationSchema
  automation.txt            main's structured-region pause/end refusal
                            (#18688), plus this branch's narrowing

The two MIXED, deliberately unrouted paths were hand-resolved by git's ordinary
three-way merge and verified rather than eyeballed: registry.ts's hand-written
remainder (generated regions stripped with the merge script's own awk) is
byte-identical across base, both sides and the merge, and its line count is
exactly additive (17142 + 121 + 74 = 17337), with both sides' migration entries
present by id. component.zod.ts is additive too (3750 + 4 + 45 = 3799) and its
single .superRefine() is untouched.

Claude-Session: https://claude.ai/code/session_01LvwGppdonww4zGLWZo5rho
Co-authored-by: Claude <noreply@anthropic.com>
os-bill pushed a commit that referenced this pull request Sep 18, 2026
`packages/spec/src/data/driver.test.ts` pinned the `transactions` retired-key
prescription with a regex requiring only that `METHOD PRESENCE` and
`beginTransaction` appear somewhere in the thrown message. #18890 rewrote that
prescription's opening from "gated on METHOD PRESENCE" to "gated on the
DRIVER'S DECLARATION, no longer on METHOD PRESENCE alone" — and the old regex
matches BOTH. Reverting the prescription to the now-false sentence kept the
test green, so the pin could not fail on the one shape it exists to catch. The
title said "points at method presence", which is the old world too.

Anchor on the two things #18890 made load-bearing instead: the negation
(`no longer on METHOD PRESENCE`) and the predicate that replaced presence
(`driverSupportsTransactions`). `beginTransaction` and `Delete the key` stay,
so nothing the old pin covered is dropped.

Deliberately NOT a ban on the phrase `METHOD PRESENCE`: `schemaSync`'s own
"Schema sync is gated on METHOD PRESENCE" sentence in driver.zod.ts is true of
the runtime today and is untouched.

Test-only. No runtime file, no published surface.

Claude-Session: https://claude.ai/code/session_01JbZnqu8bt6YqfJsr9vaFb3
Co-authored-by: Claude <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation protocol:data size/l tests tooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Decision] TursoDriver.beginTransaction 的 any 掩盖的是 Liskov 违例 —— 基类声明该服从谁,A 还是 C(重建自 #17878)

2 participants