From 09dabd38c5d211a75bf4fcd0cf55f99e4afb2d22 Mon Sep 17 00:00:00 2001 From: Meta Vens Date: Tue, 29 Sep 2026 15:03:09 +0200 Subject: [PATCH] CultNet: witness element types carry ids; peers and fixtures follow CultWitnessContractPin, CultWitnessArtifactEntry and CultWitnessTimingEntry get an id slot. The JSON schema contract and the Python schema copy gain an optional id per element, and the Python and TypeScript fixtures carry and assert ids. Co-Authored-By: Claude Sonnet 5 --- .../cultnet.witness-artifact-bundle.schema.json | 3 +++ packages/cultnet-py/src/cultnet_py/interop_peer.py | 3 +++ packages/cultnet-py/tests/test_cultnet.py | 8 +++++--- .../cultnet-ts/test/interop/cultnet-interop.test.ts | 7 ++++--- src/GameCult.Networking/CultWitnessArtifactBundle.cs | 12 ++++++++++++ 5 files changed, 27 insertions(+), 6 deletions(-) diff --git a/contracts/cultnet/cultnet.witness-artifact-bundle.schema.json b/contracts/cultnet/cultnet.witness-artifact-bundle.schema.json index 6dd6971d..936a0a04 100644 --- a/contracts/cultnet/cultnet.witness-artifact-bundle.schema.json +++ b/contracts/cultnet/cultnet.witness-artifact-bundle.schema.json @@ -59,6 +59,7 @@ ], "additionalProperties": false, "properties": { + "id": { "type": "string", "minLength": 1 }, "role": { "type": "string", "minLength": 1 }, "schemaId": { "type": "string", "minLength": 1 }, "schemaVersion": { "type": "string", "minLength": 1 }, @@ -74,6 +75,7 @@ ], "additionalProperties": false, "properties": { + "id": { "type": "string", "minLength": 1 }, "role": { "type": "string", "minLength": 1 }, "uri": { "type": "string", "minLength": 1 }, "mediaType": { "type": "string", "minLength": 1 }, @@ -92,6 +94,7 @@ ], "additionalProperties": false, "properties": { + "id": { "type": "string", "minLength": 1 }, "stage": { "type": "string", "minLength": 1 }, "startedAt": { "type": "string", "minLength": 1 }, "completedAt": { "type": "string", "minLength": 1 }, diff --git a/packages/cultnet-py/src/cultnet_py/interop_peer.py b/packages/cultnet-py/src/cultnet_py/interop_peer.py index ba236686..e6cd4601 100644 --- a/packages/cultnet-py/src/cultnet_py/interop_peer.py +++ b/packages/cultnet-py/src/cultnet_py/interop_peer.py @@ -1316,6 +1316,7 @@ def witness_artifact_bundle_schema_json() -> str: "required": ["role", "schemaId"], "additionalProperties": False, "properties": { + "id": {"type": "string", "minLength": 1}, "role": {"type": "string", "minLength": 1}, "schemaId": {"type": "string", "minLength": 1}, "schemaVersion": {"type": "string", "minLength": 1}, @@ -1327,6 +1328,7 @@ def witness_artifact_bundle_schema_json() -> str: "required": ["role", "uri", "mediaType"], "additionalProperties": False, "properties": { + "id": {"type": "string", "minLength": 1}, "role": {"type": "string", "minLength": 1}, "uri": {"type": "string", "minLength": 1}, "mediaType": {"type": "string", "minLength": 1}, @@ -1340,6 +1342,7 @@ def witness_artifact_bundle_schema_json() -> str: "required": ["stage", "startedAt", "completedAt", "latencyMs"], "additionalProperties": False, "properties": { + "id": {"type": "string", "minLength": 1}, "stage": {"type": "string", "minLength": 1}, "startedAt": {"type": "string", "minLength": 1}, "completedAt": {"type": "string", "minLength": 1}, diff --git a/packages/cultnet-py/tests/test_cultnet.py b/packages/cultnet-py/tests/test_cultnet.py index 475305d3..dc15224b 100644 --- a/packages/cultnet-py/tests/test_cultnet.py +++ b/packages/cultnet-py/tests/test_cultnet.py @@ -1870,9 +1870,9 @@ class CultNetTests(unittest.TestCase): witness_kind="interop-proof", captured_at="2026-06-13T00:00:03Z", subject={"documentType": "cultnet.interop-note", "subjectId": "note:python"}, - contracts=[{"role": "payload", "schemaId": "schema-a"}], - artifacts=[{"role": "log", "uri": "cultcache://bundle-1/log", "mediaType": "text/plain"}], - timing_witnesses=[{"stage": "roundtrip", "startedAt": "2026-06-13T00:00:03Z", "completedAt": "2026-06-13T00:00:04Z", "latencyMs": 1.0}], + contracts=[{"id": "c0e1a2b3c4d5", "role": "payload", "schemaId": "schema-a"}], + artifacts=[{"id": "a0e1a2b3c4d5", "role": "log", "uri": "cultcache://bundle-1/log", "mediaType": "text/plain"}], + timing_witnesses=[{"id": "e0e1a2b3c4d5", "stage": "roundtrip", "startedAt": "2026-06-13T00:00:03Z", "completedAt": "2026-06-13T00:00:04Z", "latencyMs": 1.0}], provenance={"pipelineId": "interop", "runId": "run-1", "runtimeId": "python-test"}, ) payload = encode_witness_artifact_bundle_payload(bundle) @@ -1888,6 +1888,8 @@ class CultNetTests(unittest.TestCase): self.assertEqual(slots[2], "2026-06-13T00:00:03Z") self.assertEqual(slots[3]["subjectId"], "note:python") self.assertEqual(slots[4][0]["schemaId"], "schema-a") + self.assertEqual([slots[4][0]["id"], slots[5][0]["id"], slots[6][0]["id"]], ["c0e1a2b3c4d5", "a0e1a2b3c4d5", "e0e1a2b3c4d5"]) + self.assertEqual(typed_decoded.contracts[0]["id"], "c0e1a2b3c4d5") self.assertEqual(slots[5][0]["uri"], "cultcache://bundle-1/log") self.assertEqual(slots[6][0]["latencyMs"], 1.0) self.assertEqual(slots[7]["runtimeId"], "python-test") diff --git a/packages/cultnet-ts/test/interop/cultnet-interop.test.ts b/packages/cultnet-ts/test/interop/cultnet-interop.test.ts index 756ef97d..7eab911f 100644 --- a/packages/cultnet-ts/test/interop/cultnet-interop.test.ts +++ b/packages/cultnet-ts/test/interop/cultnet-interop.test.ts @@ -1232,9 +1232,9 @@ test("CultNet TS/Rust/C#/Python peers discover each other and exchange raw state schemaVersion: INTEROP_SCHEMA_VERSION, schemaId: interopNoteSchemaId, }, - [{ role: "payload", schemaId: interopNoteSchemaId, schemaVersion: INTEROP_SCHEMA_VERSION }], - [{ role: "log", uri: "cultcache://bundle:ts-python-witness/log", mediaType: "text/plain" }], - [{ stage: "roundtrip", startedAt: "2026-06-13T00:00:03Z", completedAt: "2026-06-13T00:00:04Z", latencyMs: 1 }], + [{ id: "c0e1a2b3c4d5", role: "payload", schemaId: interopNoteSchemaId, schemaVersion: INTEROP_SCHEMA_VERSION }], + [{ id: "a0e1a2b3c4d5", role: "log", uri: "cultcache://bundle:ts-python-witness/log", mediaType: "text/plain" }], + [{ id: "e0e1a2b3c4d5", stage: "roundtrip", startedAt: "2026-06-13T00:00:03Z", completedAt: "2026-06-13T00:00:04Z", latencyMs: 1 }], { pipelineId: "interop", runId: "ts-python-witness", runtimeId: "ts-witness", agentId: "ts-agent" }, ]), sourceRuntimeId: "ts-witness", @@ -1252,6 +1252,7 @@ test("CultNet TS/Rust/C#/Python peers discover each other and exchange raw state assert.equal(witnessSlots[1], "interop-proof"); assert.equal(witnessSlots[3].subjectId, "note:python-peer"); assert.equal(witnessSlots[7].runtimeId, "ts-witness"); + assert.deepEqual([witnessSlots[4][0].id, witnessSlots[5][0].id, witnessSlots[6][0].id], ["c0e1a2b3c4d5", "a0e1a2b3c4d5", "e0e1a2b3c4d5"], "element ids cross TS to Python and back"); const expectedPeers = ["csharp-peer", "python-peer", "rust-peer", "ts-peer"]; diff --git a/src/GameCult.Networking/CultWitnessArtifactBundle.cs b/src/GameCult.Networking/CultWitnessArtifactBundle.cs index 045ebfbb..6baa48bb 100644 --- a/src/GameCult.Networking/CultWitnessArtifactBundle.cs +++ b/src/GameCult.Networking/CultWitnessArtifactBundle.cs @@ -104,6 +104,10 @@ namespace GameCult.Networking [MessagePackObject] public sealed class CultWitnessContractPin { + [Key(4)] + [CultElementId] + public string Id { get; set; } = string.Empty; + /// /// Gets or sets the contract role inside the bundle. /// @@ -135,6 +139,10 @@ namespace GameCult.Networking [MessagePackObject] public sealed class CultWitnessArtifactEntry { + [Key(6)] + [CultElementId] + public string Id { get; set; } = string.Empty; + /// /// Gets or sets the artifact role inside the witness bundle. /// @@ -178,6 +186,10 @@ namespace GameCult.Networking [MessagePackObject] public sealed class CultWitnessTimingEntry { + [Key(5)] + [CultElementId] + public string Id { get; set; } = string.Empty; + /// /// Gets or sets the pipeline stage name. ///