C#: database server answers writes with write_admitted / error.v1 (write-receipt spec) #61
Loading…
Reference in a new issue
No description provided.
Delete branch "libby/cs-write-receipt-server"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
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.v0andcultnet.error.v1; a later hello changes nothing. The server answers a capable hello with its own listing both. Capability is framing only;RemoteWriteAuthoritydecides as before.write_admitted(operation, schemaId, recordKey echoed) afterApplyPutAsync/ApplyDeleteAsyncreturns, also after a committed-with-log-failure. Every refusal on a capable connection iserror.v1with the write's id (remote-write refusal, unowned schema, routing error, any other exception); a write with no id ismessage_invalidwith a nil id; an undecodable frame ismessage_invalidcarrying 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_invalidfieldmessageId. In flight: no second answer. A refused or unanswered write releases its claim. Cap 1024 per connection refuses new writes witherror.v1(id, no code); remembered ids still re-receipt. A refusal never ends the connection.CultNetSchemaWriteForwardersends a hello first, then the write under its original id, and completes only on the primary's receipt; refusal throwsCultNetWriteRefusedException, silenceCultNetWriteUnconfirmedException(window default 5 s). The server relays the receipt aswrite_admitted, the refusal aserror.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.
ICultNetShardWriteForwardernow means "completes only on the primary's admission". The decode-failure answer inServer.csis 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 throughAnswerUnreadableSchemaFrame. 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 underprlimit --as=8589934592 --cpu=300withDOTNET_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).