Commit graph

2,753 commits

Author SHA1 Message Date
Meta Vens
969d78de39 C#: compatible schema ids publish a schema on open, Mesh takes only the older layout, the refusal names its file
A record whose schema id the catalog lists only as a compatible id now opens (the cache resolved a persisted schema by entry id
alone). CultMesh's fallback to the older catalog-entry reader fires only for a store in that layout, decided by the entry's slot 5,
not for any decode failure, so a current-layout store with a malformed slot is refused as CultCache refuses it.
CultStoreUnreadableException.Path names the refused file. New vectors: compatible-id-only-v3 (accepted by all four) and three
current-layout catalogs with one malformed slot. The contract states the writer's invariant.

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
6e406879be Tests: the Mesh same-schema read serves only owned rows; the unowned put answer must try to send
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-09-30 06:23:09 +02:00
Meta Vens
79f144d9ec CultNetDatabase.Dispose publishes what a writer logged and had not yet published
The dispose test showed journal-first alone still lost the change: Dispose won the race against the writer's publication and cleared the stash. Dispose now drains the stash through the publisher after the journal is disposed (so nothing is stashed afterwards) and before the subject completes.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-09-30 06:16:57 +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
Meta Vens
396cccafd4 Soul probes: commit the pass 4c probes (Q1-Q5) as recovered from 9c30f67f
The Finding-category probes fail on this commit; the fixes that follow are their tests.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-09-30 06:09:14 +02:00
Meta Vens
8d204a9473 Merge remote-tracking branch 'origin/main' into hands/pub-cut4 2026-09-30 06:08:26 +02:00
Meta Vens
7969d4dbf4 Ack Cut 1c tests (C#): the sequences that follow the Connect
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-09-30 06:03:17 +02:00
Meta Vens
d5bc28e207 Vectors: manifest row for missing-schema-versioned-v3
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-09-30 06:02:24 +02:00
Meta Vens
ed61462a70 Tests: the recovery tests assert the refusal
Rust, TypeScript and Python each had a test that a record missing its catalog entry is recovered from its payload; each now asserts
the store is unreadable.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-09-30 05:58:47 +02:00
Meta Vens
a39d04e53e Ack Cut 1c tests (C#): sessions that stood in for a handshake with a hand-built Accept now handshake
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-09-30 05:58:30 +02:00
Meta Vens
45f98c5638 Ack Cut 1c tests (Python): the timeout probe uses a clock past the epoch clock
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-09-30 05:58:29 +02:00
Meta Vens
8c950c06ea Ack Cut 1c tests (TypeScript): the request follows the Connect in sequence
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-09-30 05:57:31 +02:00
Meta Vens
acce92d406 Ack Cut 1c tests (Python): sessions that stood in for a handshake with a hand-built Accept now handshake
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-09-30 05:56:00 +02:00
Meta Vens
50d6eac405 Ack Cut 1c tests (TypeScript): sessions that stood in for a handshake with a hand-built Accept now handshake
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-09-30 05:55:39 +02:00
Meta Vens
02fa6ab854 Ack Cut 1c tests (cultmesh-py): the server keeps a peer on a retransmitted Connect and admits a restarted client
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-09-30 05:53:09 +02:00
Meta Vens
3a3de56341 Store readers: a schema is never recovered from a record's payload
The persistence contract requires the catalog to publish every schema its records name and specifies no recovery, so the catalog
is the store's own description: Rust, TypeScript and Python stop inferring a schema name from a payload's first field, as C#
never did, and a store naming an unpublished schema is unreadable in all four. missing-schema-versioned-v3 pins it with payloads
that open with a schema version.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-09-30 05:53:01 +02:00
Meta Vens
9741bfe2fa Ack Cut 1c (TypeScript): export randomInitialSequence from the package index
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-09-30 05:52:42 +02:00
Meta Vens
536e650d82 Ack Cut 1c tests (C#, Python): Connect generation, handshake-seeded watermark, restarts
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-09-30 05:52:37 +02:00
Meta Vens
64eb3bd4b2 Ack Cut 1c (TypeScript): export randomInitialSequence; the repeated Connect test names acceptConnect
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-09-30 05:52:37 +02:00
Meta Vens
5ff1ee3222 Ack Cut 1c (C#): a Connect's sequence says whether it repeats; the handshake alone seeds the watermark
The session records the sequence of the Connect that started its generation. A
Connect repeats exactly when the session is connected and carries that
sequence; anything else ends the generation, forgets the peer and accepts a new
one. AcceptConnect answers both, ConnectRepeats is the predicate for servers
that keep one session per peer, and AnswerRepeatedConnect is private. Server
mode admits a Connect from a new endpoint and the session decides; the listener
no longer compares payloads (the payload stays application evidence).

CreateConnect on a session that has had a peer starts a new generation. An
Accept counts only while the Connect is pending and only if it names it. The
watermark is seeded by the Connect or Accept alone; reliable data before that
is neither remembered, acknowledged nor delivered. The default initial sequence
is random in [1, 2^31) in every options type. An ICMP connection reset no
longer sets DisconnectReason: nothing ended. The interop peer flushes before it
disconnects.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-09-30 05:51:14 +02:00
Meta Vens
2e56b46656 Ack Cut 1c (Python): a Connect's sequence says whether it repeats; the handshake alone seeds the watermark
The session records the sequence of the Connect that started its generation. A
Connect repeats exactly when the session is connected and carries that
sequence; anything else ends the generation, forgets the peer and accepts a new
one. accept_connect answers both, connect_repeats is the predicate for servers
that keep one session per peer, and answer_repeated_connect is private.
Server-mode transports admit a Connect from a new endpoint and the session
decides; the cultmesh-py server replaces a peer only when the Connect does not
repeat, so a retransmit no longer resets its subscriptions.

create_connect on a session that has had a peer starts a new generation. An
Accept counts only while the Connect is pending and only if it names it. The
watermark is seeded by the Connect or Accept alone; reliable data before that
is neither remembered, acknowledged nor delivered. The default initial sequence
is random in [1, 2^31) wherever a default is produced, and a timeout names
itself as the disconnect reason.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-09-30 05:51:14 +02:00
Meta Vens
efefbe270a Ack Cut 1c (TypeScript): a Connect's sequence says whether it repeats; the handshake alone seeds the watermark
The session records the sequence of the Connect that started its generation. A
Connect repeats exactly when the session is connected and carries that
sequence; anything else ends the generation, forgets the peer and accepts a new
one. acceptConnect answers both, connectRepeats is the predicate for servers
that keep one session per peer, and answerRepeatedConnect is private. The
server-mode transport no longer computes its own repeat; the cultmesh-ts
document server no longer compares payloads (the payload stays application
evidence).

createConnect on a session that has had a peer starts a new generation. An
Accept counts only while the Connect is pending and only if it names it. The
watermark is seeded by the Connect or Accept alone; reliable data before that
is neither remembered, acknowledged nor delivered, and createAckForReceived
acknowledges by name only what was received. The default initial sequence is
random in [1, 2^31).

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-09-30 05:51:13 +02:00
Meta Vens
86909c1d1e Ack Cut 1c tests (TypeScript): Connect generation, handshake-seeded watermark, restarts
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-09-30 05:49:46 +02:00
Meta Vens
40c670d652 Ack Cut 1c tests (Rust): read past another client's acks; a repeat's Accept was already acknowledged
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-09-30 05:48:32 +02:00
Meta Vens
450ec33352 Ack Cut 1c tests (Rust): Connect generation, handshake-seeded watermark, receipts, boundaries
Session and transport tests for the rules the change makes: a reconnect on the
same session and a server that accepts a new client are not stranded by what an
ended session owed (Soul F1); a retransmitted Connect after data has flowed does
not reset; a Connect with another sequence is not a repeat whatever it carries;
an Accept counts only when it names the pending Connect, and not after a local
disconnect (F6); reliable data before the Accept is neither delivered nor
acknowledged (F2); the default initial sequences are random; the hub, server
mode and the cultmesh-rs document server admit a restarted client; a receipt
asked of another transport reads Invalidated (F4); a timed-out flush names the
timeout; the hold buffer takes exactly 1024 frames and 64 channels (F5).

Existing tests that handshook with a hand-built Accept now use assume_connected
or a real handshake, and pin distinct sequences for peers that must differ.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-09-30 05:42:25 +02:00
Meta Vens
f7f8b17b39 Ack Cut 1c (Rust) tests: the repeated Connect test names accept_connect
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-09-30 05:35:17 +02:00
Meta Vens
a0007f3aeb Ack Cut 1c (Rust): a Connect's sequence says whether it repeats; the handshake alone seeds the watermark
A session records the sequence of the Connect that started its generation. A
Connect repeats exactly when the session is connected and carries that
sequence; anything else ends the generation, forgets the peer and accepts a new
one. accept_connect answers both cases, connect_repeats is the predicate for
servers that keep one session per peer, and answer_repeated_connect is gone.
create_connect on a session that has had a peer starts a new generation.

An Accept is honoured only while this side's Connect is pending and only if it
names that Connect. The watermark is seeded by the Connect or Accept alone;
reliable data before that is neither remembered, acknowledged nor delivered.
create_ack_for_received acknowledges by name only what was received.

The default initial sequence is random in [1, 2^31). The hub, the cultmesh-rs
document server and the server-mode transport no longer compare payloads or
reset on every Connect. A receipt carries its session's random id. A timeout
names itself as the disconnect reason.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-09-30 05:32:40 +02:00
Meta Vens
dac712b09c CultNetDatabaseServer: a selection skips cached rows no shard owns instead of failing
ResolveShard now throws for an unowned schema, so one such cached row would
have failed the whole selection. The server passes a row filter
(CultNetDatabase.Owns) to CreateSelectionResponse, which gains an optional
rowFilter that SelectPage already took, so those rows are not part of what the
database serves and the rest is answered.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-09-30 05:29:35 +02:00
Meta Vens
a014bf1b5b Ack map: 1b merges only with 1c; R is seeded by the handshake (Cut 1 Soul)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 05:28:27 +02:00
Meta Vens
e79882a8ec CultNetDatabase: no shard owns it, no write, on every path; the fallback shard is deleted
ResolveShardInternal throws CultNetUnownedSchemaException instead of falling
back to the first shard. PutAsync, DeleteAsync, PutPredictedAsync,
ApplyPutAsync and ApplyDeleteAsync refuse an unowned schema before the cache is
touched; a remote put reaches the server's generic catch and is answered as an
application rejection like any other refused remote put. ResolveShard throws
(documented), CanWriteAuthoritatively returns false, and a bare cache write no
shard owns is neither logged nor published. The unused string overload of
EnsureClientAuthority is deleted. No GameCult consumer configures shards
outside vendored CultLib copies; the default shard owns everything.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-09-30 04:31:03 +02:00
Meta Vens
91d464a038 Rust test: a refused store's cause is the error's source
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-09-30 04:23:02 +02:00
Meta Vens
e0075217b7 Networking test: the outside-the-shard fixture is written to the cache, since the database refuses it
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-09-30 04:21:48 +02:00
Meta Vens
be5ba22264 Vectors: the variant renames itself and the legacy catalog publishes every schema it holds
variant-slot-v3 overrides the variant's name, which the reader requires of a variant, so C# reads it and the others refuse the
fifth slot. The legacy-layout catalog vectors carry an entry per schema, so a store the reader accepts differs from the one it
refuses only by the bytes after it. missing-schema-v3 is refused by C# too.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-09-30 04:21:23 +02:00
Meta Vens
67575c4ec4 Store reader: a record whose schema the catalog does not publish is unreadable
C# read such a store on flush and commit and refused it on open, with an untyped InvalidOperationException from the cache. The
store reader now refuses it with CultStoreUnreadableException, so open, flush and commit give one verdict, as Rust, TypeScript
and Python already do. The readability open test builds a cache with a codec, so a variant store opens as it would in use.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-09-30 04:19:35 +02:00
Meta Vens
fdafa15022 Python: StoreUnreadableError for a store file it cannot read; a store is exactly three slots
The refusal is a ValueError subclass with the cause chained. A store with more than three slots is refused instead of read.
The readability test walks every vector in the folder, asserts the error type and cause, the header a replaced file carries
and what it holds.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-09-30 04:15:04 +02:00
Meta Vens
c001358e8c TypeScript: a flush asks the same full reader as an open, and refuses with StoreUnreadableError
pushAll, push and delete read the file through the one store reader that pullAll uses, so a store holding a variant or a bad
body is not replaced by a header-only check. A store with fewer or more than three slots is refused. The readability test
walks every vector in the folder, asserts the error type and cause, the header a replaced file carries and what it holds.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-09-30 04:15:03 +02:00
Meta Vens
ed7fd14c1c Rust: a rewrite asks the same full reader as an open, and refuses with StoreUnreadableError
push_all and every rewrite read the file through the one store reader (framing, header, schema catalog, records), so a v3
store holding a variant or a bad body is no longer replaced by a header-only check. The refusal is one typed error with the
cause as its source. The readability test walks every vector in the folder, asserts the error type and the header a replaced
file carries, and the truncated v1 and v2 stores are refused again.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-09-30 04:15:03 +02:00
Meta Vens
cd07f03a9f Mesh: the older-catalog reader takes only a store the current catalog layout cannot decode
A store refused for its framing, slot count or a record keeps that refusal instead of being re-read with the older layout, so
the record it names is the record the message names.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-09-30 04:15:02 +02:00
Meta Vens
9f6b2d4081 CultNetDatabase: a write no shard owns is refused, not cached and never logged
PutAsync and DeleteAsync carry no door, so the journal cannot place an unowned
schema and now skips it; accepting the write anyway was a silent loss of
replication. They resolve their shard strictly and throw
CultNetUnownedSchemaException (an InvalidOperationException naming the schema)
before the cache is touched. Doors keep the fallback shard they were resolved
to. No GameCult consumer configures shards outside vendored CultLib copies:
AetheriaEve's node uses the default PrimaryAll shard, which owns everything.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-09-30 04:08:45 +02:00
Meta Vens
436eed73e4 Store readability: one typed refusal, one full reader, one store shape
CultStoreUnreadableException (a NotSupportedException) is the one refusal for a store file this runtime cannot read, thrown
by the single-file reader, the rewriting paths and CultMesh's reads, with the cause as the inner exception. The reader is exactly
one array of three slots (or an empty array): a header alone and an extra top-level slot are refused instead of read as empty or
skipped. CultMesh's older-catalog fallback is the same reader with the older entry reader, so it also requires one array.

Vectors: bad-body, header-only, missing-schema, variant-slot, extra-top-slot in the readability set, and a legacy-catalog store
with and without bytes after it for CultMesh. The persistence contract says what is true.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-09-30 04:06:44 +02:00
Meta Vens
e18624f72b Merge origin/main into hands/variants-c2a
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-09-30 03:58:11 +02:00
Meta Vens
51a6a6c1c1 Media channel test: observe expiry through the next send, where the session reclaims packets
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-09-30 02:51:45 +02:00
Meta Vens
f3076264fc Journal tests: a plain put of an unowned schema is unlogged, a door put keeps the fallback shard
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-09-30 02:51:14 +02:00
Meta Vens
a813c09b6d Media FEC: enforce the datagram invariant at the producer, refuse overflowing audio blocks
The 720-byte shard cap stood in for "an encoded record fits one datagram" and
failed it at legal maximum-width identifiers. The invariant is now checked where
records are produced: protect_video_frame and protect_audio_block take the
producer's provenance, encode each record, and refuse the block when one exceeds
MediaFecPolicy::max_wire_bytes (default 1431, the IPv4/1500 MTU budget; smaller
paths pass less). The fixed cap is deleted; it contradicted a larger budget and
bounded nothing the decoder does not already hold in memory.

Audio block arithmetic lives in one function, audio_slot, used by the protect
path, the wire validator and recovery. The validator refuses a parity record
whose last packet id or pts overflows; recovery uses the same checked function
instead of adding.

Tests pin the policy 256-shard bounds, block identity on block_count and
block_data_start, the k=16 m=4 known answer, and the burst property
(worst always-recovering burst = blocks x smallest m for 1..=399 chunks), and
the wire behaviour of the media channel profile (delivery and expiry). Docs
state the rs-gf256-v1 construction and that audio playout gates on pts.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-09-30 02:50:11 +02:00
Meta Vens
f28ca3a89f CultNetBurnedSequence: a plain struct, since netstandard2.1 has no IsExternalInit
The record struct in 2eb0e8d6 did not compile for the library's netstandard2.1
target.

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