Skip to content

[finding] /discovery advertises transactionalBatch: true for a composition whose driver cannot roll back — and the 501's own remedy tells the caller to trust that flag #18997

Description

@os-litant

Filed by the domain:spec PM seat from the at-tier contract review of PR #18890 (record 5728707331, verdict PASS). ⛔ Not a regression from that PR and it did not block it — the no-method half of this is PRE-EXISTING on main; #18063 adds a second population to a blindness that was already there, and its tombstone prose says so truthfully.

The contradiction, in one line

/discovery advertises transactionalBatch: true for a composition whose default driver cannot run a transaction, and batchData({ atomic: true }) then answers 501 NOT_IMPLEMENTED — while the 501's own remedy text tells the caller to 「probe capabilities.transactionalBatch on /discovery first」.

The gate's prescribed remedy routes the caller to a signal that is wrong in exactly the case the remedy exists for.

Measured

packages/*/metadata-protocol/protocol.ts:6333 derives the capability from the ENGINE alone:

typeof this.engine?.transaction === 'function'

engine.transaction is always a function. The question that decides the outcome is whether the DEFAULT DRIVER can roll back, which the repo already has a predicate for — engineCanRollBack (packages/core/src/utils/migration-journal.ts), now itself declaration-aware, and consumed at protocol.ts:12315 where the atomic batch answers 501.

Two populations reach the false true:

  1. a default driver with no beginTransaction at all — ⭐ pre-existing on main, not introduced here;
  2. a transport that declares transactionsUnsupported — the population [Decision] TursoDriver.beginTransaction 的 any 掩盖的是 Liskov 违例 —— 基类声明该服从谁,A 还是 C(重建自 #17878) #18063 added.

Remedy candidate (⛔ verify before taking it)

Read engineCanRollBack(this.engine) at protocol.ts:6333 instead of the typeof probe — the same predicate the 501 path already trusts, so the advertisement and the refusal stop disagreeing.

⚠️ /discovery is a published surface, so changing what it advertises is a contract change, even when the new value is the honest one: a consumer that today sees true and proceeds would begin seeing false and take its fallback. Whoever takes this must decide and declare Clause-② on that basis, ⛔ not assume 「it was a bug so it is free」. Measure the two populations separately — the pre-existing one and #18063's — because they may not warrant the same disposition.

⭐ Prove it with a control in both directions: a composition that CAN roll back must still advertise true. A fix that makes the flag honest by making it always false is worse than the bug.

Four-facet reading

  1. Who is hurt — a client that does exactly what the error message instructs: probes the capability, believes true, sends an atomic batch, gets 501.
  2. What the rule should be — an advertised capability answers the same question the refusal path asks; ⛔ one may not be derived from the engine while the other is derived from the driver.
  3. Cost of the honest fix — one line at the read site, plus a declared contract change on a published surface and its tests.
  4. What was built instead — two derivations of one capability, which is how they drifted.

Scope: metadata-protocol / domain:engine. ⛔ Not packages/spec; the spec half of #18063 is landed and correct.


Generated by Claude Code

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions