Add CultNet operation service envelopes

This commit is contained in:
Meta Vens 2026-07-10 11:11:28 +01:00
commit 2272aeff08
4 changed files with 106 additions and 2 deletions

View file

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

View file

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

View file

@ -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<CultNetSchemaVersion, ValidateFunction>();
@ -1064,5 +1097,7 @@ export const cultNetSchemas = {
schemaCatalogResponseSchema,
shardCatalogRequestSchema,
shardCatalogResponseSchema,
operationRequestSchema,
operationResponseSchema,
ghostlightAgentStateSchema,
} as const;

View file

@ -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 });