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:
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.
stream.batch'sobservedset treats an explicit null version as an ABSENCE assertion: no livenote holds that key. That is a real predicate and it is served. The single-write fences on
create,updateandstream.appendtake{key, kind, version}and reject an explicit null atparameter 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}]:create(kind=note,note_kind=head,key=job/a), identical fence, identical answer:stream.batch,atomic: true,observed[{"key": "lease/r1", "kind": "head", "version": null}],one append member, no such note existing:
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 atransaction 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: nullon acreate,updateorstream.appendfence become the same absenceassertion
observedalready makes, evaluated in the same writer transaction?If the answer is that absence predicates belong on
stream.batchand the single-write fences aredeliberately 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 Shapewould still help: something naming theparameter and saying absence is expressed with
stream.batch'sobserved.Related but distinct: #2551 asks for a deadline path on the same fence. Same object, different
missing half; neither implies the other.