Skip to content

An explicit null on a create/update/stream.append fence is rejected, while stream.batch's observed reads it as an absence assertion #2571

Description

@oceanwaves630

stream.batch's observed set treats an explicit null version as an ABSENCE assertion: no live
note holds that key. That is a real predicate and it is served. The single-write fences on
create, update and stream.append take {key, kind, version} and reject an explicit null at
parameter validation, before any predicate is evaluated, so the same assertion cannot be made on
any of them.

Measured against serving-2a17870be (kkernel 0.8.0, revision 2a17870),
a scratch daemon, one call each.

stream.append, fence [{"key": "lease/r1", "kind": "head", "version": null}]:

invalid input: bad params: data did not match any variant of untagged enum Shape

create (kind=note, note_kind=head, key=job/a), identical fence, identical answer:

invalid input: bad params: data did not match any variant of untagged enum Shape

stream.batch, atomic: true, observed [{"key": "lease/r1", "kind": "head", "version": null}],
one append member, no such note existing:

committed: true

So the assertion exists in the system and is reachable from exactly one route.

Why this matters to a consumer

A client whose contract fences a write on "this key is not held" has three options today. It can
express every such write as a one-member stream.batch, which turns a single append into a
transaction and a different result shape. It can evaluate the predicate itself before the call,
which is the race the fence exists to close. Or it can refuse the shape. Refusing is what we do,
and the consequence is that a whole class of streams is not routable at all: the ones whose
contract says "fenced on an ownership head whenever one exists", because the "whenever one exists"
half is precisely an absence assertion and there is no field to send it in.

The asymmetry is also the kind that reads as a bug from the outside. The same
{key, kind, version} object means "assert version N" on four routes and "assert absent" on one,
and the three that reject null give a parse error naming an internal enum rather than saying the
predicate is unsupported here.

Ask

Does version: null on a create, update or stream.append fence become the same absence
assertion observed already makes, evaluated in the same writer transaction?

If the answer is that absence predicates belong on stream.batch and the single-write fences are
deliberately version comparisons only, that is a usable answer and I would like it stated, so
consumers can route on it rather than wait for a field. In that case a clearer refusal than
data did not match any variant of untagged enum Shape would still help: something naming the
parameter and saying absence is expressed with stream.batch's observed.

Related but distinct: #2551 asks for a deadline path on the same fence. Same object, different
missing half; neither implies the other.

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

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions