spec: cultnet write receipt (write_admitted, error id echo, admitted/refused/unconfirmed) #59

Merged
libby merged 10 commits from libby/cultnet-write-receipt-spec into main 2026-11-08 19:49:40 +00:00
Member

Spec PR for Heimdall's ruling (a write completes on its result frame; admitted and refused never look alike). Files in contracts/cultnet/interop/: write-receipt.md (rules), cultnet.write-admitted.schema.json, cultnet.error.v1.schema.json, write-receipt-vectors.json. No runtime or server code.

Decisions:

  • cultnet.write_admitted.v0 {messageId, operation, schemaId, recordKey}, sent after the commit point; exactly one of it or an error per write.
  • Id echo = cultnet.error.v1 (v0 + required messageId, nil only for an unreadable frame). Not an extra key on v0: the v0 schema is closed and the TS decoder (Ajv) rejects unknown keys and unknown schemaVersions, so "old clients ignore the new frame" is false for today's decoders. Instead the new frames go only to connections whose cultnet.hello.v0 lists both in supportedMessageVersions; everyone else gets today's behaviour. Rust/Python/Kotlin unknown-key tolerance is unverified and not assumed.
  • Batches are N writes each with its own id; a failed batch names the member by id. Resend of an admitted id re-answers write_admitted. Forwarded writes relay the primary's answer with the original id; no primary answer means silence, not success.
  • Client outcomes: admitted | refused | unconfirmed. The 500 ms window (from the send) is the fallback for a pre-receipt server and ends unconfirmed (window_elapsed / session_ended), never success or refusal. An id-less error is attributed only when exactly one write is unanswered.
  • Vectors: 5 wire cases, 7 schema-invalid cases (all checked against the schemas with Ajv), 16 replay scenarios. Wire bytes are spec-authored with msgpack-python; each runtime's conformance PR re-captures them with its own encoder.

This changes what #55 to #58 shipped: window-with-no-refusal is unconfirmed under this spec, not success. Conformance work (all runtimes, hello capability, the forwarder, the Odin catalog) is follow-up.

🤖 Generated with Claude Code

Spec PR for Heimdall's ruling (a write completes on its result frame; admitted and refused never look alike). Files in `contracts/cultnet/interop/`: `write-receipt.md` (rules), `cultnet.write-admitted.schema.json`, `cultnet.error.v1.schema.json`, `write-receipt-vectors.json`. No runtime or server code. Decisions: - **`cultnet.write_admitted.v0`** `{messageId, operation, schemaId, recordKey}`, sent after the commit point; exactly one of it or an error per write. - **Id echo = `cultnet.error.v1`** (v0 + required `messageId`, nil only for an unreadable frame). Not an extra key on v0: the v0 schema is closed and the TS decoder (Ajv) rejects unknown keys and unknown `schemaVersion`s, so "old clients ignore the new frame" is false for today's decoders. Instead the new frames go only to connections whose `cultnet.hello.v0` lists both in `supportedMessageVersions`; everyone else gets today's behaviour. Rust/Python/Kotlin unknown-key tolerance is unverified and not assumed. - **Batches** are N writes each with its own id; a failed batch names the member by id. Resend of an admitted id re-answers `write_admitted`. Forwarded writes relay the primary's answer with the original id; no primary answer means silence, not success. - **Client outcomes**: `admitted | refused | unconfirmed`. The 500 ms window (from the send) is the fallback for a pre-receipt server and ends `unconfirmed` (`window_elapsed` / `session_ended`), never success or refusal. An id-less error is attributed only when exactly one write is unanswered. - Vectors: 5 wire cases, 7 schema-invalid cases (all checked against the schemas with Ajv), 16 replay scenarios. Wire bytes are spec-authored with msgpack-python; each runtime's conformance PR re-captures them with its own encoder. This changes what #55 to #58 shipped: window-with-no-refusal is `unconfirmed` under this spec, not success. Conformance work (all runtimes, hello capability, the forwarder, the Odin catalog) is follow-up. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
spec: cultnet write receipt (write_admitted.v0, error.v1 id echo, three-outcome clients)
Some checks failed
CultMesh QUIC native / Build win32-x64 (pull_request) Has been skipped
CultMesh QUIC native / Build linux-x64 (pull_request) Has been skipped
CultMesh QUIC native / Release native binaries (pull_request) Has been skipped
CultMesh portability and performance / ubuntu-latest reactive semantics and leases (pull_request) Has been cancelled
CultMesh QUIC native / Verify committed binaries match SHA256SUMS (pull_request) Has been cancelled
662ae048d8
Specification and shared vectors only, no runtime code. A write is answered
exactly once with cultnet.write_admitted.v0 or cultnet.error.v1 carrying its
messageId; the new frames go only to connections whose hello lists them (today's
TS decoder rejects unknown keys and versions, so "old clients ignore it" is
false). Clients settle admitted | refused | unconfirmed; the result window is the
pre-receipt fallback and ends unconfirmed. Wire vectors, schema-invalid cases and
16 replay scenarios in write-receipt-vectors.json.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Author
Member

