diff --git a/packages/eve-contracts/dist/index.d.ts b/packages/eve-contracts/dist/index.d.ts index 3e5c900..fbe3dec 100644 --- a/packages/eve-contracts/dist/index.d.ts +++ b/packages/eve-contracts/dist/index.d.ts @@ -52,3 +52,9 @@ export declare const parseEveCommandInvocation: (value: unknown) => EveCommandIn export declare const parseEveCommandReceipt: (value: unknown) => EveCommandReceipt; export declare const parseEveCommandResult: (value: unknown) => EveCommandResult; export declare const parseEveInputCapability: (value: unknown) => EveInputCapability; +/** + * Converts the canonical positional MessagePack representation used by the C# + * Eve package into the JSON-shaped provider contract used at browser/model + * boundaries. This is serialization lowering, not a second advertisement DTO. + */ +export declare function normalizeEveProviderAdvertisement(value: unknown): unknown; diff --git a/packages/eve-contracts/dist/index.js b/packages/eve-contracts/dist/index.js index 467a936..4fb1dea 100644 --- a/packages/eve-contracts/dist/index.js +++ b/packages/eve-contracts/dist/index.js @@ -44,7 +44,7 @@ export const isEveCommandInvocation = (value) => isEveContract("commandInvocatio export const isEveCommandReceipt = (value) => isEveContract("commandReceipt", value); export const isEveCommandResult = (value) => isEveContract("commandResult", value); export const isEveInputCapability = (value) => isEveContract("inputCapability", value); -export const parseEveProviderAdvertisement = (value) => parseEveContract("providerAdvertisement", value); +export const parseEveProviderAdvertisement = (value) => parseEveContract("providerAdvertisement", normalizeEveProviderAdvertisement(value)); export const parseEveSurfaceDocument = (value) => parseEveContract("surface", value); export const parseEveCommandDescriptor = (value) => parseEveContract("commandDescriptor", value); export const parseEveCommandInvocation = (value) => parseEveContract("commandInvocation", value); @@ -62,3 +62,74 @@ function requiredValidator(schemaId) { throw new Error(`Eve contract schema ${schemaId} was not registered.`); return validator; } +/** + * Converts the canonical positional MessagePack representation used by the C# + * Eve package into the JSON-shaped provider contract used at browser/model + * boundaries. This is serialization lowering, not a second advertisement DTO. + */ +export function normalizeEveProviderAdvertisement(value) { + if (!Array.isArray(value)) + return value; + if (value.length < 13) + return value; + const [schema, providerId, serviceId, verseId, title, kind, cultMeshAddress, updatedAtUtc, freshness, schemas, witnesses, surfaces, commands, authorizedBodyProducerIds] = value; + return { + schema, + providerId, + serviceId, + verseId, + title, + kind, + cultMeshAddress, + updatedAtUtc, + freshness: normalizeFreshness(freshness), + schemas, + witnesses: Array.isArray(witnesses) ? witnesses.map(normalizeWitness) : witnesses, + surfaces: Array.isArray(surfaces) ? surfaces.map(normalizeSurfaceAdvertisement) : surfaces, + commands: Array.isArray(commands) ? commands.map(normalizeAdvertisedCommand) : commands, + ...(Array.isArray(authorizedBodyProducerIds) ? { authorizedBodyProducerIds } : {}), + }; +} +function normalizeFreshness(value) { + return Array.isArray(value) + ? { state: value[0], lastSeenAtUtc: value[1], maxAgeMs: value[2] } + : value; +} +function normalizeWitness(value) { + return Array.isArray(value) + ? { kind: value[0], ref: value[1], summary: value[2] } + : value; +} +function normalizeSurfaceAdvertisement(value) { + if (!Array.isArray(value)) + return value; + return { + surfaceId: value[0], + schema: value[1], + recordRef: value[2], + transport: value[3], + status: value[4], + surfaceKind: value[5], + ...(value[6] ? { worldInteraction: normalizeWorldInteraction(value[6]) } : {}), + }; +} +function normalizeWorldInteraction(value) { + if (!Array.isArray(value)) + return value; + return { + projectionKind: value[0], + stateSchemas: value[1], + commandBoundary: value[2], + commandRecordRef: value[3], + receiptSchema: value[4], + receiptRecordRef: value[5], + assetManifestRecordRef: value[6], + loweringTargets: value[7], + ownership: value[8], + }; +} +function normalizeAdvertisedCommand(value) { + return Array.isArray(value) + ? { command: value[0], surfaceId: value[1], transport: value[2], summary: value[3] } + : value; +} diff --git a/packages/eve-contracts/src/index.ts b/packages/eve-contracts/src/index.ts index a32201a..1e7a456 100644 --- a/packages/eve-contracts/src/index.ts +++ b/packages/eve-contracts/src/index.ts @@ -92,7 +92,7 @@ export const isEveInputCapability = (value: unknown): value is EveInputCapabilit isEveContract("inputCapability", value); export const parseEveProviderAdvertisement = (value: unknown): EveProviderAdvertisement => - parseEveContract("providerAdvertisement", value); + parseEveContract("providerAdvertisement", normalizeEveProviderAdvertisement(value)); export const parseEveSurfaceDocument = (value: unknown): EveSurfaceDocument => parseEveContract("surface", value); export const parseEveCommandDescriptor = (value: unknown): EveCommandDescriptor => @@ -114,3 +114,77 @@ function requiredValidator(schemaId: string): ValidateFunction { if (!validator) throw new Error(`Eve contract schema ${schemaId} was not registered.`); return validator; } + +/** + * Converts the canonical positional MessagePack representation used by the C# + * Eve package into the JSON-shaped provider contract used at browser/model + * boundaries. This is serialization lowering, not a second advertisement DTO. + */ +export function normalizeEveProviderAdvertisement(value: unknown): unknown { + if (!Array.isArray(value)) return value; + if (value.length < 13) return value; + const [schema, providerId, serviceId, verseId, title, kind, cultMeshAddress, updatedAtUtc, + freshness, schemas, witnesses, surfaces, commands, authorizedBodyProducerIds] = value; + return { + schema, + providerId, + serviceId, + verseId, + title, + kind, + cultMeshAddress, + updatedAtUtc, + freshness: normalizeFreshness(freshness), + schemas, + witnesses: Array.isArray(witnesses) ? witnesses.map(normalizeWitness) : witnesses, + surfaces: Array.isArray(surfaces) ? surfaces.map(normalizeSurfaceAdvertisement) : surfaces, + commands: Array.isArray(commands) ? commands.map(normalizeAdvertisedCommand) : commands, + ...(Array.isArray(authorizedBodyProducerIds) ? { authorizedBodyProducerIds } : {}), + }; +} + +function normalizeFreshness(value: unknown): unknown { + return Array.isArray(value) + ? { state: value[0], lastSeenAtUtc: value[1], maxAgeMs: value[2] } + : value; +} + +function normalizeWitness(value: unknown): unknown { + return Array.isArray(value) + ? { kind: value[0], ref: value[1], summary: value[2] } + : value; +} + +function normalizeSurfaceAdvertisement(value: unknown): unknown { + if (!Array.isArray(value)) return value; + return { + surfaceId: value[0], + schema: value[1], + recordRef: value[2], + transport: value[3], + status: value[4], + surfaceKind: value[5], + ...(value[6] ? { worldInteraction: normalizeWorldInteraction(value[6]) } : {}), + }; +} + +function normalizeWorldInteraction(value: unknown): unknown { + if (!Array.isArray(value)) return value; + return { + projectionKind: value[0], + stateSchemas: value[1], + commandBoundary: value[2], + commandRecordRef: value[3], + receiptSchema: value[4], + receiptRecordRef: value[5], + assetManifestRecordRef: value[6], + loweringTargets: value[7], + ownership: value[8], + }; +} + +function normalizeAdvertisedCommand(value: unknown): unknown { + return Array.isArray(value) + ? { command: value[0], surfaceId: value[1], transport: value[2], summary: value[3] } + : value; +} diff --git a/packages/eve-contracts/test/contracts.test.mjs b/packages/eve-contracts/test/contracts.test.mjs index 1b63c50..377d94f 100644 --- a/packages/eve-contracts/test/contracts.test.mjs +++ b/packages/eve-contracts/test/contracts.test.mjs @@ -195,6 +195,28 @@ test("validates an existing canonical Eve surface fixture", async () => { assert.equal(parseEveSurfaceDocument(fixture).providerId, "eve.world-smoke"); }); +test("provider parser lowers the canonical C# MessagePack tuple without app-specific decoding", () => { + const advertisement = parseEveProviderAdvertisement([ + "gamecult.eve.provider_advertisement.v1", + "aetheria", + "aetheria-daemon", + "aetheria.local", + "Aetheria", + "game.runtime", + "cultmesh://aetheria", + "2026-08-22T00:00:00Z", + ["fresh", "2026-08-22T00:00:00Z", 15_000], + ["gamecult.eve.surface.v1"], + [["cultcache", "aetheria.cc", "typed state"]], + [["aetheria.hangar", "gamecult.eve.surface.v1", "eve:surface:aetheria.hangar", "cultmesh-record", "active", "interactive-world", + ["provider-authored-world-surface", ["aetheria.frame.v1"], "aetheria.daemon.commands", "eve:commands", "gamecult.eve.command_receipt.v1", "eve:receipts", "eve:assets", ["web-reference"], "provider owns truth"]]], + [["hangar.select_verse", "aetheria.hangar", "cultmesh", "Select Verse"]], + ["aetheria-daemon.soa"], + ]); + assert.equal(advertisement.surfaces[0].worldInteraction.commandBoundary, "aetheria.daemon.commands"); + assert.equal(advertisement.witnesses[0].ref, "aetheria.cc"); +}); + test("command results validate persisted receipts and transient Eve projections together", () => { const result = parseEveCommandResult({ schema: "gamecult.eve.command_result.v1",