Commit graph

2,753 commits

Author SHA1 Message Date
Meta Vens
1e0455028c CultMesh: AsSchemaAlias is gone; a catalog finds a handle by its type
F7 (operator, 2026-09-30): "Schema aliasing sounds like a really bad idea,
I support retiring it." One CLR type owns a schema, so a handle never needs
converting to another type for the same schema.

- Delete AsSchemaAlias from the document and collection handles and their
  interfaces, with the MessagePack round-trip converters only it used.
- The document and collection catalogs look a typed handle up by its type.
  The schema-name/version fallback existed only to find a handle of another
  type and alias it.
- Tests: the MeshNoteAliasDocument fixture is deleted. Tests that used it
  for publication, sync, snapshot and prediction paths now use
  MeshNoteDocument; the one test that only checked alias refusal is deleted.
  Catalog tests check a type with no handle is not found.
- public-api.md stops documenting aliasing; a stale comment in
  ReadDocumentFromSnapshotResponse that cited the fixture is trimmed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 16:15:48 +02:00
Meta Vens
d54110fe24 cultnet-ts: the send-failure tests send requests without response-only fields
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 16:14:20 +02:00
Meta Vens
04c0171bcc eve-browser-network: the headless deadline starts at its command
The headless C# client armed a 45 s deadline as soon as its lease was up,
but the orchestrator then runs the whole browser scenario (Chromium launch,
two clicks, provider and Odin replacement, page reconnect) before it sends
INVOKE. On ubuntu runners that scenario alone takes most of the budget: the
last green run finished with little to spare, and since 2dc7c122 the ubuntu
onboarding job has timed out in the headless process either before the
receipt or during the post-receipt benchmark.

The orchestrator already bounds each step it waits on, and closing stdin
ends the headless wait for its command. The headless process now bounds only
its own work: the 45 s deadline covers the invoke, convergence and benchmark
after INVOKE arrives.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 16:11:41 +02:00
Meta Vens
5ab0f9d337 cultmesh-rs: the at-bound fixture exercises the default served_record
The test Source overrode served_record in both modes, so no test ran the
trait's default and a default that stripped provenance survived. Source
now keeps the default, which the at-bound fixture pins (the record as
received); a separate StrippingSource overrides it for the stripping and
failing-source tests.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 16:11:15 +02:00
Meta Vens
31b6c09d64 cultnet-ts: the send-failure tests build operation requests the contract accepts
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 16:08:51 +02:00
Meta Vens
9757ed54a5 cultmesh-ts: a put is refused unless its receipt could be sent
Admission now sizes two replies built from the record the registry
would store: the snapshot reply serving it alone and the receipt
echoing its value under the put's message id with the server's default
provenance, through the one receipt builder the send path uses. A put
whose larger reply exceeds maxPayloadBytes, 65535 fragments or the
reliable queue is refused before onDocumentPutRaw, so a stored put
always gets its receipt when the application stores through the
registry and echoes that value. The existing fallback (a receipt that
cannot be sent all the same reaches onError, and the peer a
cultnet.error.v0) stays for handlers that acknowledge something larger.

The served-bound fixtures carry a long storedAt, which the served record
keeps and the receipt replaces, so they still pin the served bound; a
new test pins the receipt bound: one byte over is refused, exactly at
the limit is stored and receipted.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 16:04:01 +02:00
Meta Vens
6861b55db3 cultnet-ts: the registry decodes the value a raw put would store
CultNetDocumentRegistry.decodeRawDocumentPut returns the binding and
value applyRawDocumentPutMessage would store for a raw put, so a server
can build the receipt that would acknowledge a put before storing it.
It throws, as the put would, when no binding accepts the document.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 16:04:01 +02:00
Meta Vens
73fbb264d6 cultnet-ts: a caller's send larger than a UDP datagram throws EMSGSIZE and takes nothing
Node reports an oversized datagram only to the send's callback, so the
caller's send used to succeed and the session ended later. The transport now
checks the largest datagram of the send against the socket family's UDP
limit (65,507 bytes for IPv4, 65,527 for IPv6) before the session builds
anything: the send throws, nothing is queued, and no sequence is taken, as
the Rust and C# listeners withdraw it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 16:02:28 +02:00
Meta Vens
2d76b8d1b6 Pin the reset before a refusal's goodbye (cultmesh-ts, cultmesh-py) and C#'s once-guard
The document servers' refusal tests sent an unreliable fragment, whose
sequence the session never records, so dropping the reset before the goodbye
survived. They now refuse a reliable fragment and check the goodbye does not
acknowledge it.

C#: a BeforeSend seam orders a caller's failing send between the polling
thread's ack and its send, so both threads end the same session; the peer
hears one goodbye.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 16:02:27 +02:00
Meta Vens
de8e2e1b88 cultnet-ts: an operation server's send failure belongs to the generation that sent it
A permanent failure reported after that generation ended (the client
connected again from the same endpoint) ended the generation that replaced
it and said goodbye to the new client. The callback now acts only while the
generation that sent the datagram is live, which is the one in `sessions`.

That makes two guards unreachable, so they are deleted: `peer.unsendable`
before the goodbye (the first permanent failure ends the generation, so a
second callback from it is already ignored) and the conditional delete (a
live generation is the entry at its key). A refusal's goodbye is sent
best-effort, as the transport and the document server send theirs, instead
of through the failure handling meant for datagrams a session owes.

Tests: late failure after a reconnect, for the transport's generation check
and for the operation server.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 16:02:27 +02:00
Meta Vens
ee593f392e cultmesh-rs: the snapshot source says what it would serve for a put
CultMeshRudpSnapshotSource gains served_record: the record the source
would serve for a received put's document. Admission sizes that record,
so a source that serves another shape than it receives (dropping
provenance, or adding its own, as Odin's presence records do) keeps
admission exact by overriding it. The default serves the record as
received, so existing sources, closures included, are unchanged. A
source that fails to answer refuses the put as SnapshotSourceFailed.

Tests: a stripping source admits a put that is over the bound only as
received and serves it at exactly the bound; the existing at-bound
fixture stays as the default-method test; a failing served_record
refuses the put.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 16:02:16 +02:00
Meta Vens
9b4167d9ab pack-nuget: pack the ProjectReference closure, not a hand list
Merge 69bd45ff made GameCult.Caching.MessagePack reference CultMath
(PackageId GameCult.Math), so its nuspec depends on GameCult.Math 0.2.4.
The packer's hand-written project list never packed it, and GameCult.Math
is not on nuget.org, so every clean consumer of the local feed failed
restore with NU1101. That is the Eve surface consumer smoke in the
"one-command multiplayer onboarding" jobs, red on both OSes since then.

The packer now walks ProjectReference from the public roots (GameCult.Mesh,
GameCult.Networking.WebSockets) and packs the whole closure. The expected
package names come from MSBuild's PackageId and Version for each project,
since GameCult.Math carries its own identity.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 16:02:08 +02:00
Meta Vens
a288bb037a cultmesh-py: a peer's message that handling cannot serve ends that peer's session, never the RUDP thread
The RUDP loop caught only ValueError, so a subscribe frame with
`schemaIds: 5` raised TypeError in _handle_connection_message and ended the
thread for every peer. Any exception from handling one peer's messages now
ends that peer's session with a goodbye, as a refused packet does, and is
logged by its type alone: the error's message can quote what the peer sent.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 15:56:17 +02:00
Meta Vens
25ea551bd5 cultnet-ts: an operation handler's result belongs to the generation its request arrived on
When that generation ends while the handler runs (a goodbye, a refusal, a
timeout, a new Connect from the same endpoint), the response is not sent
and a failure sends no second goodbye: a client that connected again owns
the endpoint.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 15:54:33 +02:00
Meta Vens
e8e8e5bac1 GameCult.Networking: a Connect that replaces a peer ends the old peer's session under its gate
A caller still holding the replaced peer (a schema handler awaiting work for
it) could send into the endpoint the new session owns: a goodbye after a
partial failure disconnected the new client, a fixed initial sequence
delivered the stale frame in place of the real one, and a drawn one stalled
the new session. The replaced session now ends under the SessionGate every
Send takes, so a stale Send throws and nothing of it leaves.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 15:54:33 +02:00
Meta Vens
1f25195afe cultnet-ts, cultmesh-ts: a send the network refuses is a counted lost datagram, never an "error"
Node reports a failed send only to the send's callback, never on the socket's
"error" event. Emitting it as "error" from the transport ended any process
with no listener (invokeCultNetOperation among them) once per resend tick.
The transport and the document server now count it in sendFailures, as the
operation server, Rust and Python already do. The tests send to
255.255.255.255 with no error listener.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 15:51:47 +02:00
Meta Vens
3cc081db0f cultmesh-py: keep the generation test's replies whole; probe the small queue with an empty snapshot
The generation test reads each server packet as one message, so its
server now fragments only above 60000 bytes. The queue test's liveness
probe is an empty snapshot: a hello reply needs more fragments than its
eight-packet queue holds.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 15:51:06 +02:00
Meta Vens
2e94dc3aa3 Merge origin/main into hands/ack-cutD: Cut 3 flow window, RUDP client bind
Import lists in cultnet-ts index.ts and operation-service.ts and the
cultnet-py test block conflicted; both sides are kept.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 15:49:04 +02:00
Meta Vens
36ea08d38d Merge interop Cut 0: the interop lane runs, and fails honestly
The consolidation guard accepts cultnet-rs vendored schema copies (the lane
was red for 40 main runs). Every CI step now fails on any failed command
(job default shell bash -eo pipefail); proven red on a throwaway branch.
The cultnet-py explicit-bind test stays on loopback; the Rust drift guard
lists the vendored directory. CI run 36723314611 green at 59d85557.
Follow-up: CI never runs schema_discovery_contracts.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 15:48:50 +02:00
Meta Vens
36cd169cd6 cultmesh-py: pin the 65535-fragment bound and a reply the queue cannot take
A put over RUDP whose served reply needs 65536 one-byte fragments is
refused and one that needs 65535 is stored. A snapshot of two documents
each admitted at the queue's bound needs twice the queue: it is counted in
rudp_send_failures, not sent, and the thread serves the next request.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 15:48:09 +02:00
Meta Vens
c5de65b875 cultmesh-ts: observe the peer's refusal as the frame it cannot yet parse
A TypeScript peer refuses the two-key cultnet.error.v0 this server sends
(missing routingHint, code and details; docs/cultnet-error-contract-cut.md),
so it arrives as an invalidMessage, never a message. The served-bound rig
records those, the refusal tests count them, and the refusal's text is
asserted through onError, which receives the same error.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 15:47:10 +02:00
Meta Vens
66e439f4c8 cultmesh-rs: the fragment-bound tests pump the client while they wait
A put of 55 fragments leaves the client in flow-window steps, so the
server hears it only while the client reads acknowledgements and resends.
The helper that waits for a refusal or a stored put now pumps the client.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 15:47:09 +02:00
Meta Vens
045f9a21b6 Ack map: Cut D Soul pass 2 result; K1 gains the Kotlin resend loop
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 15:46:59 +02:00
Meta Vens
dc2a0a7eaa cultcache-rs: a failed single-file write removes its staging file
Any failure after the staging file is created (a short write under a
file-size limit, a full disk, a failed sync or rename) now removes it before
returning the error. A write that failed for lack of space no longer keeps
holding the space it failed on until the next write sweeps it. C# parity with
WriteSnapshotAtomically's finally.

The test runs the write in a child process under RLIMIT_FSIZE with SIGXFSZ
ignored, so the failure is a real EFBIG mid-write, reached without
permissions.

Odin store-recovery map, cut R1.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 15:39:52 +02:00
Meta Vens
c0b56e0674 cultcache-rs: type single-file read failures, read the header only
A single-file store whose bytes were read and not accepted now carries
CultCacheStoreUnreadable { path, kind } in its error chain, with kind
Undecodable or UnsupportedFormat, so a consumer can downcast_ref instead of
matching message text. A failure to read the bytes stays an io::Error and
never carries the type. The flattening map_err that turned every decode
error into a string is gone.

store_header reads only the array header and the first string instead of
decoding the whole array, so a store cut short after its header still names
its format: a truncated v1 store is an undecodable v1 store, not a legacy
decode mismatch, and a truncated newer-format store is UnsupportedFormat.

Odin store-recovery map, cut R1.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 15:39:51 +02:00
Meta Vens
59d8555719 cultnet-rs: the vendored-schema guard lists the directory
The equality test compared only the 22 names in a hand-kept list, so a schema
copied into packages/cultnet-rs/contracts/cultnet/ without joining the list
escaped it. Every *.schema.json under the vendored tree is now found by listing
it and compared against its canonical copy. The presence test is gone: include_str!
already fails the build for an embedded schema that is not vendored.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 15:39:46 +02:00
Meta Vens
83be3bd90f cultmesh-py: RUDP replies go in fragments, and a send failure ends no thread
The local server sent every RUDP reply as one datagram, so a snapshot of a
100 KB put raised EMSGSIZE inside _rudp_loop and ended the RUDP thread for
every peer. Replies are now fragmented by rudp_max_fragment_bytes (default
CULTMESH_RUDP_DEFAULT_MAX_FRAGMENT_BYTES, 1200, as in the Rust and
TypeScript servers), and each peer's reliable queue is bounded by
rudp_max_pending_reliable_packets; serve_node takes both.

A put arriving over RUDP whose served-alone reply would take more
fragments than one message (65535) or the peer's reliable queue can hold
is refused as document_unservable, with fragmentCount and
maxFragmentCount in its details. A datagram that cannot leave, or a reply
the peer's queue cannot take, is counted in rudp_send_failures and never
raised out of the loop.

This overlaps hands/ack-cutD (not yet on main), which classifies send
failures and ends only the affected peer's session; its counter has the
same name, and its classifier should replace the plain count here when it
merges.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 15:37:55 +02:00
Meta Vens
6cf19b136a cultmesh-ts: judge a put by what serving it sends, and tell the peer every refusal
The document server refused any delivered frame larger than
maxPayloadBytes, reporting it only to onError: the peer's put, already
acknowledged by the transport, vanished. The frame had been reassembled by
then, so the check protected no memory; it only refused puts whose frame
was over the limit because of provenance or a long message id although
their served reply fit. It is gone. maxPayloadBytes bounds what the server
sends; a put is judged by its served reply, and every refusal reaches the
peer as a cultnet.error.v0.

A stored put whose receipt cannot be sent (a receipt from onDocumentPutRaw
larger than maxPayloadBytes) now tells the peer so with a cultnet.error.v0,
as well as onError. The unservable refusal names sizes only, never the
record key, so a long key cannot make the refusal itself too large to send.

The options document states that the served-size judgement is exact only
for an application that stores through the registry it was given.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 15:37:54 +02:00
Meta Vens
b1d7d31f59 cultmesh-rs: the snapshot source keeps owning the served record's provenance
The previous commit cleared source provenance from every served record.
Odin's snapshot source serves runtime-presence records with provenance it
authors itself (source_runtime_id, source_agent_id, source_role and an
odin-observed tag), and a crate test pins that a raw response is served as
the source returned it. The server does not own that shape, so it no
longer rewrites it, and admission again sizes the record as received.
Which rule a Rust server should size by is reported as a fork.

The fragment-count admission, the construction cross-check and the
exact payload-budget fixture stand. The budget fixture's put carries a
longer message id so its frame, the exact budget, is at least its
served-alone response. Polling for a refusal sleeps when the server is
idle, so a put of many fragments has time to arrive.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 15:37:34 +02:00
Meta Vens
cc624e934d Ack map: Cut 3 merged (1210fdc7)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 15:36:26 +02:00
Meta Vens
1210fdc7ae Merge ack Cut 3: the 4 MiB send bound counts every byte above the lowest unacknowledged packet
F1: acknowledged sequences above a gap still count until the lowest
advances, in Rust, TS, Python and C#. Rust direct send purges expired
packets before the window check. Soul passes: F1 held; batch 2 held with
13 own mutants killed and interop 27/27 (ack1d-interop:2). Only docs
differ from the verified tree. Follow-up: cultmesh-rs Cargo.lock stale.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 15:36:10 +02:00
Meta Vens
5b86bc9faf Test: a flush storing a record under a new id gives it a later storedAt when the file no longer holds it
Defends the id half of the whole-view restamp rule on its own: the record C# loaded
under v1's id is gone from the file when a v2 cache flushes it back, so there are
no stored bytes to compare, and only the changed id says it is a new store.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 15:32:57 +02:00
Meta Vens
b62bfc7c8c cultnet-py: the explicit-bind test stays on loopback
The test sent from an explicit 127.0.0.1 bind to a non-loopback address, which
Windows refuses outright (WinError 10051), so it only held on Linux. It now
binds 127.0.0.2 for a 127.0.0.1 endpoint, whose derived bind is 127.0.0.1, and
checks the source address the listener sees. That still fails if the explicit
host is ignored, on every platform, with no dependence on a non-loopback route.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 15:32:32 +02:00
Meta Vens
52abce9191 Interop workflow: every command in a step counts
The Windows runner's default pwsh does not stop when a native command fails, so
a multi-command step reported only its last command: a failing cultnet-py suite
went green inside "Verify Python RUDP reliability". The job now defaults to Git
Bash, which the runner starts with -e -o pipefail. The pwsh consolidation guard
checks git ls-files' exit code instead of treating a failed listing as clean.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 15:32:28 +02:00
Meta Vens
2204ce23e9 TypeScript: a definition listing an id registers beside the one owning it; registration refuses typed
Owner beats lister, as in C# and Python. The cache kept one definition per id,
own or compatible, and refused a second with a plain Error, so a v1 definition
could not stay registered beside a v2 declaring v1's id (Soul F8, cult-cache.ts
registration). Now a definition owns its schema id and any number may list it:
a record resolves to the owner, else to the one lister. Several listers and no
owner name no single definition, and the load is refused with
SchemaConflictError naming the id, the record key and the listers.

