Commit graph CultLib/packages
Author SHA1 Message Date
Meta Vens
ac5c86033b Ack Cut D D2: pin the disconnect, the resend loop, the Python resend, ack and accept paths
The injected permanent failure now takes precedence over the injected transient
one, so a test can end a session on a peer that is also losing datagrams and
count exactly which datagrams were attempted afterwards. New tests: a hub
disconnect that can never be sent returns the error; a peer or session ended
in the resend loop is sent nothing more (Rust hub, document server, C#); the
goodbye reaches the peer; the Python server ends the session on a permanent
failure in a resend, an ack and an accept. The oversized-send test compares the
outstanding count with the one the hub's own unacknowledged Accept already
holds.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-09-30 12:25:04 +02:00
Meta Vens
73bf44212e Media FEC: a generator for rs-gf256-v1 known answers from the reference crate
Before reed-solomon-erasure is replaced by an owned GF(2^8) construction, it
produces the vectors the owned code must meet. The ignored test encodes every
standard video geometry (k = 1..=16), standard audio 4+2 at 1, 37 and 1300
bytes, a 4000-byte shard, zero and 0xff data, and the policy's edges (1+1,
255+1, 1+255, 2+254, 128+128, 200+56, 250+6), and reconstructs four erasure
patterns per case, asserting the crate returns the original data.

It is run once and deleted with the crate; git keeps its provenance.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 12:22:33 +02:00
Meta Vens
1c71d98c71 Ack Cut D D1 and D2 (Python): a vanished peer is idle, and a send that can never succeed ends only that peer's session
D1: on Windows the closed port of a vanished peer is reported by the next
recvfrom as ConnectionResetError (WinError 10054), and the RUDP thread returned
on it. It is now idle, as in the Rust document server, the Rust hub and the C#
listener. Both tests fail on Windows without the fix and pass with it.

