Clears the cultnet half of the retained debt. The two unported commits from the
retiring GameCult/cultnet-rs lineage were not portable as patches: this lineage
had reworked the same receive path with sequence domains and ACK horizons, and
applying them raised nine conflicts that would have reverted that work. Most of
what they added already existed here by another route -- received-sequence
history, fragment-set count and reassembly bytes are all bounded already. So
the gaps were taken rather than the patches.
Three were real. The per-channel maps grew without limit, and a peer picks its
own channel ids, so ordered and sequenced channel tracking is now capped at 64
each. The out-of-order hold buffer grew without limit, and a peer picks its own
send order, so it is capped at 1024 frames and 4 MB; sizes are computed from the
buffer rather than tracked in counters, which cannot drift.
The fourth was the sharpest. remember_received ran before the duplicate
early-return, so a single forged packet far past the current highest advanced
the receive state beyond every sequence still in flight and starved the
session. Such a packet is now refused without being remembered.
Reliable sequence exhaustion aborted the process through expect(). The capacity
gate that already guards the send window now also refuses when the sequence
space cannot cover the request, and allocation saturates rather than panicking
so no future path can reintroduce the abort.
Five tests cover the bounds and both refusals. cultnet-rs passes 103,
cultcache-rs 48, cultmesh-rs 7, and the TypeScript-Rust RUDP interop lanes pass
5 of 5 with the hardened peer, so legitimate traffic is unchanged.
The parity ledger now records what this actually leaves owed: these are
refusals, not wire-format changes, so the interop lanes stay green whether or
not the other four runtimes match. The gap will not announce itself.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Taking the standalone body as the CultCache base dropped a0b31b8, and it was a
real regression rather than a missing convenience. A record whose catalog entry
is absent -- written by another runtime, or older than the catalog it was
stamped against -- made decode_store_snapshot fail, and one such record failed
the entire pull. Recovering the type from the payload is what "across runtimes"
meant in that commit's title.
Restored in full: the payload is a MessagePack array whose first field is the
schema version, so the schema name is recoverable from the record itself; the
registry now also indexes schema name to entry type and refuses a name claimed
by a second type; and pull resolves a persisted type that is either a
registered type or a recovered schema name. The stale schema id is preserved on
the envelope rather than rewritten.
743eaa5's durability half needed no code. The standalone lineage reached the
same place independently through crash-atomic single-file snapshots and
abandoned staging cleanup, and its cleanup carries the same protections: only a
candidate named for this exact store plus a canonical UUID is removed, and
anything that is not a regular file is skipped. Untested, though, which is how
a careful deletion rule quietly becomes a careless one. Now asserted: an
abandoned canonical candidate is removed while a non-UUID lookalike, a bare
.tmp suffix, and another store's candidate all survive, and the committed
snapshot still reloads.
48 tests pass. This clears the cultcache half of the retained debt from b672fb7.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Separating the reliable and lossy sequence domains landed in Rust, TypeScript
and C# on a branch. The Python and Kotlin halves were written at the same time
and left uncommitted in the worktree, so three runtimes spoke the new contract
and two spoke the old one -- the exact cross-runtime disagreement this
repository exists to prevent.
Both were already complete and implement the same three rules as the others:
ordered delivery requires reliability, sequenced channels carry their own
per-channel counters with latest-wins delivery, and duplicate suppression is
gated on packet.reliable so lossy datagrams are remembered nowhere.
Python also gains the three tests Rust, TypeScript and C# each received and it
did not: lossy packets cannot create reliable ordered gaps, unreliable
sequenced delivery is scoped to its channel, and unreliable ordered delivery is
rejected. Python passes 141.
Kotlin is committed unverified. This machine has no JVM or Gradle, and the
package carries no runtime-local test file, so its only gate is the interop
lane in CI. The change mirrors the four runtimes proven here line for line, but
mirroring is not evidence and CI is the witness.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Separating the sequence domains gated duplicate suppression on packet.reliable
in Rust, TypeScript and C# alike, so lossy datagrams are remembered nowhere and
a replayed one is delivered again. That is the intended contract -- lossy
traffic is fire-and-forget and its history is bounded by not existing -- but
replay_history_is_bounded_and_idle_hub_sessions_expire still asserted the old
behaviour with a lossy packet and had been left failing on the branch.
The three tests the change added in each runtime cover ordered-requires-
reliable, per-channel sequenced scope, and lossy gaps. None covers the bounded
replay window, which was this test's real subject, so removing the assertion
would have dropped the only coverage of a security property.
The lossy assertion now states the new contract, and the reliable domain gets
its own replay check: 4,100 acknowledged reliable sends, then the oldest
replayed and rejected by the window.
Rust 151, C# 148, TypeScript 48, Python 138 all pass.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude Code reads CLAUDE.md, not AGENTS.md, so this repo's doctrine was
invisible to it. This one-line import makes the existing AGENTS.md load
without duplicating or renaming it. Codex is unaffected.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
exact_reliable_acks_clear_more_than_one_ack_window_of_fragments has been
failing on main. It was not a defect: send_many returned 32 packets where the
test expected 40, and 32 is exactly CULTNET_RUDP_RELIABLE_SEND_WINDOW_PACKETS.
The reliable send window landed and this test kept asserting the pre-window
behaviour, so a test whose name is about crossing an ack window had stopped
crossing one. It now asserts that a window's worth leaves immediately, that
the remaining eight fragments queue, and that acknowledgements promote them
until every fragment is delivered -- the same loop packages/cultnet-rs/tests
/cultnet.rs already uses for this contract.
All three Rust crates are green: 46, 95 and 7.
The NuGet packages rule hides every language package until one is named in
the re-include list, and cultmath, cultmesh-browser and cultmesh-kotlin were
never added. Nothing is currently lost -- every source file in them is
tracked -- but any new file added to those three would have been dropped in
silence. cultmesh-rs hit this on the way in.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Exact acknowledgement receipts landed; bounded session memory and hostile
sequence rollover rejection did not. Until they do, this runtime accepts
sequence and reassembly input the retiring lineage rejects, so its repository
cannot be deleted yet.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Ported from GameCult/cultnet-rs 9dcb88d into CultLib, where the wire contract
belongs. Ghostlight's committed idunn_health.rs imports
CultNetRudpReliableSendStatus to hold an Idunn process write lease current,
and the symbol existed only in the standalone lineage, so nothing could
consume this contract from its canonical home.
Three reconciliations were needed because both lineages reworked RUDP
independently since the 014f998 copy-in:
- reset_peer_state: kept this lineage's version, which also resets the
sequence and fragment counters and clears queued_reliable, and folded in
only the imported change that matters -- rotating session_scope so a peer
reset invalidates outstanding receipts. The imported body also cleared
highest_delivered_sequenced_by_channel, a field the sequence-domain work
renamed away here.
- channel_send_options is a free function in this lineage, not a method.
- The imported guard rejected reliable channels with an expiry. This lineage
has no reliable-expiry concept at all -- the only mentions of expiry in the
file arrived with this patch -- so every reliable channel is already
non-expiring and the guard reduces to the reliability check. A comment
marks where to restore the expiry half if expiry is ever added.
94 tests pass. The one rudp_server_hub failure is pre-existing and reproduces
on clean origin/main.
The other two standalone RUDP commits are deliberately not ported. Bounding
session memory and rejecting hostile sequence rollover rebuild the receive
path -- stale-sequence rejection, per-channel tracking caps, reassembly and
ordered-buffer byte accounting -- in the same code this lineage reworked with
sequence domains and ACK horizons. Merging them changes wire-observable drop
and reassembly behavior, which needs the cross-runtime interop harness to
validate, not a local build.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The only Rust CultMesh implementation in existence was 1,363 lines inside
Ghostlight's vendor directory. It was in no library repo and no standalone
repo, so it had never been checked against the browser, Kotlin, and
TypeScript CultMesh bodies it must interoperate with.
It is not a duplicate of the facade in cultnet-rs. That one is the small
surface the parity table claims for Rust: peer cards, authority leases,
endpoint parsing. This is the stream and session layer -- node, document
sets, RUDP document publication, stream descriptors, negotiation, stream
catalog, and shared-memory frame rings -- which the same table lists under
Rust's "Not claimed". A consumer grew the unclaimed surface because there was
nowhere else to put it.
Its path dependencies already matched this layout and it builds against
CultLib's cultnet-rs unchanged, so it does not depend on the forked RUDP
extras. Seven runtime-local tests pass, and CI now runs them.
The parity table gains no claim from this. The Rust row says plainly that no
interop lane exercises the stream surface, and expansion work records that
runtime-local tests are not a parity claim. Landing code is not evidence.
packages/cultmesh-rs needed an explicit .gitignore re-include; the NuGet
packages rule hides every language package until one is added by name.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The standalone GameCult/cultnet-rs holds six modules CultLib never received:
runtime_authority_contracts, service_identity, provider_health_contracts,
idunn_deployment_brake, idunn_lifecycle_brake, and snapshot_query. They own
Idunn Expected incarnations, dual-proved runtime activation, process write
leases, service-signed presence health, and Odin topology correlation -- the
wire contracts every GameCult daemon must agree on.
They belong here because wire parity conformance lives here and nothing
outside it is checked for cross-runtime agreement. Held as source to be
copied, this contract already forked: Ghostlight vendors it at e542225 while
its own authority map names f4ab237, and the two disagree on whether Present
may coexist with a disagreement. One copy makes them exclusive; the other
separates presence from readiness and gates through into_undisputed_present.
A single owner under the parity harness is the fix; re-vendoring is not.
None of the six touch RUDP, so they land without disturbing the protocol
core. rudp.rs remains genuinely forked -- 140 hunks between the lineages,
with reliable windows and ACK horizons on this side and bounded session
memory, exact acknowledgement receipts, and hostile rollover rejection on the
other. That reconciliation needs the cross-runtime interop harness and is not
attempted here.
Adds ed25519-dalek, zeroize, and the Windows Foundation/Cryptography features
the credential paths require. 96 tests pass. The two rudp_server_hub failures
are pre-existing on this base and reproduce at 230ace6 untouched.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
packages/cultcache-rs forked from GameCult/cultcache-rs at the 5c69bfc
monorepo import and the two lineages then diverged in both directions. The
standalone repo took 28 commits of engine work CultLib never saw: typed SoA
projection, atomic heterogeneous batch publication, single-entry and
conditional batch CAS, a transactional keyed redb backend with serialized
cross-process opens, lockless provider snapshot reads, RAII read-only shared
snapshots, snapshot-fenced replace/append, crash-atomic single-file snapshots,
compaction fences, portable and canonical byte fields, and unified retained
storage authority. That body is 3,714 lines with 48 tests against CultLib's
1,554 with 18.
CultLib is the canonical home because wire parity conformance lives here and
nothing outside it is tested for cross-runtime agreement. A runtime-specific
core-stack repo cannot hold the engine every other runtime must agree with.
The standalone body lands as the base rather than merging 28 commits backward.
It is already proven against a CultLib-derived cultnet-rs: Ghostlight vendors
exactly this pairing and compiles. Here, cultcache-rs passes 46 tests and the
cultnet-rs RUDP gate passes 20 with no source change.
Retained debt, not carried forward: a0b31b8 recovered schema-stamped records
missing catalog entries (SchemaStamped, infer_schema_name,
infer_schema_version_from_payload) and 743eaa5 hardened snapshot replacement
(SnapshotReplacementStage, CommittedSnapshotDurabilityUncertain, stale
candidate cleanup, with_exclusive_lock_using_unlock). Their tests were
white-box against internals this body does not have. The standalone
independently made single-file snapshots crash atomic and cleaned abandoned
staging files, so the durability guarantee is probably held under other names
-- probably is not proven. Re-establish both as black-box tests against the
new API before the standalone repo is retired.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>