Every registration refusal is now SchemaConflictError (Soul F10): a second owner
of one id, a second definition with one schema name, and a different definition
for one type. Each names the new definition's id and both schema names.
SchemaConflictError takes an optional message for these uses.

Tests: an owner and a lister register in both orders and each reads its own
records, and a lister alone reads the owner's id; two listers register in both
orders, a record under their id is refused typed without its owner and is the
owner's with it, registered before or after them; each of the three refusals is
a SchemaConflictError in both registration orders.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 15:31:31 +02:00
Meta Vens
84246b685a Python: a document listing an id registers beside the one owning it; registration refuses typed
Owner beats lister, as in C#. A document owns its schema id; others may list it
as compatible beside its owner, in either registration order (batch 9 refused
any overlap, which forbade keeping v1 beside a v2 that declares v1's id; Soul
F8). A record resolves to the document owning its id, else to the one document
listing it. Several listers and no owner name no single document, and the load
is refused with SchemaConflictError naming the id, the record key and the
listers: refusing at registration would depend on whether the owner had
registered yet.

Every registration refusal is now SchemaConflictError (Soul F10): a second owner
of one schema id, a second document with one schema name, and a second
definition of one document type. Each names the new document's id and both
schema names and document types. Before, the last two raised CultCacheError.

Tests: an owner and a lister register in both orders and each reads its own
records, and a lister alone reads the owner's id; two listers register in both
orders, a record under their id is refused typed without its owner and is the
owner's with it, registered before or after them; two owners, one schema name
and one document type twice are each refused as a schema conflict.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 15:31:25 +02:00
Meta Vens
9118be879d cultmesh-rs: admission sizes the record as served, in the fragments it is sent in
A served record carries no source provenance: the server now clears
source_runtime_id, source_agent_id, source_role and tags from every record
it sends in a snapshot response, as the TypeScript and Python servers
already serve only what their stores keep. Put admission sizes that served
record, so provenance that makes a put larger only in its received form no
longer refuses it; the sink still receives the record as it was sent.

