C#: database server answers writes with write_admitted / error.v1 (write-receipt spec) #61

Merged
libby merged 14 commits from libby/cs-write-receipt-server into main 2026-11-08 19:51:53 +00:00
Member

C# server half of the write receipt spec (#59), mirroring Rust #60 after Heimdall's review. Opt-in only: a connection is receipt-capable when its first hello (or, absent a hello, nothing: its first write fixes it legacy) lists cultnet.write_admitted.v0 and cultnet.error.v1; a later hello changes nothing. The server answers a capable hello with its own listing both. Capability is framing only; RemoteWriteAuthority decides as before.

  • A put/delete gets write_admitted (operation, schemaId, recordKey echoed) after ApplyPutAsync/ApplyDeleteAsync returns, also after a committed-with-log-failure. Every refusal on a capable connection is error.v1 with the write's id (remote-write refusal, unowned schema, routing error, any other exception); a write with no id is message_invalid with a nil id; an undecodable frame is message_invalid carrying its id when the frame is a map holding one. Non-capable connections are answered exactly as before.
  • (connection, messageId) dedupe bound to SHA-256 of [operation, schemaId, recordKey, payload] (the four spec vectors are pinned). The check runs inside the authorize callback, so authorization comes first. Same content after admission: receipt again, no re-commit. Same id other content (also other key or operation): message_invalid field messageId. In flight: no second answer. A refused or unanswered write releases its claim. Cap 1024 per connection refuses new writes with error.v1 (id, no code); remembered ids still re-receipt. A refusal never ends the connection.
  • Shard forwarder: CultNetSchemaWriteForwarder sends a hello first, then the write under its original id, and completes only on the primary's receipt; refusal throws CultNetWriteRefusedException, silence CultNetWriteUnconfirmedException (window default 5 s). The server relays the receipt as write_admitted, the refusal as error.v1, both under the original id; silence or any other failure relays nothing (and frees the claim). Peers that did not opt in see no change; an unconfirmed forward is silent for them as before.

