Rust RUDP server: pin blank schemaId/recordKey refusal, decide sequenced refusal per frame #63

Merged
libby merged 16 commits from libby/rs-receipt-key-tests-per-frame into main 2026-11-09 14:15:50 +00:00
Member

Two Rust-only follow-ups on the RUDP document server (packages/cultmesh-rs), both from Heimdall's round-three review.

  • heimdall-low-rust-keys-untested: test a_write_whose_schema_id_or_record_key_names_nothing_is_refused_and_never_handed_to_the_store. For put and delete, with schemaId or recordKey empty, a space, or "\t \n": refused message_invalid, details.field "message", the write's id; the store's authorize_raw_document is never called for it, nothing applied, session goes on (spec rule 5; vector error_v1_empty_schema_id_is_unreadable_and_names_the_write).
  • heimdall-low-refuse-sequenced-per-frame: delivered was not only this packet's frames; a reliable-sequenced packet filling the gap an ordered packet was held behind released that ordered write in the same delivery, and it was refused too. The refusal now applies to a frame only when frame.sequence <= packet.sequence (the packet's own frame; released ordered frames sit above it). Test a_sequenced_packet_releasing_a_held_ordered_write_refuses_only_its_own fails before the fix (2 refusals, expected 1).

Regions touched in src/rudp_document_server.rs: only the if refuse_sequenced condition in the result.delivered loop of poll_once (+ 2 comment lines). The text/blank-field code (missing_required_write_field use, rejection reasons) is untouched. Tests are appended before a_refused_write_keeps_the_session_from_going_idle, plus one helper swap_fixstr.

Mutation (cargo-mutants 27.1.0, prlimit --as=4GiB --cpu=300, -j1):

  • keys: cargo mutants --list generates for require_non_empty only the body-to-Ok(()) mutant (it does not generate a trim-dropping one), and it cannot run the cultmesh test against a mutation in the separate cultnet-rs crate, so I applied by hand: body to Ok(()) caught; value.trim().is_empty() to value.is_empty() caught; validate_raw_document_record to Ok(()) caught.
  • per-frame: --in-diff over the src change: 3 mutants, 2 caught (&&->||, <=->>), 1 unviable (poll_once -> Ok(Default::default())), 0 missed. The pre-fix behaviour (no per-frame condition) is the test's failing baseline.

🤖 Generated with Claude Code

Merged head and what is not in main

This PR merged at head 2f3b30ab. The two follow-ups above (key-refusal test; refuse-sequenced-per-frame condition) are what main holds.
An earlier version of this description carried an "Update 74705c7c" section (serial-arithmetic frame/packet ordering and refusing a superseded sequenced write from its own payload). That commit was never pushed to the forge and is not in main, so those changes and the write-receipt.md sentence about unanswered superseded fragments are not merged; they remain a follow-up.

