Merge Eve invocation and presentation authority
This commit is contained in:
commit
bfdaff2d68
23 changed files with 444 additions and 31 deletions
10
packages/eve-browser-lowering/dist/index.js
vendored
10
packages/eve-browser-lowering/dist/index.js
vendored
|
|
@ -1279,8 +1279,8 @@ export function createInventoryDropIntent(source, target, destinationX, destinat
|
||||||
const targetIndex = numberProp(target.targetIndex, -1);
|
const targetIndex = numberProp(target.targetIndex, -1);
|
||||||
const targetKind = firstString(target.targetKind, "");
|
const targetKind = firstString(target.targetKind, "");
|
||||||
const payload = {};
|
const payload = {};
|
||||||
copyInventoryPayloadProps(source, payload);
|
copyPayloadProps(source, payload);
|
||||||
copyInventoryPayloadProps(target, payload);
|
copyPayloadProps(target, payload);
|
||||||
Object.assign(payload, {
|
Object.assign(payload, {
|
||||||
sourceKind,
|
sourceKind,
|
||||||
originEntityKey: firstString(source.sourceEntityKey, source.entityKey, ""),
|
originEntityKey: firstString(source.sourceEntityKey, source.entityKey, ""),
|
||||||
|
|
@ -1353,7 +1353,7 @@ function parseInventoryCells(value) {
|
||||||
return Number.isInteger(x) && Number.isInteger(y) ? [{ x, y }] : [];
|
return Number.isInteger(x) && Number.isInteger(y) ? [{ x, y }] : [];
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
function copyInventoryPayloadProps(source, payload) {
|
function copyPayloadProps(source, payload) {
|
||||||
for (const [key, value] of Object.entries(source)) {
|
for (const [key, value] of Object.entries(source)) {
|
||||||
if (key.startsWith("payload.") && key.length > "payload.".length)
|
if (key.startsWith("payload.") && key.length > "payload.".length)
|
||||||
payload[key.slice("payload.".length)] = value;
|
payload[key.slice("payload.".length)] = value;
|
||||||
|
|
@ -1702,7 +1702,9 @@ export function createEveCommandIntent(commandId, props = {}, options = {}) {
|
||||||
...stringArray(props.captureBindings),
|
...stringArray(props.captureBindings),
|
||||||
])];
|
])];
|
||||||
const bindings = captureBindingValues(surface?.surface?.root, providerId, surfaceId, captureBindings, options.draftStore);
|
const bindings = captureBindingValues(surface?.surface?.root, providerId, surfaceId, captureBindings, options.draftStore);
|
||||||
const payload = commandPayload(action);
|
const payload = {};
|
||||||
|
copyPayloadProps(props, payload);
|
||||||
|
Object.assign(payload, commandPayload(action));
|
||||||
if (captureBindings.length)
|
if (captureBindings.length)
|
||||||
payload.bindings = bindings;
|
payload.bindings = bindings;
|
||||||
const intent = {
|
const intent = {
|
||||||
|
|
|
||||||
|
|
@ -1520,8 +1520,8 @@ export function createInventoryDropIntent(
|
||||||
const targetIndex = numberProp(target.targetIndex, -1);
|
const targetIndex = numberProp(target.targetIndex, -1);
|
||||||
const targetKind = firstString(target.targetKind, "");
|
const targetKind = firstString(target.targetKind, "");
|
||||||
const payload: Record<string, unknown> = {};
|
const payload: Record<string, unknown> = {};
|
||||||
copyInventoryPayloadProps(source, payload);
|
copyPayloadProps(source, payload);
|
||||||
copyInventoryPayloadProps(target, payload);
|
copyPayloadProps(target, payload);
|
||||||
Object.assign(payload, {
|
Object.assign(payload, {
|
||||||
sourceKind,
|
sourceKind,
|
||||||
originEntityKey: firstString(source.sourceEntityKey, source.entityKey, ""),
|
originEntityKey: firstString(source.sourceEntityKey, source.entityKey, ""),
|
||||||
|
|
@ -1610,7 +1610,7 @@ function parseInventoryCells(value: unknown): InventoryPlacementCell[] {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function copyInventoryPayloadProps(source: Record<string, unknown>, payload: Record<string, unknown>): void {
|
function copyPayloadProps(source: Record<string, unknown>, payload: Record<string, unknown>): void {
|
||||||
for (const [key, value] of Object.entries(source)) {
|
for (const [key, value] of Object.entries(source)) {
|
||||||
if (key.startsWith("payload.") && key.length > "payload.".length)
|
if (key.startsWith("payload.") && key.length > "payload.".length)
|
||||||
payload[key.slice("payload.".length)] = value;
|
payload[key.slice("payload.".length)] = value;
|
||||||
|
|
@ -2007,7 +2007,9 @@ export function createEveCommandIntent(
|
||||||
captureBindings,
|
captureBindings,
|
||||||
options.draftStore,
|
options.draftStore,
|
||||||
);
|
);
|
||||||
const payload = commandPayload(action);
|
const payload: Record<string, unknown> = {};
|
||||||
|
copyPayloadProps(props, payload);
|
||||||
|
Object.assign(payload, commandPayload(action));
|
||||||
if (captureBindings.length) payload.bindings = bindings;
|
if (captureBindings.length) payload.bindings = bindings;
|
||||||
const intent: EveCommandIntent = {
|
const intent: EveCommandIntent = {
|
||||||
schema: "gamecult.eve.command_invocation.v1",
|
schema: "gamecult.eve.command_invocation.v1",
|
||||||
|
|
|
||||||
|
|
@ -59,6 +59,30 @@ test("world interactions emit provider-routed command intents without local auth
|
||||||
assert.equal(intent.payload.directionX, 1);
|
assert.equal(intent.payload.directionX, 1);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("ordinary controls preserve provider-declared payload fields", () => {
|
||||||
|
const intent = createEveCommandIntent("aetheria.hangar.launch", {
|
||||||
|
label: "LAUNCH",
|
||||||
|
command: "aetheria.hangar.launch",
|
||||||
|
"payload.expectedProgressionVerseId": "gamecult.aetheria",
|
||||||
|
"payload.expectedProgressionSourceRevision": "7",
|
||||||
|
}, {
|
||||||
|
activeSurfaceId: "aetheria.hangar",
|
||||||
|
clientId: "browser.hangar-test",
|
||||||
|
provider: { providerId: "aetheria", surfaces: [{
|
||||||
|
surfaceId: "aetheria.hangar",
|
||||||
|
worldInteraction: {
|
||||||
|
commandBoundary: "aetheria.hangar.commands",
|
||||||
|
receiptSchema: "gamecult.eve.command_receipt.v1",
|
||||||
|
},
|
||||||
|
}] },
|
||||||
|
});
|
||||||
|
|
||||||
|
assert.equal(intent.payload.expectedProgressionVerseId, "gamecult.aetheria");
|
||||||
|
assert.equal(intent.payload.expectedProgressionSourceRevision, "7");
|
||||||
|
assert.equal(intent.payload.label, undefined);
|
||||||
|
assert.equal(intent.payload.command, undefined);
|
||||||
|
});
|
||||||
|
|
||||||
test("inventory drops select the provider-advertised operation and preserve spatial identity", () => {
|
test("inventory drops select the provider-advertised operation and preserve spatial identity", () => {
|
||||||
const intent = createInventoryDropIntent({
|
const intent = createInventoryDropIntent({
|
||||||
sourceKind: "cargo",
|
sourceKind: "cargo",
|
||||||
|
|
|
||||||
|
|
@ -18,4 +18,9 @@ export interface EveCommandInvocation {
|
||||||
clientId: string;
|
clientId: string;
|
||||||
commandBoundary: string;
|
commandBoundary: string;
|
||||||
receiptSchema: string;
|
receiptSchema: string;
|
||||||
|
delegation?: {
|
||||||
|
originalInvocationHash: string;
|
||||||
|
originalClientId: string;
|
||||||
|
delegatingRuntimeId: string;
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -13,9 +13,21 @@ export interface EveCommandReceipt {
|
||||||
[k: string]: any;
|
[k: string]: any;
|
||||||
}[];
|
}[];
|
||||||
issuedAtUtc?: string;
|
issuedAtUtc?: string;
|
||||||
|
/**
|
||||||
|
* Provider-state generation that causally owns this result. It is not implicitly an Eve surface version.
|
||||||
|
*/
|
||||||
sourceVersion: number;
|
sourceVersion: number;
|
||||||
|
/**
|
||||||
|
* Optional advertised base-surface version that a renderer must mount before exposing terminal presentation finality.
|
||||||
|
*/
|
||||||
|
presentationSurfaceVersion?: number;
|
||||||
|
/**
|
||||||
|
* Canonical digest of the immutable invocation envelope finalized by this receipt.
|
||||||
|
*/
|
||||||
|
invocationHash?: string;
|
||||||
navigation?: {
|
navigation?: {
|
||||||
verseId: string;
|
verseId: string;
|
||||||
|
authorityRuntimeId?: string;
|
||||||
providerId?: string;
|
providerId?: string;
|
||||||
surfaceId: string;
|
surfaceId: string;
|
||||||
surfaceKind?: string;
|
surfaceKind?: string;
|
||||||
|
|
|
||||||
|
|
@ -32,9 +32,21 @@ export interface EveCommandReceipt {
|
||||||
[k: string]: any;
|
[k: string]: any;
|
||||||
}[];
|
}[];
|
||||||
issuedAtUtc?: string;
|
issuedAtUtc?: string;
|
||||||
|
/**
|
||||||
|
* Provider-state generation that causally owns this result. It is not implicitly an Eve surface version.
|
||||||
|
*/
|
||||||
sourceVersion: number;
|
sourceVersion: number;
|
||||||
|
/**
|
||||||
|
* Optional advertised base-surface version that a renderer must mount before exposing terminal presentation finality.
|
||||||
|
*/
|
||||||
|
presentationSurfaceVersion?: number;
|
||||||
|
/**
|
||||||
|
* Canonical digest of the immutable invocation envelope finalized by this receipt.
|
||||||
|
*/
|
||||||
|
invocationHash?: string;
|
||||||
navigation?: {
|
navigation?: {
|
||||||
verseId: string;
|
verseId: string;
|
||||||
|
authorityRuntimeId?: string;
|
||||||
providerId?: string;
|
providerId?: string;
|
||||||
surfaceId: string;
|
surfaceId: string;
|
||||||
surfaceKind?: string;
|
surfaceKind?: string;
|
||||||
|
|
|
||||||
|
|
@ -596,6 +596,25 @@ export declare const eveContractSchemas: {
|
||||||
readonly type: "string";
|
readonly type: "string";
|
||||||
readonly minLength: 1;
|
readonly minLength: 1;
|
||||||
};
|
};
|
||||||
|
readonly delegation: {
|
||||||
|
readonly type: "object";
|
||||||
|
readonly required: readonly ["originalInvocationHash", "originalClientId", "delegatingRuntimeId"];
|
||||||
|
readonly properties: {
|
||||||
|
readonly originalInvocationHash: {
|
||||||
|
readonly type: "string";
|
||||||
|
readonly minLength: 1;
|
||||||
|
};
|
||||||
|
readonly originalClientId: {
|
||||||
|
readonly type: "string";
|
||||||
|
readonly minLength: 1;
|
||||||
|
};
|
||||||
|
readonly delegatingRuntimeId: {
|
||||||
|
readonly type: "string";
|
||||||
|
readonly minLength: 1;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
readonly additionalProperties: false;
|
||||||
|
};
|
||||||
};
|
};
|
||||||
readonly additionalProperties: false;
|
readonly additionalProperties: false;
|
||||||
};
|
};
|
||||||
|
|
@ -653,6 +672,17 @@ export declare const eveContractSchemas: {
|
||||||
readonly sourceVersion: {
|
readonly sourceVersion: {
|
||||||
readonly type: "integer";
|
readonly type: "integer";
|
||||||
readonly minimum: 0;
|
readonly minimum: 0;
|
||||||
|
readonly description: "Provider-state generation that causally owns this result. It is not implicitly an Eve surface version.";
|
||||||
|
};
|
||||||
|
readonly presentationSurfaceVersion: {
|
||||||
|
readonly type: "integer";
|
||||||
|
readonly minimum: 1;
|
||||||
|
readonly description: "Optional advertised base-surface version that a renderer must mount before exposing terminal presentation finality.";
|
||||||
|
};
|
||||||
|
readonly invocationHash: {
|
||||||
|
readonly type: "string";
|
||||||
|
readonly minLength: 1;
|
||||||
|
readonly description: "Canonical digest of the immutable invocation envelope finalized by this receipt.";
|
||||||
};
|
};
|
||||||
readonly navigation: {
|
readonly navigation: {
|
||||||
readonly type: "object";
|
readonly type: "object";
|
||||||
|
|
@ -662,6 +692,10 @@ export declare const eveContractSchemas: {
|
||||||
readonly type: "string";
|
readonly type: "string";
|
||||||
readonly minLength: 1;
|
readonly minLength: 1;
|
||||||
};
|
};
|
||||||
|
readonly authorityRuntimeId: {
|
||||||
|
readonly type: "string";
|
||||||
|
readonly minLength: 1;
|
||||||
|
};
|
||||||
readonly providerId: {
|
readonly providerId: {
|
||||||
readonly type: "string";
|
readonly type: "string";
|
||||||
};
|
};
|
||||||
|
|
|
||||||
40
packages/eve-contracts/dist/generated/schemas.js
vendored
40
packages/eve-contracts/dist/generated/schemas.js
vendored
|
|
@ -714,6 +714,29 @@ export const eveContractSchemas = {
|
||||||
"receiptSchema": {
|
"receiptSchema": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"minLength": 1
|
"minLength": 1
|
||||||
|
},
|
||||||
|
"delegation": {
|
||||||
|
"type": "object",
|
||||||
|
"required": [
|
||||||
|
"originalInvocationHash",
|
||||||
|
"originalClientId",
|
||||||
|
"delegatingRuntimeId"
|
||||||
|
],
|
||||||
|
"properties": {
|
||||||
|
"originalInvocationHash": {
|
||||||
|
"type": "string",
|
||||||
|
"minLength": 1
|
||||||
|
},
|
||||||
|
"originalClientId": {
|
||||||
|
"type": "string",
|
||||||
|
"minLength": 1
|
||||||
|
},
|
||||||
|
"delegatingRuntimeId": {
|
||||||
|
"type": "string",
|
||||||
|
"minLength": 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"additionalProperties": false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"additionalProperties": false
|
"additionalProperties": false
|
||||||
|
|
@ -787,7 +810,18 @@ export const eveContractSchemas = {
|
||||||
},
|
},
|
||||||
"sourceVersion": {
|
"sourceVersion": {
|
||||||
"type": "integer",
|
"type": "integer",
|
||||||
"minimum": 0
|
"minimum": 0,
|
||||||
|
"description": "Provider-state generation that causally owns this result. It is not implicitly an Eve surface version."
|
||||||
|
},
|
||||||
|
"presentationSurfaceVersion": {
|
||||||
|
"type": "integer",
|
||||||
|
"minimum": 1,
|
||||||
|
"description": "Optional advertised base-surface version that a renderer must mount before exposing terminal presentation finality."
|
||||||
|
},
|
||||||
|
"invocationHash": {
|
||||||
|
"type": "string",
|
||||||
|
"minLength": 1,
|
||||||
|
"description": "Canonical digest of the immutable invocation envelope finalized by this receipt."
|
||||||
},
|
},
|
||||||
"navigation": {
|
"navigation": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
|
|
@ -800,6 +834,10 @@ export const eveContractSchemas = {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"minLength": 1
|
"minLength": 1
|
||||||
},
|
},
|
||||||
|
"authorityRuntimeId": {
|
||||||
|
"type": "string",
|
||||||
|
"minLength": 1
|
||||||
|
},
|
||||||
"providerId": {
|
"providerId": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -20,4 +20,9 @@ issuedAt: string
|
||||||
clientId: string
|
clientId: string
|
||||||
commandBoundary: string
|
commandBoundary: string
|
||||||
receiptSchema: string
|
receiptSchema: string
|
||||||
|
delegation?: {
|
||||||
|
originalInvocationHash: string
|
||||||
|
originalClientId: string
|
||||||
|
delegatingRuntimeId: string
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -15,9 +15,21 @@ diagnostics?: {
|
||||||
[k: string]: any
|
[k: string]: any
|
||||||
}[]
|
}[]
|
||||||
issuedAtUtc?: string
|
issuedAtUtc?: string
|
||||||
|
/**
|
||||||
|
* Provider-state generation that causally owns this result. It is not implicitly an Eve surface version.
|
||||||
|
*/
|
||||||
sourceVersion: number
|
sourceVersion: number
|
||||||
|
/**
|
||||||
|
* Optional advertised base-surface version that a renderer must mount before exposing terminal presentation finality.
|
||||||
|
*/
|
||||||
|
presentationSurfaceVersion?: number
|
||||||
|
/**
|
||||||
|
* Canonical digest of the immutable invocation envelope finalized by this receipt.
|
||||||
|
*/
|
||||||
|
invocationHash?: string
|
||||||
navigation?: {
|
navigation?: {
|
||||||
verseId: string
|
verseId: string
|
||||||
|
authorityRuntimeId?: string
|
||||||
providerId?: string
|
providerId?: string
|
||||||
surfaceId: string
|
surfaceId: string
|
||||||
surfaceKind?: string
|
surfaceKind?: string
|
||||||
|
|
|
||||||
|
|
@ -34,9 +34,21 @@ diagnostics?: {
|
||||||
[k: string]: any
|
[k: string]: any
|
||||||
}[]
|
}[]
|
||||||
issuedAtUtc?: string
|
issuedAtUtc?: string
|
||||||
|
/**
|
||||||
|
* Provider-state generation that causally owns this result. It is not implicitly an Eve surface version.
|
||||||
|
*/
|
||||||
sourceVersion: number
|
sourceVersion: number
|
||||||
|
/**
|
||||||
|
* Optional advertised base-surface version that a renderer must mount before exposing terminal presentation finality.
|
||||||
|
*/
|
||||||
|
presentationSurfaceVersion?: number
|
||||||
|
/**
|
||||||
|
* Canonical digest of the immutable invocation envelope finalized by this receipt.
|
||||||
|
*/
|
||||||
|
invocationHash?: string
|
||||||
navigation?: {
|
navigation?: {
|
||||||
verseId: string
|
verseId: string
|
||||||
|
authorityRuntimeId?: string
|
||||||
providerId?: string
|
providerId?: string
|
||||||
surfaceId: string
|
surfaceId: string
|
||||||
surfaceKind?: string
|
surfaceKind?: string
|
||||||
|
|
|
||||||
|
|
@ -714,6 +714,29 @@ export const eveContractSchemas = {
|
||||||
"receiptSchema": {
|
"receiptSchema": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"minLength": 1
|
"minLength": 1
|
||||||
|
},
|
||||||
|
"delegation": {
|
||||||
|
"type": "object",
|
||||||
|
"required": [
|
||||||
|
"originalInvocationHash",
|
||||||
|
"originalClientId",
|
||||||
|
"delegatingRuntimeId"
|
||||||
|
],
|
||||||
|
"properties": {
|
||||||
|
"originalInvocationHash": {
|
||||||
|
"type": "string",
|
||||||
|
"minLength": 1
|
||||||
|
},
|
||||||
|
"originalClientId": {
|
||||||
|
"type": "string",
|
||||||
|
"minLength": 1
|
||||||
|
},
|
||||||
|
"delegatingRuntimeId": {
|
||||||
|
"type": "string",
|
||||||
|
"minLength": 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"additionalProperties": false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"additionalProperties": false
|
"additionalProperties": false
|
||||||
|
|
@ -787,7 +810,18 @@ export const eveContractSchemas = {
|
||||||
},
|
},
|
||||||
"sourceVersion": {
|
"sourceVersion": {
|
||||||
"type": "integer",
|
"type": "integer",
|
||||||
"minimum": 0
|
"minimum": 0,
|
||||||
|
"description": "Provider-state generation that causally owns this result. It is not implicitly an Eve surface version."
|
||||||
|
},
|
||||||
|
"presentationSurfaceVersion": {
|
||||||
|
"type": "integer",
|
||||||
|
"minimum": 1,
|
||||||
|
"description": "Optional advertised base-surface version that a renderer must mount before exposing terminal presentation finality."
|
||||||
|
},
|
||||||
|
"invocationHash": {
|
||||||
|
"type": "string",
|
||||||
|
"minLength": 1,
|
||||||
|
"description": "Canonical digest of the immutable invocation envelope finalized by this receipt."
|
||||||
},
|
},
|
||||||
"navigation": {
|
"navigation": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
|
|
@ -800,6 +834,10 @@ export const eveContractSchemas = {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"minLength": 1
|
"minLength": 1
|
||||||
},
|
},
|
||||||
|
"authorityRuntimeId": {
|
||||||
|
"type": "string",
|
||||||
|
"minLength": 1
|
||||||
|
},
|
||||||
"providerId": {
|
"providerId": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -82,6 +82,7 @@ test("accepts canonical provider, surface, command, invocation, and receipt docu
|
||||||
providerId: "voidbot.swarm",
|
providerId: "voidbot.swarm",
|
||||||
surfaceId: "voidbot.swarm",
|
surfaceId: "voidbot.swarm",
|
||||||
sourceVersion: 1,
|
sourceVersion: 1,
|
||||||
|
invocationHash: "sha256:command-1",
|
||||||
}).state, "reconciled");
|
}).state, "reconciled");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@
|
||||||
<ImplicitUsings>enable</ImplicitUsings>
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
<Description>Portable Eve surface and provider contracts.</Description>
|
<Description>Portable Eve surface and provider contracts.</Description>
|
||||||
<PackageId>GameCult.Eve.Surface</PackageId>
|
<PackageId>GameCult.Eve.Surface</PackageId>
|
||||||
<Version>0.3.2</Version>
|
<Version>0.3.6</Version>
|
||||||
<Authors>GameCult</Authors>
|
<Authors>GameCult</Authors>
|
||||||
<PackageLicenseExpression>MPL-2.0</PackageLicenseExpression>
|
<PackageLicenseExpression>MPL-2.0</PackageLicenseExpression>
|
||||||
<PackageProjectUrl>https://github.com/GameCult/Eve</PackageProjectUrl>
|
<PackageProjectUrl>https://github.com/GameCult/Eve</PackageProjectUrl>
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,53 @@
|
||||||
|
using System;
|
||||||
|
using System.Globalization;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Security.Cryptography;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
#nullable enable
|
||||||
|
|
||||||
|
namespace GameCult.Eve.Surface
|
||||||
|
{
|
||||||
|
/// <summary>Canonical digest for one immutable Eve command invocation.</summary>
|
||||||
|
public static class EveCommandInvocationHash
|
||||||
|
{
|
||||||
|
public static string Compute(EveSurfaceCommandRequest request)
|
||||||
|
{
|
||||||
|
if (request == null) throw new ArgumentNullException(nameof(request));
|
||||||
|
var canonical = new StringBuilder();
|
||||||
|
Append(request.Schema);
|
||||||
|
Append(request.ProviderId);
|
||||||
|
Append(request.SurfaceId);
|
||||||
|
Append(request.OperationRecord.OperationId);
|
||||||
|
Append(request.OperationRecord.SchemaId);
|
||||||
|
Append(request.OperationRecord.RouteKind);
|
||||||
|
Append(request.OperationRecord.RouteDescription);
|
||||||
|
Append(request.OperationRecord.IdempotencyKey);
|
||||||
|
foreach (var field in request.PayloadFields.OrderBy(pair => pair.Key, StringComparer.Ordinal))
|
||||||
|
{
|
||||||
|
Append(field.Key);
|
||||||
|
Append(field.Value);
|
||||||
|
}
|
||||||
|
Append(request.IssuedAt.ToUniversalTime().ToString("O", CultureInfo.InvariantCulture));
|
||||||
|
Append(request.ClientId);
|
||||||
|
Append(request.CommandBoundary);
|
||||||
|
Append(request.ReceiptSchema);
|
||||||
|
Append(request.Delegation?.OriginalInvocationHash);
|
||||||
|
Append(request.Delegation?.OriginalClientId);
|
||||||
|
Append(request.Delegation?.DelegatingRuntimeId);
|
||||||
|
using var sha256 = SHA256.Create();
|
||||||
|
var bytes = sha256.ComputeHash(Encoding.UTF8.GetBytes(canonical.ToString()));
|
||||||
|
var digest = new StringBuilder(bytes.Length * 2);
|
||||||
|
foreach (var value in bytes) digest.Append(value.ToString("x2", CultureInfo.InvariantCulture));
|
||||||
|
return digest.ToString();
|
||||||
|
|
||||||
|
void Append(string? value)
|
||||||
|
{
|
||||||
|
value ??= "";
|
||||||
|
canonical.Append(value.Length.ToString(CultureInfo.InvariantCulture));
|
||||||
|
canonical.Append(':');
|
||||||
|
canonical.Append(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,11 @@
|
||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 7d5f81f817c44d1ab25d76166354364d
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
|
|
@ -366,8 +366,10 @@ namespace GameCult.Eve.Surface
|
||||||
string message,
|
string message,
|
||||||
string issuedAtUtc,
|
string issuedAtUtc,
|
||||||
long sourceVersion,
|
long sourceVersion,
|
||||||
EveSurfaceNavigationTarget? navigation = null)
|
EveSurfaceNavigationTarget? navigation = null,
|
||||||
: this(SchemaId, receiptId, commandId, command, state, ownerRepo, authority, providerId, surfaceId, message, issuedAtUtc, sourceVersion, navigation)
|
string invocationHash = "",
|
||||||
|
long presentationSurfaceVersion = 0)
|
||||||
|
: this(SchemaId, receiptId, commandId, command, state, ownerRepo, authority, providerId, surfaceId, message, issuedAtUtc, sourceVersion, navigation, invocationHash, presentationSurfaceVersion)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -385,7 +387,9 @@ namespace GameCult.Eve.Surface
|
||||||
string message,
|
string message,
|
||||||
string issuedAtUtc,
|
string issuedAtUtc,
|
||||||
long sourceVersion,
|
long sourceVersion,
|
||||||
EveSurfaceNavigationTarget? navigation = null)
|
EveSurfaceNavigationTarget? navigation = null,
|
||||||
|
string invocationHash = "",
|
||||||
|
long presentationSurfaceVersion = 0)
|
||||||
{
|
{
|
||||||
Schema = string.IsNullOrWhiteSpace(schema) ? SchemaId : schema;
|
Schema = string.IsNullOrWhiteSpace(schema) ? SchemaId : schema;
|
||||||
ReceiptId = receiptId ?? "";
|
ReceiptId = receiptId ?? "";
|
||||||
|
|
@ -400,6 +404,8 @@ namespace GameCult.Eve.Surface
|
||||||
IssuedAtUtc = issuedAtUtc ?? "";
|
IssuedAtUtc = issuedAtUtc ?? "";
|
||||||
SourceVersion = sourceVersion;
|
SourceVersion = sourceVersion;
|
||||||
Navigation = navigation;
|
Navigation = navigation;
|
||||||
|
InvocationHash = invocationHash ?? "";
|
||||||
|
PresentationSurfaceVersion = presentationSurfaceVersion;
|
||||||
}
|
}
|
||||||
|
|
||||||
[Key(0)] public string Schema { get; }
|
[Key(0)] public string Schema { get; }
|
||||||
|
|
@ -415,6 +421,8 @@ namespace GameCult.Eve.Surface
|
||||||
[Key(10)] public string IssuedAtUtc { get; }
|
[Key(10)] public string IssuedAtUtc { get; }
|
||||||
[Key(11)] public long SourceVersion { get; }
|
[Key(11)] public long SourceVersion { get; }
|
||||||
[Key(12)] public EveSurfaceNavigationTarget? Navigation { get; }
|
[Key(12)] public EveSurfaceNavigationTarget? Navigation { get; }
|
||||||
|
[Key(13)] public string InvocationHash { get; }
|
||||||
|
[Key(14)] public long PresentationSurfaceVersion { get; }
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -424,7 +432,7 @@ namespace GameCult.Eve.Surface
|
||||||
public sealed class EveCommandReceiptCompatibilityFormatter :
|
public sealed class EveCommandReceiptCompatibilityFormatter :
|
||||||
IMessagePackFormatter<EveCommandReceiptDocument?>
|
IMessagePackFormatter<EveCommandReceiptDocument?>
|
||||||
{
|
{
|
||||||
private const int LegacyFieldCount = 13;
|
private const int LegacyFieldCount = 15;
|
||||||
private const int RequiredAndScalarFieldCount = 12;
|
private const int RequiredAndScalarFieldCount = 12;
|
||||||
|
|
||||||
public void Serialize(
|
public void Serialize(
|
||||||
|
|
@ -434,9 +442,10 @@ namespace GameCult.Eve.Surface
|
||||||
{
|
{
|
||||||
if (value == null) { writer.WriteNil(); return; }
|
if (value == null) { writer.WriteNil(); return; }
|
||||||
|
|
||||||
writer.WriteMapHeader(value.Navigation == null
|
var optionalFields = (value.Navigation == null ? 0 : 1) +
|
||||||
? RequiredAndScalarFieldCount
|
(string.IsNullOrWhiteSpace(value.InvocationHash) ? 0 : 1) +
|
||||||
: RequiredAndScalarFieldCount + 1);
|
(value.PresentationSurfaceVersion <= 0 ? 0 : 1);
|
||||||
|
writer.WriteMapHeader(RequiredAndScalarFieldCount + optionalFields);
|
||||||
Write(ref writer, "schema", value.Schema);
|
Write(ref writer, "schema", value.Schema);
|
||||||
Write(ref writer, "receiptId", value.ReceiptId);
|
Write(ref writer, "receiptId", value.ReceiptId);
|
||||||
Write(ref writer, "commandId", value.CommandId);
|
Write(ref writer, "commandId", value.CommandId);
|
||||||
|
|
@ -454,6 +463,13 @@ namespace GameCult.Eve.Surface
|
||||||
writer.Write("navigation");
|
writer.Write("navigation");
|
||||||
WriteNavigation(ref writer, value.Navigation);
|
WriteNavigation(ref writer, value.Navigation);
|
||||||
}
|
}
|
||||||
|
if (!string.IsNullOrWhiteSpace(value.InvocationHash))
|
||||||
|
Write(ref writer, "invocationHash", value.InvocationHash);
|
||||||
|
if (value.PresentationSurfaceVersion > 0)
|
||||||
|
{
|
||||||
|
writer.Write("presentationSurfaceVersion");
|
||||||
|
writer.Write(value.PresentationSurfaceVersion);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public EveCommandReceiptDocument? Deserialize(
|
public EveCommandReceiptDocument? Deserialize(
|
||||||
|
|
@ -483,10 +499,13 @@ namespace GameCult.Eve.Surface
|
||||||
var issuedAtUtc = ReadString(ref reader, fields, 10);
|
var issuedAtUtc = ReadString(ref reader, fields, 10);
|
||||||
var sourceVersion = fields > 11 ? reader.ReadInt64() : 0;
|
var sourceVersion = fields > 11 ? reader.ReadInt64() : 0;
|
||||||
var navigation = fields > 12 ? ReadNavigation(ref reader, options) : null;
|
var navigation = fields > 12 ? ReadNavigation(ref reader, options) : null;
|
||||||
|
var invocationHash = ReadString(ref reader, fields, 13);
|
||||||
|
var presentationSurfaceVersion = fields > 14 ? reader.ReadInt64() : 0;
|
||||||
for (var index = LegacyFieldCount; index < fields; index++) reader.Skip();
|
for (var index = LegacyFieldCount; index < fields; index++) reader.Skip();
|
||||||
return new EveCommandReceiptDocument(
|
return new EveCommandReceiptDocument(
|
||||||
schema, receiptId, commandId, command, state, ownerRepo, authority,
|
schema, receiptId, commandId, command, state, ownerRepo, authority,
|
||||||
providerId, surfaceId, message, issuedAtUtc, sourceVersion, navigation);
|
providerId, surfaceId, message, issuedAtUtc, sourceVersion, navigation, invocationHash,
|
||||||
|
presentationSurfaceVersion);
|
||||||
}
|
}
|
||||||
finally { reader.Depth--; }
|
finally { reader.Depth--; }
|
||||||
}
|
}
|
||||||
|
|
@ -511,6 +530,8 @@ namespace GameCult.Eve.Surface
|
||||||
var issuedAtUtc = "";
|
var issuedAtUtc = "";
|
||||||
long sourceVersion = 0;
|
long sourceVersion = 0;
|
||||||
EveSurfaceNavigationTarget? navigation = null;
|
EveSurfaceNavigationTarget? navigation = null;
|
||||||
|
var invocationHash = "";
|
||||||
|
long presentationSurfaceVersion = 0;
|
||||||
var fields = reader.ReadMapHeader();
|
var fields = reader.ReadMapHeader();
|
||||||
for (var index = 0; index < fields; index++)
|
for (var index = 0; index < fields; index++)
|
||||||
{
|
{
|
||||||
|
|
@ -529,13 +550,16 @@ namespace GameCult.Eve.Surface
|
||||||
case "issuedAtUtc": issuedAtUtc = ReadString(ref reader); break;
|
case "issuedAtUtc": issuedAtUtc = ReadString(ref reader); break;
|
||||||
case "sourceVersion": sourceVersion = reader.ReadInt64(); break;
|
case "sourceVersion": sourceVersion = reader.ReadInt64(); break;
|
||||||
case "navigation": navigation = ReadNavigation(ref reader, options); break;
|
case "navigation": navigation = ReadNavigation(ref reader, options); break;
|
||||||
|
case "invocationHash": invocationHash = ReadString(ref reader); break;
|
||||||
|
case "presentationSurfaceVersion": presentationSurfaceVersion = reader.ReadInt64(); break;
|
||||||
default: reader.Skip(); break;
|
default: reader.Skip(); break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return new EveCommandReceiptDocument(
|
return new EveCommandReceiptDocument(
|
||||||
schema, receiptId, commandId, command, state, ownerRepo, authority,
|
schema, receiptId, commandId, command, state, ownerRepo, authority,
|
||||||
providerId, surfaceId, message, issuedAtUtc, sourceVersion, navigation);
|
providerId, surfaceId, message, issuedAtUtc, sourceVersion, navigation, invocationHash,
|
||||||
|
presentationSurfaceVersion);
|
||||||
}
|
}
|
||||||
finally { reader.Depth--; }
|
finally { reader.Depth--; }
|
||||||
}
|
}
|
||||||
|
|
@ -546,8 +570,9 @@ namespace GameCult.Eve.Surface
|
||||||
{
|
{
|
||||||
if (navigation == null) { writer.WriteNil(); return; }
|
if (navigation == null) { writer.WriteNil(); return; }
|
||||||
|
|
||||||
writer.WriteMapHeader(5);
|
writer.WriteMapHeader(6);
|
||||||
Write(ref writer, "verseId", navigation.VerseId);
|
Write(ref writer, "verseId", navigation.VerseId);
|
||||||
|
Write(ref writer, "authorityRuntimeId", navigation.AuthorityRuntimeId);
|
||||||
Write(ref writer, "providerId", navigation.ProviderId);
|
Write(ref writer, "providerId", navigation.ProviderId);
|
||||||
Write(ref writer, "surfaceId", navigation.SurfaceId);
|
Write(ref writer, "surfaceId", navigation.SurfaceId);
|
||||||
Write(ref writer, "surfaceKind", navigation.SurfaceKind);
|
Write(ref writer, "surfaceKind", navigation.SurfaceKind);
|
||||||
|
|
@ -571,6 +596,7 @@ namespace GameCult.Eve.Surface
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var verseId = "";
|
var verseId = "";
|
||||||
|
var authorityRuntimeId = "";
|
||||||
var providerId = "";
|
var providerId = "";
|
||||||
var surfaceId = "";
|
var surfaceId = "";
|
||||||
var surfaceKind = "";
|
var surfaceKind = "";
|
||||||
|
|
@ -581,6 +607,7 @@ namespace GameCult.Eve.Surface
|
||||||
switch (reader.ReadString())
|
switch (reader.ReadString())
|
||||||
{
|
{
|
||||||
case "verseId": verseId = ReadString(ref reader); break;
|
case "verseId": verseId = ReadString(ref reader); break;
|
||||||
|
case "authorityRuntimeId": authorityRuntimeId = ReadString(ref reader); break;
|
||||||
case "providerId": providerId = ReadString(ref reader); break;
|
case "providerId": providerId = ReadString(ref reader); break;
|
||||||
case "surfaceId": surfaceId = ReadString(ref reader); break;
|
case "surfaceId": surfaceId = ReadString(ref reader); break;
|
||||||
case "surfaceKind": surfaceKind = ReadString(ref reader); break;
|
case "surfaceKind": surfaceKind = ReadString(ref reader); break;
|
||||||
|
|
@ -588,7 +615,8 @@ namespace GameCult.Eve.Surface
|
||||||
default: reader.Skip(); break;
|
default: reader.Skip(); break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return new EveSurfaceNavigationTarget(verseId, providerId, surfaceId, surfaceKind, endpoints);
|
return new EveSurfaceNavigationTarget(
|
||||||
|
verseId, providerId, surfaceId, surfaceKind, endpoints, authorityRuntimeId);
|
||||||
}
|
}
|
||||||
finally { reader.Depth--; }
|
finally { reader.Depth--; }
|
||||||
}
|
}
|
||||||
|
|
@ -626,9 +654,11 @@ namespace GameCult.Eve.Surface
|
||||||
string providerId,
|
string providerId,
|
||||||
string surfaceId,
|
string surfaceId,
|
||||||
string surfaceKind,
|
string surfaceKind,
|
||||||
string[]? rendezvousEndpoints = null)
|
string[]? rendezvousEndpoints = null,
|
||||||
|
string authorityRuntimeId = "")
|
||||||
{
|
{
|
||||||
VerseId = verseId ?? "";
|
VerseId = verseId ?? "";
|
||||||
|
AuthorityRuntimeId = authorityRuntimeId ?? "";
|
||||||
ProviderId = providerId ?? "";
|
ProviderId = providerId ?? "";
|
||||||
SurfaceId = surfaceId ?? "";
|
SurfaceId = surfaceId ?? "";
|
||||||
SurfaceKind = surfaceKind ?? "";
|
SurfaceKind = surfaceKind ?? "";
|
||||||
|
|
@ -640,5 +670,6 @@ namespace GameCult.Eve.Surface
|
||||||
[Key(2)] public string SurfaceId { get; }
|
[Key(2)] public string SurfaceId { get; }
|
||||||
[Key(3)] public string SurfaceKind { get; }
|
[Key(3)] public string SurfaceKind { get; }
|
||||||
[Key(4)] public string[] RendezvousEndpoints { get; }
|
[Key(4)] public string[] RendezvousEndpoints { get; }
|
||||||
|
[Key(5)] public string AuthorityRuntimeId { get; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -335,7 +335,8 @@ namespace GameCult.Eve.Surface
|
||||||
DateTimeOffset issuedAt,
|
DateTimeOffset issuedAt,
|
||||||
string clientId,
|
string clientId,
|
||||||
string commandBoundary = "",
|
string commandBoundary = "",
|
||||||
string receiptSchema = "")
|
string receiptSchema = "",
|
||||||
|
EveCommandDelegationRecord? delegation = null)
|
||||||
: this(
|
: this(
|
||||||
SchemaId,
|
SchemaId,
|
||||||
providerId,
|
providerId,
|
||||||
|
|
@ -346,7 +347,8 @@ namespace GameCult.Eve.Surface
|
||||||
issuedAt,
|
issuedAt,
|
||||||
clientId,
|
clientId,
|
||||||
commandBoundary,
|
commandBoundary,
|
||||||
receiptSchema)
|
receiptSchema,
|
||||||
|
delegation)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -360,7 +362,8 @@ namespace GameCult.Eve.Surface
|
||||||
DateTimeOffset issuedAt,
|
DateTimeOffset issuedAt,
|
||||||
string clientId,
|
string clientId,
|
||||||
string commandBoundary,
|
string commandBoundary,
|
||||||
string receiptSchema)
|
string receiptSchema,
|
||||||
|
EveCommandDelegationRecord? delegation = null)
|
||||||
{
|
{
|
||||||
Schema = string.IsNullOrWhiteSpace(schema) ? SchemaId : schema;
|
Schema = string.IsNullOrWhiteSpace(schema) ? SchemaId : schema;
|
||||||
ProviderId = providerId;
|
ProviderId = providerId;
|
||||||
|
|
@ -373,6 +376,7 @@ namespace GameCult.Eve.Surface
|
||||||
ClientId = clientId;
|
ClientId = clientId;
|
||||||
CommandBoundary = commandBoundary ?? "";
|
CommandBoundary = commandBoundary ?? "";
|
||||||
ReceiptSchema = receiptSchema ?? "";
|
ReceiptSchema = receiptSchema ?? "";
|
||||||
|
Delegation = delegation;
|
||||||
}
|
}
|
||||||
|
|
||||||
[Key(0)]
|
[Key(0)]
|
||||||
|
|
@ -411,10 +415,32 @@ namespace GameCult.Eve.Surface
|
||||||
[Key(8)]
|
[Key(8)]
|
||||||
public string ReceiptSchema { get; }
|
public string ReceiptSchema { get; }
|
||||||
|
|
||||||
|
[Key(9)]
|
||||||
|
public EveCommandDelegationRecord? Delegation { get; }
|
||||||
|
|
||||||
[IgnoreMember]
|
[IgnoreMember]
|
||||||
public string CommandId => Operation.IdempotencyKey ?? "";
|
public string CommandId => Operation.IdempotencyKey ?? "";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[MessagePackObject]
|
||||||
|
public sealed class EveCommandDelegationRecord
|
||||||
|
{
|
||||||
|
[SerializationConstructor]
|
||||||
|
public EveCommandDelegationRecord(
|
||||||
|
string originalInvocationHash,
|
||||||
|
string originalClientId,
|
||||||
|
string delegatingRuntimeId)
|
||||||
|
{
|
||||||
|
OriginalInvocationHash = originalInvocationHash ?? "";
|
||||||
|
OriginalClientId = originalClientId ?? "";
|
||||||
|
DelegatingRuntimeId = delegatingRuntimeId ?? "";
|
||||||
|
}
|
||||||
|
|
||||||
|
[Key(0)] public string OriginalInvocationHash { get; }
|
||||||
|
[Key(1)] public string OriginalClientId { get; }
|
||||||
|
[Key(2)] public string DelegatingRuntimeId { get; }
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Reads the current Eve surface envelope and the original seven-field v1 envelope.
|
/// Reads the current Eve surface envelope and the original seven-field v1 envelope.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"name": "org.gamecult.eve.surface",
|
"name": "org.gamecult.eve.surface",
|
||||||
"displayName": "Eve Surface Contract",
|
"displayName": "Eve Surface Contract",
|
||||||
"version": "0.3.2",
|
"version": "0.3.6",
|
||||||
"unity": "2021.3",
|
"unity": "2021.3",
|
||||||
"description": "Renderer-neutral C# contracts and builders for gamecult.eve.surface.v1 documents.",
|
"description": "Renderer-neutral C# contracts and builders for gamecult.eve.surface.v1 documents.",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
|
|
||||||
|
|
@ -33,7 +33,17 @@
|
||||||
"issuedAt": { "type": "string" },
|
"issuedAt": { "type": "string" },
|
||||||
"clientId": { "type": "string", "minLength": 1 },
|
"clientId": { "type": "string", "minLength": 1 },
|
||||||
"commandBoundary": { "type": "string", "minLength": 1 },
|
"commandBoundary": { "type": "string", "minLength": 1 },
|
||||||
"receiptSchema": { "type": "string", "minLength": 1 }
|
"receiptSchema": { "type": "string", "minLength": 1 },
|
||||||
|
"delegation": {
|
||||||
|
"type": "object",
|
||||||
|
"required": ["originalInvocationHash", "originalClientId", "delegatingRuntimeId"],
|
||||||
|
"properties": {
|
||||||
|
"originalInvocationHash": { "type": "string", "minLength": 1 },
|
||||||
|
"originalClientId": { "type": "string", "minLength": 1 },
|
||||||
|
"delegatingRuntimeId": { "type": "string", "minLength": 1 }
|
||||||
|
},
|
||||||
|
"additionalProperties": false
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"additionalProperties": false
|
"additionalProperties": false
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -67,7 +67,18 @@
|
||||||
},
|
},
|
||||||
"sourceVersion": {
|
"sourceVersion": {
|
||||||
"type": "integer",
|
"type": "integer",
|
||||||
"minimum": 0
|
"minimum": 0,
|
||||||
|
"description": "Provider-state generation that causally owns this result. It is not implicitly an Eve surface version."
|
||||||
|
},
|
||||||
|
"presentationSurfaceVersion": {
|
||||||
|
"type": "integer",
|
||||||
|
"minimum": 1,
|
||||||
|
"description": "Optional advertised base-surface version that a renderer must mount before exposing terminal presentation finality."
|
||||||
|
},
|
||||||
|
"invocationHash": {
|
||||||
|
"type": "string",
|
||||||
|
"minLength": 1,
|
||||||
|
"description": "Canonical digest of the immutable invocation envelope finalized by this receipt."
|
||||||
},
|
},
|
||||||
"navigation": {
|
"navigation": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
|
|
@ -80,6 +91,10 @@
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"minLength": 1
|
"minLength": 1
|
||||||
},
|
},
|
||||||
|
"authorityRuntimeId": {
|
||||||
|
"type": "string",
|
||||||
|
"minLength": 1
|
||||||
|
},
|
||||||
"providerId": {
|
"providerId": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -68,6 +68,62 @@ public sealed class EveSurfaceSerializationCompatibilityTests
|
||||||
|
|
||||||
private static readonly MessagePackSerializerOptions Options = MessagePackSerializerOptions.Standard;
|
private static readonly MessagePackSerializerOptions Options = MessagePackSerializerOptions.Standard;
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void CommandInvocationHashBindsEveryImmutableEnvelopeField()
|
||||||
|
{
|
||||||
|
var request = new EveSurfaceCommandRequest(
|
||||||
|
"provider",
|
||||||
|
"surface",
|
||||||
|
new CultMeshOperationInvocationDescriptor(
|
||||||
|
"launch",
|
||||||
|
"launch.v1",
|
||||||
|
new CultMeshRouteHint(CultMeshLocalityKind.Network, "commands"),
|
||||||
|
"command-1"),
|
||||||
|
CultMesh.OperationPayload(("shipId", "ship:one")),
|
||||||
|
DateTimeOffset.Parse("2026-08-19T00:00:00Z"),
|
||||||
|
"pilot:one",
|
||||||
|
"eve:commands",
|
||||||
|
EveCommandReceiptDocument.SchemaId);
|
||||||
|
var same = new EveSurfaceCommandRequest(
|
||||||
|
request.Schema,
|
||||||
|
request.ProviderId,
|
||||||
|
request.SurfaceId,
|
||||||
|
request.OperationRecord,
|
||||||
|
new Dictionary<string, string>(request.PayloadFields, StringComparer.Ordinal),
|
||||||
|
request.IssuedAt,
|
||||||
|
request.ClientId,
|
||||||
|
request.CommandBoundary,
|
||||||
|
request.ReceiptSchema);
|
||||||
|
var changedPayload = new EveSurfaceCommandRequest(
|
||||||
|
request.ProviderId,
|
||||||
|
request.SurfaceId,
|
||||||
|
request.Operation,
|
||||||
|
CultMesh.OperationPayload(("shipId", "ship:two")),
|
||||||
|
request.IssuedAt,
|
||||||
|
request.ClientId,
|
||||||
|
request.CommandBoundary,
|
||||||
|
request.ReceiptSchema);
|
||||||
|
|
||||||
|
Assert.That(EveCommandInvocationHash.Compute(same), Is.EqualTo(EveCommandInvocationHash.Compute(request)));
|
||||||
|
Assert.That(EveCommandInvocationHash.Compute(changedPayload), Is.Not.EqualTo(EveCommandInvocationHash.Compute(request)));
|
||||||
|
var delegated = new EveSurfaceCommandRequest(
|
||||||
|
request.Schema,
|
||||||
|
request.ProviderId,
|
||||||
|
request.SurfaceId,
|
||||||
|
request.OperationRecord,
|
||||||
|
new Dictionary<string, string>(request.PayloadFields, StringComparer.Ordinal),
|
||||||
|
request.IssuedAt,
|
||||||
|
"progression-router",
|
||||||
|
request.CommandBoundary,
|
||||||
|
request.ReceiptSchema,
|
||||||
|
new EveCommandDelegationRecord(
|
||||||
|
EveCommandInvocationHash.Compute(request),
|
||||||
|
request.ClientId,
|
||||||
|
"progression-router"));
|
||||||
|
Assert.That(EveCommandInvocationHash.Compute(delegated), Is.Not.EqualTo(EveCommandInvocationHash.Compute(request)));
|
||||||
|
Assert.That(delegated.Delegation!.OriginalInvocationHash, Is.EqualTo(EveCommandInvocationHash.Compute(request)));
|
||||||
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void LegacySevenFieldSurfaceDocumentDeserializes()
|
public void LegacySevenFieldSurfaceDocumentDeserializes()
|
||||||
{
|
{
|
||||||
|
|
@ -208,7 +264,10 @@ public sealed class EveSurfaceSerializationCompatibilityTests
|
||||||
"aetheria.daemon",
|
"aetheria.daemon",
|
||||||
"aetheria.pilot",
|
"aetheria.pilot",
|
||||||
"interactive-world",
|
"interactive-world",
|
||||||
new[] { "cultnet+tcp://odin.gamecult.example:3076" }));
|
new[] { "cultnet+tcp://odin.gamecult.example:3076" },
|
||||||
|
"commander-daemon"),
|
||||||
|
"sha256:launch-envelope",
|
||||||
|
presentationSurfaceVersion: 77);
|
||||||
|
|
||||||
var bytes = MessagePackSerializer.Serialize(document, Options);
|
var bytes = MessagePackSerializer.Serialize(document, Options);
|
||||||
var reader = new MessagePackReader(bytes);
|
var reader = new MessagePackReader(bytes);
|
||||||
|
|
@ -216,13 +275,16 @@ public sealed class EveSurfaceSerializationCompatibilityTests
|
||||||
var restored = MessagePackSerializer.Deserialize<EveCommandReceiptDocument>(bytes, Options);
|
var restored = MessagePackSerializer.Deserialize<EveCommandReceiptDocument>(bytes, Options);
|
||||||
|
|
||||||
Assert.That(reader.NextMessagePackType, Is.EqualTo(MessagePackType.Map));
|
Assert.That(reader.NextMessagePackType, Is.EqualTo(MessagePackType.Map));
|
||||||
Assert.That(reader.ReadMapHeader(), Is.EqualTo(13));
|
Assert.That(reader.ReadMapHeader(), Is.EqualTo(15));
|
||||||
Assert.That(wireJson.RootElement.GetProperty("navigation").ValueKind, Is.EqualTo(JsonValueKind.Object));
|
Assert.That(wireJson.RootElement.GetProperty("navigation").ValueKind, Is.EqualTo(JsonValueKind.Object));
|
||||||
Assert.That(restored.Navigation, Is.Not.Null);
|
Assert.That(restored.Navigation, Is.Not.Null);
|
||||||
Assert.That(restored.Navigation!.VerseId, Is.EqualTo("gamecult.aetheria"));
|
Assert.That(restored.Navigation!.VerseId, Is.EqualTo("gamecult.aetheria"));
|
||||||
|
Assert.That(restored.Navigation.AuthorityRuntimeId, Is.EqualTo("commander-daemon"));
|
||||||
Assert.That(restored.Navigation.SurfaceId, Is.EqualTo("aetheria.pilot"));
|
Assert.That(restored.Navigation.SurfaceId, Is.EqualTo("aetheria.pilot"));
|
||||||
Assert.That(restored.Navigation.SurfaceKind, Is.EqualTo("interactive-world"));
|
Assert.That(restored.Navigation.SurfaceKind, Is.EqualTo("interactive-world"));
|
||||||
Assert.That(restored.Navigation.RendezvousEndpoints, Is.EqualTo(new[] { "cultnet+tcp://odin.gamecult.example:3076" }));
|
Assert.That(restored.Navigation.RendezvousEndpoints, Is.EqualTo(new[] { "cultnet+tcp://odin.gamecult.example:3076" }));
|
||||||
|
Assert.That(restored.InvocationHash, Is.EqualTo("sha256:launch-envelope"));
|
||||||
|
Assert.That(restored.PresentationSurfaceVersion, Is.EqualTo(77));
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
|
|
@ -260,6 +322,9 @@ public sealed class EveSurfaceSerializationCompatibilityTests
|
||||||
Assert.That(restored.CommandId, Is.EqualTo("legacy-command"));
|
Assert.That(restored.CommandId, Is.EqualTo("legacy-command"));
|
||||||
Assert.That(restored.Navigation, Is.Not.Null);
|
Assert.That(restored.Navigation, Is.Not.Null);
|
||||||
Assert.That(restored.Navigation!.SurfaceId, Is.EqualTo("aetheria.pilot"));
|
Assert.That(restored.Navigation!.SurfaceId, Is.EqualTo("aetheria.pilot"));
|
||||||
|
Assert.That(restored.Navigation.AuthorityRuntimeId, Is.Empty);
|
||||||
|
Assert.That(restored.InvocationHash, Is.Empty);
|
||||||
|
Assert.That(restored.PresentationSurfaceVersion, Is.Zero);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -286,7 +351,11 @@ public sealed class EveSurfaceSerializationCompatibilityTests
|
||||||
Assert.Multiple(() =>
|
Assert.Multiple(() =>
|
||||||
{
|
{
|
||||||
Assert.That(wireJson.RootElement.TryGetProperty("navigation", out _), Is.False);
|
Assert.That(wireJson.RootElement.TryGetProperty("navigation", out _), Is.False);
|
||||||
|
Assert.That(wireJson.RootElement.TryGetProperty("invocationHash", out _), Is.False);
|
||||||
|
Assert.That(wireJson.RootElement.TryGetProperty("presentationSurfaceVersion", out _), Is.False);
|
||||||
Assert.That(restored.Navigation, Is.Null);
|
Assert.That(restored.Navigation, Is.Null);
|
||||||
|
Assert.That(restored.InvocationHash, Is.Empty);
|
||||||
|
Assert.That(restored.PresentationSurfaceVersion, Is.Zero);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,7 @@
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\..\packages\org.gamecult.eve.surface\GameCult.Eve.Surface.csproj" />
|
<ProjectReference Include="..\..\packages\org.gamecult.eve.surface\GameCult.Eve.Surface.csproj" />
|
||||||
|
<ProjectReference Include="$(CultLibRoot)\src\GameCult.Mesh\GameCult.Mesh.csproj" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue