Found while building the authz.invalidated channel for #11968 (PR on claude/issue-11968-authz-cache-substrate). Filed unassigned, no repair attempted there — it is a packages/spec change and out of that card's scope.
The contradiction, measured on origin/main
packages/spec/src/contracts/cluster-service.ts:73, the docblock on the IPubSub interface itself:
Fan-out messaging primitive. At-least-once delivery; handlers MUST be idempotent. The memory driver delivers synchronously within a process; remote drivers (redis pub/sub, postgres LISTEN/NOTIFY, nats) deliver across nodes.
content/docs/kernel/cluster.mdx §4.2, on the same at-least-once value:
No shipped driver provides this yet. The redis driver publishes over plain Redis pub/sub, which is at-most-once — fire-and-forget, no persistence, no replay for a node that was down at publish time.
And @objectstack/service-cluster-redis's own publish docblock says the same in its own words: at-most-once, "acceptable only for events that are pure cache-invalidation hints, never the source of truth." The memory driver does not cross a process boundary at all.
So the two statements disagree, and the one on the interface — the place a consumer reads before writing a subscriber — is the one that is wrong. cluster.mdx §4.2 and the driver docblocks are the measured statements.
Why this is worth a card rather than a shrug
The interface docblock is the load-bearing one for the failure mode it invites. A consumer that reads "At-least-once delivery; handlers MUST be idempotent" reasonably concludes that a message will arrive and that its only obligation is to tolerate duplicates. The actual transport's failure mode is the opposite — a lost message, with no replay and no upper bound on how long a node stays wrong. An author who designs for duplicates and not for loss has designed for the wrong hazard, and nothing in the type or the tests contradicts them.
Two things narrow the blast radius today and neither closes it:
Suggested shape (not a decision — this is a spec-seat call)
Correct the IPubSub docblock to state the shipped guarantee (at-most-once; a missed message is expected; handlers must be idempotent and tolerate loss), and point at deliverySemantics for the per-channel declaration. Whether the interface should say "at-most-once" flatly or "whatever the driver declares, and no shipped driver exceeds at-most-once" is the question worth answering deliberately, because the second phrasing keeps the door open for a durable driver without re-writing the contract when one lands.
Anything stronger — a declared per-driver guarantee the type system can read, or refusing an at-least-once channel declaration at boot while no driver can honour it — is a larger design question and deliberately not proposed here.
Not in scope of this report
Whether any existing consumer of IPubSub was written against the wrong guarantee. Only metadata.changed and (with #11968) authz.invalidated are in production use, and both are cache-invalidation hints, which is the one thing the transport is documented as acceptable for. A sweep of subscribers is a separate task if someone wants it.
Found while building the
authz.invalidatedchannel for #11968 (PR onclaude/issue-11968-authz-cache-substrate). Filed unassigned, no repair attempted there — it is apackages/specchange and out of that card's scope.The contradiction, measured on
origin/mainpackages/spec/src/contracts/cluster-service.ts:73, the docblock on theIPubSubinterface itself:content/docs/kernel/cluster.mdx§4.2, on the sameat-least-oncevalue:And
@objectstack/service-cluster-redis's ownpublishdocblock says the same in its own words: at-most-once, "acceptable only for events that are pure cache-invalidation hints, never the source of truth." Thememorydriver does not cross a process boundary at all.So the two statements disagree, and the one on the interface — the place a consumer reads before writing a subscriber — is the one that is wrong.
cluster.mdx§4.2 and the driver docblocks are the measured statements.Why this is worth a card rather than a shrug
The interface docblock is the load-bearing one for the failure mode it invites. A consumer that reads "At-least-once delivery; handlers MUST be idempotent" reasonably concludes that a message will arrive and that its only obligation is to tolerate duplicates. The actual transport's failure mode is the opposite — a lost message, with no replay and no upper bound on how long a node stays wrong. An author who designs for duplicates and not for loss has designed for the wrong hazard, and nothing in the type or the tests contradicts them.
Two things narrow the blast radius today and neither closes it:
deliverySemanticson a channel declaration is a separate, more honest surface, and §4.2 carries the correction — but it is a doc a consumer has to go find, while the docblock is the one their editor shows them at the call site.writeEpoch) +authz.invalidatedchannel + boot-time posture statement #11968'sauthz.invalidatedmodule states the correct rule at its own channel and records this contradiction inline so nobody resolves it in the wrong direction. That covers exactly one channel.Suggested shape (not a decision — this is a spec-seat call)
Correct the
IPubSubdocblock to state the shipped guarantee (at-most-once; a missed message is expected; handlers must be idempotent and tolerate loss), and point atdeliverySemanticsfor the per-channel declaration. Whether the interface should say "at-most-once" flatly or "whatever the driver declares, and no shipped driver exceeds at-most-once" is the question worth answering deliberately, because the second phrasing keeps the door open for a durable driver without re-writing the contract when one lands.Anything stronger — a declared per-driver guarantee the type system can read, or refusing an
at-least-oncechannel declaration at boot while no driver can honour it — is a larger design question and deliberately not proposed here.Not in scope of this report
Whether any existing consumer of
IPubSubwas written against the wrong guarantee. Onlymetadata.changedand (with #11968)authz.invalidatedare in production use, and both are cache-invalidation hints, which is the one thing the transport is documented as acceptable for. A sweep of subscribers is a separate task if someone wants it.