| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
5cf31d1e87 |
CultNet selection cut1 fix batch 3, Rust half: R-N/R-O/R-T, retire the Rust mutation harness
docs/cultnet-selection-cut.md, Self's rulings for fix batch 3 (2026-09-22), the Rust-owned share: - R-N: cultnet.error.v0 gains additive code/details fields. SelectionRefusal now maps to a wire code (selection_invalid, cursor_stale, cursor_invalid, reference_outside_target) and a flat string-map details payload via to_wire_message(); the legacy array wire (gamecult.networking.v0) has no slot for either and never carries them, matching v0 peers ignoring them. - R-O: the snapshot cursor is keyed. CursorKey is a random 32-byte per-process key; Cursor::mint/compute_digest now take it and use HMAC-SHA256 instead of plain SHA-256, so a cursor forged without the key, or minted under a different process's key, is refused cursor_invalid. Cursor::parse stays public and decodes as_of without the key (Huginn's dependency); only verify_digest (called from select()) needs it. The cursor body itself is now length-prefixed fields, not a delimiter-joined string, so a record key carrying any character - including the pre-fix C# delimiter U+241F - round-trips. - R-T: matches_citation (the `cites` path) now checks every one of a citer's edges against its declared target before applying the citation's own role/key filters, closing F15: a corrupt edge used to hide behind an unrelated filter and never get validated. - The retired Rust mutation harness entries file (scripts/mutate-cultnet-selection-rust.entries.ps1) is deleted; tests pin the behaviour directly. Regenerated contracts/cultnet/interop/selection-vectors.rs-written.json from the shared fixture; it is byte-identical to what was committed, so R-T's fix does not change any existing fixture vector's outcome. The Rust reader still evaluates every vector in the committed cs-written.json (15/15) unchanged - that file predates the parallel C# Hands' R-N/R-S work and was not touched here. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> |
||
|
|
8d7bee9346 |
Delete the retired hand-written mutation harness from this branch
Operator ruling, 2026-09-22: "Better to have nothing than a harness that punishes refactoring." Removes scripts/mutate-dotnet.ps1 and every scripts/*.entries.ps1 mutation-entry file for CultNet selection (networking, mesh, caching, fix-batch-2). Also drops the two live code comments in tests/GameCult.Mesh.Tests/CultMeshStreamingTests.cs that cited entry ids (MESH-KEY-Loosening, MESH-DEFAULT-Loosening), rewording them to describe the rule instead of the retired harness. Mutation testing now uses Stryker.NET on the cut's diff, run by Soul. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> |
||
|
|
0714492858 |
CultNet selection cut1 fix batch, second Hands pass: R-D mutation entries and Mesh R-F/R-L coverage
- Repairs two stale mutation entries in scripts/mutate-cultnet-selection-caching.entries.ps1 (CACHE-QJ-ExponentExpansion-Revert, CACHE-QJ-NaNInfinity-Revert): both anchored on ExpandScientificNotation/the pre-R-D switch arm, which the first fix-batch pass's R-D commit ( |
||
|
|
d94174d597 |
CultNet selection cut1 fix batch, second Hands pass: mutation entries and S9
- Adds mutation entries for R-A, R-B, R-C, R-E, R-F, R-G, R-H, R-J (scripts/mutate-cultnet-selection-networking.entries.ps1), each a revert and a loosening where the rule supports one, targeting CultNetSelectionEvaluator.cs, CultNetSelection.cs, CultNetDatabaseServer.cs and CultNetDatabaseSubscriptionServer.cs. - Adds S9 (docs/cultnet-selection-cut.md section 10): a new RUDP round-trip test proving a hop-bearing v1 subscription reconciles the whole matched set when the citer (not the page row itself) changes, plus its own mutation entry (NET-S9-HopFastPath-Revert) reverting D6's Watch routing. - Adds tests/GameCult.Networking.Tests/CultNetSelectionFixBatchTests.cs: regression coverage for R-B (edge anchor/order), R-C (code-point order in both the row tiebreak and the cursor position compare), R-E (alias matcher; door refusal), R-F (Select validates unconditionally), R-H (digest collision), R-J (numeric any_of canonical rendering) - none of these rules had a test that could fail independently of the fix-batch regression tests already in place. - Fixes a real gap R-E named but the first fix-batch pass never wrote: CultNetSelection.Validate now refuses a cites.target.schemaId that matches no declared schema, at the door, instead of MatchesCitation silently treating an unresolved target as "not found" and answering an empty page. Resolved through CultNetSchemaAliasMatching, the reference rule, against every declared schema - not only the selection's own Schemas-filtered reachable set, which a second entry (NET-RE-DoorTargetReachableOnly-Loosening) proves: a cites target is a separate structural reference, not itself constrained by Schemas. Coordinator-assigned item; the shared parity fixture's refuses_unmatched_cites_target_schema vector (Rust side, another Hands) is not yet in selection-vectors.rs-written.json, so SelectionParityVectorTests.SelectionVectorsWrittenByRustDecodeAndEvaluateIdenticallyInTheReference does not yet exercise it - it will once that vector lands, unmodified by this commit. - NetworkingTests.cs gains CultNetDatabaseServer_AnswersV1SnapshotRequestRespectingItsFields (R-A's loosening) and DatabaseSubscriptionServer_SubscribesV1AndReconcilesAHopBearingSelectionWhenTheCiterChanges (S9 / R-A's subscribe-listener revert). Discrepancy: R-A's snapshot-request v1 listener registration (CultNetDatabaseServer's _server.OnCultNet(_snapshotHandlerV1)) has no mutation entry. CultNetDatabaseServer pairs only with Server (the LiteNetLib/TCP-secured production construction site), and no existing test in this suite - before or after this pass - drives Server's real client/listener dispatch for CultNetDatabaseServer; every test calls CreateSelectionResponse/CreateSnapshotResponse directly. That revert is recorded as not yet reached in the entries file, not swapped for an easier mutant. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> |
||
|
|
a31993d518 |
CultNet selection: update mutation entries for the R-E rewrite
The alias-matching call sites changed shape (schema_alias::matches/ matches_any now take (candidate, schema_id, schema_name)), so the entries that edit them needed their Old/New anchors and Test names updated: RS-E-Alias-Revert (now targets the ported name-comparison line, not the deleted hash-stripping compare), RS-E-CitesTarget-Revert, and RS-E-EmptySchemasNeverMatchesEverything-Revert. New entries for this cut's rule, per spec: - RS-E-Alias-RevertToHashStripping: the revert - go back to hash stripping (compare schema_id against schema_id's own inferred name, the deleted matcher's behaviour). - RS-E-Alias-CaseInsensitive-Loosening: the loosening - compare case-insensitively instead of Ordinal. - RS-E-Alias-AcceptAnyVersionSuffix-Loosening: a second loosening - accept any ".vN" suffix without comparing the name. - RS-E-InferSchemaName-StripAnyLastDotSuffix-Loosening: a function-of-input mutant - infer_schema_name strips any suffix after the last dot, not only a ".v<digits>" marker. Also fixed the invocation: File/Command values are relative to the CultLib repo root and carry --manifest-path packages/cultnet-rs/Cargo.toml (there is no repo-root Cargo.toml/workspace), matching -Repo C:\wal and -Target 'packages/cultnet-rs/src/selection.rs,packages/cultnet-rs/src/snapshot_query.rs'. Verified: M0 green (4 commands, 36+81+7+1 tests), all 40 entries killed by their own test, every restore byte-exact (git diff clean against the committed source after the run). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> |
||
|
|
a088435f9f |
CultNet selection cut1 Rust fix batch: mutation entries for R-B/D/E/F/G/H/I
Adds a revert (and loosening, where the rule has one) per new rule to
scripts/mutate-cultnet-selection-rust.entries.ps1, and repairs three things
the entries file's own first run against this fix batch found:
- The RS-QJ-ExponentExpansion-Revert entry targeted
expand_scientific_notation, which R-M deleted; replaced by entries
against R-D's real replacement (the mantissa/exponent bignum).
- RS-D-ExponentBranch-Revert's first draft ("New = if false") hung: it
routes every value, including f64::MAX's exponent of ~971, into the
fraction branch, whose "k = (-exponent) as usize" wraps a positive
exponent's negation into a huge usize and spins effectively forever - the
run had to kill that process by PID. "New = if true" is the safe
direction (a negative exponent's "for _ in 0..exponent" is just an empty
range, not a cast) and is pinned against a negative-exponent test
instead.
- RS-V0Lowering-Revert (pre-existing, not from this batch) unexpectedly
survived the first run: matches_schema_keys_fields's move to
schema_alias::matches_any (R-E) picked up that helper's own "empty
candidates matches everything" shortcut, masking the v0-lowering
mutation this entry exists to catch. Fixed in the prior commit
(matches_schema_keys_fields no longer uses matches_any's shortcut); a new
entry, RS-E-EmptySchemasNeverMatchesEverything-Revert, pins the fix
directly rather than only through v0's own lowering.
All commands are narrowed with "-- --skip
selection_vectors_written_by_the_reference_decode_and_evaluate_identically_in_rust":
that vector-file test is legitimately pending the parallel C# Hands's own
regenerated cs-written.json (this suite does not touch that file), the same
carve-out this file already gave the pre-existing
reactive_document_coalesces_direct_same_schema_alias_member_writes flake.
Full run against this batch's final state: 36/36 entries killed, M0 green
on both commands, no survivors.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
|
||
|
|
c01bc0a129 |
CultNet selection cut1 fix batch: R-L (contract, Python, mutate-dotnet.ps1)
- cultnet.selection.schema.json: schemas/keys gain minItems: 1 (present and empty is refused at the door, never "every schema/key" - omit the field instead); limit documents that it is clamped 1..=200 by the answering server rather than refused outside that range. - cultnet_py/interop_peer.py: handle_server_message refused only an exact ".v1" suffix, so a v2+ selection message fell through to a silent empty-list answer instead of a loud refusal. is_unsupported_cultnet_version matches any cultnet.*.vN with N > 0 instead. test_cultnet.py's R-2 test now covers v2 and v10, not only v1. - mutate-dotnet.ps1: an entry's File (from -Entries, often written with "/") is looked up against the $targets dictionary keyed by -Target's own spelling; a "\\" vs "/" mismatch didn't throw at the lookup - $targets[$file] silently returned $null - so the crash surfaced one call later as a null-argument error inside a .NET path API instead of as "unknown target". Both -Target and every File read from an entry now normalise through Normalize-TargetPath before indexing $targets. Discrepancy: the ruling names "both R-2 sites" for the Python fix; only one site with this exact narrow-match bug exists in packages/cultnet-py and packages/cultmesh-py (cultmesh_py/server.py's handle_message already refuses every unrecognised schemaVersion, v1+ included, through its own default branch). Fixed the one that exists; did not invent a second. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> |
||
|
|
00f4c02dd7 |
CultNet selection cut1, commit 4: the parity vectors, and the ruling this branch landed under
Two things landed on this branch while commit 3 was in flight, both addressed
here rather than left for the vectors to paper over:
**Self's ruling, 2026-09-22 (commit 2 fix batch, 568e8e3/021e3f1): empty
lists, blank entries, and v0's own cleaning.** Rust already refused an empty
schemas/keys list (inherited from before the ruling); it now also matches
the reference's three further decisions exactly, not just in spirit:
- The door's refusal field is the plain list name ("schemas"/"keys"), not an
indexed "schemas[i]" - selection::validate's SelectionInvalid.field now
matches CultNetSelectionInvalidException("schemas", schema, ...) literally,
because the parity vectors compare this string across runtimes.
- snapshot_query.rs's lower_v0_list now deduplicates a mixed list the same
way CultNetV0SelectionLowering.Lower does (Distinct(StringComparer.Ordinal)
- order-preserving, first occurrence wins), not just blank-filters it.
A new test (serve_read_only_raw_snapshot_lowers_an_empty_or_blank_v0_schema_list_to_no_filter,
extended) pins the dedup case directly.
- Two mutation entries (RS-EmptySchemas-Revert, RS-EmptyKeys-Revert) added
for the door's original empty-list refusal, which had tests but no
mutation coverage before this commit.
**S12: the two-way parity vectors**, contracts/cultnet/interop/selection-vectors.{cs,rs}-written.json,
eight vectors each: the six evaluated selections from commit 3 (any_of+ge
conjunction, descending+limit, gt boundary, cited exists true/false, three
rows sharing one index value) plus two door-refusal vectors added for this
ruling - `refuses_empty_schemas_list` (schemas: []) and `refuses_blank_key_entry`
(keys: ["a-eq", " "]) - which decode their selection bytes and assert
`validate`/`Validate` refuses at exactly the named field, never evaluating.
The blank-key vector could not be generated before this commit: the door
rule it exercises did not exist in the reference until
|
||
|
|
568e8e356f |
CultNet selection cut1, commit 2 fix batch: door, shard-via-evaluator, Mesh v0 refusal, dead Reconcile code, mutation harness crash handling
Soul found commit 2 didn't pass (docs/cultnet-selection-cut.md, 2026-09-22):
empty/blank schemas-keys still reached the evaluator, the shard snapshot ran
its own loop instead of the evaluator, Mesh's v0 wire silently dropped v1-only
selection terms, the Mesh one-pass read could return the wrong schema's
record at a shared key, two decode rules for a foreign schema id disagreed,
dead identity-comparison code from the _projectRecord removal was still live,
and a disposed-socket race could abort a mutation run instead of recording
NO VERDICT. This is Self's fix batch for all of it.
- CultNetSelection.Validate now refuses a blank/whitespace entry inside
schemas or keys, not just an empty array. CultNetSelectionEvaluator no
longer treats [] as "no filter" (the door already refuses it before
evaluation). CultNetV0SelectionLowering is the one place that lowers an
empty or all-blank v0 list to null; CreateRawSnapshotResponse and
CreateShardSnapshotResponse both route through it, and CultMesh's own
Clean() now delegates to it instead of duplicating the rule.
- CreateShardSnapshotResponse no longer carries a private loop or its own
schema/key matching: it builds a CultNetSelection and answers through
CultNetDocumentRegistry.SelectPage (the same evaluator path as the
non-shard snapshot), with the shard's own membership check as a row
filter. Binding-alias expansion now runs on both paths identically.
- ResolveDefaultSelection decides "no schema filter" from the cleaned
recordKeys, not the raw array length. OverlaySelection stops deciding what
a missing override means: any explicit schemaIds/recordKeys override
replaces the default selection wholesale; no override keeps the default
untouched.
- CultMeshSnapshotRequestOptions.Selection is now checked by
EnsureV0Compatible before every send: fields, cites, cited, limit, cursor,
descending, and any non-default projection are refused typed
(CultNetSelectionInvalidException), never silently dropped from the v0
wire message. Mesh sending v1 is FU-Mesh-v1, not this cut.
- CultMeshHotBodySubscription.Selection returns a defensive copy on every
read so its arrays can't be mutated out from under the stored selection.
- ReadDocumentFromSnapshotResponse now searches candidates at one recordKey
in one order - exact schema, then alias, then a foreign schema id decoded
from the payload - instead of one OR'd condition where a case-insensitive
or structurally-lucky candidate could win first. The foreign-id decode
reads the payload's embedded schema version with the registry's shared
CultNetDocumentRegistry.TryReadSchemaVersion and alias-matches it against
the caller's own descriptor directly, rather than resolving to "whichever
registered type owns this schema string" - that resolution is ambiguous
when two document types alias the same schema id (MeshNoteDocument and
MeshNoteAliasDocument in the existing fixtures), which is what broke on
the first pass; TryResolveDescriptorByPayloadSchema stays for its existing
caller (ResolveDescriptorForRawDocument) with a note on when not to use it.
DecodeSnapshotDocuments and the Mesh copy of the payload-schema decode
(RawSnapshotPayloadMatchesSchema/TryReadSchemaVersion) are deleted in
favour of the shared one.
- CultNetDatabaseSubscriptionServer: the dead RecordKey/SchemaId identity
comparisons in Reconcile and ApplyProjectedChange, and the duplicate-key
throw in CreateProjectedSnapshot, are deleted - they can no longer fire
now that a projection's dictionary key is always the record's own key.
A new test flips authorizeRecord per key while the whole request stays
authorized (so DemandActive never toggles) and proves both halves of the
remaining diff: the removal of a now-unauthorized key and the add loop
picking up a newly-authorized one.
- Every NetworkingTests.cs test that starts an IsBackground poll thread now
joins it before its server disposes (about 20 tests; one already did).
- scripts/mutate-dotnet.ps1: a dotnet-test-host crash writing to stderr no
longer throws the whole run away under PowerShell 5.1's
$ErrorActionPreference = 'Stop' (2>&1 on a native command wraps stderr in
a terminating NativeCommandError); Run-Tests catches it and the entry loop
records NO VERDICT instead of aborting every remaining entry.
- Two probe tests: CultNetDatabase_ShardAndNonShardSnapshot_Agree{On
UnfilteredRequest,OnBindingSchemaIdOverride} prove the shard and non-shard
snapshot paths agree, including shard-membership exclusion of a document
outside the shard's key prefix and a binding schema-id override matching
on both paths.
- Mutation entries updated/added to match: the stale NET-D4-FourthMatcher
entry (its anchor was the private loop this batch deleted, not refactored)
is replaced by NET-D4-ShardMembership-Revert; the Mesh entries file's
header miscount ("two of three" loosening entries when there are only
two) is corrected and both previously-surviving loosening mutants
(MESH-DEFAULT-Loosening, MESH-KEY-Loosening) now have killing fixtures;
scripts/mutate-cultnet-selection-fixbatch2.entries.ps1 is new, covering
Reconcile's remaining two mutants.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
|
||
|
|
b090ca97f9 |
CultNet selection cut1, commit 3: the Rust runtime
docs/cultnet-selection-cut.md section 6/7: packages/cultnet-rs/src/selection.rs (new) - the Rust spelling of the vocabulary at wire parity with the corrected C# reference ( |
||
|
|
e5348d3d57 |
CultNet selection cut1, commit 2f: the Q-J fix (canonical decimal numbers)
Corrects commit 1's Number field: docs/cultnet-selection-cut.md section 2 "Numbers" (added 2026-09-22, superseding the map's own stale float64 text) specifies canonical decimal strings per the operator's Q-J ruling, and commits 0/1 shipped double instead - the Rust Hands caught the conflict before writing a line and stopped; this is the correction the map's Progress block calls for before Rust starts. - CultNetFieldPredicate.Number: double? -> string?. New CultNetCanonicalNumber (CultNetSelection.cs) is the wire's canonical grammar and comparator: IsCanonical validates ^-?(0|[1-9][0-9]*)(\.[0-9]*[1-9])?$ and excludes "-0" (the regex alone matches it); Compare orders by sign, then the integer part's length, then its digits, then the fraction padded on the right to equal length - a pure string function, no double anywhere on the path. ValidateField refuses a non-canonical number at the door (selection_invalid) rather than normalising it. - CultNetSelectionEvaluator.CompareNumber now takes two canonical strings and switches on CultNetCanonicalNumber.Compare's sign instead of comparing doubles directly. - CultCache.cs: CultDocumentDescriptor.TryGetIndexNumber and PersistedMember.NumberOf render a member's exact canonical decimal instead of a double. An integer renders exact (no 2^53 limit); decimal strips trailing fractional zeros; float/double use .NET's shortest round-trip ToString(), rewritten out of exponent notation to positional digits (ExpandScientificNotation) before the shared CanonicalizeDecimalDigits strips zeros and collapses a negative-zero result to "0"; NaN and infinity render null (TryGetIndexNumber returns false, matching "matches no comparison"). - contracts/cultnet/cultnet.selection.schema.json: "number" is now a string with the canonical pattern (a defence-in-depth (?!-0$) lookahead beyond what the door enforces in code), not a JSON number. - Tests: CultNetCanonicalNumberTests (new) pins IsCanonical/Compare directly, including the door's five named non-canonical spellings, magnitude vs. lexicographic ordering, sign, integer-part length, and a long past 2^53 against its float64 rounding. CultNetSelectionEvaluatorTests gains ValidationRefusesNonCanonicalNumberSpellings (+1, 1.0, 01, 1e3, -0) and its existing Number literals become canonical strings. CultDocumentSelectionSurfaceTests gains a dedicated FixtureLeafNumbers fixture and four tests: the 2^53+1 long rendering exactly, a decimal's trailing zeros stripped, a double's exponent notation rewritten to positional digits (1e21, 1e-7), and NaN/Infinity answering false. Mutation entries (scripts/mutate-dotnet.ps1): four new Caching entries (integer-through-float64, decimal trailing zeros, exponent expansion, NaN/Infinity) and five new Networking entries (the door's regex, the door's explicit "-0" exclusion, the comparator's integer-length check, a lexicographic-whole-string mutant, and the comparator's sign check) - all KILLED. The two pre-existing S16 (le/ge boundary) entries had to be re-anchored: their Old text quoted CompareNumber's previous double-based `left <= right`/`left >= right` spelling, which no longer exists now that CompareNumber switches on Compare's int result: `cmp <= 0`/`cmp >= 0`. Verification: dotnet test - GameCult.Networking.Tests 192/192 (was 183 before these additions), GameCult.Caching.Tests 192/192 (was 188), GameCult.Mesh.Tests 252/253 (one pre-existing unrelated skip, unaffected by this commit - no Mesh source touched). Caching mutation run: 10/10 KILLED (6 pre-existing + 4 new), M0 and final both green. Networking mutation run: 15/15 KILLED (10 pre-existing + 5 new, across CultNetSelectionEvaluator.cs, CultNetDatabase.cs and CultNetSelection.cs), M0 and final both green. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> |
||
|
|
4c33a62b29 |
CultNet selection cut1, commit 2e: mutation entries for the Mesh collapse
scripts/mutate-cultnet-selection-mesh.entries.ps1 covers the two rules moved in
commit 2d against tests/GameCult.Mesh.Tests: ResolveDefaultSelection's "no schema
filter when keys are given" default, and ReadDocumentFromSnapshotResponse's
recordKey-anchored exact read (including the foreign-schema-id decode fallback it
still honors).
M0: green, 87 passed (CultMeshStreamingTests). Verdicts:
- MESH-DEFAULT-Revert: KILLED
- MESH-DEFAULT-Loosening: SURVIVED
- MESH-KEY-Revert: KILLED
- MESH-KEY-Loosening: SURVIVED
- MESH-FOREIGN-Revert: KILLED
- Final: green, 87 passed. All restores byte-exact (working tree clean after the run).
Both SURVIVED entries are recorded as fixture gaps, not swapped for easier
mutants: no test in CultMeshStreamingTests.cs passes an explicit empty (non-null)
recordKeys array, and none varies a record key's case, so nothing distinguishes
MESH-DEFAULT-Loosening's `recordKeys != null` from the correct `recordKeys is
{ Count: > 0 }`, or MESH-KEY-Loosening's OrdinalIgnoreCase from the correct
Ordinal comparison. MESH-FOREIGN has a revert entry only; no distinct, still-wrong
"loosening" of "decode successfully as TDocument" was found that isn't already
one of the other two entries.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
|
||
|
|
4167cc4f24 |
CultNet selection cut1, commit 2c: mutation entry for the fourth matcher's collapse
Self's ruling (docs/cultnet-selection-cut.md, 2026-09-22) required a mutation
entry for the fourth schema-matching copy's collapse into
CultNetSchemaAliasMatching, landed in commit 2a. Added
NET-D4-FourthMatcher-Revert, pinned by
CultNetDatabase_Creates_ShardSnapshotResponse_ForCompatibleSchemaAlias: killed.
Every pre-existing entry in this file now carries its own File, so the whole
file runs as one invocation against both CultNetSelectionEvaluator.cs and
CultNetDatabase.cs (it previously assumed a single implicit target and would
throw given two). Ran the combined file: 10/10 entries killed, M0 green, both
targets restored byte-exact (verified by git status showing no diff on
either .cs file after the run).
A loosening mutant for the same site
('(requestedSchemaIds != null && requestedSchemaIds.Length == 0) ||', i.e.
reject only an empty-but-non-null requested-schema list) was run by hand and
SURVIVED: CultNetDatabase_Creates_ShardSnapshotResponse_ForCompatibleSchemaAlias
puts exactly one document in the whole cache, so an over-permissive filter
still returns the same single row. Recorded as a comment in the entries file
per the brief ("report it, do not replace it with an easier mutant") rather
than committed as a fake kill or swapped for an easier mutant. Fixing it needs
a second document of a different schema in the shard's fixture; not added
here.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
|
||
|
|
17d10e017f |
CultNet selection cut1, commit 1: the shape, the evaluator, v1 messages
docs/cultnet-selection-cut.md section 2/4/6/7/8: CultNetSelection (schemas, keys, fields, cites, cited, projection, descending, limit, cursor) with its door validation (S1), and CultNetSelectionEvaluator: the single-row Matches fast path (D6) and the full Select (order, hop, cursor, paging). Neither reflects over a document; both read only through the cache's public surface (commit 0). Three v1 messages beside v0 (cultnet.snapshot_request.v1, cultnet.database_subscribe.v1, cultnet.snapshot_response_raw.v1), their schema files under contracts/cultnet, registry entries and dispatch arms. CultNetSchemaAliasMatching goes internal -> public (D4), the one alias matcher. CultNetDatabase gains LastWriteSequence(schemaId, key), confirmed at the one append site (AppendMutationLogEntry) that every committed write takes a shard-log sequence, including a local PutAsync outside any shard. Three engines deleted, one evaluator wired in their place: - CultNetDocumentRegistry: CreateRawSnapshotResponse's reflection loop over cache.AllEntries and MatchesRequestedSchema gone; cache.AllStoredDocuments replaces the reflection entirely. v0 lowers into a Selection and walks the evaluator's own pages so a v0 caller still sees its whole matching set (D3: v0 has no engine of its own). CreateSelectionResponse/SelectPage/ ToRawRecord/CreateSnapshotRequestV1 added. - CultNetDatabaseServer: CreateChangeMessage/CreateRawDocumentRecord/Matches/ MatchesSchema/InferSchemaName (122 lines) deleted; CreateSubscription lowers v0 into a Selection and matches through the evaluator. - CultNetDatabaseSubscriptionServer: CreateAuthorizedChange/CreateChange/ CreateChangeCore/ResolveWireSchemaId/MatchesRequestedSchema/CreateRawRecord (92 lines) deleted, replaced by CreateMatchedRecord. _projectRecord and CreateProjectedSnapshot's collapse are commit 2's, per the map's own deletion line, and are untouched here. Discrepancy found and fixed (not in the map): CultNetDocumentBinding can carry a schema id different from its descriptor's own (CultNetDocumentBinding.ForDocument's schemaId override), which the old per-engine matchers checked and a descriptor-only evaluator cannot see by design (it stays decoupled from CultNetDocumentRegistry). Fixed at the two call sites and in CultNetDocumentRegistry.SelectPage by resolving the binding's schema id into the effective selection before evaluation (CultNetSchemaAliasMatching.WithBindingSchemaAlias, CultNetDocumentRegistry.ExpandSchemaBindingAliases), not in the evaluator. Found by DatabaseSubscription_FiltersLiveChangesByWireSchemaBinding timing out; both call sites now pass. Second discrepancy, reported not fixed: CultNetDatabase.cs:1707 has a fourth, undocumented copy of schema-matching logic (MatchesRequestedSchema, private, serving CreateShardSnapshotResponse), which the map's section 1 audit did not name among "three separate selector engines". Left untouched this commit: it is out of the map's explicit deletes list and touching it risks shard snapshot behavior with no budgeted verification. Verification: dotnet test tests/GameCult.Networking.Tests - 162 passed (150 existing + 12 new), 0 failed; the seven map-named tests all still pass, two rewritten for the new signatures (CreateChangeMessage now takes CultNetSelection; CreateChange/CreateAuthorizedChange collapsed into CreateMatchedRecord, returning CultNetRawDocumentRecord? directly). tests/GameCult.Mesh.Tests - 252 passed, 1 pre-existing skip, 0 failed (no Mesh source touched this commit). tests/GameCult.Caching.Tests - unaffected (188, unchanged from commit 0). Negative greps: GetProperty|GetField|GetCustomAttribute in GameCult.Networking - only against the generic `object change` dispatch (CultNetDatabaseChange<T>) and CultRecordHandle<T> construction, pre-existing and out of this cut's scope, never against a document's declared values. _projectRecord survives in CultNetDatabaseSubscriptionServer.cs (expected, commit 2). CultNetSchemaAliasMatching is the only schema matcher outside the one reported discrepancy. scripts/mutate-dotnet.ps1: the caching mutation runner generalized into a shared dotnet/NUnit harness (Target/TestProject/Entries/ControlFilter), replacing scripts/mutate-cultnet-selection-caching.ps1 with an entries file of the same six mutations (re-verified: all six still KILL). It is now generic to any NUnit project in this repo, not only this campaign's two targets - a candidate to sit beside tools/eureka-mutations.ps1 as the dotnet-side counterpart. Nine new networking entries (S3, S5 x2, S7 x2, S16 x2, S18) all KILLED. Not yet mutation-run: S1, S2, S6, S23 (pinned by tests, not by a committed mutant); S9-S15 (subscription reconcile lowering, v0 no-engine-of-its-own); S17, S20 (pinned by tests only). S19/S21/S22 are cache-layer, covered in commit 0. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> |
||
|
|
e420410fe9 |
CultNet selection cut1, commit 0: the cache's public read surface
docs/cultnet-selection-cut.md section 6/7/10: the reflection loop at CultNetDocumentRegistry.cs:325-342 exists only because nothing outside GameCult.Caching can read a declared index value, reference, or numeric member (Members/IndexAccessors are internal, no InternalsVisibleTo to Networking). This adds the one public door instead: CultDocumentMemberView, CultDocumentDescriptor.DeclaredMembers/TryGetIndexValue/TryGetIndexNumber/ ReferencesOf, and CultDocumentRegistry.ResolveTargetLeaves (D9, computed live against the registry, never cached or persisted). Two registration refusals land beside DiscoverMembers' existing DuplicateSlotMessage/HiddenMemberMessage: D10 renames the ArgumentException ToDictionary already threw for two differently-named members sharing one index alias into a rejection that names the schema, both members and the alias; D11 refuses a reference member whose CLR shape the cache cannot walk (many:false needs CultRecordRef<T>, many:true needs IEnumerable<CultRecordRef<T>> or IDictionary<CultRecordRef<T>, V>) instead of silently answering it as unreadable later. GameCult.Networking is untouched; the evaluator that will read this surface does not exist yet (commit 1). Verification: dotnet test tests/GameCult.Caching.Tests - 188 passed (176 existing + 12 new), 0 failed. New tests: TryGetIndexValue/TryGetIndexNumber reading an inherited alias; ReferencesOf over a single/many-list/many-dictionary reference, including two dictionary entries with different payload bytes (S19); ResolveTargetLeaves against an abstract target with no [CultDocument] of its own (D9), including a leaf registered after an earlier descriptor was built; IsNumeric agreement for a nullable numeric and a decimal member (S22); the two new registration refusals, dynamically emitted so registry discovery by attribute scan cannot see the rejected shapes. scripts/mutate-cultnet-selection-caching.ps1: a dedicated dotnet/NUnit mutation runner (the shared tools/eureka-mutations.ps1 harness only speaks cargo; scripts/mutate-cultmesh.mjs is the QUIC campaign's file and this campaign does not edit it, per the map's section 10). Byte-exact restore verified by SHA-256, a sidecar for crash recovery, M0 no-op control, one anchor match required per entry. Six entries (D8/S22, D10, D11 - revert and loosening each) all KILLED; final restore verified green. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> |
||
|
|
089859deb4 |
Write and enforce the semver policy, per operator ruling on 1.0.60
1.0.60 removed public API (CultInspectorAssetPath) under a patch bump; its own changelog says "Breaking" and the version number lied. Operator ruling: semver is policy, strictly, for every published package family. docs/semver-policy.md states what counts as breaking per surface (managed API, Unity inspector attributes, CultCache wire format/store compatibility, CultNet protocol, native bridge exports), the 0.y.z pre-1.0 convention, tag naming, and required changelog shape. Records the 1.0.60 scar as history; does not renumber it. scripts/check-changelog-semver.mjs is the mechanical check: refuses a changelog "### Breaking" section under too small a bump, and refuses a version that skips, reverses, or repeats the previous published tag. Skips re-litigating a version that is already tagged (rebuilding immutable history is not a release action). Wired into publish-packages.yml for the four npm/PyPI packages, and into build-unity-package.ps1 (cultlib), packages/cultmath/scripts/build-unity-package.ps1 (cultmath), and a new scripts/verify-caching-unity-release.ps1 (caching.unity has no build script of its own to hook into). 18 node:test cases cover the required table (breaking+major/minor/patch, non-breaking, 0.y.z, skip, downgrade) plus CLI-level git-tag resolution. Hand-mutated each rule (bump classification, pre-1.0 lane requirement, breaking-heading detection, breaking early-return, already-published skip) and confirmed each kill, then restored byte-exact. No mutation harness is committed; CultLib has none today. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
45c2f4006f |
Say the OpenSSL bridge ships with this release, and pack the changelog
Soul S1: the 1.0.60 entry claimed the OpenSSL MsQuic bridge had shipped since 1.0.59; git says both blobs first appear in this release. S2: the package build staged package.json and README only, so the produced package and the tracked template differed by the changelog. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
0c9cb57bea |
Correct stale Schannel/40,448-byte claims, ship OpenSSL's license notice
Soul flagged (F3) two docs still describing the pre-release native QUIC
bridge: unity/org.gamecult.cultlib/README.md said Schannel, and
native/GameCult.Mesh.Quic.Native/README.md still described the older
40,448-byte committed plugin as current. The shipped bridge (since the
1.0.60/1.4.0 release, commit
|
||
|
|
244154e2f5 |
Say what the restore digest is, since it is not an identity
The last report quoted the restored sha256 as though two runs agreeing on it meant they had run the same source. They had not: `text=auto` gives a Windows checkout carriage returns, so the win32-x64 run restored to b163cab7 and the linux-x64 run to e3c6528a from the same commit. The digest is a restore check — the file this run started with is the file it ended with — and it is said where it is printed, so the next report does not borrow it for something it cannot do. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
b985f07887 |
Say what the single-waiter wake actually rests on, and what is still unreached
The note beside the close's wake claimed the narrowing to `notify_one` was equivalent, and gave the weaker half of the reason. The equivalence needs two premises together: `closing` is set permanently under the gate before the notify and appears in every waiter's predicate, so after a close begins no poller can block at all and the set of waiters is fixed; and each exiting call's own scope destructor takes the gate, decrements and wakes everyone, so one wake starts a chain that reaches all of them. Neither carries it alone. Both are now written where the claim is made, so it can be checked rather than believed. Narrowing both wakes also survives, and that is not equivalent by any argument: a wake consumed by the closer, waiting on the same variable for the count to reach zero, leaves the chain stopped and the rest of the pollers sitting out their timeouts. It passes on wait-queue ordering — the pollers are queued before the closer, so a FIFO head is never the closer until it is the only waiter left. Recorded as unreached, with the reason, and deliberately not as unreachable: that claim has been wrong twice in this campaign. A scenario cannot force it while the first premise holds, since the only way to put the closer ahead of a poller in that queue is a poller that blocks after the close began. Reaching it would need a seam that chooses which waiter a notify goes to. The standard does not promise FIFO, so what survives here is the implementation's doing. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
5ab4c627d7 |
Put a call in both states so the seam's own guard is pinned
The last pass's only non-comment source change was held by nothing. Reverting the hold's guard so it fires unconditionally survived all five scenarios, because no scenario ever had a call in both states at once: the timeout scenario never arms the hold, and the hold scenarios never let a timeout expire. `holdtimeout` is the missing state. The hold is armed, the runtime is idle, and one poll asks for 150 ms while the hold stays armed for 800. A call the wait woke is the seam's to park; a call whose own timeout expired is on its way out, and parking it would let `holdclose` report the bridge keeping a host call inside the library when what kept it was the fixture. The measurement is which of those two numbers the poll comes back at. Two entries, and the loosening is the one worth reading: the same two conditions in the other order. The hold blocks, so the order is the rule — asked first, it parks the call and then decides whether it should have. Both die on `holdtimeout` at about 800 ms and on nothing else; `holdclose` and `latecall` stay green under both, which is the point. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
c682619fbd |
Close the timeout against waits derived from the argument, not only constants
The two bands ruled out a constant and ruled out nothing else. Any mapping that is the identity at 200 and 900 passed for free, and the late tolerance was a flat 300 ms, so anything within that of the truth passed at every value forever. Three ordinary ones survived all five scenarios on both targets: clamping the wait to at most a second, adding 150 ms to it, and scaling it by six fifths. The clamp is the one that matters. Its failure is verbatim the bug the last pass claimed to fix — a host asking for five seconds gets one, forever, with nothing reporting it — and "cap the wait so a shutdown gets noticed" is the most ordinary spelling that line will ever be given. So the long probe goes to 5000, above any ceiling a bridge would plausibly clamp to, which kills the clamp and the scale on the early check and the late one. And the late tolerance becomes a fraction of what was asked for — 60 ms plus an eighth — which is what kills the offset: 150 ms is 75% late at the 200 ms probe and there is no flat gap left to hide in. Measured overshoot on either target is about 10 ms, against 85 ms allowed at the short probe and 685 at the long. The table gets the clamp as an entry of its own, because the whole lesson here is that a table of constants says nothing about a wait computed from the argument, and a table of constants would go green again the moment these numbers drifted back. Its honest limit is stated where the tolerance is defined: a scaling smaller than an eighth survives, and nothing here claims otherwise. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
c177533b4f |
Offer the container line to both shells, and name the short-path requirement
The container recipe carried a PowerShell backtick continuation in a snippet whose next paragraph invites a POSIX shell to run it, where a backtick is not a continuation. A continuation has to pick a shell, so the line does not have one. The Dockerfile's own copy of the recipe still mounted one machine's checkout by absolute path; it mounts whatever checkout is running it, like the other two. And the requirement a newcomer meets by tripping over it: on win32-x64 the harness needs the checkout somewhere short. MSBuild's file tracker gives out on long paths, so a run from a deep temporary directory fails the build for every mutant, the no-op control included. That is loud — the harness stops on a red control and prints what the child said — but what the child says is MSBuild, not the path. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
a937a0b07e |
Let the bridge's own timeout, refusal and two-phase poll be seen
Three rules the bridge has always promised, none of which anything committed could see. The timeout was the structural one. Both scenarios end their waits with a close, so the duration the bridge waits on governed nothing either of them observes: a hard-coded 2000 ms survived the whole Linux matrix and Windows, and a host asking for five seconds would have got two forever with nothing reporting it. The fix is not another entry at another constant. A timeout's only observable is how long the call takes, so `polltimeout` lets an idle runtime's poll be ended by its own timeout and measures it, twice at different values, because no single constant sits in both bands. The seam's hold moves behind that too: it parks only a call the wait woke, so a poll whose own timeout expired is on its way out rather than held there by the fixture and counted as the bridge's doing. The refusal during a close (the header's section 4) had no scenario at all. A call admitted after the closer has begun increments the in-flight count behind a wait that has already read it. `latecall` holds the close open with the seam and makes a call into it. And the two-phase poll was written off as needing a listener, a credential and a connection. It needs a closed loopback port: the failure always carries a reason, which is an 87-byte payload, in about a millisecond. Moving the pop above the copy crashes on both targets. 18 killed, 0 survived, 0 skipped on linux-x64; 16 killed, 0 survived on win32-x64, where the two ThreadSanitizer entries are honestly skipped. Both runs restored the source to the same sha256, b163cab7d48ebc93. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
440709829d |
Commit the container the linux-x64 entries are honest in
The image the bridge is built and mutated in existed nowhere in the repository, and the recipe that stood in for it installed a C++ toolchain and no JavaScript runtime — so the container could not run the harness that checks the bridge it had just built. It is a file now, pinned to the same debian:13 digest as the release workflow, carrying ninja, Node, setarch and MsQuic's four runtime dependencies. The harness configures with `-G Ninja` on linux-x64, which is what scripts/build-quic-native.sh uses. CMake's Linux default is Makefiles, the image carries ninja rather than make, and the default configured nothing at all: the first run in the committed container died in cmake. It said so, because the control now prints what the child said, which is the whole point of that change. The seccomp requirement is stated where someone hitting it will read it — the bridge's README, beside the recipe — rather than being knowledge the runner assumes its caller already has. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
e4e09a086c |
Enter the rules the quiesce rests on, and stop exiting green on skips
Four entries the native target did not have. Two for the rule that a blocking crossing is a counted host call: the revert deletes the call scope from `cultmesh_quic_next_event`, and the loosening keeps the scope and its refusal but drops it at the door, before the call blocks. Two for the rule that a positive timeout blocks until an event or the close: the revert inverts the comparison, and the loosening waits a token millisecond. All four were green before this, because nothing checked that the pollers were inside the library at all. The bounded-wait loosening goes from 1 ms to 50 ms. A 1 ms bound only ever lost a race — the margin was a few hundred lock handoffs — and the same loosening at 50 ms survived on both targets. `holdclose` holds its pollers for 500 ms, so any bound shorter than the hold now expires with calls still inside. A bound longer than the hold is not distinguished, which is this entry's honest limit. A run that skips entries covered those rules with nothing, and exiting 0 let a win32-x64 run — where every ThreadSanitizer rule is skipped — be read as a green gate. It exits 2 and says so, with a killed/survived/skipped line. A red control arrived as a bare colour with the child's output thrown away, which is how a configuration that could not start at all stayed invisible. The control now prints what the child said. On linux-x64 the sanitizer configurations are re-executed through `setarch -R`, because ThreadSanitizer dies before `main` on the kernel's address-space randomisation; both that and the seccomp denial behind `personality(ADDR_NO_RANDOMIZE)` stop the run with the cause and the required invocation instead of being reported as kills. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
180917d75b |
Mutate the native bridge, and name the target each entry is honest on
The mutation suite covered only the TypeScript rules. The bridge's two runtime lifetime rules now have entries of their own, each with a revert that puts back the wrong spelling and a loosening that keeps the shape and gives away the guarantee: a notify moved outside the gate, a notify outside it that fires only on the last call out, the quiesce wait deleted, and the quiesce wait made bounded. Their killer is the scenario runner, built through CMake rather than npm, so the runner now takes a check per target instead of assuming a workspace, and a target without a dist/ keeps the verified restore without the sentinel. Every native entry names the platforms it is honest on, and a run elsewhere reports it as skipped rather than killed. There is no ThreadSanitizer for MSVC, so the notify rule cannot be seen on win32-x64 at all. Soul's Cut 3 pass found this the hard way, reporting two mutations killed from a win32-x64 release run where both in fact survive. Naming a target on the command line runs only that one, which is what lets the native entries run inside the Debian 13 container where the TypeScript workspaces have no installed dependencies. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
c729dff2ee |
Pin the Windows MsQuic to the OpenSSL build, and both of its digests
Q9 ruled A. The Schannel MsQuic refuses QUIC_CREDENTIAL_TYPE_CERTIFICATE_PKCS12 with QUIC_STATUS_NOT_SUPPORTED, and PKCS12 is the only credential type a provider has on both platforms, so a Windows provider could not open a listener against the runtime this repo was shipping. The same bridge binary passes all twenty smoke checks and the v1 gate against the OpenSSL build, which is what Soul verified and what this now downloads. Two digests, not one. The zip is pinned by FEE9A664 and 19,179,817 bytes; the msquic.dll inside it by C17C6581 and 4,181,856 bytes. The earlier report and the cut map's first record put the zip's digest on the DLL, which is a pin that checks the archive and then ships a file nobody looked at. Sizes ride along with both, because a digest that fails tells you less than a digest and a length that fail together. The CRT is linked statically. A Node or Unity host without the Visual C++ redistributable cannot load a DLL that imports vcruntime140.dll, and Windows reports that as "the specified module could not be found" while naming the module that was found. Confirmed on the rebuild: msquic.dll, CRYPT32, ntdll, KERNEL32, and no CRT import at all. The DLL goes from 83,456 to 302,080 bytes, which is the price of the host not needing anything installed. The Unity plugin's committed msquic.dll is still the Schannel one and this does not touch it; the next build-unity-package.ps1 run is what replaces it, and that is not in this batch. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
23dc0939e0 |
Give the bridge a Linux build the deploy host's own distribution can produce
There is no Linux MsQuic binary in any NuGet package, and the v2.5.9 GitHub release carries only test bundles, so the Windows script's approach does not transfer. What does exist is Microsoft's Debian 13 pool, which is the same distribution Yggdrasil runs, and the msquic source tree at the matching tag for the POSIX headers the NuGet omits. `scripts/build-quic-native.sh` fetches the pinned `libmsquic_2.5.9_amd64.deb` and the three headers `msquic.h`, `msquic_posix.h` and `quic_sal_stub.h`, and verifies all four by SHA-256 the same way `build-quic-native.ps1:14` verifies the NuGet. A digest that stops matching is a changed dependency, not a warning. Two details the deb forced. It carries three library files and no licence text, so the MIT licence is copied from the one this repo already committed for the Windows plugin — the same text for both platforms. And it ships only `libmsquic.so.2.5.9`, with no `libmsquic.so` for `-lmsquic` to resolve against, so the script makes that link in the extracted tree rather than in the image. The Linux CMake branch links `-fvisibility=hidden`, so only the twelve v2 exports leave the `.so` and the Windows-only v1 five are absent by construction rather than by discipline, and sets `RPATH $ORIGIN` so the `libmsquic.so.2` shipped beside the bridge resolves without being installed system-wide. `build-quic-native.ps1`'s default output directory becomes `artifacts/quic-native/win32-x64` so the two platforms' outputs sit beside each other instead of on top of each other. Both existing callers — `build-unity-package.ps1:80-82` and the Native.Tests csproj `:20-22` — pass an explicit `-OutputDirectory`, so neither is affected. Built and smoked in the workstation's Docker `debian:13` engine pinned by digest: g++ 14.2.0, glibc 2.41, `libgamecult_mesh_quic_native.so` 85,456 bytes with `NEEDED libmsquic.so.2` and `RUNPATH $ORIGIN`, and the same 15-check v2 FFI smoke that runs on Windows passes under Node 24 on that image. That is a Linux build on a Linux host and nothing more: Q6 ruled the release artifact comes from the Actions job, and this one is the development loop. A Linux host still needs MsQuic's own runtime dependencies present — libssl3t64, libnuma1, libxdp1, libnl-route-3-200 — which is now written down, because the first smoke run failed on a missing libxdp.so.1 and the reason was not obvious from the error. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> |
||
|
|
9a4c9396b4 |
Stop the realtime decoder handing back a view of its own frame
Two mutants the cut-2 pass recorded as equivalent were not. Both notes were reasoning, and both were wrong in the same way: they argued from the shape of the code instead of trying the input that breaks it. The decoder's length refusal reads `payloadLength < 0 || expected !== length`. The note said the first clause could never fire on its own, because a negative payload length makes the sum smaller than the frame. It does not: the sum also carries the three identity lengths, and a negative payload cancels against them. A 37-byte frame declaring `channelLength=65535, payloadLength=-65535` sums to exactly 37, matches its own byte length, and without the clause decodes to three blank identities and an empty payload where `CultMeshRealtimeWireProtocol` refuses it. Two smaller spellings do the same, `ch=1, pl=-1` over 37 bytes and `ch=2, pl=-1` over 38. No test covered that shape; one does now, and the mutation is listed and killed rather than excused. The payload copy was worse, because the fix and the bug looked identical. The decoder used `bytes.slice(...)`, which copies a `Uint8Array` — and the existing test passes one, so it was green — but Node's `Buffer` overrides `slice` with an alias of `subarray`. A `Buffer` is what a socket read hands a Node caller, which is exactly how the QUIC plane in cut 4 will reach this function, so every decoded payload would have been a live view of a frame buffer the transport is free to reuse. `Uint8Array.prototype.slice.call(bytes, ...)` was the obvious repair and does copy, but `%TypedArray%.prototype.slice` takes its result constructor from `@@species`, and `Buffer`'s is `FastBuffer`: the payload then comes back a `Buffer` from a `Buffer` frame and a `Uint8Array` from a `Uint8Array` one, so the same frame compares equal or unequal under `deepStrictEqual` depending on how it arrived. `new Uint8Array(bytes.subarray(...))` copies and is one type on both. Three mutations now guard the line — return a view, return a `Buffer` view, return a species-typed copy — and the first two die only on their own input type. Mutation runner: 75 mutations, 75 killed, 0 survivors, three green controls. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> |
||
|
|
65f2b666ef |
Run the TypeScript workspaces by the name cultcache-ts actually has
`npm run build:ts` and `test:ts` have been dead since |
||
|
|
ae29910b30 |
Mutate the realtime frame codec, one mutation per rule
A third target in the committed runner, `realtime`, over `packages/cultmesh-ts/src/realtime-wire.ts`, killed by that package's own suite. It gets the machinery the other two already have: a no-op control through the same write path, the sidecar and its verified restore, and the post-restore rebuild that proves `dist/` no longer carries a mutant, with `0x31545343` as the sentinel its last mutation removes. Thirty-seven mutations, one per rule the codec owns: each header field's offset, width and endianness; the field order the reference fixed; the magic, the stamped header size and wire version; the delivery ordinal; UTF-8 identities and byte-counting length prefixes; the identity and payload ceilings; the ALPN, the two QUIC error codes and the two stream kinds; the four encode refusals from the transport contract; each decode refusal and the order they fire in; the byte-order mark; and the two aliasing rules, that decode reads only the viewed bytes of an offset view and that the decoded payload does not alias the frame. A mutation that changes the encoding symmetrically still dies, because the vector bytes were written on the other side. All seventy-two mutations across the three targets are killed and all three controls are green. One mutant is not listed and says why in the table, as the P1363 length guard already did: removing `payloadLength < 0` from the decoder's length refusal is equivalent. The clause mirrors the reference but cannot fire alone, because reaching it means the frame is already 37 bytes and a negative length can then never make the sum match. It survived when tried. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> |
||
|
|
6cbc73fb17 |
Encode a CultMesh realtime frame in TypeScript
One codec, `packages/cultmesh-ts/src/realtime-wire.ts`, rule for rule what `CultMeshRealtimeWireProtocol` does: the 37-byte little-endian fixed header with magic 0x31545343, the delivery byte as the C# enum ordinal, the i64 epoch and sequence, three u16 identity lengths, the i32 payload length, the stamped header size and wire version, then UTF-8 identities and the payload. It touches no socket; QUIC stream framing stays transport-owned. The blank reading in `Validate` is `IsNullOrWhiteSpace`, whose whitespace set is not `String.prototype.trim`'s, so it borrows `isNullOrWhiteSpaceCSharp` from `cultnet-ts` rather than growing a second reading of the same rule. Byte parity is proven in both directions and in two processes, which is what the authority vectors cost us to learn. `contracts/cultmesh/realtime-frame-vectors.json` is written by the new C# test under CULTMESH_WRITE_VECTORS=1 and decoded and re-encoded here; `realtime-frame-vectors.ts-written.json` is written by `scripts/write-cultmesh-realtime-frame-vectors.mjs` with this codec, over different identities and epochs, and decoded by the same C# test with the reference implementation alone. Both files share one field shape, so each side reads both with one reader. The two cases that would put megabytes into a committed contract file, identities at exactly 65,535 UTF-8 bytes and a 64 MiB payload, are recorded as a build rule plus the digest of the whole encoding; both sides build them the same way. Three things the C# reference gets from its type system and TypeScript does not, each refused rather than left to surface as a stray runtime error: a `bigint` outside the i64 range, a delivery string the union cannot spell, and a leading U+FEFF, which `TextDecoder` deletes by default and `Encoding.UTF8.GetString` keeps. The last one is a real divergence the test caught; the decoder now sets `ignoreBOM`. U+FEFF is not whitespace to .NET, so such an identity passes the transport contract and reaches the wire. There was no C# test for this codec before; this is also its first. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> |
||
|
|
fe2555da63 |
Repair only the file states the mutation runner itself wrote
A SIGKILL leaves the sidecar behind, and the next run wrote it over whatever the file then held. That is right after a kill and destructive after one: a fix written to the source between the kill and the next run was reverted to bytes from before the kill, with a line claiming a repair, and the run then reported mutation results for code nobody had written. The mutation table is the record of what a live run can leave behind: the original (which is the sidecar, and also what the no-op control writes) or the original with exactly one of that target's mutations applied. Repair now recognises those and nothing else. A file matching neither stops the run with its own digest and the sidecar's and changes nothing. Reproduced both halves against the shared target. Killed mid-mutation, then appended a line to the source: the rerun exits 1, prints both sha256s, and the hand-written line is still there afterwards. With the mutant put back and the sidecar unchanged, the rerun repairs as before and now names the mutation the dead run was inside. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> |
||
|
|
c64a2da81d |
Refuse the URL-repaired spellings in the protection gate too
`isLoopbackEndpoint` hands its endpoint to `refusedByCSharpUriShape` before `URL` sees it; `isProtectedEndpoint` did not, and `URL` repairs eleven spellings `System.Uri` refuses outright. Every one of them answered protected here and unprotected in the C# reference, which is the dangerous direction: the channel-protection gate opened on a route `IsProtected` would have closed. `wss:x`, `wss:host`, `wss:host:8443`, `wss:/host` and `wss:///host` have no authority for `System.Uri`; `wss://host\path` and `wss://host:8443\` carry a backslash; `wss://ho\tst/m`, `wss://host\t/m` and `quic://ho\tst` a tab, and `wss://host\r\n/m` a CR/LF, all of which `URL` deletes. Soul's 71 raw-shape spellings go in as a table with the answer the C# `IsProtected` gave for each on 2026-09-16, and the test asserts the invariant rather than equality: TypeScript may never call an endpoint protected that C# refuses. Eleven of the 71 now differ the safe way round, C# protected and this not, and they are pinned by name rather than excluded, so neither a twelfth joining them nor one of them flipping passes quietly. Two throw in `URL` before any rule of ours runs (an IPv6 zone id, a non-breaking-space host); the other nine are host repairs `refusedByCSharpUriShape` refuses on purpose, because the same repair would make `isLoopbackEndpoint` lie. None is dialable as a browser CultMesh route. The mutation that drops the pre-check from `isProtectedEndpoint` alone, leaving `isLoopbackEndpoint`'s in place, is killed by that test. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> |
||
|
|
dfe582edbc |
Kill the browser's local reading of "unsigned", and mutate the browser too
Soul replaced the shared `isUnsignedCertificate` call in the browser's session-acceptance check with a local empty-string comparison and every one of the 18 browser tests still passed, because every test used an empty signature. A whitespace signature is where the two readings part: the shared route verifier accepts such a route as unsigned on a loopback local-development endpoint, and an inline comparison then demands a provider proof for the session the verifier just let through. The test now opens that session with three spaces, U+0085 and a no-break-space/tab/newline mix as well as the empty string, and asserts U+FEFF the other way, since it is whitespace to JavaScript and a signature to C#. The mutation runner grew a second target. `shared` is the one verifier, killed by the cultnet-ts tests; `browser` is `packages/cultmesh-browser/src/index.ts`, killed by the browser tests. A browser mutation cannot be killed by cultnet-ts tests, so the killer is the target's own workspace, and each target carries its own sidecar, control, sentinel and post-restore dist assertion. Two repairs this pass earned: - The sidecar is now removed inside the `finally`, right after the verified restore. A throw mid-run left it behind holding the pre-run bytes, and the next run "repaired" the file from them, silently reverting everything fixed in between. That happened here, twice, before it was noticed. - The NUL in the loopback pre-check's strip regex had been written as a literal byte rather than an escape. It read the same, but git classified the file as binary and stopped normalising its line endings, so the whole file diffed as rewritten. Escaped, along with the ASCII host range beside it. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> |
||
|
|
803ee12a7a |
Refuse the endpoint spellings WHATWG URL repairs and System.Uri does not
`isLoopbackEndpoint` handed the raw endpoint to `URL` and read the host it got back. `URL` is a repairing parser and `System.Uri` is not, so seven spellings were loopback here and remote in C#: a trailing dot on the host, which `URL` drops; `ws:127.0.0.1`, `ws:/127.0.0.1` and `ws:///127.0.0.1`, whose authority `URL` infers; a backslash path separator, which `URL` reads as a separator; fullwidth digits and letters in the host, which `URL` folds to ASCII; and a tab in the host, which `URL` deletes. Each of those is a local-development bypass: a route the C# consumer would have demanded a certificate for opened unsigned here. `refusedByCSharpUriShape` refuses exactly those, on the raw string, before `URL` sees it. Leading and trailing C0 controls and spaces around the whole endpoint are stripped first, because both sides accept them. The spellings Hands probed earlier and C# accepts -- `127.1`, hex, decimal, `[::FFFF:7F00:1]`, userinfo, `localhost:8443` -- are untouched. The doc comment claimed equivalence with `System.Uri.IsLoopback`. It is not equivalent and now says so: two divergences remain, both unreachable from a browser CultMesh route, and both named. An IPv6 zone id throws in `URL` and is a host-local interface name no browser socket can dial; `file:` and `mailto:` have no `URL` host and are refused by `openSocket`, which takes `ws:` and `wss:` only. The test is Soul's endpoint list with the C# answer as the expectation: 82 of the 88 it probed, the six unreachable ones named in the doc comment instead. Mutations: the trailing-dot refusal dropped, the scheme-and-authority shape inverted, the backslash refusal dropped, and the non-ASCII host refusal dropped -- each killed by the table. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> |
||
|
|
c4c2330805 |
Clean the route fields before the transcript, as the C# constructors do
The C# reference signs a transcript over a *constructed* route, and the constructors clean on the way in: `CultMeshAuthorityRoute.Clean` trims each protocol id, drops the blank ones, keeps one of each ordinally and sorts ordinally; `RequireNonEmpty` trims the runtime id and endpoint; `Require` trims the provider key id and coordinates; `CultMeshRouteCertificate` trims the Odin key id; a blank generation becomes the runtime id and endpoint joined by "@". TypeScript cleaned none of it and transcribed the caller's string, so a route C# signed and accepted was refused here the moment any field carried padding or a duplicate. Soul's tamper loop is the evidence: C# accepted a duplicated protocol id, a padded generation and a padded Odin key id against the committed vector's own signature, and TypeScript refused all three. The test now reruns those, plus a padded protocol id, a blank one, a reversed list, a padded endpoint and a padded provider key id, against the C#-written vector's signature. A padded Odin key id must also find its root, so the trust map keys on the trimmed id as the C# dictionary does. `verseId` is left alone on purpose: it is not a route field, and `CanonicalRoute` passes the caller's string into `Canonical` untrimmed. The test pins that too. `protocolId ?? "cultmesh.documents.v1"` is gone. A C# route with no protocol ids is `Array.Empty<string>()` and transcribes as the empty string; reading the handshake's `protocolId` as a one-element list wrote bytes the reference never writes. `canonicalSession` matches its C# counterpart the other way: it cleans nothing on the request, because the handshake message is what the wire carried, and only the endpoint is cleaned, because the one C# passes is `Route.Endpoint`. Mutations: the dedupe removed, the blank-id filter removed, the generation trim removed, the provider key id trim removed, and the Odin key id trim removed from the lookup -- each killed by its test. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> |
||
|
|
e67e695a63 |
Read whitespace and the signature refusals as C# does
Two edge alignments that have to land together, because the test for the first asserts the messages of the second. `String.prototype.trim` is not `char.IsWhiteSpace`. JavaScript counts U+FEFF, which .NET does not; .NET counts U+0085, which JavaScript does not. Every `trim()` in the shared verifier stood in for a C# `IsNullOrWhiteSpace` or `Trim()`, so both were wrong at those two code points: a U+0085 signature was an unsigned route to C# and a bad signature to TypeScript, and a U+FEFF signature the other way round. `isCSharpWhiteSpace`, `trimCSharp` and `isNullOrWhiteSpaceCSharp` are that set, and `isUnsignedCertificate` and the signature trim now use it. The test pins all 34 code points Soul probed on both sides against the answers .NET 10 gave on 2026-09-16. The C# `Verify` also has three signature refusals, not one: a signature that is not base64, a decoded length that is not 64, and a signature that decodes but does not verify. TypeScript collapsed the first two into the third and lost the distinction the reference draws. Split, with each message pinned by its exact C# string. `verifyP256` keeps its own checks: it is the silent `VerifySession` path, which returns false for all three. Mutations: the whitespace-set site reverted to `String.prototype.trim`, U+0085 dropped from the set, and the two new messages merged back into "invalid" -- each killed by its test. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> |
||
|
|
a801e0a441 |
Share one reading of an unsigned certificate between verifier and browser
The browser's local-development session-proof short-circuit tested `!route.certificate`, while the shared verifier already treated an empty or whitespace signature as unsigned, matching C# `Verify` (`:120`). A loopback route carrying a blank-signature certificate therefore passed the verifier and was then refused for lacking a provider key, where C# returns `IsLocalDevelopment` and skips the session proof. `isUnsignedCertificate` in `cultnet-ts/authority` is now the one owner of that reading; the verifier and the browser both call it. A browser test pins the local-development loopback open without a provider proof, the signed loopback certificate still demanding it, and the remote counterpart refused in both trust modes. The mutation runner gains two predicate mutations (whitespace treated as signed; only a missing certificate treated as unsigned), both killed by the existing whitespace-signature test in cultnet-ts. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> |
||
|
|
0e4f479ac5 |
Make the mutation runner leave no mutant behind
Every test run inside the runner rebuilds `packages/cultnet-ts/dist/` from the mutant under test, so after the final restore of the source the compiled module still carried the last mutant. The runner now rebuilds the package after the restore and asserts `dist/cultmesh-authority.js` contains a sentinel the last listed mutation removes, checked against the list itself so a reordering cannot make the assertion vacuous. The original bytes are also written to `<target>.mutation-original` before the first write and removed after the verified restore. A run killed mid-mutation is repaired from that sidecar at the next run's start, instead of by whoever notices git status. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> |
||
|
|
1590145a81 |
Pin the TypeScript-signed direction of the authority parity check
`contracts/cultmesh/authority-route-vectors.json` proves C# bytes verify in TypeScript. Nothing in the repo proved the reverse; Soul's probe did it once in a scratch directory. It is now a committed check in the same shape, mirrored: - `scripts/sign-cultmesh-authority-vectors.mjs` builds the route and session transcripts with the shared module, signs them with fixed TypeScript-owned test keys over awkward inputs (a quic scheme with a query, three unsorted protocol ids, non-ASCII generation text, priority 13), self-verifies, and writes `contracts/cultmesh/authority-route-vectors.ts-signed.json`. - `CultMeshAuthorityProofTests.AuthorityRouteVectorsSignedByTypeScriptVerifyHere` reads that file with keys taken from the file, asserts the protocol ids are unsorted, that `Validate` and `VerifySessionProof` accept, and that a priority-tampered route and a replayed nonce are refused. - The TypeScript vector test runs over both files, so a transcript change on the TypeScript side fails here before C# could trust a stale file. Checked negatively: one base64 character changed in the committed signature fails the C# test. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> |
||
|
|
c2ffc32ff6 |
Make the TypeScript route verifier the C# rules exactly
verifyP256 handed WebCrypto `view.slice().buffer`. For a Node Buffer,
`slice` is `subarray`, so `.buffer` is the shared pool: a pooled Buffer or
a subarray of a larger allocation verified false while the same bytes as a
plain Uint8Array verified true. Copy exactly the viewed bytes.
Trust rules that had drifted from `CultMeshAuthorityProof.cs`:
- A certificate whose signature is empty or whitespace is an unsigned
route (C# `Verify` line 120): local-development loopback accepts it,
everything else refuses with the unsigned message.
- Loopback is `System.Uri.IsLoopback`: any 127.0.0.0/8 host, `::1`, the
IPv4-mapped `::ffff:127.0.0.1` (only `.1`), and the host names
`localhost` and `loopback`. WHATWG URL normalises `127.1`, `0x7f000001`
and `2130706433` the way System.Uri does. Probed on the C# side through
the public policy before the rule was written; the test pins that set.
- The root lookup runs before the validity window, so an expired route
under an unknown root is refused as untrusted on both sides.
- A policy listing one Odin key id twice is refused at first use, the
closest a plain object gets to the C# constructor throwing.
One test per rule, one mutation per rule in the runner's list. A fixture
whose protocol ids are unsorted and whose expected transcript is the
sorted one means removing the sort is now killed.
The runner now matches anchors in the file's own line ending. On an
autocrlf checkout the two multi-line anchors it already had ("route
transcript field order", "session transcript binds the nonce") matched
nothing, so the run threw at those entries instead of testing them.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
|
||
|
|
062a5c6678 |
Add a mutation check for the shared route verifier
One mutation per rule the cut map names, applied to the source bytes and restored from the original bytes with a digest check; a no-op control runs first through the same write path. A survivor is a rule no test pins and fails the script. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> |
||
|
|
ae194d5eb2 |
Release CultLib Unity 1.0.57 with deterministic plugin assemblies
Some checks failed
Publish packages / cultcache-py to PyPI (push) Has been cancelled
Publish packages / cultmesh-py to PyPI (push) Has been cancelled
Publish packages / cultnet-py to PyPI (push) Has been cancelled
Publish packages / @gamecult/cultcache-ts to npm (push) Has been cancelled
Bumps org.gamecult.cultlib to 1.0.57 and rebuilds its managed plugins from main, so the Unity package carries the store-routing CultCache (one home store per type, AddBackingStore(CacheBackingStore, Type[]), atomic commit) instead of the Sep 3 assemblies. Install docs point at the new tag. The build script now publishes without Source Link or the source revision, with CI path mapping and cleared Release intermediates. The previous DLLs named the commit and the worktree they were built in, so committed plugins could never match a fresh build of the commit that holds them. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
96ee5fdce3 |
Delete the CultCache MessagePack source generator
The reflective CultDocumentRegistry is now the only descriptor authority, and payloads always go through MessagePackSerializer with OptionsFor(document assembly). No consumer builds AOT, IL2CPP or trimmed, Unity always ran the reflective path, and no benchmark showed a gain, while the generator duplicated the registry's shape rules. Removed: GameCult.Caching.MessagePack.Generator and .Analyzers (solution, project and analyzer references, NuGet pack list), CultGeneratedDocumentMetadata (provider attribute, interface, definitions, loader), the generated descriptor and codec branches in CultCache and CultDocumentMessagePackSerialization, DocumentShapeSweepTests and the generator half of the unkeyed test, the Microsoft.CodeAnalysis.CSharp test reference and InternalsVisibleTo. The registry rejects, with a message naming the fix, what MessagePackSerializer cannot serialize and what the generator used to paper over: a document without [MessagePackObject], a non-public document without AllowPrivate = true, and a non-public member of an AllowPrivate document that is not [IgnoreMember]. DocumentShapeTests pins every rejection message and the accepted shapes' round trips against MessagePackSerializer bytes (keeps the 93A16E0305 pin). Rejected shapes are emitted into a dynamic assembly, which registry discovery skips. Nested member type names use reflection's Outer+Inner form, not the generator's dot form, so ReferenceHolderEntry's fixture id is re-pinned (bd8506... -> 4ec0c5...). A scan of the schema catalogs in all 2786 .cc stores under F:\Projects (AetheriaEve and CultLib-* worktrees, bin and obj excluded) found no member type name naming a nested type in either form. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
89687fceb4 |
Let the browser sample read the provider's advertisement
Eve refuses to build a command intent for a surface that has not
advertised a command boundary and receipt schema, and the intent it
builds now carries the operation under operation.operationId. The
browser sample did neither: the click was refused inside the page and
the checkpoint saw only a 30 s timeout on the receipt.
The C# provider now publishes its gamecult.eve.provider_advertisement.v1
document with the surface's worldInteraction, and the page leases that
and hands it to the lowering as `provider`. The provider owns the
declaration; the page reads it rather than restating it. The command
sink reads intent.operation.operationId.
The checkpoint now forwards the page's console and pageerror output.
"CultMesh browser operation is required." took one run to find with it
and had been a bare timeout without it.
scripts/verify-eve-getting-started.ps1 -EveRoot ..\Eve (Eve main)
-> Eve getting-started verification passed: clean .NET and TypeScript
artifacts, DOM and headless convergence, real Chromium and C#
clients, Odin discovery, authority-bound routes, receipts,
persistence, and route replacement.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
|
||
|
|
f94f5581b0 |
Let the verse catalog schema say what the reference emits
The C# reference publishes authorityRoutes with certificate: null for loopback routes, and its own verifier treats null as "no certificate" in local development. The TypeScript JSON schema only allowed the object, so the browser client rejected every catalog the getting-started provider served: "certificate: must be object", Odin could not resolve the Verse, and the real browser/C# checkpoint has failed since |
||
|
|
830a5d718f |
Make the Eve onboarding checkpoint run against the contract that exists
The portability workflow has been red since 2026-08-22. Three things,
each hidden behind the one before it:
- Eve started refusing to build a command intent for a surface that has
not advertised a command boundary and receipt schema (Eve 4338a42,
2026-08-22). The two-runtime sample never advertised one. It now passes
a provider advertisement with worldInteraction for its surface, which is
the declaration the lowering was right to demand.
- The sample and the package-closure script still imported "cultcache-ts";
the package became @gamecult/cultcache-ts on 2026-09-04 (
|
||
|
|
95493862bd |
Split the Python runtime into cultcache-py, cultnet-py, cultmesh-py
One distribution named cultcache-py carried three libraries. Rust and TypeScript layer them as cultcache < cultnet < cultmesh; Python now does the same, one package per layer, module names unchanged so every `from cultmesh_py import ...` keeps working. Two imports ran against that layering and are resolved by moving code to where the dependency direction says it lives, not by adding a shim: - cultnet_py.interop_peer imported the Verse/peer catalog, authority lease, stream, and simulation-fact contracts from cultmesh_py. They are wire contracts a bare peer has to speak without owning a node, which is why cultnet-rs and cultnet-ts keep them in the net layer. They now live in cultnet_py.cultmesh_contracts; cultmesh_py re-exports them. - cultcache_py.benchmark imported cultnet_py. It measures framed CultNet parse beside cache paths, so it and compare_csharp move to cultnet_py. verify checks all three packages and moves to cultmesh_py. The single 6,693-line test module is split by what each test touches: a test using any cultmesh_py name lands in cultmesh-py, else cultnet_py in cultnet-py, else cultcache-py. Each suite runs with only its layer and the layers below on the path, so a test that reaches up fails to import rather than quietly binding the packages back together. 11 / 53 / 78 = the same 142. Workflows install all three; the publish job is one matrix over the three packages keyed by tag prefix. The TS test harnesses put all three src directories on PYTHONPATH. cultcache-py bumps to 0.2.0 because it no longer contains cultnet_py or cultmesh_py; the new packages start at 0.1.0. Nothing was ever published, so no PyPI consumer moves. cultcache-py 11 tests, path: cultcache-py/src cultnet-py 53 tests, path: + cultnet-py/src cultmesh-py 78 tests, path: + cultmesh-py/src python -m cultmesh_py.verify -> ok, all five checks cultnet_py.interop_peer imports with cultmesh_py absent Not run here: the cultnet-ts interop ring and cultcache-ts store matrix need dotnet/kotlin peers; their PYTHONPATH change is four lines. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> |
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5cf31d1e87 | ||
|
|
8d7bee9346 | ||
|
|
0714492858 | ||
|
|
d94174d597 | ||
|
|
a31993d518 | ||
|
|
a088435f9f | ||
|
|
c01bc0a129 | ||
|
|
00f4c02dd7 | ||
|
|
568e8e356f | ||
|
|
b090ca97f9 | ||
|
|
e5348d3d57 | ||
|
|
4c33a62b29 | ||
|
|
4167cc4f24 | ||
|
|
17d10e017f | ||
|
|
e420410fe9 | ||
|
|
089859deb4 | ||
|
|
45c2f4006f | ||
|
|
0c9cb57bea | ||
|
|
244154e2f5 | ||
|
|
b985f07887 | ||
|
|
5ab4c627d7 | ||
|
|
c682619fbd | ||
|
|
c177533b4f | ||
|
|
a937a0b07e | ||
|
|
440709829d | ||
|
|
e4e09a086c | ||
|
|
180917d75b | ||
|
|
c729dff2ee | ||
|
|
23dc0939e0 | ||
|
|
9a4c9396b4 | ||
|
|
65f2b666ef | ||
|
|
ae29910b30 | ||
|
|
6cbc73fb17 | ||
|
|
fe2555da63 | ||
|
|
c64a2da81d | ||
|
|
dfe582edbc | ||
|
|
803ee12a7a | ||
|
|
c4c2330805 | ||
|
|
e67e695a63 | ||
|
|
a801e0a441 | ||
|
|
0e4f479ac5 | ||
|
|
1590145a81 | ||
|
|
c2ffc32ff6 | ||
|
|
062a5c6678 | ||
|
|
ae194d5eb2 | ||
|
|
96ee5fdce3 | ||
|
|
89687fceb4 | ||
|
|
f94f5581b0 | ||
|
|
830a5d718f | ||
|
|
95493862bd |