Skip to content

P1: 'honor atomic when not rejected' qualifies a MUST with a rejection mechanism that does not exist #3

Description

@ssilvius

Severity: P1. Reviewed at 82b715c. The spec qualifies a MUST with a mechanism that does not exist anywhere in the document.

The hole

S10.1 item 5 (SPEC.md:206), under "A v0.1 conforming server MUST":

Honor atomic: true on batch requests when not rejected.

There is no rejection mechanism for atomic:

  • The S7 registered error code table has no not_supported (or equivalent).
  • S4.2's only rejection clause is statement-count overflow to 413 / payload_too_large -- which rejects the batch, not the atomicity.

So "when not rejected" is a qualifier pointing at nothing. A server that genuinely cannot do transactions has no conforming way to say so. It must either lie -- accept atomic: true and execute non-atomically -- or misuse an unrelated error code. A client cannot distinguish those two from success.

What this is not

Worth stating explicitly, because it is the first thing a reader assumes and it is wrong: atomic being OPTIONAL at SPEC.md:64 does not create the escape hatch. OPTIONAL there governs whether the field appears in the request. The same sentence puts a MUST on the server: "when true, the server MUST execute all statements in a single transaction."

Both reference servers honor it today, verified at 82b715c:

  • examples/cloudflare-worker-to-d1/src/index.ts -- the atomic branch prepares the whole batch and calls db.batch(), which is a transaction.
  • examples/cloudflare-durable-object/src/index.ts -- ctx.storage.transactionSync.

So this is not a case of servers declining in practice. It is a case of a stated escape hatch that no server can actually use.

Why it matters to a client

The consequence is undetectability, and it is stronger than "some servers might not support transactions." A client cannot confirm that a given target honored atomic, in any deployment, ever. Correctness therefore must not rest on a primitive whose presence is unobservable -- which is why a migration applier has to be correct without atomic even in a future where every server honors it perfectly.

That property is what makes this worth fixing rather than documenting.

Resolution -- pick one

  1. Register a rejection mechanism. Add not_supported to the S7 table with a defined status, and state that a server unable to execute atomically MUST reject atomic: true with it rather than silently degrading. Preserves "when not rejected" and makes it mean something. Neither reference server changes -- both honor atomic, so neither ever emits it.
  2. Drop the qualifier. Make S10.1 item 5 an unconditional MUST. Simpler, and honest about what the spec already requires, but it leaves non-transactional backends with no conforming path at all.

Recommend (1): it costs one error code, and it converts a silent failure into a detectable one, which is the entire value at stake.

Acceptance

  • The "when not rejected" qualifier either has a mechanism behind it or is gone.
  • If (1): not_supported registered in S7; S4.2 states the reject-rather-than-degrade obligation; conformance case added.
  • Both reference servers still conform with no behavior change.

Generalizable tell

A conformance list that qualifies a MUST with a mechanism defined nowhere is a repeatable spec bug worth grepping for -- "when not rejected", "where supported", "if available". Each one needs the mechanism to exist.

Origin: platform, from a misread of this same passage by smugglr that inverted on inspection -- the conclusion (do not depend on atomic) survived, the reason changed from "servers may decline" to "no server can declare".

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions