Attach EveTui grid capture evidence

This commit is contained in:
Meta Vens 2026-07-09 12:40:45 +01:00
commit ea372b1176
17 changed files with 502 additions and 35 deletions

View file

@ -507,9 +507,11 @@ Recently cut:
summary, terminal-grid lowering, and the `tui` world/editor lowering claim;
it now carries an executable terminal capture request contract through
`tools/evetui/evetui-capture-contract.mjs` and
`scripts/run-evetui-capture-contract-smoke.ps1`. Plugin projection, terminal
package release, and actual transcript/cell-grid capture artifacts remain
pending.
`scripts/run-evetui-capture-contract-smoke.ps1`, plus a deterministic
`gamecult.eve.tui_grid.v1` JSON capture artifact through
`tools/evetui/evetui-capture-artifact.mjs` and
`scripts/run-evetui-capture-smoke.ps1`. Plugin projection, terminal package
release, and owner-repo production of the capture path remain pending.
- EveTui release lifecycle now carries a structured pending release request
contract. The runtime capability manifest reads version truth from
`runtimes/incubating/eve-tui/package.json`, names the
@ -568,6 +570,14 @@ Recently cut:
`scripts/run-eveelectron-capture-contract-smoke.ps1` proves that request path
without pretending an Electron window PNG has been captured. The actual
capture artifact remains a split blocker for EveElectron.
- EveTui capture lifecycle now attaches a typed terminal-grid artifact instead
of stopping at a request contract. `tools/evetui/evetui-capture-artifact.mjs`
lowers `web/fixtures/aetheria-world-surface.json` through `EveTuiShell`,
writes `artifacts/evetui-capture/latest/tui-grid.json`, and stores the paired
capture request beside it. Parity validates the artifact schema, provider,
surface, and linked request path, then exports it as `runtime.captureArtifacts[]`.
The split blocker is no longer "no artifact"; it is "EveTui must own this
capture path outside Eve incubation."
- EveElectron release lifecycle now carries a structured pending release
request contract. `releaseContract` names
`gamecult.eve.runtime_release_request.v1`, the

View file

@ -389,6 +389,15 @@ exist. The current lifecycle proof is deliberately split:
manifest and Aetheria provider advertisement; Unity editor or batchmode PNG
capture remains a split blocker.
TUI capture evidence is already typed even though EveTui remains incubating.
`tools/evetui/evetui-capture-artifact.mjs` lowers the Aetheria world surface
through `EveTuiShell` into `gamecult.eve.tui_grid.v1` and writes
`artifacts/evetui-capture/latest/tui-grid.json`. The parity export includes
that record as `runtime.captureArtifacts[]`; runtime-owner consumers can assert
it without depending on Eve's source layout. The remaining blocker is
owner-repo production of the same capture path, not absence of a cell-grid
artifact.
EveUnity split handoff evidence:
```powershell
@ -511,8 +520,8 @@ Pending runtimes are allowed. Silent fake parity is not.
## Next Cuts
1. Extend runtime-owned image/layout probes to the Unity/Electron/TUI capture
blockers.
1. Extend runtime-owned image/layout probes to the Unity/Electron capture
blockers and move EveTui's JSON grid capture path into the EveTui owner repo.
2. Normalize text scale and font loading across web, Flutter Android, Flutter
desktop, and iOS.
3. Give iOS a real `vn.stage` scene compositor instead of compact stacked

View file

@ -114,7 +114,7 @@
},
"capture": {
"ownerRepo": "EveTui",
"status": "pending-terminal-capture",
"status": "cell-grid-json-capture",
"captureContract": {
"ownerRepo": "EveTui",
"runtimeId": "tui",
@ -122,27 +122,41 @@
"requestSchema": "gamecult.eve.runtime_capture_request.v1",
"requestBuilder": "tools/evetui/evetui-capture-contract.mjs",
"advertisementPath": "web/fixtures/aetheria.provider-advertisement.json",
"captureKind": "terminal-transcript-or-cell-grid",
"artifactKind": "ansi-transcript-or-json-grid",
"artifactPattern": "artifacts/evetui-capture/{stamp}/tui-transcript.ansi",
"captureKind": "terminal-cell-grid",
"artifactKind": "json-grid",
"artifactPattern": "artifacts/evetui-capture/{stamp}/tui-grid.json",
"conformanceAttachment": "runtime.captureArtifacts[]",
"requiredProvider": "aetheria",
"requiredSurface": "aetheria.daemon.game",
"authority": "Capture observes terminal projection only; provider state, command acceptance, and receipts stay provider-owned.",
"publishProof": "Terminal transcript or cell-grid capture artifact is attached to the runtime conformance report from EveTui."
"publishProof": "Terminal cell-grid JSON capture artifact is attached to the runtime conformance report from EveTui."
},
"evidencePaths": [
"tools/evetui/evetui-capture-contract.mjs",
"tools/evetui/evetui-capture-contract.test.mjs",
"scripts/run-evetui-capture-contract-smoke.ps1"
"tools/evetui/evetui-capture-artifact.mjs",
"tools/evetui/evetui-capture-artifact.test.mjs",
"scripts/run-evetui-capture-contract-smoke.ps1",
"scripts/run-evetui-capture-smoke.ps1"
],
"pendingProofs": [
"TUI terminal transcript or cell-grid capture artifact is attached to the runtime conformance report."
"TUI capture artifact is produced from the EveTui owner repo rather than Eve incubation."
]
}
},
"capture": {
"status": "missing",
"note": "TUI transcript or cell-grid capture is a split blocker until EveTui attaches durable terminal artifacts."
"status": "json-grid",
"note": "TUI attaches a provider-advertisement-backed gamecult.eve.tui_grid.v1 JSON capture artifact; packaged runtime ownership remains an EveTui split blocker.",
"artifacts": [
{
"kind": "json-grid",
"schema": "gamecult.eve.tui_grid.v1",
"path": "artifacts/evetui-capture/latest/tui-grid.json",
"providerId": "aetheria",
"surfaceId": "aetheria.daemon.game",
"sourceSurfacePath": "web/fixtures/aetheria-world-surface.json",
"requestPath": "artifacts/evetui-capture/latest/capture-request.json"
}
]
}
}

View file

@ -47,10 +47,13 @@
"currentPaths": [
"tools/evetui/evetui-capture-contract.mjs",
"tools/evetui/evetui-capture-contract.test.mjs",
"scripts/run-evetui-capture-contract-smoke.ps1"
"tools/evetui/evetui-capture-artifact.mjs",
"tools/evetui/evetui-capture-artifact.test.mjs",
"scripts/run-evetui-capture-contract-smoke.ps1",
"scripts/run-evetui-capture-smoke.ps1"
],
"destinationOwner": "EveTui",
"replacementProof": "EveTui builds a provider-advertisement-backed terminal capture request now; the split is not complete until EveTui attaches deterministic terminal transcript or cell-grid capture artifacts to conformance output."
"replacementProof": "EveTui builds a provider-advertisement-backed terminal capture request and deterministic gamecult.eve.tui_grid.v1 JSON capture artifact now; the split is not complete until EveTui owns that capture path outside Eve incubation."
}
],
"contractInputs": [
@ -75,6 +78,6 @@
"EveTui consumes at least one non-Aetheria provider advertisement.",
"EveTui consumes Aetheria's agent-facing TUI surface through provider advertisements without importing Aetheria internals.",
"EveTui emits gamecult.eve.command.v1 intents and renders provider receipts.",
"EveTui attaches terminal transcript or cell-grid capture artifacts to conformance output."
"EveTui attaches terminal transcript or cell-grid capture artifacts to conformance output from its owner repo."
]
}

View file

@ -1113,6 +1113,12 @@
"type": "string",
"minLength": 1
},
"captureArtifacts": {
"type": "array"
},
"captureArtifactErrors": {
"type": "array"
},
"lifecycle": {
"type": "object"
},

View file

@ -489,6 +489,61 @@
}
},
"additionalProperties": true
},
"capture": {
"type": "object",
"properties": {
"status": {
"type": "string",
"minLength": 1
},
"note": {
"type": "string"
},
"artifacts": {
"type": "array",
"items": {
"type": "object",
"required": [
"kind",
"schema",
"path",
"providerId",
"surfaceId"
],
"properties": {
"kind": {
"type": "string",
"minLength": 1
},
"schema": {
"type": "string",
"minLength": 1
},
"path": {
"type": "string",
"minLength": 1
},
"providerId": {
"type": "string",
"minLength": 1
},
"surfaceId": {
"type": "string",
"minLength": 1
},
"sourceSurfacePath": {
"type": "string"
},
"requestPath": {
"type": "string"
}
},
"additionalProperties": true
}
}
},
"additionalProperties": true
}
},
"additionalProperties": true

View file

@ -84,15 +84,15 @@ foreach ($expectation in @(
@{ field = "targetId"; value = "tui" },
@{ field = "commandBoundary"; value = "aetheria.daemon.commands" },
@{ field = "receiptSchema"; value = "aetheria.eve_command_acceptance_status.v1" },
@{ field = "captureKind"; value = "terminal-transcript-or-cell-grid" },
@{ field = "artifactKind"; value = "ansi-transcript-or-json-grid" },
@{ field = "captureKind"; value = "terminal-cell-grid" },
@{ field = "artifactKind"; value = "json-grid" },
@{ field = "conformanceAttachment"; value = "runtime.captureArtifacts[]" }
)) {
if ($request.($expectation.field) -ne $expectation.value) {
throw "EveTui capture request $($expectation.field) expected $($expectation.value) got $($request.($expectation.field))"
}
}
if ($request.artifactPath -ne "artifacts/evetui-capture/$Stamp/tui-transcript.ansi") {
if ($request.artifactPath -ne "artifacts/evetui-capture/$Stamp/tui-grid.json") {
throw "Unexpected EveTui capture artifact path: $($request.artifactPath)"
}

View file

@ -0,0 +1,73 @@
param(
[string] $CapabilityManifestPath = "runtimes\incubating\eve-tui\eve-runtime-capability.json",
[string] $AdvertisementPath = "web\fixtures\aetheria.provider-advertisement.json",
[string] $SurfacePath = "web\fixtures\aetheria-world-surface.json",
[string] $OutputPath = "artifacts\evetui-capture\latest\tui-grid.json",
[string] $RequestOutputPath = "artifacts\evetui-capture\latest\capture-request.json",
[string] $Stamp = "latest"
)
$ErrorActionPreference = "Stop"
$projectRoot = Split-Path -Parent $PSScriptRoot
function Resolve-RepoPath([string] $candidate) {
if ([System.IO.Path]::IsPathRooted($candidate)) {
return $candidate
}
return Join-Path $projectRoot $candidate
}
$absoluteCapabilityPath = Resolve-RepoPath $CapabilityManifestPath
$absoluteAdvertisementPath = Resolve-RepoPath $AdvertisementPath
$absoluteSurfacePath = Resolve-RepoPath $SurfacePath
$absoluteOutputPath = Resolve-RepoPath $OutputPath
$absoluteRequestOutputPath = Resolve-RepoPath $RequestOutputPath
foreach ($path in @($absoluteCapabilityPath, $absoluteAdvertisementPath, $absoluteSurfacePath)) {
if (-not (Test-Path -LiteralPath $path)) {
throw "EveTui capture input not found: $path"
}
}
node --test (Join-Path $projectRoot "tools\evetui\evetui-capture-artifact.test.mjs")
if ($LASTEXITCODE -ne 0) {
throw "EveTui capture artifact tests failed with exit code $LASTEXITCODE"
}
node (Join-Path $projectRoot "tools\evetui\evetui-capture-artifact.mjs") `
--capability $absoluteCapabilityPath `
--advertisement $absoluteAdvertisementPath `
--surface $absoluteSurfacePath `
--output $absoluteOutputPath `
--request-output $absoluteRequestOutputPath `
--stamp $Stamp
if ($LASTEXITCODE -ne 0) {
throw "EveTui capture artifact builder failed with exit code $LASTEXITCODE"
}
foreach ($path in @($absoluteOutputPath, $absoluteRequestOutputPath)) {
if (-not (Test-Path -LiteralPath $path)) {
throw "EveTui capture artifact output missing: $path"
}
}
$grid = Get-Content -LiteralPath $absoluteOutputPath -Raw | ConvertFrom-Json
foreach ($expectation in @(
@{ field = "schema"; value = "gamecult.eve.tui_grid.v1" },
@{ field = "runtimeId"; value = "tui" },
@{ field = "providerId"; value = "aetheria" },
@{ field = "surfaceId"; value = "aetheria.daemon.game" },
@{ field = "commandBoundary"; value = "aetheria.daemon.commands" },
@{ field = "receiptSchema"; value = "aetheria.eve_command_acceptance_status.v1" },
@{ field = "artifactKind"; value = "json-grid" },
@{ field = "captureKind"; value = "terminal-cell-grid" }
)) {
if ($grid.($expectation.field) -ne $expectation.value) {
throw "EveTui grid $($expectation.field) expected $($expectation.value) got $($grid.($expectation.field))"
}
}
if (@($grid.lines).Count -lt 1) {
throw "EveTui grid capture must contain terminal lines"
}
Write-Host "EveTui capture smoke passed: $absoluteOutputPath"

View file

@ -159,7 +159,7 @@ if ($captureContract.targetId -ne "tui") {
if ($captureContract.requestSchema -ne "gamecult.eve.runtime_capture_request.v1") {
throw "EveTui capture contract has unexpected request schema: $($captureContract.requestSchema)"
}
if ($captureContract.captureKind -ne "terminal-transcript-or-cell-grid") {
if ($captureContract.captureKind -ne "terminal-cell-grid") {
throw "EveTui capture contract has unexpected capture kind: $($captureContract.captureKind)"
}
foreach ($pathProperty in @("requestBuilder", "advertisementPath")) {
@ -174,5 +174,6 @@ foreach ($pathProperty in @("requestBuilder", "advertisementPath")) {
& (Join-Path $projectRoot "scripts\run-evetui-release-contract-smoke.ps1")
& (Join-Path $projectRoot "scripts\run-evetui-provider-shell-smoke.ps1")
& (Join-Path $projectRoot "scripts\run-evetui-capture-contract-smoke.ps1")
& (Join-Path $projectRoot "scripts\run-evetui-capture-smoke.ps1")
Write-Host "EveTui lifecycle smoke passed: $absoluteManifestPath"

View file

@ -20,6 +20,7 @@ try {
-OutputPath "artifacts\web-reference-layout-probe\latest\sai-vn.json" `
-Port 8893 `
-SkipBuild
& (Join-Path $PSScriptRoot "run-evetui-capture-smoke.ps1")
node .\tools\parity\run-parity.mjs
& (Join-Path $PSScriptRoot "run-eveconformance-handoff-smoke.ps1")
& (Join-Path $PSScriptRoot "run-plugin-handoff-smoke.ps1")

View file

@ -122,7 +122,8 @@ node $consumerScript $consumerExport `
--expect-runtime-capture-status unity-uitoolkit:semantic `
--expect-runtime-capture-status unity-scene:missing `
--expect-runtime-capture-status electron-shell:missing `
--expect-runtime-capture-status tui:missing `
--expect-runtime-capture-status tui:json-grid `
--expect-runtime-capture-artifact tui:json-grid:gamecult.eve.tui_grid.v1:aetheria:aetheria.daemon.game `
--expect-runtime-capture-status direct2d:missing `
--expect-screenshot-metric web:embedded-surface:structure:pass `
--expect-screenshot-metric web:embedded-surface:color-tokens:pass `
@ -133,7 +134,7 @@ node $consumerScript $consumerExport `
--expect-screenshot-metric web:sai-vn:bounding-boxes:pass `
--expect-screenshot-metric unity-scene:aetheria-world:bounding-boxes:pending-capture `
--expect-screenshot-metric electron-shell:aetheria-world:bounding-boxes:pending-capture `
--expect-screenshot-metric tui:aetheria-world:bounding-boxes:pending-capture `
--expect-screenshot-metric tui:aetheria-world:bounding-boxes:terminal-grid-capture `
--expect-runtime-lifecycle-status unity-uitoolkit:release:incubating-upm-package `
--expect-runtime-lifecycle-status unity-uitoolkit:test:batchmode-editmode-tests-and-consumer-build-smoke `
--expect-runtime-lifecycle-status unity-uitoolkit:capture:pending-editor-capture `
@ -203,10 +204,10 @@ node $consumerScript $consumerExport `
--expect-runtime-lifecycle-field "electron-shell:capture:captureContract.requiredSurface:aetheria.daemon.game" `
--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:pending-terminal-capture `
--expect-runtime-lifecycle-status tui:capture:cell-grid-json-capture `
--expect-runtime-lifecycle-pending "tui:release:TUI package release" `
--expect-runtime-lifecycle-pending "tui:test:TUI provider-advertisement smoke runs from EveTui with durable terminal transcript or cell-grid artifacts" `
--expect-runtime-lifecycle-pending "tui:capture:TUI terminal transcript or cell-grid capture artifact" `
--expect-runtime-lifecycle-pending "tui:capture:TUI capture artifact is produced from the EveTui owner repo" `
--expect-runtime-lifecycle-field "tui:release:releaseContract.artifactKind:terminal-runtime" `
--expect-runtime-lifecycle-field "tui:release:releaseContract.versionSource:runtimes/incubating/eve-tui/package.json" `
--expect-runtime-lifecycle-field "tui:release:releaseContract.requestSchema:gamecult.eve.runtime_release_request.v1" `
@ -214,7 +215,9 @@ node $consumerScript $consumerExport `
--expect-runtime-lifecycle-field "tui:release:releaseContract.artifactPattern:artifacts/evetui-release/{version}/eve-tui-{version}.tgz" `
--expect-runtime-lifecycle-field "tui:test:testContract.runnerKind:terminal-provider-shell-smoke" `
--expect-runtime-lifecycle-field "tui:test:testContract.runnerScript:scripts/run-evetui-provider-shell-smoke.ps1" `
--expect-runtime-lifecycle-field "tui:capture:captureContract.captureKind:terminal-transcript-or-cell-grid" `
--expect-runtime-lifecycle-field "tui:capture:captureContract.captureKind:terminal-cell-grid" `
--expect-runtime-lifecycle-field "tui:capture:captureContract.artifactKind:json-grid" `
--expect-runtime-lifecycle-field "tui:capture:captureContract.artifactPattern:artifacts/evetui-capture/{stamp}/tui-grid.json" `
--expect-runtime-lifecycle-field "tui:capture:captureContract.targetId:tui" `
--expect-runtime-lifecycle-field "tui:capture:captureContract.requestSchema:gamecult.eve.runtime_capture_request.v1" `
--expect-runtime-lifecycle-field "tui:capture:captureContract.requestBuilder:tools/evetui/evetui-capture-contract.mjs" `

View file

@ -51,6 +51,7 @@ if (!exportDirectory) {
" --expect-runtime-handoff <runtimeId>",
" --expect-runtime-command-schema <runtimeId:schema>",
" --expect-runtime-capture-status <runtimeId:status>",
" --expect-runtime-capture-artifact <runtimeId:kind:schema:providerId:surfaceId>",
" --expect-runtime-lifecycle-status <runtimeId:stage:status>",
" --expect-runtime-lifecycle-pending <runtimeId:stage:pending-proof-substring>",
" --expect-runtime-lifecycle-field <runtimeId:stage:field.path:value>",
@ -588,6 +589,26 @@ function validateIndex(index, directory, expectations, errors) {
errors.push(`runtimes:${expectation.runtimeId}:captureStatus:expected ${expectation.status} got ${runtime.captureStatus || ""}`);
}
}
for (const expectation of expectations.runtimeCaptureArtifacts) {
const runtime = runtimes.find(candidate => candidate.runtimeId === expectation.runtimeId);
if (!runtime) {
errors.push(`runtimes:${expectation.runtimeId}:missing`);
continue;
}
const artifacts = Array.isArray(runtime.captureArtifacts) ? runtime.captureArtifacts : [];
const artifact = artifacts.find(candidate =>
candidate.kind === expectation.kind
&& candidate.schema === expectation.schema
&& candidate.providerId === expectation.providerId
&& candidate.surfaceId === expectation.surfaceId);
if (!artifact) {
errors.push(`runtimes:${expectation.runtimeId}:captureArtifacts:${expectation.kind}:${expectation.schema}:${expectation.providerId}:${expectation.surfaceId}:missing`);
}
const artifactErrors = Array.isArray(runtime.captureArtifactErrors) ? runtime.captureArtifactErrors : [];
if (artifactErrors.length) {
errors.push(`runtimes:${expectation.runtimeId}:captureArtifactErrors:${artifactErrors.join(",")}`);
}
}
for (const expectation of expectations.runtimeLifecycleStatuses) {
const runtime = runtimes.find(candidate => candidate.runtimeId === expectation.runtimeId);
if (!runtime) {
@ -1100,6 +1121,7 @@ function parseArguments(args) {
runtimeHandoffs: [],
runtimeCommandSchemas: [],
runtimeCaptureStatuses: [],
runtimeCaptureArtifacts: [],
runtimeLifecycleStatuses: [],
runtimeLifecyclePendingProofs: [],
runtimeLifecycleFields: [],
@ -1154,6 +1176,7 @@ function parseArguments(args) {
["--expect-runtime-handoff", expectations.runtimeHandoffs],
["--expect-runtime-command-schema", expectations.runtimeCommandSchemas],
["--expect-runtime-capture-status", expectations.runtimeCaptureStatuses],
["--expect-runtime-capture-artifact", expectations.runtimeCaptureArtifacts],
["--expect-runtime-lifecycle-status", expectations.runtimeLifecycleStatuses],
["--expect-runtime-lifecycle-pending", expectations.runtimeLifecyclePendingProofs],
["--expect-runtime-lifecycle-field", expectations.runtimeLifecycleFields],
@ -1240,6 +1263,8 @@ function parseArguments(args) {
target.push(parseRuntimeExpectation(value, "schema"));
} else if (option === "--expect-runtime-capture-status") {
target.push(parseRuntimeExpectation(value, "status"));
} else if (option === "--expect-runtime-capture-artifact") {
target.push(parseRuntimeCaptureArtifactExpectation(value));
} else if (option === "--expect-runtime-lifecycle-status") {
target.push(parseRuntimeLifecycleExpectation(value, "status"));
} else if (option === "--expect-runtime-lifecycle-pending") {
@ -1293,6 +1318,21 @@ function parseScreenshotMetricExpectation(value) {
};
}
function parseRuntimeCaptureArtifactExpectation(value) {
const parts = value.split(":");
if (parts.length !== 5 || parts.some(part => !part)) {
console.error(`Expected runtime capture artifact in <runtimeId:kind:schema:providerId:surfaceId> form, got: ${value}`);
process.exit(2);
}
return {
runtimeId: parts[0],
kind: parts[1],
schema: parts[2],
providerId: parts[3],
surfaceId: parts[4],
};
}
function parseWorldLoweringGapExpectation(value) {
const parts = value.split(":");
if (parts.length !== 5 || parts.some(part => !part)) {

View file

@ -0,0 +1,115 @@
import { mkdirSync, writeFileSync } from "node:fs";
import path from "node:path";
import { fileURLToPath } from "node:url";
import { EveTuiShell } from "../../runtimes/incubating/eve-tui/src/eve-tui-shell.mjs";
import { buildTuiCaptureRequest, loadJsonFile, writeCaptureRequest } from "./evetui-capture-contract.mjs";
export function buildTuiCaptureArtifact({
advertisement,
capabilityManifest,
surfaceDocument,
advertisementPath = "",
capabilityManifestPath = "",
stamp = "latest",
width = 80,
} = {}) {
const request = buildTuiCaptureRequest({
advertisement,
capabilityManifest,
advertisementPath,
capabilityManifestPath,
stamp,
});
const shell = new EveTuiShell({ width });
const grid = shell.lowerSurface(surfaceDocument, advertisement, request.surfaceId);
validateGridArtifact(grid, request);
return {
request,
grid: {
...grid,
artifactKind: request.artifactKind,
captureKind: request.captureKind,
sourceAdvertisementPath: request.sourceAdvertisementPath,
sourceCapabilityManifestPath: request.sourceCapabilityManifestPath,
issuedAtUtc: request.issuedAtUtc,
},
};
}
export function writeTuiCaptureArtifact({ request, grid }, { outputPath, requestOutputPath = "" } = {}) {
if (!outputPath) throw new Error("outputPath is required");
mkdirSync(path.dirname(outputPath), { recursive: true });
writeFileSync(outputPath, `${JSON.stringify(grid, null, 2)}\n`, "utf8");
if (requestOutputPath) {
writeCaptureRequest(request, requestOutputPath);
}
}
function validateGridArtifact(grid, request) {
if (grid.schema !== "gamecult.eve.tui_grid.v1") {
throw new Error(`Unexpected TUI grid schema: ${grid.schema || ""}`);
}
for (const [field, expected] of [
["runtimeId", request.runtimeId],
["providerId", request.providerId],
["surfaceId", request.surfaceId],
["projectionKind", request.projectionKind],
["commandBoundary", request.commandBoundary],
["receiptSchema", request.receiptSchema],
]) {
if ((grid[field] || "") !== (expected || "")) {
throw new Error(`TUI grid ${field} expected ${expected || ""} got ${grid[field] || ""}`);
}
}
if (!Array.isArray(grid.lines) || !grid.lines.length) {
throw new Error("TUI grid artifact must contain at least one line");
}
}
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", "surface", "output"]) {
if (!args[required]) {
throw new Error(`missing required --${required}`);
}
}
const stamp = args.stamp || "latest";
const artifact = buildTuiCaptureArtifact({
advertisement: loadJsonFile(args.advertisement),
capabilityManifest: loadJsonFile(args.capability),
surfaceDocument: loadJsonFile(args.surface),
advertisementPath: args.advertisement,
capabilityManifestPath: args.capability,
stamp,
width: Number(args.width || 80),
});
writeTuiCaptureArtifact(artifact, {
outputPath: args.output,
requestOutputPath: args["request-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();
}

View file

@ -0,0 +1,33 @@
import assert from "node:assert/strict";
import { readFileSync } from "node:fs";
import test from "node:test";
import { buildTuiCaptureArtifact } from "./evetui-capture-artifact.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("../../runtimes/incubating/eve-tui/eve-runtime-capability.json", import.meta.url), "utf8"));
const surfaceDocument = JSON.parse(readFileSync(new URL("../../web/fixtures/aetheria-world-surface.json", import.meta.url), "utf8"));
test("builds TUI JSON grid capture artifact from provider advertisement and surface", () => {
const { request, grid } = buildTuiCaptureArtifact({
advertisement,
capabilityManifest,
surfaceDocument,
advertisementPath: "web/fixtures/aetheria.provider-advertisement.json",
capabilityManifestPath: "runtimes/incubating/eve-tui/eve-runtime-capability.json",
stamp: "smoke",
width: 72,
});
assert.equal(request.artifactKind, "json-grid");
assert.equal(request.artifactPath, "artifacts/evetui-capture/smoke/tui-grid.json");
assert.equal(grid.schema, "gamecult.eve.tui_grid.v1");
assert.equal(grid.runtimeId, "tui");
assert.equal(grid.providerId, "aetheria");
assert.equal(grid.surfaceId, "aetheria.daemon.game");
assert.equal(grid.commandBoundary, "aetheria.daemon.commands");
assert.equal(grid.receiptSchema, "aetheria.eve_command_acceptance_status.v1");
assert.equal(grid.artifactKind, "json-grid");
assert.equal(grid.captureKind, "terminal-cell-grid");
assert.equal(grid.width, 72);
assert.ok(grid.lines.some(line => line.includes("aetheria.daemon.game")));
});

View file

@ -24,9 +24,9 @@ test("builds TUI capture request from provider advertisement", () => {
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.captureKind, "terminal-transcript-or-cell-grid");
assert.equal(request.artifactKind, "ansi-transcript-or-json-grid");
assert.equal(request.artifactPath, "artifacts/evetui-capture/smoke/tui-transcript.ansi");
assert.equal(request.captureKind, "terminal-cell-grid");
assert.equal(request.artifactKind, "json-grid");
assert.equal(request.artifactPath, "artifacts/evetui-capture/smoke/tui-grid.json");
assert.match(request.authority, /provider state/);
});

View file

@ -716,7 +716,7 @@
],
"pendingProofs": [
"TUI runtime body graduates to EveTui outside provider product code.",
"TUI terminal transcript or cell-grid captures are attached to conformance output."
"TUI terminal transcript or cell-grid captures are attached to conformance output from the EveTui owner repo."
]
}
],
@ -868,9 +868,12 @@
"scripts/run-evetui-lifecycle-smoke.ps1",
"scripts/run-evetui-provider-shell-smoke.ps1",
"scripts/run-evetui-capture-contract-smoke.ps1",
"scripts/run-evetui-capture-smoke.ps1",
"scripts/run-evetui-release-contract-smoke.ps1",
"tools/evetui/evetui-capture-contract.mjs",
"tools/evetui/evetui-capture-contract.test.mjs",
"tools/evetui/evetui-capture-artifact.mjs",
"tools/evetui/evetui-capture-artifact.test.mjs",
"tools/evetui/evetui-release-contract.mjs",
"tools/evetui/evetui-release-contract.test.mjs"
],
@ -885,11 +888,19 @@
},
{
"path": "tools/evetui/evetui-capture-contract.mjs",
"contains": ["buildTuiCaptureRequest", "gamecult.eve.runtime_capture_request.v1", "ansi-transcript-or-json-grid", "loweringTargets", "writeCaptureRequest"]
"contains": ["buildTuiCaptureRequest", "gamecult.eve.runtime_capture_request.v1", "artifactKind", "loweringTargets", "writeCaptureRequest"]
},
{
"path": "tools/evetui/evetui-capture-contract.test.mjs",
"contains": ["builds TUI capture request from provider advertisement", "rejects capture request when the surface does not advertise TUI", "artifacts/evetui-capture/smoke/tui-transcript.ansi"]
"contains": ["builds TUI capture request from provider advertisement", "rejects capture request when the surface does not advertise TUI", "artifacts/evetui-capture/smoke/tui-grid.json"]
},
{
"path": "tools/evetui/evetui-capture-artifact.mjs",
"contains": ["buildTuiCaptureArtifact", "EveTuiShell", "gamecult.eve.tui_grid.v1", "writeTuiCaptureArtifact", "request-output"]
},
{
"path": "tools/evetui/evetui-capture-artifact.test.mjs",
"contains": ["builds TUI JSON grid capture artifact", "gamecult.eve.tui_grid.v1", "artifacts/evetui-capture/smoke/tui-grid.json"]
},
{
"path": "tools/evetui/evetui-release-contract.mjs",
@ -901,8 +912,19 @@
}
],
"capture": {
"status": "missing",
"note": "TUI transcript or cell-grid capture is a split blocker until EveTui attaches durable terminal artifacts."
"status": "json-grid",
"note": "TUI attaches a provider-advertisement-backed gamecult.eve.tui_grid.v1 JSON capture artifact; packaged runtime ownership remains an EveTui split blocker.",
"artifacts": [
{
"kind": "json-grid",
"schema": "gamecult.eve.tui_grid.v1",
"path": "artifacts/evetui-capture/latest/tui-grid.json",
"providerId": "aetheria",
"surfaceId": "aetheria.daemon.game",
"sourceSurfacePath": "web/fixtures/aetheria-world-surface.json",
"requestPath": "artifacts/evetui-capture/latest/capture-request.json"
}
]
}
},
{

View file

@ -318,6 +318,8 @@ async function evaluateRuntime(runtime, fixtureResults) {
const capabilityManifestDocument = await readRuntimeCapabilityManifestDocument(runtime);
const lifecycleManifestDocument = await readRuntimeLifecycleManifestDocument(runtime);
const lifecycle = runtime.lifecycle || capabilityManifestDocument?.lifecycle || readLifecycleStages(lifecycleManifestDocument);
const capture = runtime.capture || capabilityManifestDocument?.capture || null;
const captureArtifactErrors = validateRuntimeCaptureArtifacts(capture);
const lifecycleErrors = await validateRuntimeLifecycle(runtime, lifecycle, lifecycleManifestDocument);
const splitHandoffPath = await readRuntimeSplitHandoffPath(runtime);
const splitHandoffErrors = await validateRuntimeSplitHandoff(runtime, splitHandoffPath);
@ -333,6 +335,7 @@ async function evaluateRuntime(runtime, fixtureResults) {
if (runtime.kind === "active" && commandTransportSmokeErrors.length) status = "missing-command-transport-smoke";
if (runtime.kind === "active" && capabilityManifestErrors.length) status = "invalid-runtime-capability";
if (runtime.kind === "active" && lifecycleErrors.length) status = "invalid-runtime-lifecycle";
if (runtime.kind === "active" && captureArtifactErrors.length) status = "invalid-runtime-capture-artifact";
if (runtime.kind === "active" && splitHandoffErrors.length) status = "invalid-runtime-split-handoff";
if (runtime.kind === "active" && localProviderCatalogErrors.length) status = "invalid-local-provider-catalog";
if (runtime.kind === "active" && missingIncubationFields.length) status = "missing-incubation-metadata";
@ -378,6 +381,7 @@ async function evaluateRuntime(runtime, fixtureResults) {
localProviderCatalog: runtime.localProviderCatalog || null,
localProviderCatalogErrors,
lifecycle,
captureArtifactErrors,
ownerRepo: runtime.ownerRepo || "",
repoRole: runtime.repoRole || "",
graduationTrigger: runtime.graduationTrigger || "",
@ -387,7 +391,7 @@ async function evaluateRuntime(runtime, fixtureResults) {
activationCriteria: runtime.activationCriteria || [],
missingIncubationFields,
pluginCapabilityGaps,
capture: runtime.capture,
capture,
};
}
@ -1914,6 +1918,8 @@ function buildConformanceExport(report) {
lifecycleManifestPath: runtime.lifecycleManifest?.manifestPath || "",
commandTransportSchema: runtimeCommandTransportSchema(runtime),
captureStatus: runtime.capture?.status || "",
captureArtifacts: runtime.capture?.artifacts || [],
captureArtifactErrors: runtime.captureArtifactErrors || [],
lifecycle: runtime.lifecycle,
lifecycleErrors: runtime.lifecycleErrors || [],
missingEvidence: [
@ -1923,6 +1929,7 @@ function buildConformanceExport(report) {
...runtime.commandTransportSmokeErrors.map(id => `command-smoke:${id}`),
...runtime.capabilityManifestErrors.map(id => `runtime-capability:${id}`),
...runtime.lifecycleErrors.map(id => `runtime-lifecycle:${id}`),
...runtime.captureArtifactErrors.map(id => `runtime-capture:${id}`),
...runtime.localProviderCatalogErrors.map(id => `local-provider-catalog:${id}`),
],
}))
@ -2013,6 +2020,8 @@ function buildConformanceExport(report) {
splitHandoffExportPath: makeHandoffExportPath("runtime", runtime.id, runtime.splitHandoffPath || ""),
commandTransportSchema: runtimeCommandTransportSchema(runtime),
captureStatus: runtime.capture?.status || "",
captureArtifacts: runtime.capture?.artifacts || [],
captureArtifactErrors: runtime.captureArtifactErrors || [],
...(runtime.lifecycle ? { lifecycle: runtime.lifecycle } : {}),
worldSurfaceLowering: runtime.worldSurfaceLowering || [],
})),
@ -2075,6 +2084,7 @@ function buildCapabilityMatrix(report) {
worldSurfaceLowering: runtime.worldSurfaceLowering || [],
commandTransportSchema: runtimeCommandTransportSchema(runtime),
captureStatus: runtime.capture?.status || "",
captureArtifacts: runtime.capture?.artifacts || [],
splitHandoffExportPath: makeHandoffExportPath("runtime", runtime.id, runtime.splitHandoffPath || ""),
}));
@ -2184,6 +2194,7 @@ function collectCapabilityGaps(report) {
...(runtime.commandTransportSmokeErrors || []).map(id => `command-smoke:${id}`),
...(runtime.capabilityManifestErrors || []).map(id => `runtime-capability:${id}`),
...(runtime.lifecycleErrors || []).map(id => `runtime-lifecycle:${id}`),
...(runtime.captureArtifactErrors || []).map(id => `runtime-capture:${id}`),
...(runtime.splitHandoffErrors || []).map(id => `runtime-split-handoff:${id}`),
...(runtime.localProviderCatalogErrors || []).map(id => `local-provider-catalog:${id}`),
...(runtime.pluginCapabilityGaps || []).map(id => `plugin-capability:${id}`),
@ -2545,6 +2556,29 @@ function buildBoundingBoxMetric(runtime, fixture) {
}
const captureStatus = runtime.capture?.status || "";
if (captureStatus === "json-grid") {
const artifact = (runtime.capture?.artifacts || []).find(candidate => candidate.kind === "json-grid");
const errors = runtime.captureArtifactErrors || [];
return buildScreenshotMetric(runtime, fixture, {
metricKind: "bounding-boxes",
status: errors.length ? "fail" : "terminal-grid-capture",
score: errors.length ? 0 : 1,
expected: {
source: "runtime terminal grid capture",
schema: artifact?.schema || "gamecult.eve.tui_grid.v1",
path: artifact?.path || "",
},
actual: {
captureStatus,
artifactKind: artifact?.kind || "",
errors,
},
evidenceLayer: "runtime-terminal-grid-capture",
detail: errors.length
? "Declared terminal grid capture artifact is missing or invalid."
: "Terminal grid capture is present; graphical bounding boxes are not a TUI invariant.",
});
}
const runtimeHasCaptureBody = ["chrome-headless", "ssh-png", "adb-png", "golden", "ssh-golden"].includes(captureStatus);
let status = "pass";
let score = 1;
@ -2645,6 +2679,54 @@ function readRuntimeLayoutProbe(runtime, fixtureId) {
return { config, document, errors };
}
function validateRuntimeCaptureArtifacts(capture) {
const artifacts = Array.isArray(capture?.artifacts) ? capture.artifacts : [];
const errors = [];
for (const artifact of artifacts) {
const artifactPath = artifact.path || "";
if (!artifactPath) {
errors.push("artifact.path:missing");
continue;
}
const absoluteArtifactPath = path.join(repoRoot, artifactPath);
if (!existsSync(absoluteArtifactPath)) {
errors.push(`${artifactPath}:missing`);
continue;
}
let document;
try {
document = JSON.parse(readFileSync(absoluteArtifactPath, "utf8"));
} catch (error) {
errors.push(`${artifactPath}:invalid-json:${error instanceof Error ? error.message : String(error)}`);
continue;
}
for (const [field, expected] of [
["schema", artifact.schema],
["runtimeId", artifact.runtimeId],
["providerId", artifact.providerId],
["surfaceId", artifact.surfaceId],
]) {
if (expected && document[field] !== expected) {
errors.push(`${artifactPath}:${field}:expected ${expected} got ${document[field] || ""}`);
}
}
if (artifact.schema && manifest.schemas?.[artifact.schema]) {
try {
const schema = JSON.parse(readFileSync(path.join(repoRoot, manifest.schemas[artifact.schema]), "utf8"));
errors.push(...validateSchemaSubset(schema, document, `captureArtifact:${artifactPath}`));
} catch (error) {
errors.push(`${manifest.schemas[artifact.schema]}:unreadable:${error instanceof Error ? error.message : String(error)}`);
}
}
for (const linkedPath of [artifact.sourceSurfacePath, artifact.requestPath].filter(Boolean)) {
if (!existsSync(path.join(repoRoot, linkedPath))) {
errors.push(`${artifactPath}:linked-path:${linkedPath}:missing`);
}
}
}
return errors;
}
function collectSplitTargetBlockers(report) {
return (report.splitTargets || []).flatMap(target => target.blockerRecords || []);
}