Skip to content

docs(spec): state IPubSub's real delivery guarantee on the contract docblock (#12651) - #12837

Merged
os-sales merged 2 commits into
mainfrom
claude/issue-12651-ipubsub-delivery-docblock
Aug 28, 2026
Merged

docs(spec): state IPubSub's real delivery guarantee on the contract docblock (#12651)#12837
os-sales merged 2 commits into
mainfrom
claude/issue-12651-ipubsub-delivery-docblock

Conversation

@os-sales

Copy link
Copy Markdown
Collaborator

Fixes#12651

The IPubSub contract docblock claimed a delivery guarantee no shipped driver provides, on the one surface a consumer reads before writing a subscriber.

The contradiction, as it stood

packages/spec/src/contracts/cluster-service.ts:73 said:

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.

Three measured statements in this repo disagreed with it, and they agree with each other:

  • content/docs/kernel/cluster.mdx §4.2, on the 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."
  • @objectstack/service-cluster-redis's publish docblock: at-most-once, no delivery guarantee to subscribers, no replay — "acceptable only for events that are pure cache-invalidation hints, never the source of truth."
  • @objectstack/core's authz.invalidated channel module, which recorded the contradiction inline rather than resolving it in the wrong direction.

The interface docblock is the load-bearing one for the hazard it invites. "At-least-once" tells an author their only obligation is to tolerate duplicates; the transport's actual failure mode is the opposite — a lost message, no replay, no upper bound on how long a node that was down at publish time stays wrong. An author who designs for duplicates and not for loss has designed for the wrong hazard, and neither the type nor the tests contradict them.

What this changes

One docblock, in the shape triage adopted on the card:

  • Driver-relative, not flat. "Delivery is whatever the configured driver declares, and no shipped driver exceeds at-most-once" — chosen deliberately over a flat "at-most-once" so a future durable driver lands without rewriting the contract.
  • A missed message is EXPECTED, and handlers must be idempotent and tolerate loss, with the failure named out loud: designing only for duplicates is designing for the wrong hazard.
  • The staleness bound lives outside the bus (a TTL, a reload, a durable outbox of its own) — the same rule attachAuthzInvalidationPubSub and the authz.invalidated bridge already state.
  • Points at deliverySemantics as the per-channel surface, and says what that surface is: what a channel asks for, not what it gets. at-least-once parses there and is the default for cluster / tenant scope, but no shipped driver provides it yet.
  • The driver-reach sentence is unchanged (memory = synchronous in-process; remote drivers cross nodes). It was never part of the false claim, so it was not touched.

The new text is worded to agree with cluster.mdx §4.2 and the redis driver rather than introduce a third wording of the same facts.

Prose only: no schema, no type, no accept/reject behaviour change. Out of scope per triage and left alone: type-readable per-driver guarantees, boot refusal of an at-least-once channel declaration, and any subscriber sweep.

