Bind Eve receipts to immutable invocations
This commit is contained in:
parent
351f70245b
commit
4167fc32ca
12 changed files with 65 additions and 13 deletions
|
|
@ -14,6 +14,10 @@ export interface EveCommandReceipt {
|
||||||
}[];
|
}[];
|
||||||
issuedAtUtc?: string;
|
issuedAtUtc?: string;
|
||||||
sourceVersion: number;
|
sourceVersion: number;
|
||||||
|
/**
|
||||||
|
* Canonical digest of the immutable invocation envelope finalized by this receipt.
|
||||||
|
*/
|
||||||
|
invocationHash?: string;
|
||||||
navigation?: {
|
navigation?: {
|
||||||
verseId: string;
|
verseId: string;
|
||||||
authorityRuntimeId?: string;
|
authorityRuntimeId?: string;
|
||||||
|
|
|
||||||
|
|
@ -622,6 +622,11 @@ export declare const eveContractSchemas: {
|
||||||
readonly type: "integer";
|
readonly type: "integer";
|
||||||
readonly minimum: 0;
|
readonly minimum: 0;
|
||||||
};
|
};
|
||||||
|
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";
|
||||||
readonly required: readonly ["verseId", "surfaceId"];
|
readonly required: readonly ["verseId", "surfaceId"];
|
||||||
|
|
@ -630,6 +635,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";
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -746,6 +746,11 @@ export const eveContractSchemas = {
|
||||||
"type": "integer",
|
"type": "integer",
|
||||||
"minimum": 0
|
"minimum": 0
|
||||||
},
|
},
|
||||||
|
"invocationHash": {
|
||||||
|
"type": "string",
|
||||||
|
"minLength": 1,
|
||||||
|
"description": "Canonical digest of the immutable invocation envelope finalized by this receipt."
|
||||||
|
},
|
||||||
"navigation": {
|
"navigation": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"required": [
|
"required": [
|
||||||
|
|
@ -757,6 +762,10 @@ export const eveContractSchemas = {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"minLength": 1
|
"minLength": 1
|
||||||
},
|
},
|
||||||
|
"authorityRuntimeId": {
|
||||||
|
"type": "string",
|
||||||
|
"minLength": 1
|
||||||
|
},
|
||||||
"providerId": {
|
"providerId": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,10 @@ diagnostics?: {
|
||||||
}[]
|
}[]
|
||||||
issuedAtUtc?: string
|
issuedAtUtc?: string
|
||||||
sourceVersion: number
|
sourceVersion: number
|
||||||
|
/**
|
||||||
|
* Canonical digest of the immutable invocation envelope finalized by this receipt.
|
||||||
|
*/
|
||||||
|
invocationHash?: string
|
||||||
navigation?: {
|
navigation?: {
|
||||||
verseId: string
|
verseId: string
|
||||||
authorityRuntimeId?: string
|
authorityRuntimeId?: string
|
||||||
|
|
|
||||||
|
|
@ -746,6 +746,11 @@ export const eveContractSchemas = {
|
||||||
"type": "integer",
|
"type": "integer",
|
||||||
"minimum": 0
|
"minimum": 0
|
||||||
},
|
},
|
||||||
|
"invocationHash": {
|
||||||
|
"type": "string",
|
||||||
|
"minLength": 1,
|
||||||
|
"description": "Canonical digest of the immutable invocation envelope finalized by this receipt."
|
||||||
|
},
|
||||||
"navigation": {
|
"navigation": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"required": [
|
"required": [
|
||||||
|
|
|
||||||
|
|
@ -81,6 +81,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.3</Version>
|
<Version>0.3.4</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>
|
||||||
|
|
|
||||||
|
|
@ -366,8 +366,9 @@ 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 = "")
|
||||||
|
: this(SchemaId, receiptId, commandId, command, state, ownerRepo, authority, providerId, surfaceId, message, issuedAtUtc, sourceVersion, navigation, invocationHash)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -385,7 +386,8 @@ namespace GameCult.Eve.Surface
|
||||||
string message,
|
string message,
|
||||||
string issuedAtUtc,
|
string issuedAtUtc,
|
||||||
long sourceVersion,
|
long sourceVersion,
|
||||||
EveSurfaceNavigationTarget? navigation = null)
|
EveSurfaceNavigationTarget? navigation = null,
|
||||||
|
string invocationHash = "")
|
||||||
{
|
{
|
||||||
Schema = string.IsNullOrWhiteSpace(schema) ? SchemaId : schema;
|
Schema = string.IsNullOrWhiteSpace(schema) ? SchemaId : schema;
|
||||||
ReceiptId = receiptId ?? "";
|
ReceiptId = receiptId ?? "";
|
||||||
|
|
@ -400,6 +402,7 @@ namespace GameCult.Eve.Surface
|
||||||
IssuedAtUtc = issuedAtUtc ?? "";
|
IssuedAtUtc = issuedAtUtc ?? "";
|
||||||
SourceVersion = sourceVersion;
|
SourceVersion = sourceVersion;
|
||||||
Navigation = navigation;
|
Navigation = navigation;
|
||||||
|
InvocationHash = invocationHash ?? "";
|
||||||
}
|
}
|
||||||
|
|
||||||
[Key(0)] public string Schema { get; }
|
[Key(0)] public string Schema { get; }
|
||||||
|
|
@ -415,6 +418,7 @@ 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; }
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -424,7 +428,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 = 14;
|
||||||
private const int RequiredAndScalarFieldCount = 12;
|
private const int RequiredAndScalarFieldCount = 12;
|
||||||
|
|
||||||
public void Serialize(
|
public void Serialize(
|
||||||
|
|
@ -434,9 +438,9 @@ 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);
|
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 +458,8 @@ 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);
|
||||||
}
|
}
|
||||||
|
|
||||||
public EveCommandReceiptDocument? Deserialize(
|
public EveCommandReceiptDocument? Deserialize(
|
||||||
|
|
@ -483,10 +489,11 @@ 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);
|
||||||
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);
|
||||||
}
|
}
|
||||||
finally { reader.Depth--; }
|
finally { reader.Depth--; }
|
||||||
}
|
}
|
||||||
|
|
@ -511,6 +518,7 @@ namespace GameCult.Eve.Surface
|
||||||
var issuedAtUtc = "";
|
var issuedAtUtc = "";
|
||||||
long sourceVersion = 0;
|
long sourceVersion = 0;
|
||||||
EveSurfaceNavigationTarget? navigation = null;
|
EveSurfaceNavigationTarget? navigation = null;
|
||||||
|
var invocationHash = "";
|
||||||
var fields = reader.ReadMapHeader();
|
var fields = reader.ReadMapHeader();
|
||||||
for (var index = 0; index < fields; index++)
|
for (var index = 0; index < fields; index++)
|
||||||
{
|
{
|
||||||
|
|
@ -529,13 +537,14 @@ 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;
|
||||||
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);
|
||||||
}
|
}
|
||||||
finally { reader.Depth--; }
|
finally { reader.Depth--; }
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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.3",
|
"version": "0.3.4",
|
||||||
"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": {
|
||||||
|
|
|
||||||
|
|
@ -69,6 +69,11 @@
|
||||||
"type": "integer",
|
"type": "integer",
|
||||||
"minimum": 0
|
"minimum": 0
|
||||||
},
|
},
|
||||||
|
"invocationHash": {
|
||||||
|
"type": "string",
|
||||||
|
"minLength": 1,
|
||||||
|
"description": "Canonical digest of the immutable invocation envelope finalized by this receipt."
|
||||||
|
},
|
||||||
"navigation": {
|
"navigation": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"required": [
|
"required": [
|
||||||
|
|
|
||||||
|
|
@ -209,7 +209,8 @@ public sealed class EveSurfaceSerializationCompatibilityTests
|
||||||
"aetheria.pilot",
|
"aetheria.pilot",
|
||||||
"interactive-world",
|
"interactive-world",
|
||||||
new[] { "cultnet+tcp://odin.gamecult.example:3076" },
|
new[] { "cultnet+tcp://odin.gamecult.example:3076" },
|
||||||
"commander-daemon"));
|
"commander-daemon"),
|
||||||
|
"sha256:launch-envelope");
|
||||||
|
|
||||||
var bytes = MessagePackSerializer.Serialize(document, Options);
|
var bytes = MessagePackSerializer.Serialize(document, Options);
|
||||||
var reader = new MessagePackReader(bytes);
|
var reader = new MessagePackReader(bytes);
|
||||||
|
|
@ -217,7 +218,7 @@ 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(14));
|
||||||
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"));
|
||||||
|
|
@ -225,6 +226,7 @@ public sealed class EveSurfaceSerializationCompatibilityTests
|
||||||
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"));
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
|
|
@ -263,6 +265,7 @@ public sealed class EveSurfaceSerializationCompatibilityTests
|
||||||
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.Navigation.AuthorityRuntimeId, Is.Empty);
|
||||||
|
Assert.That(restored.InvocationHash, Is.Empty);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -289,7 +292,9 @@ 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(restored.Navigation, Is.Null);
|
Assert.That(restored.Navigation, Is.Null);
|
||||||
|
Assert.That(restored.InvocationHash, Is.Empty);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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