Revised per Heimdall: dedupe by (actor, messageId) bound to a content hash (same id, other content = message_invalid, never the old receipt); late answers recorded admitted_late/refused_late; forwarder with no primary answer relays nothing; RECEIPT 5 s / LEGACY 500 ms named constants; capability is framing only; batches can commit partially.

Revised per Heimdall: dedupe by (actor, messageId) bound to a content hash (same id, other content = message_invalid, never the old receipt); late answers recorded admitted_late/refused_late; forwarder with no primary answer relays nothing; RECEIPT 5 s / LEGACY 500 ms named constants; capability is framing only; batches can commit partially.
spec: write receipt per Heimdall's review (dedupe by actor+content hash, admitted-late, 5 s window, capability is framing only)
Some checks failed
CultMesh QUIC native / Build win32-x64 (pull_request) Has been skipped
CultMesh QUIC native / Build linux-x64 (pull_request) Has been skipped
CultMesh QUIC native / Release native binaries (pull_request) Has been skipped
CultMesh portability and performance / ubuntu-latest reactive semantics and leases (pull_request) Failing after 6m28s
CultMesh QUIC native / Verify committed binaries match SHA256SUMS (pull_request) Successful in 2m19s
ab7bc771e3
- messageId unique per authenticated actor and session; dedupe key (actor, id) bound to SHA-256 of
  [operation, schemaId, recordKey, payload]; same id with other content is message_invalid, never the old receipt.