D2: is_permanent_send_error (EMSGSIZE, EINVAL, EAFNOSUPPORT and their Winsock
equivalents) separates a datagram that can never be sent as built from a lost
one. The server ends that peer's session with a goodbye whose reason names the
error and never the thread; transient failures stay counted losses.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-09-30 12:19:47 +02:00
Meta Vens
64ed64a894 Ack Cut D D2 (Rust): a send that can never succeed is the caller's error inside a send and ends only that peer's session inside a poll
The hub and the document server swallowed every send failure as a counted
loss, so a 70,000-byte reliable frame returned Ok and was resent forever.
Failures now split in two. Permanent means the datagram can never be sent as
built (EMSGSIZE, EINVAL, EAFNOSUPPORT and their Winsock equivalents,
is_permanent_send_error in cultnet-rs); everything else stays a counted loss.
A caller-directed hub send returns the error and queues nothing: the packets
it created are withdrawn and their sequences reissued, so the peer never sees
a gap (a permanent failure after an earlier fragment left ends the session).
Inside a poll the failure ends that peer's session with a goodbye whose reason
names the error, reported to the hub caller as Disconnected, and never fails
the poll. The document server ends the session the same way and reports a
snapshot response it can never send as an application rejection. Tests pin
each path, the classification per OS, BytesSent after a healthy send and a zero
failure count for healthy sends.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-09-30 12:19:46 +02:00
Meta Vens
436b423762 Ack Cut 3 F1: the acknowledged-bytes tests pin pruning on the ack that closes the gap
The bytes counted above a lost packet stop counting once it is acknowledged;
every runtime now asserts that another MiB is admitted after the gap closes.
Rust prunes on expiry only when something expired, so the acknowledgement path
owns the pruning it can observe.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-09-30 12:12:23 +02:00
Meta Vens
0daf6f4cc0 Ack Cut 3 F1: Rust pins that an expired lowest packet releases the acknowledged bytes above it
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-09-30 12:07:28 +02:00
Meta Vens
d7eacd1011 Ack Cut 3 F1: the 4 MiB flow bound counts acknowledged bytes above the lowest unacknowledged sequence
The bound summed only packets still awaiting an ack. Once frames above a lost
packet were acknowledged they stopped counting, so a receiver could be made to
hold more than 4 MiB behind one loss, and its hold buffer ended the session.
The sender now keeps the payload size of each acknowledged sequence above the
lowest pending one until the lowest advances past it, in Rust, TypeScript,
Python and C#. Tests per runtime: one lost packet, four acknowledged 1 MiB
frames, the fifth refused until the gap closes; and the floor moving to a new
lowest sequence.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-09-30 12:04:19 +02:00
Meta Vens
da0315e69e Test: TypeScript writes a record under an older schema id, C# declares it and rewrites it
The C# interop note declares a compatible id and the peer gains a rewrite mode; the TypeScript
cross-runtime test has TS write a record under that id and another schema name, C# open and
rewrite it, and C#, TypeScript, Rust and Python open the result.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-09-30 11:32:47 +02:00
Meta Vens
2125635dff Ack Cut D: the document-server send-failure tests take initial_sequence as an Option
Ack Cut 1c made CultNetRudpSocketTransportOptions.initial_sequence optional; the
new client helper still passed a bare u32.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-09-30 11:26:51 +02:00
Meta Vens
5926804e2b Python test: a document that owns an id is resolved before one registered earlier that lists it
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-09-30 11:14:38 +02:00
Meta Vens
1a04b0d788 Rust: a registered type owns its schema id, so only it may name an id over records the file holds
The previous cut let any supplied type name a schema id over the arrived one, which let a raw
envelope of another type retype the records under an id (the dynamic-payload test refused it and
failed). A rename under a stable id is a write of the registered type that owns the id; a Rust
registered type's id is its entry type, so a supplied envelope whose type is its schema id names
the id and any other type under an occupied id is refused, typed, with the file left as it was.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-09-30 11:10:06 +02:00
Meta Vens
0996fc96f5 Ack Cut 3: the hold-buffer test acknowledges the gap by hand
Cut 1's the_hold_buffer_takes_exactly_1024_frames test held 1,024 frames behind a
sequence the receiver never sees, by sending them all above one unacked packet.
Cut 3's flow window stops a sender 1,023 sequences above its lowest unacked packet,
so the test's sender was refused at frame 1,023. The missing packet is now
acknowledged by hand (the receiver never saw it), so the window slides and what
the test probes is the receiver's hold buffer, not the sender.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-09-30 11:07:30 +02:00
Meta Vens
e6f9b2f8c1 TypeScript: the type check runs across tiers; a renamed stable id reads across runtimes
The writer refuses a write that would change which type an existing record resolves to, unless
the entries of both the existing records and the written ones own the id (a rename; the registered
descriptor wins). Tests pin the arrived-overwrites-registered guard in both id orders, the
content-hash tie-break against the compatible-list order, sorted id processing and the cross-tier
refusal; a cross-runtime test has TS write a schema renamed under a stable id and TS, Rust and
Python open it.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-09-30 11:04:14 +02:00
Meta Vens
6f219bd919 Rust: a record resolves by schema id, and the registered type names a renamed schema id
pull_all_backing_stores resolves a record to a registered type by its schema id (a registered
type's id is its entry type) before the catalog name is tried. The writer tells the entries the
file already holds from the ones its caller supplies, and a supplied type names the schema id over
an arrived one, so a stable-id rename is not a conflict; two types of one tier under one id still
refuse the write.

Tests: a stable-id rename write, a renamed schema opening by id, and an id not overruled by a name.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-09-30 11:04:11 +02:00
Meta Vens
ebe15ab8c9 Python: a record resolves to its document by schema id, and the type check runs across tiers
A record now resolves to the document that owns its schema id, else one that lists it as
compatible, before the catalog name is tried; a schema renamed under a stable id opens, and an id
that names one document is not overruled by a name that names another. The name resolves a record
only when no local document has the id.

The writer refuses a write that would change which type an existing record resolves to, unless the
entries of both the existing records and the written ones own the id (a rename, the registered
descriptor wins).

Tests pin the arrived-overwrites-registered guard in both id orders, the content-hash tie-break
against the compatible-list order, sorted id processing, the cross-tier refusal and the rename, and
a cache whose document does not list the id a loaded record sits under.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-09-30 11:04:07 +02:00
Meta Vens
b8c00117b3 Merge origin/main into hands/ack-cutD 2026-09-30 10:58:23 +02:00
Meta Vens
ede87f7423 Ack Cut 3: the flow-window tests shake hands the way Cut 1 requires
An Accept now counts only while the session's own Connect awaits it, and reliable
data is refused until the handshake seeds the watermark, so the tests' bare Accept
no longer connected the session. The helpers now pair a sender and a receiver
through CreateConnect/AcceptConnect in all four runtimes; the sender's Connect
is acknowledged, so nothing is pending on it when the tests begin. Rust also
takes initial_sequence as an Option.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-09-30 10:57:53 +02:00
Meta Vens
8c71b5c6dd Merge origin/main into hands/ack-cut3
# Conflicts:
#	packages/cultnet-ts/src/rudp.ts
2026-09-30 10:52:49 +02:00
Meta Vens
05b5705d69 Merge ack Cuts 1, 1b and 1c: one watermark, the write belongs to its session, a new generation is recognised
Cut 1: one watermark owns ordered delivery, seeded only by the handshake.
Cut 1b: every ending drops what the session still owed; flush is bound
to its generation. Cut 1c: a Connect repeats only by its sequence;
stale data and stale Connects are handled as TCP does (challenge Ack);
an unanswered attempt retries at +window-1; the session draws its
initial sequence. Wire bytes unchanged in all four runtimes. Five Soul
passes. Public option types changed: initial_sequence is optional.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 10:28:05 +02:00
Meta Vens
acbf031c5b Tests: the arrived entries' hashes sort before the registered one's, so a writer that ignores the tiers picks the arrived one
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-09-30 10:16:53 +02:00
Meta Vens
ca0b62023d Writers: drop the same-tier dedupe refusal (unreachable), and pin tie order and the registered lister
Two entries of one tier that share an own id are refused when the id is carried, so a refusal at dedupe could never fire.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-09-30 10:14:33 +02:00
Meta Vens
7a5b64ad88 TypeScript and Python writers: records are compared for type within a tier only
A read-back record's type is its schema's name and a supplied one's is the document type, so records of different tiers are never
compared; two supplied (or two arrived) records of different types under one id still refuse the write.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-09-30 10:13:44 +02:00
Meta Vens
e77246b657 Writers derive the catalog from the records they write; the merging is deleted
Every writer (C# single-file and directory, TypeScript, Python; Rust already did) chooses, for each schema id a record carries, one
entry that publishes it: the entry that owns the id (a registered descriptor, else an arrived entry), else one that lists it as
compatible. A registered descriptor wins over an arrived entry with the same own id. Entries are written as chosen, with no union of
compatible ids and no dependence on arrival order; an entry no record needs is not written. Two entries of one tier that share an own id
and disagree on the schema name, records of different types under one id, or a record no chosen entry publishes refuse the write with a
typed error (CultSchemaConflictException, SchemaConflictError) naming the id, the names and a record key, and the store is left as it was.
CultSchemaCatalogEntry.MergeById and the Python and TypeScript merge paths are gone. A cache-held record unchanged since the file was read
is rewritten under its registered id (C# single-file and directory), so an entry that lists an older id is not needed to publish it.
Changelogs for cultnet-py (removed export) and cultcache-py.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-09-30 10:03:34 +02:00
Meta Vens
c45edf5858 Ack Cut 1d batch 2 (Python): an Ack does not retire a Connect awaiting its Accept; the fresh attempt jumps by the receive window less one
While a Connect awaits its Accept, only an Accept the client honours retires it. An Ack that names the Connect (a server's reply to a repeat or a stale copy) no longer wedges the client with nothing to resend. The Connect payload lives on the session, so a fresh attempt can always be built. The fresh attempt uses the abandoned sequence plus the receive window less one (serial), so a late copy of the abandoned Connect stays inside the new one's stale window; only a session's first Connect draws at random.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-09-30 09:53:06 +02:00
Meta Vens
9c562adaf2 Ack Cut 1d batch 2 (TypeScript): an Ack does not retire a Connect awaiting its Accept; the fresh attempt jumps by the receive window less one
While a Connect awaits its Accept, only an Accept the client honours retires it. An Ack that names the Connect (a server's reply to a repeat or a stale copy) no longer wedges the client with nothing to resend. The Connect payload lives on the session, so a fresh attempt can always be built. The fresh attempt uses the abandoned sequence plus the receive window less one (serial), so a late copy of the abandoned Connect stays inside the new one's stale window; only a session's first Connect draws at random.

The cultmesh-ts document server has a test that it does not acknowledge by name a reliable packet its session did not receive.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-09-30 09:53:06 +02:00
Meta Vens
7b46b4c481 Ack Cut 1d batch 2 (Rust): an Ack does not retire a Connect awaiting its Accept; the fresh attempt jumps by the receive window less one
While a Connect awaits its Accept, only an Accept the client honours retires it. An Ack that names the Connect (a server's reply to a repeat or a stale copy) no longer wedges the client with nothing to resend. The Connect payload lives on the session, so a fresh attempt can always be built. The fresh attempt uses the abandoned sequence plus the receive window less one (serial), so a late copy of the abandoned Connect stays inside the new one's stale window; only a session's first Connect draws at random.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-09-30 09:53:05 +02:00
Meta Vens
e56c96d6be Ack Cut 1d (Python): stale generations are duplicates, a stale Connect is answered not obeyed, sessions draw their own sequence
A frame at or before the seeded watermark is a duplicate of an earlier generation and is acknowledged, never delivered again (serial compare within the receive window). A Connect that precedes the current generation's within the window is a stale copy of an earlier attempt: it is answered like a repeat (Accept owed or Ack), changes no state and does not refresh liveness. A Connect unanswered for 3 s is replaced by a fresh Connect with a newly drawn sequence and the same payload. In server mode a Connect from another endpoint is always a new generation. The initial sequence is unset by default and each session draws its own; the public random helper is removed.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-09-30 09:11:51 +02:00
Meta Vens
ba873d04c4 Ack Cut 1d (TypeScript): stale generations are duplicates, a stale Connect is answered not obeyed, sessions draw their own sequence
A frame at or before the seeded watermark is a duplicate of an earlier generation and is acknowledged, never delivered again (serial compare within the receive window). A Connect that precedes the current generation's within the window is a stale copy of an earlier attempt: it is answered like a repeat (Accept owed or Ack), changes no state and does not refresh liveness. A Connect unanswered for 3 s is replaced by a fresh Connect with a newly drawn sequence and the same payload. In server mode a Connect from another endpoint is always a new generation. The initial sequence is unset by default and each session draws its own; the public random helper is removed.

The cultmesh-ts document server acknowledges what the session received, not every reliable packet by name.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-09-30 09:11:51 +02:00
Meta Vens
e02d027019 Ack Cut 1d (Rust): stale generations are duplicates, a stale Connect is answered not obeyed, sessions draw their own sequence
A frame at or before the seeded watermark is a duplicate of an earlier generation and is acknowledged, never delivered again (serial compare within the receive window). A Connect that precedes the current generation's within the window is a stale copy of an earlier attempt: it is answered like a repeat (Accept owed or Ack), changes no state and does not refresh liveness. A Connect unanswered for 3 s is replaced by a fresh Connect with a newly drawn sequence and the same payload. In server mode a Connect from another endpoint is always a new generation. The initial sequence is unset by default and each session draws its own; the public random helper is removed.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-09-30 09:11:51 +02:00
Meta Vens
29e60353f0 Merge media FEC Cut 3: one Reed-Solomon codec for audio and video
rs-gf256-v1 (construction documented, independent implementation matches
every KAT); audio parity with checked block arithmetic; every encoded
record must fit the producer's datagram budget (default 1431), and no
media record exceeds 64 KiB on encode, decode, send or reassembly. The
"audio" channel is retired. Muninn and Ratatoskr bump in lockstep at
Cuts 4-5. Three Soul passes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 09:05:11 +02:00
Meta Vens
59f9e9aedb One local schema id in cultmesh-py; catalog writers merge only the same schema; a compatible entry before its owner reads as the owner
cultnet-py's resolve_document_and_schema_id_for_raw_record is replaced by resolve_document_for_raw_record, which resolves the
document only; cultmesh-py keys previous values by the local schema id, so a snapshot under a compatible id or the schema name
reports an update with its previous value. Writers (C#, TypeScript, Python) merge same-id entries only when their schema names match;
another schema's entry is dropped or the record gets a default entry under its own id, and a record's own id is claimed before an
entry that lists it as compatible. The C# single-file writer prefers the entry being written now, as the directory writer does, so
name, content hash and members come from the registered schema and the compatible ids are the union. Vector:
compatible-before-owner-v3. A cultnet-py test's schema path no longer depends on the working directory.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-09-30 09:01:00 +02:00
Meta Vens
4d93dc858e Media wire tests: hold an unfragmented media packet to the ceiling
Mutation run showed the unfragmented receive path of the RUDP session was
undefended (only fragmented reassembly was tested).

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-09-30 08:29:05 +02:00
Meta Vens
df892e40e3 Ack Cut 1c tests (cultmesh-rs): the budget test sets a snapshot limit that fits its session budget
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-09-30 08:20:26 +02:00
Meta Vens
f96a0e7f7a Catalog writers merge entries that share an id; every reader resolves an id by its owner first
Entries that share a schema id are one schema seen by different writers, so the writer lists every id any of them lists, in any
order: C# single-file and directory (CultSchemaCatalogEntry.MergeById), TypeScript and Python. Rust synthesises one entry per id and
has no such merge. An id that one entry owns and another lists as compatible names the owner in Rust, TypeScript, Python and the TS
inspector, as C# already did (own-id-over-compatible-v3). Also: a directory manifest in an unreadable format is the typed refusal
naming it, cultnet-py applies and reports a record under the local schema id, and Rust's error prints its cause once.
Vectors for the Mesh layout discriminator: a nil and an integer at catalog slot 5.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-09-30 08:17:59 +02:00
Meta Vens
7c149bf0c8 Media wire tests: fix helper name clash and move fixtures to the last-pts deadline rule
The sized parity helpers collided with the existing fixtures of the same name,
so they are now sized_audio_parity and sized_video_parity. Two existing cases
encoded the old base-pts deadline rule: the corruption table expects the new
last pts_ticks message, the edge-of-bound cases admit a deadline at the last
pts, and the 254-shard audio case sets a deadline that covers its last slot.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-09-30 08:14:22 +02:00
Meta Vens
1bbc0ace69 Media wire: the encoder refuses a record over the ceiling too
A producer that builds an oversized record without going through the FEC
policy would otherwise learn only when the receiver silently dropped it.
encode_media_wire_record now refuses anything over
GAMECULT_MEDIA_MAX_WIRE_BYTES, with the decoder's message. The decode ceiling
test pads a valid record by one byte to stand in for a peer that sent one.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-09-30 08:11:26 +02:00
Meta Vens
51e9a550f9 Media wire: bound every record by one ceiling, and validate audio parity against its last pts
Removing the 720-byte shard cap left the decoder with no per-record size
bound: RUDP reassembles up to 16 MiB and the media channel set no cap, so a
4 MiB parity record decoded and recovery work scaled with it.

GAMECULT_MEDIA_MAX_WIRE_BYTES (64 KiB, one maximum UDP datagram, ~7x a jumbo
frame) is now the ceiling. decode_media_wire_record refuses a larger record,
the RUDP session caps the media channel to it on send and on reassembly (and
the profile advertises it), and MediaFecPolicy::validate refuses a
max_wire_bytes above it so no producer can promise a record the decoder drops.

validate_audio_parity_record now checks the deadline against the last slot's
pts, not the base, so recovery can no longer produce a packet that violates
deadline_ticks >= pts_ticks.

Tests: ceiling +-1 at decode for audio and video parity; jumbo record accepted;
session send and reassembly on the media channel only; profile advertisement;
the audio deadline boundary and recovery refusal; a policy over the ceiling
refused; a budget at the ceiling end to end; audio parity larger than every
packet it protects (the budget check relies on it). MediaFecPolicy docs tell
callers to build by struct update from STANDARD.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-09-30 08:10:07 +02:00
Meta Vens
3f942a57e7 Cut 4 Soul 4d: a skipped entry is relayed as the primary sent it; probes, wire cases and the closed-enum rule committed
F1: RecordUnappliedEntry keeps the primary's wire entry on the in-memory log
entry (internal Relayed) and ToLogEntryMessage returns it through the same
normalisation the store uses, so a replica with no store no longer relays a
skipped put as a delete; memory and disk relay identically.
Soul's 4d probes are committed (chained replica keeps the row, key-prefixed
reads serve only owned keys, primary-all serves everything; the two
measurements are explicit) with a memory/disk relay-parity test.
Rust: captured-bytes cases for unowned_schema and variant_unsupported (with and
without a subscription).
The error-code set is stated as a closed enum in the schema description (both
copies) and in cultnet-distributed-database.md: adding a code is one
coordinated change across runtimes, since Rust and TypeScript refuse an unknown
code. The Soul 4c probe file's header says what it guards.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-09-30 08:03:09 +02:00
Meta Vens
2d2fad1f45 Tests: the directory store names its refused manifest and keeps the entry that publishes a compatible id
The two paths the previous batch left unpinned. Replication routing comments in cultnet-py and cultnet-ts say what the payload-based
and binding-based resolution is and is not.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-09-30 07:45:31 +02:00
Meta Vens
27edda82ea Python and TypeScript: tests for the store's publishing rule and the admission stamp; cultnet-py keeps its payload-based resolution
cultnet-py resolves a raw record whose schema id no document lists by the schema version its payload opens with, and three
cultmesh-py tests (foreign UI-generated ids) depend on it, so that resolution is restored pending a ruling. The compatible-id
replication test goes through the real apply_raw_document_record and the reopened store.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-09-30 07:28:17 +02:00
Meta Vens
f66fd3d714 Ack Cut 1c tests (cultmesh-rs): the restarted client's message is the same size as the first
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-09-30 07:03:27 +02:00
Meta Vens
b9ce601844 Python: a record is stamped with its registered schema id and the catalog publishes it; cultnet no longer infers a schema from a payload
put_envelope, put_envelopes and load stamp the registered schema's id, and the store writer publishes every record's id, so a
replicated record under a compatible id no longer leaves a store nothing can open. cultnet-py resolves a raw record by the schema
ids its documents list and nothing else.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-09-30 06:54:18 +02:00
Meta Vens
39a9de3b23 TypeScript: a record is stamped with its registered schema id and the catalog publishes it; the inspector reads what the reader reads
putEnvelope and admission stamp the registered schema's id, and the store writer publishes every record's id (the entry the
envelope carries when it lists that id, else a default), so a replicated record under a compatible id no longer leaves a store
nothing can open. cultnet-ts stops preserving the incoming id. The inspector refuses a store of the wrong slot count and a record
whose schema the catalog does not publish, instead of recovering one from the payload.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-09-30 06:54:18 +02:00
Meta Vens
c6f87fd3bf Rust: compatible schema ids publish a schema, and the refusal's message carries its cause
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-09-30 06:54:17 +02:00
Meta Vens
0abb8d4380 Ack Cut 1c: a new generation drops what the old one owed before the queue is asked for room
A bounded session whose queue still held an unacknowledged Accept for a peer
that vanished refused the next Connect; the old server-mode path reset before
accepting, so this would have been a regression. accept_connect and
create_connect now end the generation first in every runtime, and a test per
runtime holds a one-slot queue to it.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-09-30 06:50:23 +02:00
Meta Vens
b90fdc45aa Pin the zero-field policy refusal to the validator, not to a downstream failure
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-09-30 06:46:14 +02:00
Meta Vens
a179e9c7c6 Ack Cut 1c tests (cultmesh-rs): a restarted client starts with a fresh payload budget
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-09-30 06:40:06 +02:00
Meta Vens
86c170fa0b Ack Cut 1c tests: a reconnect forgets what the old server sent; an Accept for an abandoned Connect does not connect; the cultmesh-py server test observes what the server sent
Two rules the first mutation pass found undefended in every runtime: a new
Connect on a session that had a peer clears the old peer's received window (a
restarted server's sequences overlap the old ones), and ending a session
abandons its pending Connect. The cultmesh-py server test now counts the
server's own data packets by sequence, so a peer replaced on a retransmitted
Connect is seen, and asserts a restarted client inherits no subscription.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-09-30 06:34:23 +02:00
Meta Vens
9d0701d4a7 Ack Cut 1c tests (cultmesh-ts): the document server replaces a restarted client and keeps a retransmitted Connect's session
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-09-30 06:15:12 +02:00
Meta Vens
557e70fd03 Cut 4 Soul 4c: committed-unlogged wire writes, database serves only what it owns, typed unowned refusal
Q1: HandlePutAsync/HandleDeleteAsync catch a CultNetShardLogException (or an
AggregateException holding one) before the generic catch and answer nothing: the
write committed and was published, so the peer is not told it was refused. It is
logged at warning level. The wire has no committed-with-degraded-log message.
Q2: GetAsync/GetAll/GetByName/GetByIndex, the shard-less snapshot, the selection
and the subscription snapshot serve only rows a shard owns, so read and watch
agree; the CultMesh collections follow through GetAll, and the Mesh untyped
variant fallback checks ownership. The raw cache still holds everything.
Q3: a replica log apply skips a row its shard does not own, as the snapshot path
does, and still records the entry so a chained replica sees no gap.
API: rowFilter is internal (public CreateSelectionResponse and
CreateRawSnapshotResponse signatures restored, internal overloads added);
exception tags on the write methods; Commit's unwrap rule is XML doc; the
unowned refusal answers on the wire with code unowned_schema (schema enum in both
copies, Rust CultNetErrorCode, CultNetErrorMessage.ForUnownedSchema) and put and
delete log it at warning level.
Dispose keeps journal-first; a test now fails if the observer goes first.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-09-30 06:14:49 +02:00