From 2272aeff0834e92f09d9c86fccb503c29a3f070d Mon Sep 17 00:00:00 2001 From: Meta Vens Date: Fri, 10 Jul 2026 11:11:28 +0100 Subject: [PATCH] Add CultNet operation service envelopes --- .../cultnet.operation-request.schema.json | 19 +++++++++ .../cultnet.operation-response.schema.json | 20 ++++++++++ packages/cultnet-ts/src/contracts.ts | 39 ++++++++++++++++++- packages/cultnet-ts/test/cultnet.test.ts | 30 ++++++++++++++ 4 files changed, 106 insertions(+), 2 deletions(-) create mode 100644 packages/cultnet-ts/contracts/cultnet.operation-request.schema.json create mode 100644 packages/cultnet-ts/contracts/cultnet.operation-response.schema.json diff --git a/packages/cultnet-ts/contracts/cultnet.operation-request.schema.json b/packages/cultnet-ts/contracts/cultnet.operation-request.schema.json new file mode 100644 index 00000000..07cd9d8d --- /dev/null +++ b/packages/cultnet-ts/contracts/cultnet.operation-request.schema.json @@ -0,0 +1,19 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://github.com/GameCult/cultnet-ts/contracts/cultnet.operation-request.schema.json", + "title": "CultNet Operation Request", + "type": "object", + "required": ["schemaVersion", "messageId", "serviceId", "operation", "payloadSchema", "payload"], + "additionalProperties": false, + "properties": { + "schemaVersion": { "const": "cultnet.operation_request.v0" }, + "messageId": { "type": "string", "minLength": 1 }, + "serviceId": { "type": "string", "minLength": 1 }, + "operation": { "type": "string", "minLength": 1 }, + "payloadSchema": { "type": "string", "minLength": 1 }, + "payloadEncoding": { "const": "messagepack-base64" }, + "payload": { "type": "string", "minLength": 1, "contentEncoding": "base64" }, + "sourceRuntimeId": { "type": "string", "minLength": 1 }, + "targetRuntimeId": { "type": "string", "minLength": 1 } + } +} diff --git a/packages/cultnet-ts/contracts/cultnet.operation-response.schema.json b/packages/cultnet-ts/contracts/cultnet.operation-response.schema.json new file mode 100644 index 00000000..c3be917d --- /dev/null +++ b/packages/cultnet-ts/contracts/cultnet.operation-response.schema.json @@ -0,0 +1,20 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://github.com/GameCult/cultnet-ts/contracts/cultnet.operation-response.schema.json", + "title": "CultNet Operation Response", + "type": "object", + "required": ["schemaVersion", "messageId", "serviceId", "operation", "status", "payloadSchema", "payload"], + "additionalProperties": false, + "properties": { + "schemaVersion": { "const": "cultnet.operation_response.v0" }, + "messageId": { "type": "string", "minLength": 1 }, + "serviceId": { "type": "string", "minLength": 1 }, + "operation": { "type": "string", "minLength": 1 }, + "status": { "type": "string", "minLength": 1 }, + "payloadSchema": { "type": "string", "minLength": 1 }, + "payloadEncoding": { "const": "messagepack-base64" }, + "payload": { "type": "string", "minLength": 1, "contentEncoding": "base64" }, + "diagnostics": { "type": "array", "items": { "type": "string" } }, + "sourceRuntimeId": { "type": "string", "minLength": 1 } + } +} diff --git a/packages/cultnet-ts/src/contracts.ts b/packages/cultnet-ts/src/contracts.ts index 2e89c025..8795b06a 100644 --- a/packages/cultnet-ts/src/contracts.ts +++ b/packages/cultnet-ts/src/contracts.ts @@ -23,6 +23,8 @@ import schemaCatalogRequestSchema from "../contracts/cultnet.schema-catalog-requ import schemaCatalogResponseSchema from "../contracts/cultnet.schema-catalog-response.schema.json"; import shardCatalogRequestSchema from "../contracts/cultnet.shard-catalog-request.schema.json"; import shardCatalogResponseSchema from "../contracts/cultnet.shard-catalog-response.schema.json"; +import operationRequestSchema from "../contracts/cultnet.operation-request.schema.json"; +import operationResponseSchema from "../contracts/cultnet.operation-response.schema.json"; import ghostlightAgentStateSchema from "../contracts/ghostlight.agent-state.schema.json"; export type CultNetWireContract = "cultnet.schema.v0" | "gamecult.networking.v0"; @@ -45,7 +47,9 @@ export type CultNetSchemaVersion = | "cultnet.schema_catalog_request.v0" | "cultnet.schema_catalog_response.v0" | "cultnet.shard_catalog_request.v0" - | "cultnet.shard_catalog_response.v0"; + | "cultnet.shard_catalog_response.v0" + | "cultnet.operation_request.v0" + | "cultnet.operation_response.v0"; export type CultNetSchemaKind = "wire_message" | "document_payload" | "shared_contract"; export type CultNetRawPayloadEncoding = "messagepack"; @@ -265,6 +269,31 @@ export interface CultNetShardCatalogResponseMessage { shards: CultNetShardDescriptor[]; } +export interface CultNetOperationRequestMessage { + schemaVersion: "cultnet.operation_request.v0"; + messageId: string; + serviceId: string; + operation: string; + payloadSchema: string; + payloadEncoding?: "messagepack-base64"; + payload: string; + sourceRuntimeId?: string; + targetRuntimeId?: string; +} + +export interface CultNetOperationResponseMessage { + schemaVersion: "cultnet.operation_response.v0"; + messageId: string; + serviceId: string; + operation: string; + status: string; + payloadSchema: string; + payloadEncoding?: "messagepack-base64"; + payload: string; + diagnostics?: string[]; + sourceRuntimeId?: string; +} + export interface GhostlightPressure { pressure_id: string; label: string; @@ -509,7 +538,9 @@ export type CultNetMessage = | CultNetSchemaCatalogRequestMessage | CultNetSchemaCatalogResponseMessage | CultNetShardCatalogRequestMessage - | CultNetShardCatalogResponseMessage; + | CultNetShardCatalogResponseMessage + | CultNetOperationRequestMessage + | CultNetOperationResponseMessage; const ajv = new Ajv2020({ allErrors: true, @@ -535,6 +566,8 @@ const CULTNET_MESSAGE_SCHEMAS = [ schemaCatalogResponseSchema, shardCatalogRequestSchema, shardCatalogResponseSchema, + operationRequestSchema, + operationResponseSchema, ] as const; const cultNetValidators = new Map(); @@ -1064,5 +1097,7 @@ export const cultNetSchemas = { schemaCatalogResponseSchema, shardCatalogRequestSchema, shardCatalogResponseSchema, + operationRequestSchema, + operationResponseSchema, ghostlightAgentStateSchema, } as const; diff --git a/packages/cultnet-ts/test/cultnet.test.ts b/packages/cultnet-ts/test/cultnet.test.ts index 04107a0e..7f370c5c 100644 --- a/packages/cultnet-ts/test/cultnet.test.ts +++ b/packages/cultnet-ts/test/cultnet.test.ts @@ -1221,6 +1221,36 @@ test("CultNet raw replication preserves CultCache payload bytes for bit-compatib } }); +test("operation envelopes preserve typed service routing and payload correlation", () => { + const request = parseCultNetMessage({ + schemaVersion: "cultnet.operation_request.v0", + messageId: "plugin-42", + serviceId: "sai.vn", + operation: "project", + payloadSchema: "gamecult.eve.plugin_abi.request.v1", + payloadEncoding: "messagepack-base64", + payload: "gaZzY2hlbWHEJ2dhbWVjdWx0LmV2ZS5wbHVnaW5fYWJpLnJlcXVlc3QudjE=", + sourceRuntimeId: "eve-unity", + }); + assert.equal(request.schemaVersion, "cultnet.operation_request.v0"); + assert.equal(request.messageId, "plugin-42"); + assert.equal(request.serviceId, "sai.vn"); + + const response = parseCultNetMessage({ + schemaVersion: "cultnet.operation_response.v0", + messageId: "plugin-42", + serviceId: "sai.vn", + operation: "project", + status: "ok", + payloadSchema: "gamecult.eve.plugin_abi.response.v1", + payloadEncoding: "messagepack-base64", + payload: "gaZzdGF0dXOkb2s=", + diagnostics: [], + }); + assert.equal(response.schemaVersion, "cultnet.operation_response.v0"); + assert.equal(response.messageId, request.messageId); +}); + test("rudp sequence-neutral acknowledgements interoperate with ordered receivers", () => { const sender = new CultNetRudpSession({ connectionId: 125, initialSequence: 1 }); const receiver = new CultNetRudpSession({ connectionId: 125, initialSequence: 100 });