- A late answer after the window is recorded on the write (admitted_late / refused_late); no blind resend.
- A forwarder with no primary answer relays nothing; the client reports unconfirmed.
- RECEIPT_RESULT_WINDOW_MS 5000 (default), LEGACY_RESULT_WINDOW_MS 500 (fallback only), DEDUPE_RETENTION_MS as vector constants.
- The hello capability changes framing only and never authorizes.
- Batches can commit partially; the caller owns retry.
- Vectors: constants, worked content hashes, 7 dedupe cases, late-answer and window scenarios.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
spec: dedupe scope is at least the session; a resend after a reconnect is a new write
Some checks failed
CultMesh QUIC native / Build win32-x64 (pull_request) Has been skipped
CultMesh QUIC native / Build linux-x64 (pull_request) Has been skipped
CultMesh QUIC native / Release native binaries (pull_request) Has been skipped
CultMesh portability and performance / ubuntu-latest reactive semantics and leases (pull_request) Failing after 5m52s
CultMesh QUIC native / Verify committed binaries match SHA256SUMS (pull_request) Successful in 41s
7a560b1984
Per Heimdall's review. The dedupe key lives for the life of its session, bounded per
session without forgetting an id the server promised to answer. A longer retention
is optional and never to be relied on. After a reconnect callers resend only writes
that are idempotent by record key and read back before resending any other.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
cultnet: add limit_reached to error.v1 code enum, with captured vector
Some checks failed
CultMesh QUIC native / Build win32-x64 (pull_request) Has been skipped
CultMesh QUIC native / Build linux-x64 (pull_request) Has been skipped
CultMesh QUIC native / Release native binaries (pull_request) Has been skipped
CultMesh portability and performance / ubuntu-latest reactive semantics and leases (pull_request) Failing after 4m51s
CultMesh QUIC native / Verify committed binaries match SHA256SUMS (pull_request) Successful in 26s
eafcdcdf7c
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
spec: C# RUDP schema server is not receipt-capable; advertise only if end to end
Some checks failed
CultMesh QUIC native / Build win32-x64 (pull_request) Has been skipped
CultMesh QUIC native / Build linux-x64 (pull_request) Has been skipped
CultMesh QUIC native / Release native binaries (pull_request) Has been skipped
CultMesh QUIC native / Verify committed binaries match SHA256SUMS (pull_request) Has been cancelled
CultMesh portability and performance / ubuntu-latest reactive semantics and leases (pull_request) Has been cancelled
b89a883700
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Spec #59: id size, digest memory, limit refusals remembered, required fields, reliable channel, local send failure, per-class conformance table
Some checks failed
CultMesh QUIC native / Build win32-x64 (pull_request) Has been skipped
CultMesh QUIC native / Build linux-x64 (pull_request) Has been skipped
CultMesh QUIC native / Release native binaries (pull_request) Has been skipped
CultMesh portability and performance / ubuntu-latest reactive semantics and leases (pull_request) Failing after 6m0s
CultMesh QUIC native / Verify committed binaries match SHA256SUMS (pull_request) Successful in 46s
734087766a
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Spec #59: error.v1 code enum names limit_reached; server-wide budget is a conformance column; an overflowed refusal set refuses unseen ids for the session
Some checks failed
CultMesh QUIC native / Build win32-x64 (pull_request) Has been skipped
CultMesh QUIC native / Build linux-x64 (pull_request) Has been skipped
CultMesh QUIC native / Release native binaries (pull_request) Has been skipped
CultMesh QUIC native / Verify committed binaries match SHA256SUMS (pull_request) Has been cancelled
CultMesh portability and performance / ubuntu-latest reactive semantics and leases (pull_request) Has been cancelled
3443e1dc93
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Spec #59: local_send_failed is its own unconfirmed reason
Some checks failed
CultMesh QUIC native / Build win32-x64 (pull_request) Has been skipped
CultMesh QUIC native / Build linux-x64 (pull_request) Has been skipped
CultMesh QUIC native / Release native binaries (pull_request) Has been skipped
CultMesh portability and performance / ubuntu-latest reactive semantics and leases (pull_request) Failing after 5m31s
CultMesh QUIC native / Verify committed binaries match SHA256SUMS (pull_request) Successful in 31s
2c5203dc82
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Spec #59: receipt writes need a reliable, non-sequenced channel; error.v1 schema description names limit_reached; #55-#58 paragraph states which clients still window-succeed
Some checks failed
CultMesh QUIC native / Build win32-x64 (pull_request) Has been skipped
CultMesh QUIC native / Build linux-x64 (pull_request) Has been skipped
CultMesh QUIC native / Release native binaries (pull_request) Has been skipped
CultMesh portability and performance / ubuntu-latest reactive semantics and leases (pull_request) Failing after 9m8s
CultMesh QUIC native / Verify committed binaries match SHA256SUMS (pull_request) Successful in 1m14s
74adad916a
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Spec #59: an empty or blank schemaId or recordKey is message_invalid (details.field message, the write's id), never applied; vector for it
Some checks failed
CultMesh QUIC native / Build win32-x64 (pull_request) Has been skipped
CultMesh QUIC native / Build linux-x64 (pull_request) Has been skipped
CultMesh QUIC native / Release native binaries (pull_request) Has been skipped
CultMesh portability and performance / ubuntu-latest reactive semantics and leases (pull_request) Failing after 5m55s
CultMesh QUIC native / Verify committed binaries match SHA256SUMS (pull_request) Successful in 46s
621b86ca15
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
libby merged commit 5bf2428295 into main 2026-11-08 19:49:40 +00:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
GameCult/CultLib!59
No description provided.