From ce7a7040c8732e6ded2b93a0e995b5cdf3db6c46 Mon Sep 17 00:00:00 2001 From: Meta Vens Date: Thu, 9 Jul 2026 10:48:08 +0100 Subject: [PATCH] Add EveUnity capture request contract --- docs/eve-migration-roadmap.md | 10 +- docs/parity-testing-harness.md | 5 +- .../README.md | 15 ++- .../eve-runtime-capability.json | 11 +- .../eveunity-split-handoff.json | 6 +- ...cult.eve.runtime_capability.v1.schema.json | 16 +++ .../run-eveunity-capture-contract-smoke.ps1 | 99 ++++++++++++++ scripts/run-eveunity-lifecycle-smoke.ps1 | 20 ++- ...ntime-owner-conformance-consumer-smoke.ps1 | 4 + tools/eveunity/eveunity-capture-contract.mjs | 122 ++++++++++++++++++ .../eveunity-capture-contract.test.mjs | 40 ++++++ tools/parity/parity-manifest.json | 11 +- tools/parity/run-parity.mjs | 4 + 13 files changed, 353 insertions(+), 10 deletions(-) create mode 100644 scripts/run-eveunity-capture-contract-smoke.ps1 create mode 100644 tools/eveunity/eveunity-capture-contract.mjs create mode 100644 tools/eveunity/eveunity-capture-contract.test.mjs diff --git a/docs/eve-migration-roadmap.md b/docs/eve-migration-roadmap.md index e37df48..a77efe0 100644 --- a/docs/eve-migration-roadmap.md +++ b/docs/eve-migration-roadmap.md @@ -477,9 +477,13 @@ Recently cut: - EveUnity capture lifecycle now carries a structured pending capture contract. `captureContract` names the Unity UI Toolkit runtime, capture kind, PNG artifact pattern, conformance attachment point, required Aetheria provider - surface, and authority rule. The lifecycle smoke and runtime-owner consumer - smoke assert those fields while keeping the actual capture artifact as a - split blocker for EveUnity. + surface, request schema, request builder, advertisement input, and authority + rule. `tools/eveunity/eveunity-capture-contract.mjs` now builds a + `gamecult.eve.runtime_capture_request.v1` request from the runtime capability + manifest and provider advertisement, and + `scripts/run-eveunity-capture-contract-smoke.ps1` proves that request path + without pretending a Unity PNG has been captured. The actual capture artifact + remains a split blocker for EveUnity. - Runtime-owner conformance consumption now asserts EveUnity lifecycle stage evidence directly. The generic consumer supports `--expect-runtime-lifecycle-status` and diff --git a/docs/parity-testing-harness.md b/docs/parity-testing-harness.md index d59dd03..bff5785 100644 --- a/docs/parity-testing-harness.md +++ b/docs/parity-testing-harness.md @@ -357,7 +357,10 @@ exist. The current lifecycle proof is deliberately split: - release: incubating UPM package identity and import surface exist in Eve; - test: package-owned EditMode tests run through Aetheria in Unity batchmode, and Aetheria can build the package through Unity's generated project; -- capture: Unity editor or batchmode capture remains a split blocker. +- capture: `tools/eveunity/eveunity-capture-contract.mjs` builds a typed + `gamecult.eve.runtime_capture_request.v1` request from the runtime capability + manifest and Aetheria provider advertisement; Unity editor or batchmode PNG + capture remains a split blocker. EveUnity split handoff evidence: diff --git a/packages/org.gamecult.eve.unity-uitoolkit/README.md b/packages/org.gamecult.eve.unity-uitoolkit/README.md index 83b8bc2..8581ce3 100644 --- a/packages/org.gamecult.eve.unity-uitoolkit/README.md +++ b/packages/org.gamecult.eve.unity-uitoolkit/README.md @@ -40,7 +40,8 @@ evidence: - release: incubating UPM package identity and import surface; - test: package-owned EditMode tests run through Aetheria in Unity batchmode, plus Aetheria consumer-build smoke through Unity's generated project; -- capture: pending Unity editor or batchmode artifact. +- capture: typed capture request contract and pending Unity editor or + batchmode PNG artifact. The release stage also declares the UPM release contract: `org.gamecult.eve.unity-uitoolkit` is released from this package root, reads @@ -55,6 +56,12 @@ under `artifacts/aetheria-unity-editmode/{stamp}`, and temporarily adds Unity package consumption proof. CultLib still owns the .NET/NuGet dependency story for its assemblies. +The capture stage declares `gamecult.eve.runtime_capture_request.v1` request +construction through `tools/eveunity/eveunity-capture-contract.mjs`. That +request is derived from the runtime capability manifest and Aetheria's provider +advertisement, then points at the PNG artifact EveUnity must produce later. It +does not fake a screenshot; it makes the capture input contract executable. + The manifest also declares `worldSurfaceLowering` target `unity-uitoolkit`. That means this package can lower provider-advertised `interactive-world` and `interactive-world-editor` surfaces as semantic UI Toolkit command surfaces. @@ -82,7 +89,11 @@ paths, runs the Aetheria Unity package consumer-build smoke, and verifies the split handoff manifest. Pass `-RunUnityEditMode` when the local Unity editor should also execute the incubating batchmode EditMode test runner. The final tagged UPM release, batchmode runner ownership, and capture artifact still -graduate to `EveUnity`. +graduate to `EveUnity`. The capture contract smoke is: + +```powershell +powershell -ExecutionPolicy Bypass -File .\scripts\run-eveunity-capture-contract-smoke.ps1 +``` For Aetheria, the Unity evidence path is: diff --git a/packages/org.gamecult.eve.unity-uitoolkit/eve-runtime-capability.json b/packages/org.gamecult.eve.unity-uitoolkit/eve-runtime-capability.json index aee14fe..0501f57 100644 --- a/packages/org.gamecult.eve.unity-uitoolkit/eve-runtime-capability.json +++ b/packages/org.gamecult.eve.unity-uitoolkit/eve-runtime-capability.json @@ -119,6 +119,10 @@ "captureContract": { "ownerRepo": "EveUnity", "runtimeId": "unity-uitoolkit", + "targetId": "unity-uitoolkit", + "requestSchema": "gamecult.eve.runtime_capture_request.v1", + "requestBuilder": "tools/eveunity/eveunity-capture-contract.mjs", + "advertisementPath": "web/fixtures/aetheria.provider-advertisement.json", "captureKind": "unity-editor-or-batchmode-png", "artifactKind": "png", "artifactPattern": "artifacts/eveunity-uitoolkit-capture/{stamp}/unity-uitoolkit.png", @@ -128,7 +132,12 @@ "authority": "Capture observes runtime projection only; provider state, command acceptance, and receipts stay provider-owned.", "publishProof": "Unity editor or batchmode capture artifact is attached to the runtime conformance report from EveUnity." }, - "evidencePaths": [], + "evidencePaths": [ + "tools/eveunity/eveunity-capture-contract.mjs", + "tools/eveunity/eveunity-capture-contract.test.mjs", + "scripts/run-eveunity-capture-contract-smoke.ps1", + "web/fixtures/aetheria.provider-advertisement.json" + ], "pendingProofs": [ "Unity batchmode or editor capture artifact attached to the runtime conformance report." ] diff --git a/packages/org.gamecult.eve.unity-uitoolkit/eveunity-split-handoff.json b/packages/org.gamecult.eve.unity-uitoolkit/eveunity-split-handoff.json index 3b9f46c..92fda92 100644 --- a/packages/org.gamecult.eve.unity-uitoolkit/eveunity-split-handoff.json +++ b/packages/org.gamecult.eve.unity-uitoolkit/eveunity-split-handoff.json @@ -37,7 +37,11 @@ { "id": "unity-capture-lifecycle", "stage": "capture", - "currentPaths": [], + "currentPaths": [ + "tools/eveunity/eveunity-capture-contract.mjs", + "tools/eveunity/eveunity-capture-contract.test.mjs", + "scripts/run-eveunity-capture-contract-smoke.ps1" + ], "destinationOwner": "EveUnity", "replacementProof": "Unity batchmode or editor capture artifact attached to the runtime conformance report." } diff --git a/schemas/gamecult.eve.runtime_capability.v1.schema.json b/schemas/gamecult.eve.runtime_capability.v1.schema.json index 7a852ef..065c447 100644 --- a/schemas/gamecult.eve.runtime_capability.v1.schema.json +++ b/schemas/gamecult.eve.runtime_capability.v1.schema.json @@ -400,6 +400,22 @@ "type": "string", "minLength": 1 }, + "targetId": { + "type": "string", + "minLength": 1 + }, + "requestSchema": { + "type": "string", + "minLength": 1 + }, + "requestBuilder": { + "type": "string", + "minLength": 1 + }, + "advertisementPath": { + "type": "string", + "minLength": 1 + }, "captureKind": { "type": "string", "minLength": 1 diff --git a/scripts/run-eveunity-capture-contract-smoke.ps1 b/scripts/run-eveunity-capture-contract-smoke.ps1 new file mode 100644 index 0000000..a0981fd --- /dev/null +++ b/scripts/run-eveunity-capture-contract-smoke.ps1 @@ -0,0 +1,99 @@ +param( + [string] $CapabilityManifestPath = "packages\org.gamecult.eve.unity-uitoolkit\eve-runtime-capability.json", + [string] $AdvertisementPath = "web\fixtures\aetheria.provider-advertisement.json", + [string] $OutputPath = "artifacts\eveunity-uitoolkit-capture-contract\latest\capture-request.json", + [string] $Stamp = "smoke" +) + +$ErrorActionPreference = "Stop" + +$projectRoot = Split-Path -Parent $PSScriptRoot +$absoluteCapabilityPath = if ([System.IO.Path]::IsPathRooted($CapabilityManifestPath)) { + $CapabilityManifestPath +} else { + Join-Path $projectRoot $CapabilityManifestPath +} +$absoluteAdvertisementPath = if ([System.IO.Path]::IsPathRooted($AdvertisementPath)) { + $AdvertisementPath +} else { + Join-Path $projectRoot $AdvertisementPath +} +$absoluteOutputPath = if ([System.IO.Path]::IsPathRooted($OutputPath)) { + $OutputPath +} else { + Join-Path $projectRoot $OutputPath +} + +foreach ($path in @($absoluteCapabilityPath, $absoluteAdvertisementPath)) { + if (-not (Test-Path -LiteralPath $path)) { + throw "EveUnity capture contract input not found: $path" + } +} + +$manifest = Get-Content -LiteralPath $absoluteCapabilityPath -Raw | ConvertFrom-Json +$captureContract = $manifest.lifecycle.capture.captureContract +if ($null -eq $captureContract) { + throw "EveUnity capture contract missing lifecycle.capture.captureContract" +} +foreach ($field in @("ownerRepo", "runtimeId", "targetId", "requestSchema", "requestBuilder", "advertisementPath", "captureKind", "artifactKind", "artifactPattern", "conformanceAttachment", "requiredProvider", "requiredSurface", "authority", "publishProof")) { + if (-not $captureContract.$field) { + throw "EveUnity capture contract missing $field" + } +} +if ($captureContract.ownerRepo -ne "EveUnity") { + throw "Unexpected EveUnity capture owner: $($captureContract.ownerRepo)" +} +if ($captureContract.runtimeId -ne "unity-uitoolkit") { + throw "Unexpected EveUnity capture runtime: $($captureContract.runtimeId)" +} +if ($captureContract.targetId -ne "unity-uitoolkit") { + throw "Unexpected EveUnity capture target: $($captureContract.targetId)" +} +if ($captureContract.requestSchema -ne "gamecult.eve.runtime_capture_request.v1") { + throw "Unexpected EveUnity capture request schema: $($captureContract.requestSchema)" +} + +$builderPath = Join-Path $projectRoot $captureContract.requestBuilder +if (-not (Test-Path -LiteralPath $builderPath)) { + throw "EveUnity capture request builder not found: $($captureContract.requestBuilder)" +} +if ((Join-Path $projectRoot $captureContract.advertisementPath) -ne $absoluteAdvertisementPath) { + throw "EveUnity capture advertisement path mismatch: $($captureContract.advertisementPath) vs $AdvertisementPath" +} + +node --test (Join-Path $projectRoot "tools\eveunity\eveunity-capture-contract.test.mjs") +if ($LASTEXITCODE -ne 0) { + throw "EveUnity capture contract tests failed with exit code $LASTEXITCODE" +} + +node $builderPath --capability $absoluteCapabilityPath --advertisement $absoluteAdvertisementPath --output $absoluteOutputPath --stamp $Stamp +if ($LASTEXITCODE -ne 0) { + throw "EveUnity capture request builder failed with exit code $LASTEXITCODE" +} +if (-not (Test-Path -LiteralPath $absoluteOutputPath)) { + throw "EveUnity capture request was not written: $absoluteOutputPath" +} + +$request = Get-Content -LiteralPath $absoluteOutputPath -Raw | ConvertFrom-Json +foreach ($expectation in @( + @{ field = "schema"; value = "gamecult.eve.runtime_capture_request.v1" }, + @{ field = "runtimeId"; value = "unity-uitoolkit" }, + @{ field = "ownerRepo"; value = "EveUnity" }, + @{ field = "providerId"; value = "aetheria" }, + @{ field = "surfaceId"; value = "aetheria.daemon.game" }, + @{ field = "targetId"; value = "unity-uitoolkit" }, + @{ field = "commandBoundary"; value = "aetheria.daemon.commands" }, + @{ field = "receiptSchema"; value = "aetheria.eve_command_acceptance_status.v1" }, + @{ field = "captureKind"; value = "unity-editor-or-batchmode-png" }, + @{ field = "artifactKind"; value = "png" }, + @{ field = "conformanceAttachment"; value = "runtime.captureArtifacts[]" } +)) { + if ($request.($expectation.field) -ne $expectation.value) { + throw "EveUnity capture request $($expectation.field) expected $($expectation.value) got $($request.($expectation.field))" + } +} +if ($request.artifactPath -ne "artifacts/eveunity-uitoolkit-capture/$Stamp/unity-uitoolkit.png") { + throw "Unexpected EveUnity capture artifact path: $($request.artifactPath)" +} + +Write-Host "EveUnity capture contract smoke passed: $absoluteOutputPath" diff --git a/scripts/run-eveunity-lifecycle-smoke.ps1 b/scripts/run-eveunity-lifecycle-smoke.ps1 index 3a9e346..1d8487b 100644 --- a/scripts/run-eveunity-lifecycle-smoke.ps1 +++ b/scripts/run-eveunity-lifecycle-smoke.ps1 @@ -168,17 +168,35 @@ if ($captureContract.ownerRepo -ne "EveUnity") { if ($captureContract.runtimeId -ne "unity-uitoolkit") { throw "EveUnity capture contract has unexpected runtime: $($captureContract.runtimeId)" } +if ($captureContract.targetId -ne "unity-uitoolkit") { + throw "EveUnity capture contract has unexpected target: $($captureContract.targetId)" +} +if ($captureContract.requestSchema -ne "gamecult.eve.runtime_capture_request.v1") { + throw "EveUnity capture contract has unexpected request schema: $($captureContract.requestSchema)" +} if ($captureContract.captureKind -ne "unity-editor-or-batchmode-png") { throw "EveUnity capture contract has unexpected capture kind: $($captureContract.captureKind)" } if ($captureContract.artifactKind -ne "png") { throw "EveUnity capture contract has unexpected artifact kind: $($captureContract.artifactKind)" } -foreach ($field in @("artifactPattern", "conformanceAttachment", "requiredProvider", "requiredSurface", "authority", "publishProof")) { +foreach ($field in @("requestBuilder", "advertisementPath", "artifactPattern", "conformanceAttachment", "requiredProvider", "requiredSurface", "authority", "publishProof")) { if (-not $captureContract.$field) { throw "EveUnity capture contract missing $field" } } +foreach ($pathProperty in @("requestBuilder", "advertisementPath")) { + $relativePath = $captureContract.$pathProperty + $absolutePath = Join-Path $projectRoot $relativePath + if (-not (Test-Path -LiteralPath $absolutePath)) { + throw "EveUnity capture contract $pathProperty does not exist: $relativePath" + } +} + +& (Join-Path $projectRoot "scripts\run-eveunity-capture-contract-smoke.ps1") +if ($LASTEXITCODE -ne 0) { + throw "EveUnity capture contract smoke failed with exit code $LASTEXITCODE" +} & (Join-Path $projectRoot "scripts\run-aetheria-unity-package-smoke.ps1") if ($LASTEXITCODE -ne 0) { diff --git a/scripts/run-runtime-owner-conformance-consumer-smoke.ps1 b/scripts/run-runtime-owner-conformance-consumer-smoke.ps1 index 625b16a..3f18b9a 100644 --- a/scripts/run-runtime-owner-conformance-consumer-smoke.ps1 +++ b/scripts/run-runtime-owner-conformance-consumer-smoke.ps1 @@ -117,6 +117,10 @@ node $consumerScript $consumerExport ` --expect-runtime-lifecycle-field "unity-uitoolkit:test:testContract.testPlatform:EditMode" ` --expect-runtime-lifecycle-field "unity-uitoolkit:capture:captureContract.captureKind:unity-editor-or-batchmode-png" ` --expect-runtime-lifecycle-field "unity-uitoolkit:capture:captureContract.artifactKind:png" ` + --expect-runtime-lifecycle-field "unity-uitoolkit:capture:captureContract.targetId:unity-uitoolkit" ` + --expect-runtime-lifecycle-field "unity-uitoolkit:capture:captureContract.requestSchema:gamecult.eve.runtime_capture_request.v1" ` + --expect-runtime-lifecycle-field "unity-uitoolkit:capture:captureContract.requestBuilder:tools/eveunity/eveunity-capture-contract.mjs" ` + --expect-runtime-lifecycle-field "unity-uitoolkit:capture:captureContract.advertisementPath:web/fixtures/aetheria.provider-advertisement.json" ` --expect-runtime-lifecycle-field "unity-uitoolkit:capture:captureContract.conformanceAttachment:runtime.captureArtifacts[]" ` --expect-runtime-lifecycle-field "unity-uitoolkit:capture:captureContract.requiredProvider:aetheria" ` --expect-runtime-lifecycle-field "unity-uitoolkit:capture:captureContract.requiredSurface:aetheria.daemon.game" ` diff --git a/tools/eveunity/eveunity-capture-contract.mjs b/tools/eveunity/eveunity-capture-contract.mjs new file mode 100644 index 0000000..8fdbbd7 --- /dev/null +++ b/tools/eveunity/eveunity-capture-contract.mjs @@ -0,0 +1,122 @@ +import { mkdirSync, readFileSync, writeFileSync } from "node:fs"; +import path from "node:path"; +import { fileURLToPath } from "node:url"; + +export function buildUnityCaptureRequest({ + advertisement, + capabilityManifest, + advertisementPath = "", + capabilityManifestPath = "", + stamp = new Date().toISOString().replace(/[:.]/g, "-"), +} = {}) { + if (!advertisement || typeof advertisement !== "object") { + throw new Error("advertisement is required"); + } + if (!capabilityManifest || typeof capabilityManifest !== "object") { + throw new Error("capabilityManifest is required"); + } + + const captureContract = capabilityManifest.lifecycle?.capture?.captureContract; + if (!captureContract) { + throw new Error("capability manifest is missing lifecycle.capture.captureContract"); + } + + if (advertisement.providerId !== captureContract.requiredProvider) { + throw new Error( + `capture provider mismatch: expected ${captureContract.requiredProvider} got ${advertisement.providerId || "missing"}`, + ); + } + + const targetId = captureContract.targetId || captureContract.runtimeId; + const surface = (advertisement.surfaces || []).find(candidate => candidate.surfaceId === captureContract.requiredSurface); + if (!surface) { + throw new Error(`capture surface not advertised: ${captureContract.requiredSurface}`); + } + + const loweringTargets = surface.worldInteraction?.loweringTargets || []; + if (!loweringTargets.includes(targetId)) { + throw new Error(`surface ${surface.surfaceId} does not advertise lowering target ${targetId}`); + } + + const artifactPath = replaceStamp(captureContract.artifactPattern, stamp); + + return { + schema: captureContract.requestSchema || "gamecult.eve.runtime_capture_request.v1", + runtimeId: capabilityManifest.runtimeId, + ownerRepo: captureContract.ownerRepo, + providerId: advertisement.providerId, + surfaceId: surface.surfaceId, + surfaceKind: surface.surfaceKind || "", + targetId, + projectionKind: surface.worldInteraction?.projectionKind || "", + commandBoundary: surface.worldInteraction?.commandBoundary || "", + receiptSchema: surface.worldInteraction?.receiptSchema || "", + captureKind: captureContract.captureKind, + artifactKind: captureContract.artifactKind, + artifactPath, + conformanceAttachment: captureContract.conformanceAttachment, + authority: captureContract.authority, + sourceAdvertisementPath: advertisementPath, + sourceCapabilityManifestPath: capabilityManifestPath, + issuedAtUtc: stamp, + }; +} + +export function loadJsonFile(filePath) { + return JSON.parse(readFileSync(filePath, "utf8")); +} + +export function writeCaptureRequest(request, outputPath) { + mkdirSync(path.dirname(outputPath), { recursive: true }); + writeFileSync(outputPath, `${JSON.stringify(request, null, 2)}\n`, "utf8"); +} + +function replaceStamp(pattern, stamp) { + if (!pattern || !pattern.includes("{stamp}")) { + throw new Error(`capture artifact pattern must include {stamp}: ${pattern || "missing"}`); + } + return pattern.replace("{stamp}", stamp); +} + +function parseArgs(argv) { + const args = {}; + for (let index = 0; index < argv.length; index += 1) { + const value = argv[index]; + if (!value.startsWith("--")) continue; + const key = value.slice(2); + const next = argv[index + 1]; + if (!next || next.startsWith("--")) { + args[key] = true; + } else { + args[key] = next; + index += 1; + } + } + return args; +} + +function runCli() { + const args = parseArgs(process.argv.slice(2)); + for (const required of ["advertisement", "capability", "output"]) { + if (!args[required]) { + throw new Error(`missing required --${required}`); + } + } + + const stamp = args.stamp || new Date().toISOString().replace(/[:.]/g, "-"); + const request = buildUnityCaptureRequest({ + advertisement: loadJsonFile(args.advertisement), + capabilityManifest: loadJsonFile(args.capability), + advertisementPath: args.advertisement, + capabilityManifestPath: args.capability, + stamp, + }); + writeCaptureRequest(request, args.output); + process.stdout.write(`${args.output}\n`); +} + +const invokedPath = process.argv[1] ? path.resolve(process.argv[1]) : ""; +const modulePath = fileURLToPath(import.meta.url); +if (invokedPath === modulePath) { + runCli(); +} diff --git a/tools/eveunity/eveunity-capture-contract.test.mjs b/tools/eveunity/eveunity-capture-contract.test.mjs new file mode 100644 index 0000000..d65d892 --- /dev/null +++ b/tools/eveunity/eveunity-capture-contract.test.mjs @@ -0,0 +1,40 @@ +import assert from "node:assert/strict"; +import { readFileSync } from "node:fs"; +import test from "node:test"; +import { buildUnityCaptureRequest } from "./eveunity-capture-contract.mjs"; + +const advertisement = JSON.parse(readFileSync(new URL("../../web/fixtures/aetheria.provider-advertisement.json", import.meta.url), "utf8")); +const capabilityManifest = JSON.parse(readFileSync(new URL("../../packages/org.gamecult.eve.unity-uitoolkit/eve-runtime-capability.json", import.meta.url), "utf8")); + +test("builds Unity UI Toolkit capture request from provider advertisement", () => { + const request = buildUnityCaptureRequest({ + advertisement, + capabilityManifest, + advertisementPath: "web/fixtures/aetheria.provider-advertisement.json", + capabilityManifestPath: "packages/org.gamecult.eve.unity-uitoolkit/eve-runtime-capability.json", + stamp: "smoke", + }); + + assert.equal(request.schema, "gamecult.eve.runtime_capture_request.v1"); + assert.equal(request.runtimeId, "unity-uitoolkit"); + assert.equal(request.ownerRepo, "EveUnity"); + assert.equal(request.providerId, "aetheria"); + assert.equal(request.surfaceId, "aetheria.daemon.game"); + assert.equal(request.targetId, "unity-uitoolkit"); + assert.equal(request.projectionKind, "provider-authored-world-surface"); + assert.equal(request.commandBoundary, "aetheria.daemon.commands"); + assert.equal(request.receiptSchema, "aetheria.eve_command_acceptance_status.v1"); + assert.equal(request.artifactPath, "artifacts/eveunity-uitoolkit-capture/smoke/unity-uitoolkit.png"); + assert.match(request.authority, /provider state/); +}); + +test("rejects a capture request when the surface does not advertise the runtime target", () => { + const withoutTarget = structuredClone(advertisement); + const surface = withoutTarget.surfaces.find(candidate => candidate.surfaceId === "aetheria.daemon.game"); + surface.worldInteraction.loweringTargets = surface.worldInteraction.loweringTargets.filter(target => target !== "unity-uitoolkit"); + + assert.throws( + () => buildUnityCaptureRequest({ advertisement: withoutTarget, capabilityManifest, stamp: "smoke" }), + /does not advertise lowering target unity-uitoolkit/, + ); +}); diff --git a/tools/parity/parity-manifest.json b/tools/parity/parity-manifest.json index ead008a..1a4bf0b 100644 --- a/tools/parity/parity-manifest.json +++ b/tools/parity/parity-manifest.json @@ -1380,6 +1380,10 @@ "captureContract": { "ownerRepo": "EveUnity", "runtimeId": "unity-uitoolkit", + "targetId": "unity-uitoolkit", + "requestSchema": "gamecult.eve.runtime_capture_request.v1", + "requestBuilder": "tools/eveunity/eveunity-capture-contract.mjs", + "advertisementPath": "web/fixtures/aetheria.provider-advertisement.json", "captureKind": "unity-editor-or-batchmode-png", "artifactKind": "png", "artifactPattern": "artifacts/eveunity-uitoolkit-capture/{stamp}/unity-uitoolkit.png", @@ -1389,7 +1393,12 @@ "authority": "Capture observes runtime projection only; provider state, command acceptance, and receipts stay provider-owned.", "publishProof": "Unity editor or batchmode capture artifact is attached to the runtime conformance report from EveUnity." }, - "evidencePaths": [], + "evidencePaths": [ + "tools/eveunity/eveunity-capture-contract.mjs", + "tools/eveunity/eveunity-capture-contract.test.mjs", + "scripts/run-eveunity-capture-contract-smoke.ps1", + "web/fixtures/aetheria.provider-advertisement.json" + ], "pendingProofs": [ "Unity batchmode or editor capture artifact attached to the runtime conformance report." ] diff --git a/tools/parity/run-parity.mjs b/tools/parity/run-parity.mjs index cc12133..4db7c14 100644 --- a/tools/parity/run-parity.mjs +++ b/tools/parity/run-parity.mjs @@ -1288,6 +1288,10 @@ function compareCaptureContract(expected, actual, label) { for (const key of [ "ownerRepo", "runtimeId", + "targetId", + "requestSchema", + "requestBuilder", + "advertisementPath", "captureKind", "artifactKind", "artifactPattern",