Two PM mechanism assumptions, measured

  • "The docblock likely appears in generated reference pages" — it does not.packages/spec/scripts/build-docs.ts regenerates content/docs/references/** from JSON Schemas, and IPubSub is a plain TypeScript interface with no Zod schema; the only two spec artifacts naming it (api-surface/contracts.json, export-origins/contracts.json) record the symbol name, never docblock prose. Confirmed empirically: a full pnpm --filter @objectstack/spec build (which runs gen:schema and gen:openapi) left git status carrying nothing but the two files in this diff, and check:generated, check:docs and check:authorable-surface are all green with no regenerated artifact.
  • "cluster.mdx §4.2 may need its cross-reference touched" — it does not. §4.2 carries no reference to the interface docblock; the only IPubSub mention in content/docs is at cluster.mdx:173, in §4.3 about partitionKey, which this change does not touch. §4.2's correction now agrees with the interface instead of contradicting it, so it reads correctly unedited. No docs file is changed.

Changeset, not skip-changeset

This is not docs-only. The docblock is compiled into the published type declarations — verified in packages/spec/dist/contracts/index.d.ts, which now carries the corrected text — so the repair reaches consumers' editors through a released @objectstack/spec, which is the entire point of it. Shipped as patch.

Verification

All runs anchored to the final commit 8368e05b (origin/main merged in first, per the landing relay). Heavy runs went through scripts/pm/os-verify-lock.sh; every exit code was captured before any pipe.

  • pnpm --filter @objectstack/spec buildos-verify-lock: VERDICT command-exit 0; check-dts-emitted: @objectstack/spec - 34/34 declared declaration file(s) present.
  • Gate union from node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack, re-derived after the merge against the actual changed set (2 paths, three-dot semantics): 32 families, 30 green. Includes check:generated, check:docs, check:authorable-surface, check:liveness, check:strictness-ledger, check:empty-state, check:variant-docs, check:skill-refs, check:doc-authoring, check:doc-formula-expressions, check:merge-driver, check:adr-0087-registration, check:empty-changeset, check:changeset-no-major, check:nul-bytes.
  • The 2 non-green families are NOT MEASURED, not red — each says so in its own words. check-dev-prereqs exits on "The workspace is not built — 1 unmet precondition, not a list of problems" (only spec, formula and lint are built in this worktree; a full workspace build is CI's run). scripts/pm/check-half-states.mjs exits 3 on "PREREQUISITE NOT MET — the token in the environment is not a valid GitHub credential ... Nothing was swept ... it is no reading at all"; it inspects board state, not this diff.
  • pnpm --filter @objectstack/spec typecheck — exit 0, including check:test-typecheck: OK.
  • vitest run src/kernel/cluster.test.ts — 1 file, 19 tests passed.
  • Repo-wide ESLint, not a narrowed run: eslint . --no-inline-config --format json over 5339 files (eslint's own enumeration) — 0 errors, 0 warnings, 0 fatal, with cluster-service.ts confirmed present in the run.

One finding filed, not fixed here

#12836 records two docblocks elsewhere that reference the retired at-least-once claim and go stale exactly when this lands: the "known contradiction" paragraph in packages/core/src/security/authz-invalidation-channel.ts (which declares its own expiry — "filed separately" is this card), and at-least-once semantics held vacuously in packages/services/service-cluster/src/memory/pubsub.ts. Both sit outside this card's declared file surface and in two other packages, so they are out of scope here and #12836 stays open for a follow-up.

Landing note: several spec PRs are ahead in the landing relay — left as a draft deliberately, PM lands.


Generated by Claude Code

…ocblock
The IPubSub docblock claimed "At-least-once delivery; handlers MUST be
idempotent". No shipped driver provides that, and the repo's own measured
statements said so elsewhere: content/docs/kernel/cluster.mdx 4.2 ("No shipped
driver provides this yet."), service-cluster-redis's publish docblock
(at-most-once, fire-and-forget, no persistence), and the authz.invalidated
channel module in @objectstack/core, which recorded the contradiction inline
rather than resolving it in the wrong direction.
The interface docblock is the load-bearing one for the hazard it invites: it is
what a consumer's editor shows at the call site. "At-least-once" tells that
author their only obligation is to tolerate duplicates, while the transport's
actual failure mode is a LOST message with no replay and no upper bound on how
long a node that was down at publish time stays wrong.
State it driver-relatively instead -- delivery is whatever the configured driver
declares, and no shipped driver exceeds at-most-once -- so a future durable
driver lands without rewriting the contract. A missed message is expected;
handlers must be idempotent AND loss-tolerant; the staleness bound lives outside
the bus; deliverySemantics is named as the per-channel surface, along with what
it is (what a channel asks for, not what it gets). The driver-reach sentence is
unchanged -- it was never part of the false claim.
Prose only: no schema, no type, no accept/reject behaviour change.
Co-authored-by: Claude <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

⚠️1 changed file(s) yielded no anchor (packages/spec/src/contracts/cluster-service.ts), so the pages documenting them are NOT COVERED by this run — this is not a clean bill of health for those files. Nothing else in this diff resolved to a documentable surface (no symbol, route or SDK anchor derived from 1 changed package(s)).

What this run could not see
  • 1 changed file(s) yielded no anchor (packages/spec/src/contracts/cluster-service.ts) — pages documenting those are invisible to this run
  • a page that states a rule by its inputs shares no identifier with the emitter that implements the rule, so an emitter-only diff cannot list it — not on this run and not on any run. Measured on fix(driver-sql): emit varchar(maxLength) for a text field a declared index keys on #11430: content/docs/protocol/objectql/types.mdx documents the text-family column mapping by the ObjectQL type names it maps FROM (text / textarea / html) while the diff changed createColumn; it went unlisted, and it was the page that diff falsified, in four places. No shared token exists to detect this on, so a rule your change carries has to be re-read by hand in the pages that restate it.

Coarse fallback — 126 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json f907fbe9ed62c68d3de1a8a49d5ac3b9ba13abfcpackageMentionDocs.

@github-actionsgithub-actionsBot added documentation Improvements or additions to documentation tooling labels Aug 28, 2026
@os-salesos-sales added needs:contract-review and removed documentation Improvements or additions to documentation tooling labels Aug 28, 2026 — with Claude
@os-sales
os-sales marked this pull request as ready for review August 28, 2026 04:32
@os-sales
os-sales enabled auto-merge August 28, 2026 04:32
@os-sales
os-sales added this pull request to the merge queueAug 28, 2026
Merged via the queue into main with commit a3765f6Aug 28, 2026
47 checks passed
@os-sales
os-sales deleted the claude/issue-12651-ipubsub-delivery-docblock branch August 28, 2026 04:55
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

IPubSub's contract docblock states a delivery guarantee no shipped driver provides ("At-least-once delivery")

2 participants

@os-sales@claude