Variants map: C1 cut map (one schema id on the wire); two operator questions
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
4a7fa1d63f
commit
ebb1eb2528
1 changed files with 625 additions and 0 deletions
|
|
@ -706,3 +706,628 @@ This is what Odin enforces, Rust and Kotlin use, and Eve and Gjallar already han
|
|||
- Path 1b/2b's dependence on (g).
|
||||
- Path 5 being broken.
|
||||
- The Aetheria `.cc` catalogs were decoded from the files, with no git.
|
||||
|
||||
## C1 cut: one schema id on the wire
|
||||
|
||||
Imagination, 2026-09-30. This maps the operator's C1 ruling. On the wire, a document's schema id is its declared
|
||||
version string, `<name>.v<N>`. Every runtime stamps it and every runtime resolves it exactly. A wire binding whose
|
||||
version lacks `.v<digits>` is refused. The AetheriaEve readers are archaeology, and C2 (store identity) is deferred.
|
||||
|
||||
**Anchors.** Line numbers are against CultLib `hands/variants-c2a` at `976b57f1`. That commit is F2 in progress, the
|
||||
Rust catalog lay-back. I read it from a `git archive` snapshot and did not touch the worktree. Where main
|
||||
(`4a7fa1d6`, code as of `daedfdd6`) or `hands/cultmesh-put-serve-bound` (`fa34b685`) differs in a file C1 edits, it is
|
||||
said at the spot. Each anchor also names its symbol, so it can be found again after c2a merges main (see Sequencing).
|
||||
|
||||
### 0. Probe facts
|
||||
|
||||
These are new and were run, not inferred. The sources are in `scratchpad/c1-probe/`. They ran on Yggdrasil in
|
||||
`ack1d-interop:2` from the scratch ref `refs/scratch/c1probe`, a detached commit on `976b57f1` that is deleted after
|
||||
this pass. Output is in `scratchpad/c1-probe3.log`. The run was commit `43677b97ac`.
|
||||
|
||||
- **P-VER: a C# version change does not orphan stored records.** The probe used two builds of one type,
|
||||
`probe.faction`, declared first with version `"1"` and then with `"probe.faction.v1"`, plus a sibling
|
||||
`probe.ship "1"`. The same members.
|
||||
- The id changes: `sha256:64eb…` becomes `sha256:0603…`.
|
||||
- The new build reads the old store through the catalog's schema name (`Claimant`, `CultCache.cs:697`) and reports
|
||||
`CompatibleDrift` with warnings `compatible_schema_id_drift` and `content_hash_drift`. The record decodes.
|
||||
- **Opening and flushing without edits leaves the file byte-identical** (1529 bytes, old id).
|
||||
- **Upserting restamps** that record and its catalog entry to the new id. The untouched sibling keeps its own.
|
||||
- The old build then reads the rewritten store the same way.
|
||||
- So a version-string change is a restamp on the next write, not an orphan. This holds only while one local type owns
|
||||
the schema name. Two local versions of one name make the fallback ambiguous and it throws (`CultCache.cs:697-705`).
|
||||
- C1 does not need this. It is recorded for C2.
|
||||
- **P-WIRE: what C1 changes on the C# wire.**
|
||||
- Binding both types by their version string: with `"1"` twice, c2a's `Register` refuses the second binding
|
||||
(`CultSchemaConflictException … schema id "1"`). With `probe.faction.v1` and `"1"`, it accepts both.
|
||||
- The default wire id is still sha256 in both builds.
|
||||
- With an unbound registry, `DeserializeRawDocument` returned `Faction` for `"1"`, `probe.faction` (the name) and
|
||||
`probe.faction.v7` (a version nobody declared). Only `some.other.v1` was refused.
|
||||
- So today a C# receiver resolves a version it has never seen by stripping `.vN` and matching the name. That is the
|
||||
path C1.0 deletes, and the negative check in §8 pins its death.
|
||||
|
||||
Earlier facts this map rests on, from the §1-§4 probes above: C# hashes the version into the id (V5). The C# wire
|
||||
id is sha256 unless the binding overrides it. C# CultNet accepts any id through sniffing. TS refuses a version id.
|
||||
Kotlin stamps its version.
|
||||
|
||||
### 1. What the consumer sweep settles
|
||||
|
||||
This comes from three Eyes-class sweeps over F:\Projects. I read the rows marked (R); the rest were confirmed by the
|
||||
sweeping agent reading the files.
|
||||
|
||||
- **Aetheria's `"1"` versions never reach the wire.** Aetheria has no CultNet or CultMesh code. `Aetheria.Shared.csproj`
|
||||
references only the caching assemblies. So C1's refusal never fires for its 24 declarations, and **nothing in
|
||||
Aetheria changes, on disk or in source.**
|
||||
- The other C# `"1"` declarations are in StreamPixels `apps/overlay-unity` (24). They are client-only and never bound:
|
||||
frame ids map to types by hand (`OverlayFrameDocuments.cs:98-116`).
|
||||
- Every C# type that is bound in any repo already declares `.vN`: AetheriaEve 61, AquaSynth 28, Mimir 37, Brokkr,
|
||||
Eve, Gjallar, Ymir, Delvehold. **No repo outside CultLib passes `ForDocument(schemaId:)`.**
|
||||
- **Live C# hosts with default (sha256) bindings:**
|
||||
- AquaSynth, `AquaSynthCultNetDaemon.cs:431-449`, 19 bindings. Its peers are C# (`AquaDaemonClient.cs`).
|
||||
- Mimir: `EveDashboard/Program.cs:877-886`, read by Eve Android, which ignores schema ids
|
||||
(`MainActivity.kt:618-625`); and `CultMeshMedia/Program.cs:277`, C#.
|
||||
- Delvehold: no server.
|
||||
- All of these are C#-to-C# or id-blind. They change id together when they rebuild on C1, so no shim is needed.
|
||||
- **The C# publishers to Odin bypass bindings.** AetheriaEve `Program.cs:4574-4831` and Gjallar `Program.cs:3045,3069`
|
||||
build their ids by hand and serialize payloads directly. C1 does not change them. Odin's rule is at
|
||||
`Odin/crates/odin-daemon/src/main.rs:1311-1326` (R).
|
||||
- **Every Rust wire binding in every repo already binds `.vN`** through `cultmesh_documents!` or
|
||||
`for_entry_with_schema_id` with `.vN` constants: Odin, Muninn, Ghostlight, Epiphany, Ratatoskr, Huginn. No consumer
|
||||
uses bare `for_entry::<T>()`.
|
||||
- **The AetheriaEve readers are archaeology:**
|
||||
- EveElectron's only code consumer is AetheriaEve (`Aetheria.Rts.Web/package.json:30`).
|
||||
- Hermodr was parked on 2026-09-30: `gamecult-ops/inventory.md:631-634`, `runbooks/odin-yggdrasil.md:35-48`, and
|
||||
`scripts/idunn/idunn-deployment-targets.ps1:36-46` (status "archived").
|
||||
- cultmesh-browser has two consumers. One is the AetheriaEve witness. The other is CultLib's own sample
|
||||
`samples/eve-browser-network/browser/main.ts:8`.
|
||||
- **So C1 edits neither Hermodr nor EveElectron.** Both are archived, so C1 deletes the CultLib fallbacks that exist
|
||||
only for them and does not migrate them.
|
||||
- **Non-C# consumers that change id under C1:**
|
||||
- **Stonks is the only one that breaks.** `F:\Projects\Stonks\src\stonks-daemon.cjs:152-195` declares
|
||||
`schemaVersion: "v1"` on six definitions, one of them `provider_advertisement` published to Odin at `:303`. Under
|
||||
C1 its TS registration is refused. Stonks loads `../CultLib` from disk, so it breaks on the first restart after C1
|
||||
reaches that checkout. The fix is in Stonks: set each `schemaVersion` to its current `schemaId`. That is six lines,
|
||||
and they land before C1 is merged to main.
|
||||
- Bifrost `tools/agent-transport.mjs:62-66`: the id is hyphenated (`…update-request.v0`) and the version has
|
||||
underscores (`…update_request.v0`). The wire id changes to the version. The snapshot and apply sides are the same
|
||||
tool on one pinned CultLib (`f67f5122`), so they change together. Its store keeps the hyphenated id.
|
||||
- Brokkr Python `surfaces/blender/brokkr_bridge/blender_target.py:630-637` declares eight type-only definitions whose
|
||||
type already ends in `.v0`. The Python default version would make the wire id `….v0.v1`. The fix is in Brokkr:
|
||||
declare `schema_version=<the type>`. No live reader was found, so this is hygiene, not a break.
|
||||
- repixelizer: `verse_state.py:139-230` declares version ≠ id. Its wire id moves to the `.v0` version, which Odin
|
||||
accepts.
|
||||
- Everything else already has id == version: VoidBot, Vili, weksa, Heimdall, StreamPixels, Bifrost's other tools,
|
||||
Hermodr.
|
||||
- VoidBot's persona stores use a vendored cultcache-ts and are untouched.
|
||||
- **Raw health puts are out of C1's reach.** Vili, Stonks, weksa, Bifrost and repixelizer send hand-built
|
||||
`idunn.daemon_health` with no version, and Bifrost sends `idunn.signed_daemon_health`. These never register a
|
||||
binding. No receiving binding was found in Idunn or Odin main. That is Soul's consumer check, not a C1 edit.
|
||||
|
||||
**Q3, settled:** no declaration in any repo has to change to satisfy the refusal on a binding that actually exists.
|
||||
Aetheria and the StreamPixels overlay are the `"1"` repos, and neither binds. Stonks's `"v1"` is TS; it is fixed in
|
||||
Stonks and touches no store. Stonks's store keeps the ids it holds today, because TS stores under `schemaId`. So
|
||||
**no declaration change touches disk.** Whether a future change of Aetheria's version string would orphan its records
|
||||
is the P-VER probe in §0. It is recorded for C2 and not needed by C1.
|
||||
|
||||
**Q4, settled:** C1 changes no store record in any runtime. The store id and the wire id become two derivations:
|
||||
- C#: sha256 on disk, version on the wire.
|
||||
- TS: `schemaId ?? schemaName ?? type` on disk, version on the wire.
|
||||
- Python: `schema_id or type` on disk, version on the wire.
|
||||
- Rust: the store id and apply-path stamping are unchanged (see C1.4).
|
||||
- Kotlin: version for both, as today.
|
||||
|
||||
Four places where one variable serves both today must be split. Each has its cut below:
|
||||
- TS `replication.ts:363-368`, `schemaIdForEnvelope`, which serves the stored id.
|
||||
- Python `cultmesh_py/node.py:767` and `interop_peer.py:1085`.
|
||||
- The C# durable shard log, `CultNetShardMutationLogStorage.cs:75-88`. It persists wire-form entries, and so it is the
|
||||
one C# store that holds wire ids.
|
||||
- Rust `pull_rudp_catalog_snapshot`, `cultmesh-rs/src/lib.rs:742`.
|
||||
|
||||
### 2. Authority map
|
||||
|
||||
- **Owner.** The declaration's version string is the only wire identity:
|
||||
- C# `CultDocumentAttribute.SchemaVersion` → `CultDocumentDescriptor.SchemaVersion`;
|
||||
- TS `definition.schemaVersion`, defaulting to `${schemaName}.v1`, through `schemaIdentityOf(...).schemaVersion`;
|
||||
- Python `schema_version`, defaulting to `f"{schema_name}.v1"`;
|
||||
- Rust: the version given to `cultmesh_documents!` or `for_entry_with_schema_id`;
|
||||
- Kotlin `codec.schemaVersion`.
|
||||
- **One derivation per runtime:**
|
||||
- C#: `CultNetDocumentRegistry.WireSchemaId(descriptor)` returns `descriptor.SchemaVersion`. It is the single place
|
||||
`.vN` is checked, and it is also called eagerly from `Register`.
|
||||
- TS: `wireSchemaIdOf(definition)` in cultnet-ts.
|
||||
- Python: `wire_schema_id(definition)` in cultnet_py.
|
||||
- Rust: `CultNetDocumentBinding::schema_id`, which is already the version.
|
||||
- Kotlin: `codec.schemaVersion`.
|
||||
- **Inputs:** the declaration only. Never a payload, a schema name, an inferred name, a record's position in a
|
||||
response, or a stored record's id.
|
||||
- **Outputs:** every schema id that crosses a process boundary:
|
||||
- raw records, puts and deletes;
|
||||
- snapshot and selection responses, headers, edges and cursors;
|
||||
- database change messages and shard-log entries;
|
||||
- snapshot, subscribe and selection filters, and the shard catalog;
|
||||
- the document-payload schema catalog;
|
||||
- CultMesh projection-source and state-binding records.
|
||||
- **Resolution.** A wire id resolves to the binding that owns it (exact). Otherwise it resolves to the one binding that
|
||||
*declares* it compatible (`CompatibleSchemaIds`, owner beats lister), which is the rule stores already use. **OQ-C1.2
|
||||
decides whether declared compatibility applies on the wire.** Anything else is refused.
|
||||
- **Derived state after C1:**
|
||||
- C# `descriptor.SchemaId` (sha256) is **store-only**. It keys CultCache, `_predictedDocuments`,
|
||||
`_lastWriteSequence`, and in-process dictionaries.
|
||||
- TS and Python `schemaId` are **store-only**.
|
||||
- `CultNetDocumentBinding.SchemaId` is **derived** (the version). It is no longer an owner.
|
||||
- `schemaName` is **metadata only**, never matched.
|
||||
- Payload slot 0 or `schemaVersion` is **data only**, never read for routing.
|
||||
- `schemaContentHash` is a **C#-to-C# drift witness**, carried and never matched.
|
||||
- **Forbidden writers, all deleted:**
|
||||
- C#: `CultNetSchemaAliasMatching`, `TryResolveDescriptorBySchemaAlias`, `TryResolveDescriptorByPayloadSchema`,
|
||||
`PayloadMatchesSchema`, `TryReadSchemaVersion`, both `InferSchemaName`, `ResolvePayloadSchemaCandidates`,
|
||||
`ExpandSchemaBindingAliases`, the `ForDocument(schemaId:)` override (OQ-C1.1), the CultMesh `aliased` and
|
||||
`byPayload` branches, and the descriptor-name and version keys in the C# and TS mesh catalogs;
|
||||
- TS: `firstRecordAtKey`, cultmesh-browser's name and version arms, `shard-catalog.ts` `inferSchemaName`, and
|
||||
`inferCultMeshSchemaName` / `bySchemaNameVersion`;
|
||||
- Python: the name and version keys in `schema_document_map`, `_infer_schema_version_from_payload`, and every
|
||||
`_infer_schema_name`;
|
||||
- Rust: `strip_schema_version_suffix` and `selection::schema_alias`;
|
||||
- Kotlin: `codecs[schemaId]`, the documentType fallback.
|
||||
- **Shared paths.** Put, delete, snapshot (v0, v1, raw), selection, subscription, database change, shard log (live and
|
||||
replayed from disk), shard catalog, schema catalog, and CultMesh peer reads all take the id from the one derivation.
|
||||
A served stored record is stamped by the derivation, never by its stored id.
|
||||
|
||||
### 3. Deletes first (C1.0, subtraction)
|
||||
|
||||
C1.0 deletes every inference path and makes each comparison exact against the existing wire id: the binding id, or
|
||||
sha256 for an unbound type. C#-to-C# peers keep working. Only readers that relied on a fallback stop resolving, and
|
||||
those are the AetheriaEve readers, which are archaeology. C1.0 adds no behaviour; C1.1 flips the id.
|
||||
|
||||
**C#, `src/GameCult.Networking`:**
|
||||
- `CultNetDatabase.cs:47-104`: delete the whole `CultNetSchemaAliasMatching` class (58 lines, public).
|
||||
- Replace every caller with an exact `string.Equals` against the registry's wire id for the descriptor.
|
||||
- Callers: `:143, :150` (`ClientAuthorityScope.Matches`); `:252, :260` (`Shard.Matches`); `:1757` (shard-catalog
|
||||
filter).
|
||||
- `Shard.Matches(string)` and `ClientAuthorityScope.Matches(string)` compare a configured id with the wire id.
|
||||
- `EnsureClientAuthority(string)` at `:1640-1652` has no callers; delete it.
|
||||
- `CultNetDocumentRegistry.cs`:
|
||||
- Delete `:779-799` `TryResolveDescriptorBySchemaAlias`, `:813-830` `TryResolveDescriptorByPayloadSchema` (public),
|
||||
`:832-842` `ResolvePayloadSchemaCandidates`, `:852-854` `PayloadMatchesSchema` (public), `:865-898`
|
||||
`TryReadSchemaVersion`, `:900-910` `InferSchemaName`, and `:538-578` `ExpandSchemaBindingAliases` with its comment.
|
||||
About 175 lines in all, doc comments included.
|
||||
- `ResolveDescriptorForRawDocument` (`:755-765`) and `ResolveDescriptorForSchemaId` (`:767-777`) collapse into one
|
||||
exact lookup. It uses `_bindingsBySchemaId`, or, for a registry with no bindings, the one descriptor whose wire id
|
||||
equals the id. A miss throws `InvalidOperationException` naming the id.
|
||||
- Call sites stay on the one method: `CultNetDatabase.cs:717, 947, 998, 1234, 1455`,
|
||||
`CultNetDatabaseSubscriptionClient.cs:254`, and `CultNetDocumentRegistry.cs:691, 713`.
|
||||
- `CultNetDocumentRegistry.cs:407, 507`: drop the `ExpandSchemaBindingAliases` calls. `CultNetDatabaseServer.cs:566-567`
|
||||
and `CultNetDatabaseSubscriptionServer.cs:559-560` pass the selection unchanged.
|
||||
- `CultNetSelection.cs:393, 453` and `CultNetSelectionEvaluator.cs:88, 422`: exact comparison against the wire id.
|
||||
- The evaluator reads only descriptors, so C1.0 gives it the registry's `WireSchemaId` as a `Func<CultDocumentDescriptor,
|
||||
string>`.
|
||||
- After C1.1 the id is `descriptor.SchemaVersion`, and the parameter collapses to that (see C1.1).
|
||||
- `CultNetSelection.cs:178` doc: "schema ids", not "or aliases".
|
||||
|
||||
**C#, `src/GameCult.Mesh`:**
|
||||
- `CultMesh.cs:2724-2774` `ReadDocumentFromSnapshotResponse`: delete the `aliased` and `byPayload` branches
|
||||
(`:2744-2745, 2754-2757`). Pick `exact` or throw. Rewrite the comment block at `:2724-2733`.
|
||||
- `CultMesh.cs:3441-3464` `IsSameCultDocumentSchema`: the id is local (a `CultNetDatabaseChange` sha256). Replace with
|
||||
`schemaId == descriptor.SchemaId`, or `storedDescriptor.SchemaId == descriptor.SchemaId`. Delete the comment
|
||||
claiming "every … decision goes through" the matcher.
|
||||
- `CultMeshSnapshots.cs:982-985` `DecodeSnapshotDocuments`: keep only `canDeserializeWithBinding`, or
|
||||
`record.SchemaId == wire id of TDocument`.
|
||||
- `CultMeshDocumentRegistries.cs:64-71`: the `GroupBy(SchemaId)…Reverse()` loop is left over from aliasing. With c2a's
|
||||
`Register` refusal, collapse it to one `Register` per distinct type.
|
||||
- `CultMeshPrimitives.cs` ~`1806-1860` and ~`1996-2050`, the C# `CultMeshDocumentCatalog` and
|
||||
`CultMeshCollectionCatalog`: drop the schema-version and schema-name keys. A lookup by schema answers by the handle's
|
||||
type only. **A lookup that more than one handle answers is refused, naming their document ids.** This is the "typed
|
||||
catalog lookup returns the last handle added" defect, and TS has the same fix below.
|
||||
|
||||
**TS:**
|
||||
- `cultmesh-ts/src/index.ts:6297-6328` `requestCultNetRawSnapshotDocument`: delete the HELD comment, the
|
||||
`firstRecordAtKey` option, and `?? atKey[0]`. Delete the caller's `firstRecordAtKey:` at `:2691`.
|
||||
- `cultmesh-ts/src/index.ts:6175-6191` `normalizeCultMeshDocumentSchema`: drop the `inferCultMeshSchemaName` arm and the
|
||||
version-from-id arm, and delete `inferCultMeshSchemaName` at `:6228-6238`.
|
||||
- `cultmesh-ts` catalogs: `CultMeshDocumentCatalog` `:1462-1519` and `CultMeshCollectionCatalog` `:1688-1731`.
|
||||
- Delete `#bySchemaNameVersion`, `cultMeshSchemaNameVersionKey` (`:6220-6226`), and its arm in
|
||||
`cultMeshRefuseSecondCatalogType` (`:6197-6218`).
|
||||
- `tryDocument` and `tryCollection` answer by type or by wire id. When `#byType` holds several handles for one type
|
||||
(`add` at `:1482` overwrites, so today the last one wins), the lookup is refused with the handles' document ids.
|
||||
That needs `#byType: Map<string, handle[]>`.
|
||||
- `cultmesh-browser/src/index.ts:770-774` `recordMatchesSchema`: exact `record.schemaId === requestedSchema`. Callers
|
||||
are `:704, :721`. After C1.2, CultLib's `samples/eve-browser-network` asks by version.
|
||||
- `cultnet-ts/src/shard-catalog.ts:103-130`: `schemaIdsMatch` becomes `===`. Delete `inferSchemaName`.
|
||||
|
||||
**Python:**
|
||||
- `cultnet_py/replication.py:21-30` `schema_document_map`: key by wire id and compatible ids only; drop `schema_name` and
|
||||
`schema_version` keys that differ from the wire id.
|
||||
- `replication.py:158-194`: `resolve_document_for_raw_record` becomes `dict` lookup or `KeyError`. Delete
|
||||
`_infer_schema_version_from_payload` and `_infer_schema_name`.
|
||||
- `cultnet_py/shard_catalog.py:143-154`: exact.
|
||||
- `cultmesh_py/node.py:1311-1343`: `_schema_matches_request` and `_document_matches_schema_id` become exact; delete
|
||||
`_infer_schema_name`.
|
||||
- `cultmesh_py/server.py:687-721`: the map copy and request filter become exact; delete `_infer_schema_name`.
|
||||
- Keep `_documents_alias` / `_resolve_document_alias` (`node.py:1188-1243`). They compare local definitions, not wire
|
||||
ids (follow-up F-C1-PYALIAS).
|
||||
|
||||
**Rust:**
|
||||
- `cultmesh-rs/src/lib.rs:750-773`: delete `strip_schema_version_suffix`. `registered_document_type_for_schema` becomes
|
||||
`binding_by_schema_id(..).map(|b| b.document_type.clone())` and nothing else. Delete the `documents.binding(schema_id)`
|
||||
type-name arm.
|
||||
- `cultnet-rs/src/selection.rs:709-851`: delete `schema_alias`, tests included. Its uses at `:1432, 1487, 1651, 2112`
|
||||
become exact comparisons against the row's binding id.
|
||||
- `cultnet-rs/src/snapshot_query.rs:160-181` `RawSnapshotRow`: stop defaulting `schema_name` to the id; it reaches
|
||||
`schema_alias` from `serve_read_only_raw_snapshot` (`:129`).
|
||||
|
||||
**Kotlin:** `cultmesh-kotlin/.../CultMesh.kt:154-155` `codecForSchema`: drop `?: codecs[schemaId]`. That also fixes
|
||||
`deleteBySchema` at `:170-171`.
|
||||
|
||||
**Not edited, archaeology:** Hermodr `hermodr-daemon.cjs:1335-1363`, EveElectron, and AetheriaEve's own readers.
|
||||
|
||||
**Tests deleted or inverted in C1.0.** Each rule that dies keeps a test that proves it is dead.
|
||||
- C#:
|
||||
- `NetworkingTests.cs:3177` (a selection by version matches through the alias): invert to not-found before C1.1.
|
||||
- `NetworkingTests.cs:4022` (foreign id by sniffing): invert to refused.
|
||||
- `NetworkingTests.cs:4109, 4140, 4276, 4317, 4366` (shard and scope configured by version): configure by the wire id.
|
||||
- `CultNetSelectionFixBatchTests.cs:121, 158` (name alias): invert.
|
||||
- `CultMeshStreamingTests.cs:2638-2708, 3062, 3105` (payload fallback): invert to refused or skipped.
|
||||
- TS:
|
||||
- `cultmesh.test.ts:3061-3074, 3244-3249` (HELD): invert to not-found.
|
||||
- `cultmesh.test.ts:1170-1196, 1241-1286`: catalog by version against a sha256 handle.
|
||||
- `cultnet.test.ts:1064-1068` (shard by name).
|
||||
- `cultmesh-browser.test.ts:139, 452, 530, 536, 692-697`.
|
||||
- Python:
|
||||
- `test_cultnet.py:1685-1726, 2059-2063`.
|
||||
- `test_cultmesh.py:291-339, 343-379` (drop the name case).
|
||||
- `test_cultmesh.py:4150-4302` (sniffing and deletes by version against a sha256 id).
|
||||
- Rust: `tests/selection.rs:2134, 2151, 2179, 2193, 2216-2219`.
|
||||
|
||||
### 4. C1.1: the C# wire id is the version
|
||||
|
||||
This is a behaviour cut on `src/GameCult.Networking` and `src/GameCult.Mesh`, plus the shared selection fixture with
|
||||
Rust.
|
||||
|
||||
**The store id and the wire id stay separate.** `descriptor.SchemaId` (sha256) keeps keying CultCache and every
|
||||
in-process dictionary. The wire id is `descriptor.SchemaVersion`. Once the override is deleted (OQ-C1.1), the wire id
|
||||
derives from the descriptor alone, with no registry. That is what lets the selection evaluator, the mesh and the
|
||||
database compute it without a binding.
|
||||
|
||||
**How a C# reader maps `<name>.vN` to its descriptor:**
|
||||
- `_bindingsBySchemaId` is keyed by `binding.SchemaId`, which now equals `SchemaVersion`.
|
||||
- An unbound registry looks the version up among its descriptors.
|
||||
- C# already refuses two types for one `(name, version)` in a registry (`CultCache.cs` `RegisterDescriptor`, "One type
|
||||
owns a schema"), and c2a's `Register` refuses two types for one binding id. So an exact version lookup has at most one
|
||||
answer.
|
||||
- Two types sharing version `"1"` cannot both be bound, because `"1"` is refused first.
|
||||
|
||||
File changes:
|
||||
- `CultNetDocumentRegistry.cs:76-135` `ForDocument<T>` and `ForDocument(Type…)`: delete the `schemaId` parameter
|
||||
(OQ-C1.1). The binding id is `descriptor.SchemaVersion`.
|
||||
- `CultNetDocumentBinding`: the internal constructor at `:44` takes the id from the descriptor.
|
||||
- `CultNetDocumentRegistry.cs:173` `Register`: before c2a's conflict check, refuse a binding whose id fails
|
||||
`^.+\.v[0-9]+$`. Throw `CultSchemaConflictException` or `ArgumentException`, naming the type, the declared version,
|
||||
and the rule. The refusal happens at registration, never at first send.
|
||||
- `CultNetDocumentRegistry.cs:675-676` `WireSchemaId`: becomes `descriptor.SchemaVersion`, checked by the same rule, so
|
||||
an unbound `"1"` type is refused when it is first emitted. Every emitter below calls it.
|
||||
- `CultNetDocumentRegistry.cs:616-645` `ToRawRecord`: the synthesized binding at `:621-626` uses `WireSchemaId`, not
|
||||
`descriptor.SchemaId`.
|
||||
- `CultNetDocumentRegistry.cs:217-228` `CreateDocumentDeleteMessage(string schemaId…)`: add
|
||||
`CreateDocumentDeleteMessage<T>(messageId, CultRecordHandle<T>)`, which derives the wire id. Keep the string overload,
|
||||
documented as "the wire id".
|
||||
- `CultNetDatabase.cs` emitters: publish the wire id wherever a `CultNetDatabaseChange.SchemaId` leaves the process.
|
||||
- `CultNetDatabaseServer.cs:555/581` (removal change) sends `WireSchemaId(descriptor)`, not `change.SchemaId`. The
|
||||
change record keeps the sha256 locally.
|
||||
- `CultNetDatabase.cs:1554` `ToLogEntryMessage` `Delete.SchemaId` becomes the wire id.
|
||||
- `CultNetDatabase.cs:1512` `ApplyCommittedDelete` resolves the incoming delete id through the one exact lookup, not
|
||||
`GetRequiredBySchemaId`.
|
||||
- Error text at `:1664, :1725` names the wire id.
|
||||
- **Durable shard log** (`CultNetShardMutationLogStorage.cs:60-88`, `CultNetDatabase.cs:586-591, 1385-1409`):
|
||||
- It persists store ids. `Append` rewrites each entry's `schemaId` (put document and delete) to
|
||||
`descriptor.SchemaId`.
|
||||
- `GetMutationLogMessages` restamps with `WireSchemaId` on the way out.
|
||||
- `KeyOf` (`:1399-1409`, the restart rebuild of `_lastWriteSequence`) reads store ids and so agrees with the live
|
||||
writes at `:1294, 1486, 1531`. That also fixes the rebuild disagreement that exists today when a binding overrides
|
||||
the id.
|
||||
- **Existing log files hold sha256 for default bindings, which is the store id, so nothing on disk changes.** Only
|
||||
logs written through an override hold a foreign id, and only the interop peer used one. No repo outside CultLib uses
|
||||
`CultNetFileShardMutationLogStore` (grep over F:\Projects).
|
||||
- Request filters that send the sha256 today switch to `WireSchemaId`:
|
||||
- `CultNetDatabaseSubscriptionClient.cs:70-71`;
|
||||
- `CultMeshClient.cs:602, 843, 944`;
|
||||
- `CultMesh.cs:2696` (`RequestPeerSnapshotAsync`) and `:2038`;
|
||||
- `CultMeshSnapshots.cs:874` `ResolveDefaultSelection`.
|
||||
- **Shard catalog and config:** `CultNetShardDescriptor.SchemaIds` and `CultNetClientAuthorityScope.SchemaIds` are wire
|
||||
ids. They are documented so, and matched exactly (C1.0).
|
||||
- CultMesh projection sources and binding records (`CultMesh.cs:1521, 1602, 1972, 2032, 2273, 2300, 2331, 2357, 2384,
|
||||
2413`; `CultMeshSnapshots.cs:530, 555`) cross the wire as `CultMeshStateBindingRecord [Key(3)]`
|
||||
(`CultMeshPrimitives.cs:3334`). Stamp them with the version.
|
||||
- The collection and feed ids built from them (`CultMesh.cs:2298, 2329, 2382, 2411, 3266`) move to the version too.
|
||||
- The cultmesh-ts type of the same shape then agrees.
|
||||
- **Selection order and cursor** (`CultNetSelectionEvaluator.cs:213-214, 318, 345, 586, 593`): tie-break and cursor on
|
||||
the wire id, as Rust does on its binding id.
|
||||
- This changes a wire-visible order: the parity vector R-AJ flips (`leaf_a.v1 < leaf_b.v1`, where the sha256 order
|
||||
was `b1f2… < ff35…`).
|
||||
- Cursors minted before the upgrade stop resuming. That is acceptable, because cursors are per session.
|
||||
- **Shared fixture, landed in this cut with the Rust side:**
|
||||
- `contracts/cultnet/interop/selection-vectors.fixture.json`: `schemaId` becomes the version. Delete `nameAlias` and
|
||||
`hashAlias`.
|
||||
- Regenerate `selection-vectors.cs-written.json` and `selection-vectors.rs-written.json`.
|
||||
- `SelectionParityVectorTests.cs:112-117, 156-159` assert the fixture ids equal the declared versions. Delete the
|
||||
alias cases at `:235, :238, :329`; keep the refusal of an unknown id at `:325`.
|
||||
- Rust `cultnet-rs/tests/selection.rs:1561-1690, 1849` (vector writer): drop the alias rows; order by the version.
|
||||
- **Left alone:** `global:{descriptor.SchemaId}` record keys (`CultCache.cs:2473`, `CultNetDatabase.cs:1120`). They are
|
||||
a record key, not a schema id, and changing them changes store keys. See F-C1-GLOBALKEY.
|
||||
- **Schema catalog:** `CultNetSchemaRegistry` holds JSON-Schema contracts keyed by `$id` URL, and no production C# host
|
||||
answers a catalog request (`Server.cs:565` is an allowlist only). The only document-payload registrations are the
|
||||
interop peer's, which C1.5 changes. Nothing to cut here.
|
||||
|
||||
### 5. C1.2: TypeScript
|
||||
|
||||
Runtime packages `cultcache-ts`, `cultnet-ts`, `cultmesh-ts` and `cultmesh-browser`.
|
||||
|
||||
- `cultcache-ts/src/document.ts:82-91` `schemaIdentityOf` stays the store identity, unchanged.
|
||||
- **cultnet-ts owns the wire:** `export function wireSchemaIdOf(definition)` returns
|
||||
`schemaIdentityOf(definition).schemaVersion`. The store keeps a definition with no declared version valid; only a
|
||||
wire registration refuses it.
|
||||
- `cultnet-ts/src/replication.ts:52-80` `register`:
|
||||
- refuse a version failing `/\.v\d+$/` with `SchemaConflictError` or `TypeError`, naming the type;
|
||||
- key `#owners` by `wireSchemaIdOf`;
|
||||
- `#listers` keep the declared compatible ids (OQ-C1.2).
|
||||
- The conflict check at `:59-71` also compares wire ids.
|
||||
- Outgoing ids: `schemaIdForBinding` (`:349-351`) splits in two.
|
||||
- Outgoing messages (`:123, :178, :287` put echo) use `wireSchemaIdOf`.
|
||||
- `applyRawDocumentPutMessage` (`:280-290`) keeps storing under the store id, `schemaIdentityOf(...).schemaId`.
|
||||
That is the leak point: today the same function feeds both.
|
||||
- **Serving** (`:196-260`, `:363-368` `schemaIdForEnvelope`, `:336-345` `#createRawDocumentRecord`):
|
||||
- A served record carries `wireSchemaIdOf(binding)`, never `envelope.schemaId`.
|
||||
- A record stored under a compatible id holds that version's bytes. It is decoded and re-encoded through the owning
|
||||
definition before it is served under the owner's id. This matches C#, which serializes from the decoded document
|
||||
(`CultNetDocumentRegistry.cs:640`).
|
||||
- `answersTo` (`:354-361`) matches the requested id against the binding's wire id (and its compatible ids if
|
||||
OQ-C1.2 is yes).
|
||||
- Tests `cultnet.test.ts:1627-1664` (the wire id equals the store id), `:1703-1708` (served under the stored id) and
|
||||
`:1713-1744` (a version id does not resolve) are inverted.
|
||||
- `cultnet-ts/src/shard-catalog.ts`: exact after C1.0; shard ids are wire ids.
|
||||
- **cultmesh-ts:**
|
||||
- `cultMeshSchemaFromDefinition` (`index.ts:6168-6173`): `schemaId` for peer reads is `wireSchemaIdOf`.
|
||||
- `syncDocumentFromPeerSnapshot` (`:3995-4019`) and `cultMeshDocumentFromPeerSnapshot` (`:2668-2710`) ask by wire id.
|
||||
- `resolveCultMeshStoreDocumentRecord` / `cultMeshAcceptedSchemaIds` (`:6243-6256`) are store-local and stay on
|
||||
`schemaIdentityOf`.
|
||||
- The receipt filter at `:5976` compares `receiptBinding.definition.schemaId`, which is `undefined` when not
|
||||
declared. Compare with `wireSchemaIdOf`.
|
||||
- Put-serve overlap (not on c2a): `hands/cultmesh-put-serve-bound` adds `#rawPutEnvelope` and
|
||||
`createRawSnapshotResponseForPut` to this file, on main's shape resolution (`preserveIncomingSchemaId`). After the
|
||||
merge, the put-sizing response must be built by the same serving path, so it carries the wire id.
|
||||
- `cultmesh-browser`: exact after C1.0. `samples/eve-browser-network` declares or asks by version.
|
||||
- **Consumer action, not in CultLib:** Stonks `stonks-daemon.cjs:152-195` sets `schemaVersion` to the full id. It
|
||||
lands in Stonks before C1 merges to main.
|
||||
|
||||
### 6. C1.3: Python
|
||||
|
||||
- `cultcache_py/documents.py:105-143` `catalog_entry` stays the store identity. Add `wire_schema_id(definition)` in
|
||||
cultnet_py, returning `schema_version`.
|
||||
- Registration in `cultnet_py` and `cultmesh_py` refuses a version failing `\.v\d+$`.
|
||||
- Outgoing ids use `wire_schema_id`:
|
||||
- `cultmesh_py/node.py:577` (put), `:641` (delete), `:824-827` (sync);
|
||||
- `client.py:430` (subscribe);
|
||||
- `node.py:767, 776` (snapshot, never the stored id; a compatible-id record is re-encoded as in TS);
|
||||
- `server.py:617-633` (catalog).
|
||||
- `server.py:416, 444` echo a record that has already been resolved exactly, so it carries a wire id.
|
||||
- Applied records keep being stored under the local store id: `replication.py:42, 84`, `node.py:664-670`.
|
||||
- Inverted tests: `test_cultmesh.py:4116-4148` (a version request is served under the sha256 id) and `:4215-4258`
|
||||
(echo of a foreign id).
|
||||
- `interop_peer.py:1085` serves the stored id; that moves in C1.5.
|
||||
|
||||
### 7. C1.4: Rust mesh and Kotlin
|
||||
|
||||
- **Rust.** The wire id is already the version on every consumer binding.
|
||||
- `for_entry::<T>()` (`cultnet-rs/src/replication.rs:44-47`) has no consumer. It binds `T::TYPE`, which fails the
|
||||
refusal unless it ends in `.vN`. Refuse it at `register` like any other id without a version. Keep the constructor,
|
||||
so a type whose TYPE is `x.v1` still works.
|
||||
- `for_entry_with_schema_id(id, version)` (`:50-64`): refuse `id != version` or a missing `.vN`. That is one id, and
|
||||
`cultmesh_documents!` already passes the same string twice.
|
||||
- `register` (`replication.rs`) applies the rule.
|
||||
- The apply path keeps stamping `binding.schema_id` (`:400`). That is what Odin, Muninn and the rest store today, so
|
||||
nothing on disk changes.
|
||||
- `pull_rudp_catalog_snapshot` (`cultmesh-rs/src/lib.rs:714-747`) stamps the resolved binding's id. It is exact after
|
||||
C1.0, so no foreign id is written.
|
||||
- Rust's mixed store ids (typed put stamps `T::TYPE` at `cultcache-rs/src/lib.rs:2362`; apply stamps the version) are
|
||||
left as they are. That is C2's question, F-C1-RUSTSTOREID.
|
||||
- **Kotlin.**
|
||||
- `register` (`CultMesh.kt:79-93`) refuses a `schemaVersion` failing `\.v\d+$`.
|
||||
- The interop codecs at `CultMesh.kt:3872-3880, 4317-4322, 4351-4354, 3779-3789` put the contract URL in
|
||||
`schemaVersion`. They become `cultnet.interop_note.v0` and the capability `*.v0` versions. Kotlin's own suite
|
||||
(`CultMesh.kt` self-checks) moves with them.
|
||||
|
||||
### 7b. C1.5: the interop harness tests the default binding
|
||||
|
||||
The parity witness hid the mismatch because every peer names the note by its JSON-Schema `$id` URL. C1.5 removes the
|
||||
overrides so each runtime's default binding meets the others.
|
||||
|
||||
- C# `tests/GameCult.Networking.InteropPeer/Program.cs`:
|
||||
- `:77-80, :201-204`: `ForDocument<CultNetInteropNote>(payloadSerializer:…, payloadDeserializer:…)` with no id. The
|
||||
id is `cultnet.interop_note.v0`.
|
||||
- `:1183-1196`: four capability bindings with no id; their versions are `*.v0` (`:1560-1572`).
|
||||
- Delete the `*SchemaId` URL constants.
|
||||
- `LoadSchemaRegistration` (`:1116-1137`): the catalog registration's `SchemaId` becomes `InteropSchemaVersion`. The
|
||||
JSON `$id` stays inside `SchemaJson`.
|
||||
- The catalog server at `:682-714` stops copying `SchemaId` into `SchemaVersion`.
|
||||
- TS `packages/cultnet-ts/test/interop/cultnet-interop-shared.ts:10-19, 289-306` and `cultnet-interop-peer.ts:123,
|
||||
135, 896-930`: definitions declare `schemaVersion` and no `schemaId` override. The note id constant is at
|
||||
`cultnet-interop.test.ts:43`.
|
||||
- Python `cultnet_py/interop_peer.py:59-68, 869-884, 936-981, 1085, 1149`.
|
||||
- Rust `cultnet-rs/examples/cultnet_interop_peer.rs:56-67, 1227-1261`: `for_entry_with_schema_id` with the version
|
||||
only.
|
||||
- Kotlin: C1.4.
|
||||
- The Python cultmesh scripts inside `cultnet-interop.test.ts:3872-4060` declare `schema_version` only.
|
||||
- **The added negative lane** is one scenario in `cultnet-interop.test.ts`. A C# host serves a note from a
|
||||
**CultMesh-created registry** (`CultMesh.CreateCultNetDocumentRegistry`, the production path). A TS reader:
|
||||
- asks by `cultnet.interop_note.v0` and gets it;
|
||||
- asks by `cultnet.interop-note` (the name) and gets not-found;
|
||||
- asks by the sha256 id and gets not-found.
|
||||
- CI: `.github/workflows/cultnet-interop.yml:141-163` already runs every pair lane. The new scenario joins the
|
||||
`TypeScript and C#` pattern.
|
||||
|
||||
### 8. Verification
|
||||
|
||||
Each rule gets a behavioural test that fails when the rule breaks, plus the ecosystem mutation tool on the cut's
|
||||
diff: Stryker.NET, StrykerJS, mutmut, cargo-mutants. Kotlin has no tool, so its rules are defended by scenario.
|
||||
|
||||
**Positive, across runtimes, with default bindings** (C1.5 lanes, run on Yggdrasil in `ack1d-interop:2`):
|
||||
- A C# host with a CultMesh-created registry publishes a note, and TS, Rust, Python and Kotlin each read it. Each asks
|
||||
only by `cultnet.interop_note.v0`.
|
||||
- Then each non-C# host publishes, and C# reads.
|
||||
- The wire bytes carry `schemaId = cultnet.interop_note.v0`. Assert the decoded `CultNetRawDocumentRecord`, not a log
|
||||
line.
|
||||
|
||||
**Negative checks:**
|
||||
- **No fallback.** In each runtime, a record at the requested key under another id is not returned (TS, C#, browser),
|
||||
and a payload whose slot 0 names a registered version but whose id is foreign is refused (C#, Python).
|
||||
- Mutant: restore `?? atKey[0]`, or restore the `byPayload` or `aliased` branch. It must die.
|
||||
- **Unknown id refused.**
|
||||
- C# `DeserializeRawDocument` with `gamecult.unknown.v1` throws. Today it returns `Note` (probed).
|
||||
- TS, Python, Rust and Kotlin `applyRawDocumentPut` with an unknown id throw.
|
||||
- **No name match.** Asking by `schemaName` finds nothing, in every runtime and in the selection evaluator (C# and
|
||||
Rust). Mutant: add `|| candidate == descriptor.SchemaName`.
|
||||
- **Refusal.**
|
||||
- Registering a C# binding for a `[CultDocument("x", "1")]` type throws at `Register` and names the type.
|
||||
- An unbound `"1"` type in a registry with no bindings throws at first emission.
|
||||
- TS, Python, Rust and Kotlin refuse `"v1"` and `"1"`, and accept `x.v0` and `x.v12`.
|
||||
- Mutants that must die: `\.v\d*$` (accepts `x.v`), `.v` anywhere, and `\d+$` alone (accepts `"1"`).
|
||||
- **The wire id is derived from the declaration, not a constant.** Two types with different versions each emit their
|
||||
own. A mutant that returns `SchemaName + ".v1"` must die, so use a fixture whose version is not `<name>.v1`: the
|
||||
interop note's name is `cultnet.interop-note` and its version is `cultnet.interop_note.v0`.
|
||||
- **The store id is untouched.**
|
||||
- A C# store written before C1 is read after it. `dump.py` shows record ids unchanged after a C1 host reads and
|
||||
serves.
|
||||
- A TS store with `schemaId: "p1.shared"` keeps that id after a raw put arrives under `p1.shared.v1`.
|
||||
- Mutant: store under the wire id. It must die.
|
||||
- **The served id is not the stored id.** A TS or Python record stored under a compatible id is served under the
|
||||
owner's wire id, with bytes that decode as the owner. Mutant: serve `envelope.schemaId`.
|
||||
- **Durable log.**
|
||||
- A C# database with `CultNetFileShardMutationLogStore` writes, restarts, and replays to a replica. The replayed
|
||||
entries carry the version, and `LastWriteSequence` after the restart equals the value before it.
|
||||
- A log file written before C1 (sha256 entries) replays under the version.
|
||||
- **Selection.** The parity vectors agree C#↔Rust with version ids. The tie-break vector R-AJ orders by version.
|
||||
- **Catalog lookup.** A catalog with two handles of one type refuses a by-schema lookup and names both document ids
|
||||
(C# and TS). Mutant: return the first or last handle.
|
||||
|
||||
**Builds.** These are the focused set, with no workspace-wide build:
|
||||
- `dotnet test` on `GameCult.Networking.Tests` and `GameCult.Mesh.Tests`;
|
||||
- `npm test` in `cultnet-ts`, `cultmesh-ts` and `cultmesh-browser`;
|
||||
- `pytest` in `cultnet-py` and `cultmesh-py`;
|
||||
- `cargo test -p cultnet-rs -p cultmesh-rs`;
|
||||
- the Kotlin self-check;
|
||||
- the interop lanes.
|
||||
All of them run on Yggdrasil through `ygg-verify.sh`.
|
||||
|
||||
**Negative greps**, which must return nothing in `src/` and `packages/*/src`:
|
||||
- `CultNetSchemaAliasMatching|InferSchemaName|inferSchemaName|_infer_schema_name|infer_schema_name`
|
||||
- `strip_schema_version_suffix|schema_alias|PayloadMatchesSchema|TryReadSchemaVersion|firstRecordAtKey`
|
||||
- `bySchemaNameVersion|ExpandSchemaBindingAliases`
|
||||
- in `CultNetDocumentRegistry.cs`: `schemaId:` as a `ForDocument` parameter.
|
||||
|
||||
**Live consumer checks**, after release and before any consumer bumps:
|
||||
- **Stonks** has its six versions fixed. `idunn` shows Odin holding Stonks' `provider_advertisement` after a Stonks
|
||||
restart on C1.
|
||||
- **AquaSynth** daemon and clients rebuilt together; the Dings host round-trips.
|
||||
- **Mimir** EveDashboard is visible in Eve Android.
|
||||
- **Bifrost** agent-transport snapshot and apply round-trip.
|
||||
- **Odin** still ingests from Heimdall, VoidBot, weksa and StreamPixels. Those ids do not change.
|
||||
- **Grep** Idunn and Odin for a receiving binding of `idunn.daemon_health` with no version. If one exists, it is
|
||||
refused at startup under C1.4. It was not found in main.
|
||||
|
||||
### 9. Sequencing
|
||||
|
||||
1. **F2 finishes first.** C1 and F2 touch different layers:
|
||||
- F2 is store-side: `CultCache.cs` `Claimant`, the single-file and directory stores, and the Rust catalog lay-back.
|
||||
- C1 is wire-side.
|
||||
- They overlap in three places:
|
||||
- `cultnet-ts/src/replication.ts`. F2 and batch 10 built owner-beats-lister and the `#listers` refusal (F9, F10)
|
||||
there, and C1.2 re-keys `#owners` by wire id. The lister rule is kept, with OQ-C1.2 deciding its wire reach.
|
||||
- `cultnet-ts/test/cultnet.test.ts:1346-1394, 1627-1744`. C1.2 inverts tests F2's batches wrote.
|
||||
- `cultnet_py/replication.py` `schema_document_map`.
|
||||
- C1 does not relabel any stored record and adds no store write, so F2's rule ("never relabels … a write over it is
|
||||
refused") is not in play. F2's test `976b57f1` ("each runtime's writes leave another runtime's records and catalog
|
||||
entries unchanged") must stay green through C1.
|
||||
- Hands on F2 should not be interrupted. C1 starts after F2's Soul pass holds.
|
||||
2. **Then put-serve merges to main** (`hands/cultmesh-put-serve-bound`, based on `b04d03b6`).
|
||||
- It edits `cultnet-ts/src/replication.ts` on main's resolution. Main still has `preserveIncomingSchemaId` and
|
||||
payload-shape resolution, which c2a deleted in `0ee98e8c`.
|
||||
- It also edits `cultmesh-ts/src/index.ts`, `cultmesh_py/server.py` and `node.py`, and `cultmesh-rs`.
|
||||
3. **Then c2a merges main.** This is the conflict to resolve before C1, whatever C1 is:
|
||||
- c2a's exact resolution plus put-serve's `createRawSnapshotResponseForPut`;
|
||||
- main's R1 changes to `CultNetDocumentRegistry.cs` (`rowFilter`) against c2a's `Register` refusal.
|
||||
- C1 re-anchors on that merge commit. Every anchor above names its symbol for that reason.
|
||||
4. **C1 cuts, in order, on the variants branch** (or `hands/variants-c1id` cut from it):
|
||||
- C1.0 C# subtraction;
|
||||
- C1.0 TS, Python, Rust and Kotlin subtraction, one cut per runtime or together, since they are deletions only;
|
||||
- C1.1 C# wire id with the shared selection fixture and Rust selection order;
|
||||
- C1.2 TS;
|
||||
- C1.3 Python;
|
||||
- C1.4 Rust and Kotlin;
|
||||
- C1.5 harness.
|
||||
- The interop lane is red between C1.1 and C1.5 on the branch. Soul measures at C1.5.
|
||||
- Stonks' fix lands before the variants branch merges to main.
|
||||
5. **Release.** C1 is wire-breaking between C# peers of different releases, because an old host sends sha256 and a new
|
||||
receiver refuses it. It rides the C0/C2a release that the 2026-09-30 ruling already gates. The release note names:
|
||||
C# peers upgrade together, the refusal of versions without `.vN`, and Stonks.
|
||||
|
||||
### 10. Ledger estimate
|
||||
|
||||
| Cut | Deleted (src) | Added (src) | Tests | Notes |
|
||||
|---|---|---|---|---|
|
||||
| C1.0 C# | ~290: alias class 58; registry block ~175; mesh branches ~35; expand ~40 | ~40 exact lookups | ~15 tests inverted, ~6 deleted | public API removed: `CultNetSchemaAliasMatching`, `TryResolveDescriptorByPayloadSchema`, `PayloadMatchesSchema` |
|
||||
| C1.0 TS/Py/Rust/Kt | ~230: selection `schema_alias` ~140 with tests; Python infer/sniff ~45; TS infer/HELD/catalog keys ~45 | ~20 | ~25 tests deleted or inverted | |
|
||||
| C1.1 C# | ~30 (override parameter, sha256 fallbacks) | ~80 (refusal, log restamp, emitters) | ~20 changed; fixture and vectors regenerated | wire-visible order change |
|
||||
| C1.2 TS | ~15 | ~50 (`wireSchemaIdOf`, refusal, serve re-encode) | ~10 | |
|
||||
| C1.3 Py | ~10 | ~40 | ~8 | |
|
||||
| C1.4 Rs/Kt | ~5 | ~25 | ~6 | |
|
||||
| C1.5 harness | ~60 URL constants and overrides | ~40 negative lane | — | |
|
||||
| **Net** | **~-640 / +295 src**, about **-345** | | | Nothing on disk changes. Two public C# methods and one public C# class are gone. |
|
||||
|
||||
### 11. Operator questions
|
||||
|
||||
**OQ-C1.1: delete the per-binding wire-id override?**
|
||||
- The overrides are C# `ForDocument(schemaId:)`, Rust's free first argument of `for_entry_with_schema_id`, and the
|
||||
implicit TS and Python `schemaId`-on-the-wire.
|
||||
- A. Delete. The declared version is the one wire id, and it derives from the descriptor alone, so the selection
|
||||
evaluator, the durable log and the mesh need no registry to compute it. No repo outside CultLib passes the override;
|
||||
only CultLib's interop peer and two conflict tests do.
|
||||
- B. Keep it, but refuse ids without `.vN`. A second declaration of identity survives beside the version, and every
|
||||
descriptor-only site (evaluator order, cursor, log, projections) needs the registry again. That is today's
|
||||
`WireSchemaId` split.
|
||||
- **Recommendation: A.** It removes a public parameter, which the CultLib consumer rule asks to be put to you rather
|
||||
than cut silently. What a reasonable consumer loses is the ability to put an id on the wire that its declaration
|
||||
does not state. That is the thing C1 rules out.
|
||||
- C1.1's shape depends on the answer.
|
||||
|
||||
**OQ-C1.2: does declared compatibility (`CompatibleSchemaIds`) resolve on the wire?**
|
||||
- A. Yes, with the same rule the stores use: owner beats lister, and two listers with no owner are refused. A v2 type
|
||||
that declares `x.v1` compatible accepts a v1 peer's records, so mixed-version peers work when an author declares it.
|
||||
It is declared, never inferred. c2a's TS and Python registries already do this. Served records always go out under
|
||||
the owner's wire id, re-encoded (§5).
|
||||
- B. No, wire resolution is the owner only. Compatible ids become store-only. It is simpler, and a version bump is a
|
||||
hard break between peers.
|
||||
- **Recommendation: A.** It is the rule the operator ruled for stores (owner beats lister), applied to one more layer.
|
||||
It is not aliasing, because nothing is guessed.
|
||||
- C1.0's Python map, C1.2's `#listers`, and the C# lookup depend on the answer.
|
||||
|
||||
**Defaulted, not asked. Veto if wrong:**
|
||||
- TS and Python keep their default version `${name}.v1` when none is declared.
|
||||
- A served record stored under a compatible id is re-encoded through its owner, as C# does.
|
||||
- `global:{sha256}` record keys stay.
|
||||
- Rust's apply path keeps stamping the binding id.
|
||||
- A catalog lookup by schema that more than one handle answers is refused.
|
||||
- Stonks is fixed in Stonks.
|
||||
|
||||
### 12. Follow-ups (no cut owns them)
|
||||
|
||||
- **F-C1-GLOBALKEY:** `global:{descriptor.SchemaId}` keys (`CultCache.cs:2473`, `CultNetDatabase.cs:1120`) carry the
|
||||
sha256 across the wire as a record key. This is for C2.
|
||||
- **F-C1-RUSTSTOREID:** Rust stores hold `T::TYPE` for typed puts and the version for replicated puts. Its catalog
|
||||
fabricates version `<TYPE>.v1` (`cultcache-rs/src/lib.rs:2694-2708`) even where `cultmesh_documents!` declares another
|
||||
one. The derive cannot declare a version (`cultcache-rs-derive/src/lib.rs:24-33`), which is a gap beside the other
|
||||
runtimes. This is for C2.
|
||||
- **F-C1-PYALIAS:** Python cultmesh `_documents_alias` compares two local definitions by name and version. It is
|
||||
redundant under one-type-per-schema, but local.
|
||||
- **F-C1-HEALTHID:** Bifrost sends `idunn.signed_daemon_health` without `.v1`
|
||||
(`persona-feedback-idunn-health.cjs:41`), where every other sender uses `.v1`. It is pre-existing and unrelated to
|
||||
C1.
|
||||
- **F-C1-AEVE-CONST:** the AetheriaEve daemon compares wire ids with version constants (`Program.cs:1591`) and so never
|
||||
matched under sha256. It is archaeology; record only.
|
||||
|
|
|
|||
Loading…
Reference in a new issue