Two Rust-only follow-ups on the RUDP document server (packages/cultmesh-rs), both from Heimdall's round-three review. - `heimdall-low-rust-keys-untested`: test `a_write_whose_schema_id_or_record_key_names_nothing_is_refused_and_never_handed_to_the_store`. For put and delete, with `schemaId` or `recordKey` empty, a space, or `"\t \n"`: refused `message_invalid`, `details.field` `"message"`, the write's id; the store's `authorize_raw_document` is never called for it, nothing applied, session goes on (spec rule 5; vector `error_v1_empty_schema_id_is_unreadable_and_names_the_write`). - `heimdall-low-refuse-sequenced-per-frame`: `delivered` was not only this packet's frames; a reliable-sequenced packet filling the gap an ordered packet was held behind released that ordered write in the same delivery, and it was refused too. The refusal now applies to a frame only when `frame.sequence <= packet.sequence` (the packet's own frame; released ordered frames sit above it). Test `a_sequenced_packet_releasing_a_held_ordered_write_refuses_only_its_own` fails before the fix (2 refusals, expected 1). Regions touched in `src/rudp_document_server.rs`: only the `if refuse_sequenced` condition in the `result.delivered` loop of `poll_once` (+ 2 comment lines). The text/blank-field code (`missing_required_write_field` use, rejection reasons) is untouched. Tests are appended before `a_refused_write_keeps_the_session_from_going_idle`, plus one helper `swap_fixstr`. Mutation (cargo-mutants 27.1.0, `prlimit --as=4GiB --cpu=300`, `-j1`): - keys: `cargo mutants --list` generates for `require_non_empty` only the body-to-`Ok(())` mutant (it does not generate a trim-dropping one), and it cannot run the cultmesh test against a mutation in the separate cultnet-rs crate, so I applied by hand: body to `Ok(())` caught; `value.trim().is_empty()` to `value.is_empty()` caught; `validate_raw_document_record` to `Ok(())` caught. - per-frame: `--in-diff` over the src change: 3 mutants, 2 caught (`&&`->`||`, `<=`->`>`), 1 unviable (`poll_once` -> `Ok(Default::default())`), 0 missed. The pre-fix behaviour (no per-frame condition) is the test's failing baseline. 🤖 Generated with [Claude Code](https://claude.com/claude-code) ### Merged head and what is not in main This PR merged at head 2f3b30ab. The two follow-ups above (key-refusal test; refuse-sequenced-per-frame condition) are what main holds. An earlier version of this description carried an "Update 74705c7c" section (serial-arithmetic frame/packet ordering and refusing a superseded sequenced write from its own payload). That commit was never pushed to the forge and is not in main, so those changes and the `write-receipt.md` sentence about unanswered superseded fragments are not merged; they remain a follow-up.
heimdall-low-rust-keys-untested.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Rust RUDP server: decide the sequenced-channel refusal per frame
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 7m5s
CultMesh QUIC native / Verify committed binaries match SHA256SUMS (pull_request) Successful in 32s
2cd55aae47
A sequenced packet's arrival can release held ordered frames in the same delivery; refuse only the packet's own frame.
heimdall-low-refuse-sequenced-per-frame.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Rust RUDP server: answer a superseded sequenced write; order frame and packet by serial arithmetic
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
74705c7cd1
Superseded packet: a reliable-sequenced packet that arrives after a newer one
delivers no frame, yet is acknowledged and never resent, so its write went
unanswered. The test (red first: only "new" was refused, "old" got nothing)
showed the drop happens in the session below frame decoding, but an unfragmented
packet carries its whole frame, so the server can read the id from the packet
payload. Chosen: refuse it from the packet payload. A superseded fragment of a
larger write has no readable id in the packet, so write-receipt.md now says that
write may go unanswered and the client's unconfirmed outcome covers it.

Ordering: frame.sequence <= packet.sequence is replaced by sequence_at_or_before,
serial arithmetic modulo 2^32. Deviation from the brief: cultnet-rs'
at_or_before carries a 4096-sequence receive window made for duplicate detection;
a frame's first fragment may lie further back than that, so reusing it would
leave such a sequenced write unrefused and applied. The new test pins that, and a
hand mutant with the window fails it. A wrapped sequence cannot reach this
compare through the session today (it is judged a duplicate), so the unit test
pins the helper, not an end-to-end wrap.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Rust RUDP server: pin the superseded-write refusal against a releasing packet and the half-space bound
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 5m25s
CultMesh QUIC native / Verify committed binaries match SHA256SUMS (pull_request) Successful in 26s
6d49136c2e
cargo-mutants on the previous commit missed two mutants: `&&` to `||` in the
superseded-packet check (a packet that also releases another write hid it) and
`<` to `<=` at the half-space bound of sequence_at_or_before. A test and two
assertions kill them; the rerun catches 14 of 15 (1 unviable).

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Rust RUDP server: answer a sequenced write once, not once per send
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
f6b27a86f3
A repeat of a packet the session already took, and a packet too far ahead to be
remembered, delivered no frame and so drew the superseded-write refusal again on
every send, breaking write-receipt rule 1. The refusal now applies only to the
first packet the session takes for a sequence; CultNetRudpSession::has_received
exposes that test.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
libby force-pushed libby/rs-receipt-key-tests-per-frame from f6b27a86f3
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
to 636b387cf1
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 16s
2026-11-09 09:13:33 +00:00
Compare
validateBinaryMessageRoot, requireNonEmptyString and requireOptionalStringArray
used String.prototype.trim(), which takes U+FEFF and misses U+0085, so the TS
decoder and the servers disagreed on what names nothing (write-receipt rule 5).

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
write-receipt: seven tracker vectors, a tracker that ends with its session, runners that count
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 7m39s
CultMesh QUIC native / Verify committed binaries match SHA256SUMS (pull_request) Successful in 22s
f0c0f936ba
New scenario vectors: id-less error.v1 with one and with two writes unanswered,
sendFailed, an id-less error after the window, a receipt one ms inside the window
and at the exact boundary (late), a send after sessionEnd. The last exposed a
TS tracker leaving such a write pending forever; it now settles session_ended.
The file declares counts, and the TS replay runner fails on a count it did not
run or an event key scenarios.semantics does not name.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Merge remote-tracking branch 'origin/libby/write-receipt-text-blank-rules' into libby/rs-receipt-key-tests-per-frame
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 8m39s
CultMesh QUIC native / Verify committed binaries match SHA256SUMS (pull_request) Successful in 1m0s
2f3b30ab85
libby merged commit 5ba3e9e2aa into main 2026-11-09 14:15:50 +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!63
No description provided.