Attach EveTui grid capture evidence
This commit is contained in:
parent
0462cacadc
commit
ea372b1176
17 changed files with 502 additions and 35 deletions
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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."
|
||||
]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1113,6 +1113,12 @@
|
|||
"type": "string",
|
||||
"minLength": 1
|
||||
},
|
||||
"captureArtifacts": {
|
||||
"type": "array"
|
||||
},
|
||||
"captureArtifactErrors": {
|
||||
"type": "array"
|
||||
},
|
||||
"lifecycle": {
|
||||
"type": "object"
|
||||
},
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)"
|
||||
}
|
||||
|
||||
|
|
|
|||
73
scripts/run-evetui-capture-smoke.ps1
Normal file
73
scripts/run-evetui-capture-smoke.ps1
Normal 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"
|
||||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
|
|
|
|||
|
|
@ -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" `
|
||||
|
|
|
|||
|
|
@ -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)) {
|
||||
|
|
|
|||
115
tools/evetui/evetui-capture-artifact.mjs
Normal file
115
tools/evetui/evetui-capture-artifact.mjs
Normal 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();
|
||||
}
|
||||
33
tools/evetui/evetui-capture-artifact.test.mjs
Normal file
33
tools/evetui/evetui-capture-artifact.test.mjs
Normal 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")));
|
||||
});
|
||||
|
|
@ -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/);
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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 || []);
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue