R1's rules win where both sides decided the same question: a zero-byte file is
not a store (refused as unreadable in every runtime), only nothing at the path is
empty, a dangling link is an I/O error, refusals never echo stored values and echo
a header only as cultcache.store.v<digits>, writes report Rejected, NotReplaced
and ReplacedNotDurable. C2a's rules win on what it added: the v3 element-id
header, variants, whole-file proof, typed unreadable errors in C#, TypeScript and
Python. Rust keeps R1's CultCacheStoreUnreadable{path, kind}; C2a's
StoreUnreadableError is deleted. The readability manifest's zero-byte row is
refuses x4.
Not yet built or tested: verification follows in the next commits.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
4002a996's refusal caught the self-test itself: the reconciliation block
registered kotlin.reactive_note beside kotlin.reactive_note.ui under one
schema id in one cache. The block tests reconciliation, not two types for one
schema, so its note is now kotlin.reconcile_note under its own schema id.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
F7 (operator, 2026-09-30): "Schema aliasing sounds like a really bad idea,
I support retiring it." CultCache.register wrote its type and schema maps
unconditionally, and put, get, getAll and delete register implicitly, so one
Kotlin cache could hold two types for one schema and codecForSchema answered
with the last.
- register refuses a codec whose type already holds another schema id, or
whose schema id another type holds, with the new
CultSchemaConflictException (schema id, both document types, empty record
key), the Kotlin counterpart of the other runtimes' typed conflict. The
implicit registration in put, get, getAll and delete goes through the same
check. Registering the same type for the same schema again stays allowed.
- Self-test: other-type same-schema and same-type other-schema claimants,
through register, put, get, getAll and delete, each in a fresh cache; the
first codec still answers for its schema afterwards.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A JVM wildcard socket is dual-stack, so a client bound to 0.0.0.0 reports the
IPv6 wildcard as its local address and the self-test's first check failed on
Linux. Assert that a client for a remote endpoint binds a wildcard, and keep the
exact loopback checks.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The CultMesh RUDP client entry points defaulted bindHost to 127.0.0.1. On
Windows a socket bound to loopback cannot send off the host (os error 10051),
so a default client could not reach a remote endpoint.
cultNetRudpClientBindAddress owns the rule: loopback for a loopback endpoint,
otherwise the unspecified address of the endpoint's family (0.0.0.0 or ::).
bindHost defaults to null on every client entry point, which uses the rule. An
explicit bindHost still wins; a CultMesh server still binds 127.0.0.1 unless
told otherwise. The self-test pins all three.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
REC-032 commit 1: move the canonical 33 TS + witness-artifact-bundle
schema files (34 total) into runtime-neutral CultLib/contracts/cultnet,
move the interop-note schema (and delete the zero-consumer discovery-*
schemas per D3, tagged parked/cultnet-discovery-contracts) into
contracts/cultnet/interop, and delete the 27 C# and 24 Rust duplicate
copies after confirming byte-for-byte equivalence (only
cultnet.snapshot-request differs: TS carries shardId/shardEpoch, a
known drift already resolved in favor of the TS shape).
Rewrite every $id from https://github.com/GameCult/cultnet-ts/contracts/
and .../cultnet-ts/integration/contracts/ to
https://github.com/GameCult/CultLib/contracts/cultnet/(interop/); give
the two shard-catalog schemas URL ids per D1, replacing the bare
cultnet.shard_catalog_*.v0 identifiers; leave ghostlight.agent-state's
Ghostlight-owned $id untouched per D5.
Repoint every runtime's load path and id literal at the new location:
C# csproj EmbeddedResource + LogicalName, Rust include_str! paths,
TS contracts.ts imports + swarm-contracts manifest/tooling + package.json
files/prepack, and the id/path literals in the C#, Rust, Python and
Kotlin interop tests and peers. Python and Kotlin still build their own
schema JSON in-process (D2); only their base constants, id literals,
and (for Kotlin) contentHash literals were swapped. Every Kotlin
contentHash was recomputed from the final canonical files using the
runtime's own stable-stringify + SHA-256 function; all matched the
planner's precomputed values except cultnet.schema-catalog-response,
where the planner's proposed value did not match recomputation and the
freshly computed value was used instead.
REC-032, decisions D1/D2/D3/D5.
Co-Authored-By: Claude Sonnet 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>