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 <noreply@anthropic.com>
This commit is contained in:
Meta Vens 2026-09-29 15:03:09 +02:00
commit 09dabd38c5
5 changed files with 27 additions and 6 deletions

View file

@ -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 },

View file

@ -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},

View file

@ -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")

View file

@ -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"];

View file

@ -104,6 +104,10 @@ namespace GameCult.Networking
[MessagePackObject]
public sealed class CultWitnessContractPin
{
[Key(4)]
[CultElementId]
public string Id { get; set; } = string.Empty;
/// <summary>
/// Gets or sets the contract role inside the bundle.
/// </summary>
@ -135,6 +139,10 @@ namespace GameCult.Networking
[MessagePackObject]
public sealed class CultWitnessArtifactEntry
{
[Key(6)]
[CultElementId]
public string Id { get; set; } = string.Empty;
/// <summary>
/// Gets or sets the artifact role inside the witness bundle.
/// </summary>
@ -178,6 +186,10 @@ namespace GameCult.Networking
[MessagePackObject]
public sealed class CultWitnessTimingEntry
{
[Key(5)]
[CultElementId]
public string Id { get; set; } = string.Empty;
/// <summary>
/// Gets or sets the pipeline stage name.
/// </summary>