Departures / limits: actor is the LiteNetLib connection, not an authenticated identity; dedupe lasts the connection (matches #59's session scope). A forwarding primary that predates receipts costs the forwarder a 5 s unconfirmed. ICultNetShardWriteForwarder now means "completes only on the primary's admission". The decode-failure answer in Server.cs is not reached by a test (it needs a live LiteNetLib peer; the fixed port 3075 makes a loopback test unsafe): mutant survives, the answer itself is tested through AnswerUnreadableSchemaFrame. Tests were written after the code, so red-first is evidenced by mutants, not a failing run.

Tests: 38 new/changed in CultNetWriteReceiptServerTests + forwarder tests; Networking suite 543 pass. 17 hand mutants, 16 killed, 1 survivor (above). Build ran without a cap (MSBuild cannot start under --as); tests ran under prlimit --as=8589934592 --cpu=300 with DOTNET_GCHeapHardLimit=0x100000000 (testhost cannot start under 4 GiB). No tag, no publish.

🤖 Generated with Claude Code

Test evidence note: the shard forwarder tests (NotSent boundary, unconfirmed/no re-forward) stand on mutants, not red-then-green; they were not run against the old code first. Later round-two and fixed-text tests were run red first. Not implemented in C#: the unreliable-channel refusal (the schema channel has no reliability check today).

C# server half of the write receipt spec (#59), mirroring Rust #60 after Heimdall's review. Opt-in only: a connection is receipt-capable when its first hello (or, absent a hello, nothing: its first write fixes it legacy) lists `cultnet.write_admitted.v0` and `cultnet.error.v1`; a later hello changes nothing. The server answers a capable hello with its own listing both. Capability is framing only; `RemoteWriteAuthority` decides as before. - A put/delete gets `write_admitted` (operation, schemaId, recordKey echoed) after `ApplyPutAsync/ApplyDeleteAsync` returns, also after a committed-with-log-failure. Every refusal on a capable connection is `error.v1` with the write's id (remote-write refusal, unowned schema, routing error, any other exception); a write with no id is `message_invalid` with a nil id; an undecodable frame is `message_invalid` carrying its id when the frame is a map holding one. Non-capable connections are answered exactly as before. - `(connection, messageId)` dedupe bound to SHA-256 of `[operation, schemaId, recordKey, payload]` (the four spec vectors are pinned). The check runs inside the authorize callback, so authorization comes first. Same content after admission: receipt again, no re-commit. Same id other content (also other key or operation): `message_invalid` field `messageId`. In flight: no second answer. A refused or unanswered write releases its claim. Cap 1024 per connection refuses new writes with `error.v1` (id, no code); remembered ids still re-receipt. A refusal never ends the connection. - Shard forwarder: `CultNetSchemaWriteForwarder` sends a hello first, then the write under its original id, and completes only on the primary's receipt; refusal throws `CultNetWriteRefusedException`, silence `CultNetWriteUnconfirmedException` (window default 5 s). The server relays the receipt as `write_admitted`, the refusal as `error.v1`, both under the original id; silence or any other failure relays nothing (and frees the claim). Peers that did not opt in see no change; an unconfirmed forward is silent for them as before. Departures / limits: actor is the LiteNetLib connection, not an authenticated identity; dedupe lasts the connection (matches #59's session scope). A forwarding primary that predates receipts costs the forwarder a 5 s unconfirmed. `ICultNetShardWriteForwarder` now means "completes only on the primary's admission". The decode-failure answer in `Server.cs` is not reached by a test (it needs a live LiteNetLib peer; the fixed port 3075 makes a loopback test unsafe): mutant survives, the answer itself is tested through `AnswerUnreadableSchemaFrame`. Tests were written after the code, so red-first is evidenced by mutants, not a failing run. Tests: 38 new/changed in `CultNetWriteReceiptServerTests` + forwarder tests; Networking suite 543 pass. 17 hand mutants, 16 killed, 1 survivor (above). Build ran without a cap (MSBuild cannot start under `--as`); tests ran under `prlimit --as=8589934592 --cpu=300` with `DOTNET_GCHeapHardLimit=0x100000000` (testhost cannot start under 4 GiB). No tag, no publish. 🤖 Generated with [Claude Code](https://claude.com/claude-code) **Test evidence note:** the shard forwarder tests (NotSent boundary, unconfirmed/no re-forward) stand on mutants, not red-then-green; they were not run against the old code first. Later round-two and fixed-text tests were run red first. Not implemented in C#: the unreliable-channel refusal (the schema channel has no reliability check today).
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
C#: database server answers writes with write_admitted / error.v1 (write-receipt spec)
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 5m46s
CultMesh QUIC native / Verify committed binaries match SHA256SUMS (pull_request) Successful in 36s
b0ea18371f
Opt-in per connection by the first hello (or first write); the server answers a
capable hello with its own. A put or delete commits, then gets write_admitted under
its id; every refusal is error.v1 with the id (an unreadable frame carries the id
when it still holds one). (actor, messageId) dedupe is bound to the content hash and
asked only after the write authority grants; same id other content is message_invalid.
The shard write forwarder says hello, waits for the primary's receipt, and the server
relays only what the primary answered under the original id.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
C#: receipt server follows Heimdall's review of #61
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 5m30s
CultMesh QUIC native / Verify committed binaries match SHA256SUMS (pull_request) Successful in 21s
ed5f9d30d1
Look the id up first and authorize only on first sighting; remember refusals within the cap; never re-forward an id whose primary stayed silent; end the connection when an answer cannot be queued; fixed refusal texts; limit_reached cap refusal; commit-point doc on ApplyPut/Delete.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
C#: drop the redundant Silent state of a write claim (a silent claim is an unanswered one)
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
74b8a4cbf8
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
C#: loopback test for the unreadable-frame answer; Server.Port seam; the answer now reaches the wire
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
2e5df24e96
An undecodable frame is classed legacy by Decode, so the schema-side guard was unreachable
and a receipt peer was never answered. The guard moves to the legacy decode.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
C#: a forward is retryable only when provably not sent; unconfirmed otherwise; refusals carry fixed text
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
541d734675
CultNetWriteNotSentException names the pre-send boundary (connect, hello, no endpoint,
or a send port that proves nothing was written). Every other forwarding failure keeps
the session claim. ForUnownedSchema and the routing refusal no longer carry exception text.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
C#: receipt ids capped at 256 bytes and held as digests; limit refusals remembered, no slot freed; required fields refused with fixed text; RUDP server advertises no receipt frames
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
c4d1efe000
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
C#: v0 errors for snapshot, shard log and variant refusals carry fixed text, never an exception's message
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 5m35s
CultMesh QUIC native / Verify committed binaries match SHA256SUMS (pull_request) Successful in 36s
ef5cd88111
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
C#: tests kill the replay-details and unreliable non-write mutants
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
48cf3b3323
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
C#: test kills the schema id detail-value mutant
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 5m59s
CultMesh QUIC native / Verify committed binaries match SHA256SUMS (pull_request) Successful in 31s
34bf7aa0ee
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
C#: a put or delete with an empty or blank schemaId or recordKey is refused message_invalid (as an unreadable frame, like the Rust server) and not applied
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 5m35s
CultMesh QUIC native / Verify committed binaries match SHA256SUMS (pull_request) Successful in 31s
1a228349f1
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
libby merged commit 92b4cb7ce9 into main 2026-11-08 19:51:53 +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!61
No description provided.