Admission also counts the response's fragments: a put whose served-alone
response would take more fragments of max_fragment_bytes than
max_pending_reliable_packets_per_session or 65535 is refused with
DocumentUnservable, which now names the fragment count and its limit.
Construction refuses options that could not carry an empty snapshot
response, in bytes or in fragments.

The payload-budget fixture sets both budgets to exactly the put's frame, so
a charge equal to a limit must fit.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 15:28:55 +02:00
Meta Vens
9629aa119b C#: a type listing an id registers beside the type that owns it; the owner wins
Batch 9 refused any overlap between two types' ids, which forbade the migration
CompatibleSchemaIds exists for: v2 declares v1's id while v1's class stays
registered (Soul F8). The rule is now owner beats lister. A type owns its own
schema id; any number of types may list an id as compatible beside its owner,
registering in either order. A record under an id resolves to its owner, else to
the one type listing it. An id no type owns and several list names no single
type, so a record under it is refused, typed (CultSchemaConflictException naming
the id and the listers), where it resolves: refusing at registration would
depend on whether the owner had registered yet.

A second owner of one id is still refused at registration, typed. A schema id
hashes the schema's name and version, so the only second owner is a type of the
same name and version, which the name-and-version check already meets; the
separate own-id lookup could never fire and is gone.

