Select runtime capture surfaces explicitly

This commit is contained in:
Meta Vens 2026-07-09 17:24:28 +01:00
commit 718e9f773b
17 changed files with 166 additions and 10 deletions

View file

@ -709,6 +709,11 @@ Recently cut:
Aetheria `runtime.captureArtifacts[]` for `aetheria.daemon.game`. That makes
the provider-side proof consume generic lowering clients instead of stopping
at advertised target coverage.
- Runtime capture requests now select a declared provider surface, not only a
provider id. Unity UI Toolkit, Electron shell, and TUI declare
`aetheria.daemon.editor` as an additional capture surface, while Unity Scene
rejects the editor surface because Aetheria does not advertise `unity-scene`
for editor lowering. This keeps game/editor capture authority explicit.
- Conformance export now carries first-class `capabilityGaps[]` records derived
from plugin errors, provider advertisement/scenario errors, runtime missing
evidence, declared unsupported plugin projection, capture gaps, and split

View file

@ -296,6 +296,11 @@ Electron shell, and TUI, so provider consumption proves real runtime artifacts
rather than only advertised lowering targets. Generated copies remain under
Eve's `artifacts/aetheria-conformance-consumer-smoke` so the provider worktree
does not become dirty merely by proving the boundary.
Runtime capture request builders select a declared provider surface by id when
requested. Unity UI Toolkit, Electron shell, and TUI declare
`aetheria.daemon.editor` as an additional capture surface; Unity Scene does not,
because the editor advertisement does not include the `unity-scene` lowering
target.
Plugin owner handoff smoke:

View file

@ -205,6 +205,11 @@
"providerId": "eve.world-smoke",
"surfaceId": "eve.world-smoke.surface",
"purpose": "Generic non-product interactive-world capture control."
},
{
"providerId": "aetheria",
"surfaceId": "aetheria.daemon.editor",
"purpose": "Aetheria interactive-world-editor capture control."
}
],
"authority": "Capture observes runtime projection only; provider state, command acceptance, and receipts stay provider-owned.",

View file

@ -149,6 +149,11 @@
"providerId": "eve.world-smoke",
"surfaceId": "eve.world-smoke.surface",
"purpose": "Generic non-product interactive-world capture control."
},
{
"providerId": "aetheria",
"surfaceId": "aetheria.daemon.editor",
"purpose": "Aetheria interactive-world-editor capture control."
}
],
"authority": "Capture observes Electron projection only; provider state, command acceptance, and receipts stay provider-owned.",

View file

@ -154,6 +154,11 @@
"providerId": "eve.world-smoke",
"surfaceId": "eve.world-smoke.surface",
"purpose": "Generic non-product interactive-world capture control."
},
{
"providerId": "aetheria",
"surfaceId": "aetheria.daemon.editor",
"purpose": "Aetheria interactive-world-editor capture control."
}
],
"authority": "Capture observes terminal projection only; provider state, command acceptance, and receipts stay provider-owned.",

View file

@ -215,6 +215,8 @@ node $consumerScript $consumerExport `
--expect-runtime-lifecycle-field "unity-uitoolkit:capture:captureContract.requiredSurface:aetheria.daemon.game" `
--expect-runtime-lifecycle-field "unity-uitoolkit:capture:captureContract.additionalProviderSurfaces.0.providerId:eve.world-smoke" `
--expect-runtime-lifecycle-field "unity-uitoolkit:capture:captureContract.additionalProviderSurfaces.0.surfaceId:eve.world-smoke.surface" `
--expect-runtime-lifecycle-field "unity-uitoolkit:capture:captureContract.additionalProviderSurfaces.1.providerId:aetheria" `
--expect-runtime-lifecycle-field "unity-uitoolkit:capture:captureContract.additionalProviderSurfaces.1.surfaceId:aetheria.daemon.editor" `
--expect-runtime-lifecycle-status unity-scene:release:pending-unity-scene-package `
--expect-runtime-lifecycle-status unity-scene:test:provider-shell-contract-skeleton `
--expect-runtime-lifecycle-status unity-scene:capture:pending-unity-scene-capture `
@ -262,6 +264,8 @@ node $consumerScript $consumerExport `
--expect-runtime-lifecycle-field "electron-shell:capture:captureContract.requiredSurface:aetheria.daemon.game" `
--expect-runtime-lifecycle-field "electron-shell:capture:captureContract.additionalProviderSurfaces.0.providerId:eve.world-smoke" `
--expect-runtime-lifecycle-field "electron-shell:capture:captureContract.additionalProviderSurfaces.0.surfaceId:eve.world-smoke.surface" `
--expect-runtime-lifecycle-field "electron-shell:capture:captureContract.additionalProviderSurfaces.1.providerId:aetheria" `
--expect-runtime-lifecycle-field "electron-shell:capture:captureContract.additionalProviderSurfaces.1.surfaceId:aetheria.daemon.editor" `
--expect-runtime-lifecycle-status tui:release:pending-tui-package `
--expect-runtime-lifecycle-status tui:test:provider-shell-contract-skeleton `
--expect-runtime-lifecycle-status tui:capture:cell-grid-json-capture `
@ -284,7 +288,9 @@ node $consumerScript $consumerExport `
--expect-runtime-lifecycle-field "tui:capture:captureContract.advertisementPath:web/fixtures/aetheria.provider-advertisement.json" `
--expect-runtime-lifecycle-field "tui:capture:captureContract.requiredSurface:aetheria.daemon.game" `
--expect-runtime-lifecycle-field "tui:capture:captureContract.additionalProviderSurfaces.0.providerId:eve.world-smoke" `
--expect-runtime-lifecycle-field "tui:capture:captureContract.additionalProviderSurfaces.0.surfaceId:eve.world-smoke.surface"
--expect-runtime-lifecycle-field "tui:capture:captureContract.additionalProviderSurfaces.0.surfaceId:eve.world-smoke.surface" `
--expect-runtime-lifecycle-field "tui:capture:captureContract.additionalProviderSurfaces.1.providerId:aetheria" `
--expect-runtime-lifecycle-field "tui:capture:captureContract.additionalProviderSurfaces.1.surfaceId:aetheria.daemon.editor"
if ($LASTEXITCODE -ne 0) {
throw "Runtime owner conformance consumer smoke failed with exit code $LASTEXITCODE"

View file

@ -10,6 +10,7 @@ export function buildElectronCaptureArtifact({
surfaceDocument,
advertisementPath = "",
capabilityManifestPath = "",
captureSurfaceId = "",
stamp = "latest",
} = {}) {
const request = buildElectronCaptureRequest({
@ -17,6 +18,7 @@ export function buildElectronCaptureArtifact({
capabilityManifest,
advertisementPath,
capabilityManifestPath,
captureSurfaceId,
stamp,
});
const shell = new EveElectronShell();
@ -97,6 +99,7 @@ function runCli() {
surfaceDocument: loadJsonFile(args.surface),
advertisementPath: args.advertisement,
capabilityManifestPath: args.capability,
captureSurfaceId: args["surface-id"] || "",
stamp,
});
writeElectronCaptureArtifact(artifact, {

View file

@ -7,6 +7,7 @@ export function buildElectronCaptureRequest({
capabilityManifest,
advertisementPath = "",
capabilityManifestPath = "",
captureSurfaceId = "",
stamp = new Date().toISOString().replace(/[:.]/g, "-"),
} = {}) {
if (!advertisement || typeof advertisement !== "object") {
@ -21,8 +22,13 @@ export function buildElectronCaptureRequest({
throw new Error("capability manifest is missing lifecycle.capture.captureContract");
}
const captureSurface = selectCaptureSurface(captureContract, advertisement.providerId);
const captureSurface = selectCaptureSurface(captureContract, advertisement.providerId, captureSurfaceId);
if (!captureSurface) {
if (captureSurfaceId) {
throw new Error(
`capture surface mismatch: expected ${captureSurfaceList(captureContract)} got ${advertisement.providerId || "missing"}:${captureSurfaceId}`,
);
}
throw new Error(
`capture provider mismatch: expected ${captureProviderList(captureContract)} got ${advertisement.providerId || "missing"}`,
);
@ -79,14 +85,20 @@ function replaceStamp(pattern, stamp) {
return pattern.replace("{stamp}", stamp);
}
function selectCaptureSurface(captureContract, providerId) {
return captureSurfaceClaims(captureContract).find(claim => claim.providerId === providerId) || null;
function selectCaptureSurface(captureContract, providerId, surfaceId = "") {
const claims = captureSurfaceClaims(captureContract).filter(claim => claim.providerId === providerId);
if (surfaceId) return claims.find(claim => claim.surfaceId === surfaceId) || null;
return claims[0] || null;
}
function captureProviderList(captureContract) {
return captureSurfaceClaims(captureContract).map(claim => claim.providerId).filter(Boolean).join(", ") || "missing";
}
function captureSurfaceList(captureContract) {
return captureSurfaceClaims(captureContract).map(claim => `${claim.providerId}:${claim.surfaceId}`).join(", ") || "missing";
}
function captureSurfaceClaims(captureContract) {
return [
{
@ -134,6 +146,7 @@ function runCli() {
capabilityManifest: loadJsonFile(args.capability),
advertisementPath: args.advertisement,
capabilityManifestPath: args.capability,
captureSurfaceId: args["surface-id"] || "",
stamp,
});
writeCaptureRequest(request, args.output);

View file

@ -29,6 +29,25 @@ test("builds Electron shell capture request from provider advertisement", () =>
assert.match(request.authority, /provider state/);
});
test("builds Electron shell capture request for Aetheria editor surface", () => {
const request = buildElectronCaptureRequest({
advertisement,
capabilityManifest,
captureSurfaceId: "aetheria.daemon.editor",
advertisementPath: "web/fixtures/aetheria.provider-advertisement.json",
capabilityManifestPath: "runtimes/incubating/eve-electron/eve-runtime-capability.json",
stamp: "smoke",
});
assert.equal(request.runtimeId, "electron-shell");
assert.equal(request.providerId, "aetheria");
assert.equal(request.surfaceId, "aetheria.daemon.editor");
assert.equal(request.surfaceKind, "interactive-world-editor");
assert.equal(request.projectionKind, "provider-authored-world-editor-surface");
assert.equal(request.commandBoundary, "aetheria.daemon.commands");
assert.equal(request.receiptSchema, "aetheria.eve_command_acceptance_status.v1");
});
test("builds Electron shell capture request from a declared generic world provider", () => {
const request = buildElectronCaptureRequest({
advertisement: worldSmokeAdvertisement,

View file

@ -10,6 +10,7 @@ export function buildTuiCaptureArtifact({
surfaceDocument,
advertisementPath = "",
capabilityManifestPath = "",
captureSurfaceId = "",
stamp = "latest",
width = 80,
} = {}) {
@ -18,6 +19,7 @@ export function buildTuiCaptureArtifact({
capabilityManifest,
advertisementPath,
capabilityManifestPath,
captureSurfaceId,
stamp,
});
const shell = new EveTuiShell({ width });
@ -98,6 +100,7 @@ function runCli() {
surfaceDocument: loadJsonFile(args.surface),
advertisementPath: args.advertisement,
capabilityManifestPath: args.capability,
captureSurfaceId: args["surface-id"] || "",
stamp,
width: Number(args.width || 80),
});

View file

@ -7,6 +7,7 @@ export function buildTuiCaptureRequest({
capabilityManifest,
advertisementPath = "",
capabilityManifestPath = "",
captureSurfaceId = "",
stamp = new Date().toISOString().replace(/[:.]/g, "-"),
} = {}) {
if (!advertisement || typeof advertisement !== "object") {
@ -21,8 +22,13 @@ export function buildTuiCaptureRequest({
throw new Error("capability manifest is missing lifecycle.capture.captureContract");
}
const captureSurface = selectCaptureSurface(captureContract, advertisement.providerId);
const captureSurface = selectCaptureSurface(captureContract, advertisement.providerId, captureSurfaceId);
if (!captureSurface) {
if (captureSurfaceId) {
throw new Error(
`capture surface mismatch: expected ${captureSurfaceList(captureContract)} got ${advertisement.providerId || "missing"}:${captureSurfaceId}`,
);
}
throw new Error(
`capture provider mismatch: expected ${captureProviderList(captureContract)} got ${advertisement.providerId || "missing"}`,
);
@ -79,14 +85,20 @@ function replaceStamp(pattern, stamp) {
return pattern.replace("{stamp}", stamp);
}
function selectCaptureSurface(captureContract, providerId) {
return captureSurfaceClaims(captureContract).find(claim => claim.providerId === providerId) || null;
function selectCaptureSurface(captureContract, providerId, surfaceId = "") {
const claims = captureSurfaceClaims(captureContract).filter(claim => claim.providerId === providerId);
if (surfaceId) return claims.find(claim => claim.surfaceId === surfaceId) || null;
return claims[0] || null;
}
function captureProviderList(captureContract) {
return captureSurfaceClaims(captureContract).map(claim => claim.providerId).filter(Boolean).join(", ") || "missing";
}
function captureSurfaceList(captureContract) {
return captureSurfaceClaims(captureContract).map(claim => `${claim.providerId}:${claim.surfaceId}`).join(", ") || "missing";
}
function captureSurfaceClaims(captureContract) {
return [
{
@ -134,6 +146,7 @@ function runCli() {
capabilityManifest: loadJsonFile(args.capability),
advertisementPath: args.advertisement,
capabilityManifestPath: args.capability,
captureSurfaceId: args["surface-id"] || "",
stamp,
});
writeCaptureRequest(request, args.output);

View file

@ -31,6 +31,25 @@ test("builds TUI capture request from provider advertisement", () => {
assert.match(request.authority, /provider state/);
});
test("builds TUI capture request for Aetheria editor surface", () => {
const request = buildTuiCaptureRequest({
advertisement,
capabilityManifest,
captureSurfaceId: "aetheria.daemon.editor",
advertisementPath: "web/fixtures/aetheria.provider-advertisement.json",
capabilityManifestPath: "runtimes/incubating/eve-tui/eve-runtime-capability.json",
stamp: "smoke",
});
assert.equal(request.runtimeId, "tui");
assert.equal(request.providerId, "aetheria");
assert.equal(request.surfaceId, "aetheria.daemon.editor");
assert.equal(request.surfaceKind, "interactive-world-editor");
assert.equal(request.projectionKind, "provider-authored-world-editor-surface");
assert.equal(request.commandBoundary, "aetheria.daemon.commands");
assert.equal(request.receiptSchema, "aetheria.eve_command_acceptance_status.v1");
});
test("builds TUI capture request from a declared generic world provider", () => {
const request = buildTuiCaptureRequest({
advertisement: worldSmokeAdvertisement,

View file

@ -7,6 +7,7 @@ export function buildUnityCaptureRequest({
capabilityManifest,
advertisementPath = "",
capabilityManifestPath = "",
captureSurfaceId = "",
stamp = new Date().toISOString().replace(/[:.]/g, "-"),
} = {}) {
if (!advertisement || typeof advertisement !== "object") {
@ -21,8 +22,13 @@ export function buildUnityCaptureRequest({
throw new Error("capability manifest is missing lifecycle.capture.captureContract");
}
const captureSurface = selectCaptureSurface(captureContract, advertisement.providerId);
const captureSurface = selectCaptureSurface(captureContract, advertisement.providerId, captureSurfaceId);
if (!captureSurface) {
if (captureSurfaceId) {
throw new Error(
`capture surface mismatch: expected ${captureSurfaceList(captureContract)} got ${advertisement.providerId || "missing"}:${captureSurfaceId}`,
);
}
throw new Error(
`capture provider mismatch: expected ${captureProviderList(captureContract)} got ${advertisement.providerId || "missing"}`,
);
@ -79,14 +85,20 @@ function replaceStamp(pattern, stamp) {
return pattern.replace("{stamp}", stamp);
}
function selectCaptureSurface(captureContract, providerId) {
return captureSurfaceClaims(captureContract).find(claim => claim.providerId === providerId) || null;
function selectCaptureSurface(captureContract, providerId, surfaceId = "") {
const claims = captureSurfaceClaims(captureContract).filter(claim => claim.providerId === providerId);
if (surfaceId) return claims.find(claim => claim.surfaceId === surfaceId) || null;
return claims[0] || null;
}
function captureProviderList(captureContract) {
return captureSurfaceClaims(captureContract).map(claim => claim.providerId).filter(Boolean).join(", ") || "missing";
}
function captureSurfaceList(captureContract) {
return captureSurfaceClaims(captureContract).map(claim => `${claim.providerId}:${claim.surfaceId}`).join(", ") || "missing";
}
function captureSurfaceClaims(captureContract) {
return [
{
@ -134,6 +146,7 @@ function runCli() {
capabilityManifest: loadJsonFile(args.capability),
advertisementPath: args.advertisement,
capabilityManifestPath: args.capability,
captureSurfaceId: args["surface-id"] || "",
stamp,
});
writeCaptureRequest(request, args.output);

View file

@ -30,6 +30,25 @@ test("builds Unity UI Toolkit capture request from provider advertisement", () =
assert.match(request.authority, /provider state/);
});
test("builds Unity UI Toolkit capture request for Aetheria editor surface", () => {
const request = buildUnityCaptureRequest({
advertisement,
capabilityManifest,
captureSurfaceId: "aetheria.daemon.editor",
advertisementPath: "web/fixtures/aetheria.provider-advertisement.json",
capabilityManifestPath: "packages/org.gamecult.eve.unity-uitoolkit/eve-runtime-capability.json",
stamp: "smoke",
});
assert.equal(request.runtimeId, "unity-uitoolkit");
assert.equal(request.providerId, "aetheria");
assert.equal(request.surfaceId, "aetheria.daemon.editor");
assert.equal(request.surfaceKind, "interactive-world-editor");
assert.equal(request.projectionKind, "provider-authored-world-editor-surface");
assert.equal(request.commandBoundary, "aetheria.daemon.commands");
assert.equal(request.receiptSchema, "aetheria.eve_command_acceptance_status.v1");
});
test("builds Unity UI Toolkit capture request from a declared generic world provider", () => {
const request = buildUnityCaptureRequest({
advertisement: worldSmokeAdvertisement,
@ -94,3 +113,15 @@ test("builds Unity scene capture request from a declared generic world provider"
assert.equal(request.commandBoundary, "eve.world-smoke.commands");
assert.equal(request.receiptSchema, "eve.world_smoke.command_receipt.v1");
});
test("rejects Unity scene capture request for Aetheria editor surface", () => {
assert.throws(
() => buildUnityCaptureRequest({
advertisement,
capabilityManifest: sceneCapabilityManifest,
captureSurfaceId: "aetheria.daemon.editor",
stamp: "smoke",
}),
/capture surface mismatch/,
);
});

View file

@ -9,6 +9,7 @@ export function buildUnitySceneCaptureArtifact({
surfaceDocument,
advertisementPath = "",
capabilityManifestPath = "",
captureSurfaceId = "",
stamp = "latest",
} = {}) {
const request = buildUnityCaptureRequest({
@ -16,6 +17,7 @@ export function buildUnitySceneCaptureArtifact({
capabilityManifest,
advertisementPath,
capabilityManifestPath,
captureSurfaceId,
stamp,
});
const projection = buildUnitySceneProjection(surfaceDocument, advertisement, request.surfaceId);
@ -292,6 +294,7 @@ function runCli() {
surfaceDocument: loadJsonFile(args.surface),
advertisementPath: args.advertisement,
capabilityManifestPath: args.capability,
captureSurfaceId: args["surface-id"] || "",
stamp,
});
writeUnitySceneCaptureArtifact(artifact, {

View file

@ -9,6 +9,7 @@ export function buildUnityUiToolkitCaptureArtifact({
surfaceDocument,
advertisementPath = "",
capabilityManifestPath = "",
captureSurfaceId = "",
stamp = "latest",
} = {}) {
const request = buildUnityCaptureRequest({
@ -16,6 +17,7 @@ export function buildUnityUiToolkitCaptureArtifact({
capabilityManifest,
advertisementPath,
capabilityManifestPath,
captureSurfaceId,
stamp,
});
const projection = buildUnityUiToolkitProjection(surfaceDocument, advertisement, request.surfaceId);
@ -308,6 +310,7 @@ function runCli() {
surfaceDocument: loadJsonFile(args.surface),
advertisementPath: args.advertisement,
capabilityManifestPath: args.capability,
captureSurfaceId: args["surface-id"] || "",
stamp,
});
writeUnityUiToolkitCaptureArtifact(artifact, {

View file

@ -1789,6 +1789,11 @@
"providerId": "eve.world-smoke",
"surfaceId": "eve.world-smoke.surface",
"purpose": "Generic non-product interactive-world capture control."
},
{
"providerId": "aetheria",
"surfaceId": "aetheria.daemon.editor",
"purpose": "Aetheria interactive-world-editor capture control."
}
],
"authority": "Capture observes runtime projection only; provider state, command acceptance, and receipts stay provider-owned.",