Tests: an owner and a lister register in both orders, the owner's id resolves to
the owner, the lister's own id to the lister, and without the owner to the
lister; two listers register in both orders, a record under their id is refused
typed with no owner and resolves to the owner with one, registered before or
after them; a cache with v1 beside v2 reads each version's records as its own
type, in both registration orders. The emitted-fixture note says why a fixture
is emitted now that v1/v2 pairs no longer need hiding from the shared registry.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 15:26:06 +02:00
Meta Vens
188d4ad88d C#: a whole-view write that stores a record differently mints it a later storedAt
A condition names a record by (stored schema id, storedAt), and that name must
say which bytes are stored. A single-file whole-view write (plain flush, or an
unconditional commit) re-encoded every record under its own type's id with the
storedAt it loaded, so the pair could name two byte strings: v1 shed a member
keeping storedAt, a second v2 cache restored v2's id keeping it again, and a v2
cache holding the original bytes committed its condition (Soul F6, A-B-A).

Now such a write stores a record under a later storedAt whenever it stores it
differently: under another id than it was loaded under, or with other bytes than
the file holds at that storedAt (a record written with the ids it minted at
load, for one). A record stored exactly as the file holds it keeps its storedAt.
The store's entries and the cache's take the id and storedAt the write gave them.

Tests: the A-B-A through commits and through plain flushes, with Expect and
ExpectUnchanged; a flush that re-encodes a record restamps the cache that
flushed (a condition holds straight after, SM12); a record stored unchanged keeps
its storedAt through commits and flushes; persisting load-minted ids gives a
later storedAt. The rolling-deploy test asserted v1's rewrite kept storedAt; it
now asserts a later one, since that rewrite is a new store of the record.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 15:25:22 +02:00
Meta Vens
6d0cc96316 Variants map: F7 ruled, retire AsSchemaAlias
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 15:25:19 +02:00
Meta Vens
c8e3ef917e Error contract map: Q1 B, Q2 B, Q3 A, Q8 A ruled
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 15:20:03 +02:00
Meta Vens
beb7c46162 Interop guard: accept cultnet-rs's vendored schema copies
The consolidation guard rejected every *.schema.json outside contracts/cultnet/,
including the R-AB byte copies vendored into packages/cultnet-rs/contracts/cultnet/.
The interop workflow has been red at that step on main ever since, so no later
cross-runtime step ran. The guard now accepts that one path; drift of those copies
is judged by the Rust equality test in tests/schema_discovery_contracts.rs.
The retired-namespace check is unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 15:17:58 +02:00
Meta Vens
50f834e457 Map the CultNet error contract cut
The cultnet.error.v0 contract is not decodable across runtimes, and the
interop CI lane has been red for 40 main runs. Cut 0 unblocks the lane;
Q1, Q2, Q3 and Q8 must be ruled before Cut 1.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 15:15:20 +02:00
Meta Vens
d7a98a2763 Variants map: C2a F2 ruled B (carry unclaimed records untouched); batch 9 Soul result
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 15:12:27 +02:00
Meta Vens
cce4d391b8 Ack Cut D D2 (C#): only the polling thread changes the peer set
A caller-directed Send that failed after a fragment left called
EndUnsendablePeer on the caller's thread, removing from _peers and raising
PeerDisconnected while the polling thread could be changing _peers too.
Send now ends the session and sends the goodbye under the peer's
SessionGate, and queues the peer; TryReceiveOnce and PollResends retire
queued peers before anything else. A peer a new Connect already replaced
is skipped, so a stale peer object cannot remove the peer that replaced it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 15:03:22 +02:00
Meta Vens
b96420b606 Ack Cut D (Rust): pin the receive reset, the ack after an unsendable reply, and a refusal at every fragment
- Document server: a reset reported by a receive is idle, not an error
  (Windows only, where the system reports it; run on Starfire).
- Document server: no ack follows a reply that can never be sent. The
  guard is not decoration: without it the ack's success overwrote the
  failure and the session that owed the Pong was kept.
- Hub: a send refused at any fragment index delivers nothing of it; at
  index 0 the next send arrives whole (Soul's probe, committed).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 15:03:16 +02:00
Meta Vens
06f8a31cb5 Ack Cut D (Python): a packet one peer's session refuses ends that session, never the RUDP thread
session.receive raises ValueError on bad fragment metadata, and nothing
in _rudp_loop caught it, so one admitted peer could end the thread for
every peer. The server now ends that peer's session with "session refused
a packet", as the socket transport and the Rust servers do.

Tests pin that, and two D2 rules that were unpinned: a resend loop sends
nothing more to a peer it just ended (P1), and responses after one that
can never be sent are not sent (P3).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 15:03:11 +02:00
Meta Vens
cc750d4e4c Ack Cut D D2 (cultmesh-ts): the document server's sends are guarded, classified, and a refused packet ends only its peer
Every socket send in the RUDP document server goes through
sendRudpDatagram. A Connect from UDP port 0 used to store a session whose
pending Accept threw from the resend interval on every tick, an
uncaughtException that ends the process. Now:

- a Connect whose Accept can never be sent starts no session and leaves a
  session already at that endpoint as it was;
- a datagram that can never be sent as built ends only that peer's session
  (goodbye, onSessionClosed); any other failure is a lost datagram,
  reported through onError, and ends nothing;
- a packet the session refuses (bad fragment metadata) ends that peer's
  session with "session refused a packet" instead of leaving a session
  that has recorded a sequence it never delivered.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 15:03:05 +02:00
Meta Vens
a44e0199ac Ack Cut D D2 (cultnet-ts): a send Node refuses never escapes a timer or a receive handler
Node reports a failed UDP send two ways: it throws for an argument it
refuses (port 0, which any spoofer can claim as a source) and passes a
system failure to the callback later. sendRudpDatagram routes both to one
failure handler, and isPermanentSendError classifies with the permanent set
the other runtimes use (EMSGSIZE, EINVAL, EAFNOSUPPORT, and Node's own
ERR_SOCKET_BAD_PORT for port 0).

- The socket transport's receive and resend paths hand a failure to that
  handler: a datagram that can never be sent ends the session (goodbye
  names the error code, never its message); any other failure is emitted
  on "error" as the socket always reported it. Caller-directed sends still
  throw to the caller. A resend loop stops at the generation it ended.
- The operation server starts no session for a Connect whose Accept can
  never be sent (counted as dropped), ends a stored peer whose datagram
  can never be sent, and counts other failures in sendFailures.
- CultNetRudpSession.endUnsendableSession, as in the other runtimes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 15:03:01 +02:00
Meta Vens
7c1a792a28 Media FEC: one table lookup per shard byte in combine
Soul measured the owned math at 1.7-2.6x the crate's time at realistic sizes
(16+4 x 1200 B encode 64k -> 122k ns), because combine called gf_mul per byte:
two zero checks and three lookups. combine now tabulates each coefficient's
256 products once and spends one lookup and one XOR per byte; coefficient 0
is skipped and 1 is a plain XOR. The construction and every wire byte are
unchanged: the known-answer fixture passes untouched.

tests/media_fec_bench.rs is an ignored, dependency-free timing test over the
public surface (4+2 at 160 and 1300 B, 16+4 at 1200 B, 255+1 at 1300 B):

    cargo test --release --test media_fec_bench -- --ignored --nocapture

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