Initial EveTui runtime
This commit is contained in:
commit
b01f481483
23 changed files with 2159 additions and 0 deletions
48
.github/workflows/runtime.yml
vendored
Normal file
48
.github/workflows/runtime.yml
vendored
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
name: EveTui runtime
|
||||
|
||||
on:
|
||||
push:
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
test-and-pack:
|
||||
runs-on: windows-latest
|
||||
env:
|
||||
GIT_LFS_SKIP_SMUDGE: "1"
|
||||
steps:
|
||||
- uses: actions/checkout@v7
|
||||
with:
|
||||
path: EveTui
|
||||
|
||||
- uses: actions/checkout@v7
|
||||
with:
|
||||
repository: GameCult/Eve
|
||||
path: Eve
|
||||
|
||||
- uses: actions/checkout@v7
|
||||
with:
|
||||
repository: GameCult/Aetheria
|
||||
path: Aetheria
|
||||
lfs: false
|
||||
|
||||
- name: Verify runtime lifecycle
|
||||
working-directory: EveTui
|
||||
shell: powershell
|
||||
run: .\scripts\run-evetui-lifecycle-smoke.ps1
|
||||
|
||||
- name: Pack runtime
|
||||
working-directory: EveTui
|
||||
shell: powershell
|
||||
run: |
|
||||
New-Item -ItemType Directory -Force artifacts\release | Out-Null
|
||||
npm pack --pack-destination artifacts\release
|
||||
|
||||
- name: Upload runtime package
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: eve-tui-${{ github.sha }}
|
||||
path: EveTui/artifacts/release/*.tgz
|
||||
if-no-files-found: error
|
||||
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
artifacts/
|
||||
node_modules/
|
||||
15
README.md
Normal file
15
README.md
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
# EveTui
|
||||
|
||||
EveTui is the provider-agnostic compact terminal lowerer for Eve surfaces. It
|
||||
consumes provider advertisements and surface documents, emits typed command
|
||||
intents through provider-owned boundaries, and publishes terminal-grid capture
|
||||
evidence without owning provider state or plugin semantics.
|
||||
|
||||
Eve owns the surface contract. EveTui owns terminal projection, package and test
|
||||
lifecycle, and capture artifacts. Providers remain the only writers of live
|
||||
state.
|
||||
|
||||
```powershell
|
||||
npm test
|
||||
powershell -ExecutionPolicy Bypass -File .\scripts\run-evetui-lifecycle-smoke.ps1
|
||||
```
|
||||
20
conformance/captures/aetheria/capture-request.json
Normal file
20
conformance/captures/aetheria/capture-request.json
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
{
|
||||
"schema": "gamecult.eve.runtime_capture_request.v1",
|
||||
"runtimeId": "tui",
|
||||
"ownerRepo": "EveTui",
|
||||
"providerId": "aetheria",
|
||||
"surfaceId": "aetheria.daemon.game",
|
||||
"surfaceKind": "interactive-world",
|
||||
"targetId": "tui",
|
||||
"projectionKind": "provider-authored-world-surface",
|
||||
"commandBoundary": "aetheria.daemon.commands",
|
||||
"receiptSchema": "aetheria.eve_command_acceptance_status.v1",
|
||||
"captureKind": "terminal-cell-grid",
|
||||
"artifactKind": "json-grid",
|
||||
"artifactPath": "artifacts/evetui-capture/latest/tui-grid.json",
|
||||
"conformanceAttachment": "runtime.captureArtifacts[]",
|
||||
"authority": "Capture observes terminal projection only; provider state, command acceptance, and receipts stay provider-owned.",
|
||||
"sourceAdvertisementPath": "E:\\Projects\\Eve\\..\\Aetheria\\conformance\\eve\\aetheria.provider-advertisement.json",
|
||||
"sourceCapabilityManifestPath": "E:\\Projects\\Eve\\runtimes\\incubating\\eve-tui\\eve-runtime-capability.json",
|
||||
"issuedAtUtc": "latest"
|
||||
}
|
||||
40
conformance/captures/aetheria/tui-grid.json
Normal file
40
conformance/captures/aetheria/tui-grid.json
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
{
|
||||
"schema": "gamecult.eve.tui_grid.v1",
|
||||
"runtimeId": "tui",
|
||||
"providerId": "aetheria",
|
||||
"surfaceId": "aetheria.daemon.game",
|
||||
"projectionKind": "provider-authored-world-surface",
|
||||
"commandBoundary": "aetheria.daemon.commands",
|
||||
"receiptSchema": "aetheria.eve_command_acceptance_status.v1",
|
||||
"ownership": "provider-owns-world-state-assets-command-acceptance-and-receipts",
|
||||
"width": 80,
|
||||
"lines": [
|
||||
"EveTui aetheria",
|
||||
"surface aetheria.daemon.game",
|
||||
"command aetheria.daemon.commands",
|
||||
"receipt aetheria.eve_command_acceptance_status.v1",
|
||||
"surface aetheria.daemon.game.root",
|
||||
" card aetheria.daemon.game.summary Daemon World",
|
||||
" text aetheria.daemon.game.summary.text Provider-owned world state project...",
|
||||
" metric aetheria.daemon.game.frame Frame",
|
||||
" partition aetheria.daemon.game.partition",
|
||||
" world aetheria.daemon.game.playable Daemon ARPG View",
|
||||
" world aetheria.daemon.game.entity.player Vanguard",
|
||||
" world aetheria.daemon.game.entity.raider Raider Scout",
|
||||
" world aetheria.daemon.game.entity.station Anchor Station",
|
||||
" world aetheria.daemon.game.flow Volumetric Flow",
|
||||
" world aetheria.daemon.game.gravity Current Zone Gravity",
|
||||
" card aetheria.daemon.game.commands Command Boundary",
|
||||
" command aetheria.daemon.game.command.focus Focus Relay",
|
||||
" command aetheria.daemon.game.command.move Issue Move Intent",
|
||||
" text aetheria.daemon.game.authority Commands are intents against Aether..."
|
||||
],
|
||||
"embeddedDocumentSlots": [],
|
||||
"pluginProjections": [],
|
||||
"lossiness": "terminal-grid-command-surface; summarizes provider-authored world surface without owning provider state",
|
||||
"artifactKind": "json-grid",
|
||||
"captureKind": "terminal-cell-grid",
|
||||
"sourceAdvertisementPath": "E:\\Projects\\Eve\\..\\Aetheria\\conformance\\eve\\aetheria.provider-advertisement.json",
|
||||
"sourceCapabilityManifestPath": "E:\\Projects\\Eve\\runtimes\\incubating\\eve-tui\\eve-runtime-capability.json",
|
||||
"issuedAtUtc": "latest"
|
||||
}
|
||||
20
conformance/captures/world-smoke/capture-request.json
Normal file
20
conformance/captures/world-smoke/capture-request.json
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
{
|
||||
"schema": "gamecult.eve.runtime_capture_request.v1",
|
||||
"runtimeId": "tui",
|
||||
"ownerRepo": "EveTui",
|
||||
"providerId": "eve.world-smoke",
|
||||
"surfaceId": "eve.world-smoke.surface",
|
||||
"surfaceKind": "interactive-world",
|
||||
"targetId": "tui",
|
||||
"projectionKind": "provider-authored-world-surface",
|
||||
"commandBoundary": "eve.world-smoke.commands",
|
||||
"receiptSchema": "eve.world_smoke.command_receipt.v1",
|
||||
"captureKind": "terminal-cell-grid",
|
||||
"artifactKind": "json-grid",
|
||||
"artifactPath": "artifacts/evetui-capture/latest/tui-grid.json",
|
||||
"conformanceAttachment": "runtime.captureArtifacts[]",
|
||||
"authority": "Capture observes terminal projection only; provider state, command acceptance, and receipts stay provider-owned.",
|
||||
"sourceAdvertisementPath": "E:\\Projects\\Eve\\web\\fixtures\\eve-world-smoke.provider-advertisement.json",
|
||||
"sourceCapabilityManifestPath": "E:\\Projects\\Eve\\runtimes\\incubating\\eve-tui\\eve-runtime-capability.json",
|
||||
"issuedAtUtc": "latest"
|
||||
}
|
||||
33
conformance/captures/world-smoke/tui-grid.json
Normal file
33
conformance/captures/world-smoke/tui-grid.json
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
{
|
||||
"schema": "gamecult.eve.tui_grid.v1",
|
||||
"runtimeId": "tui",
|
||||
"providerId": "eve.world-smoke",
|
||||
"surfaceId": "eve.world-smoke.surface",
|
||||
"projectionKind": "provider-authored-world-surface",
|
||||
"commandBoundary": "eve.world-smoke.commands",
|
||||
"receiptSchema": "eve.world_smoke.command_receipt.v1",
|
||||
"ownership": "provider-owns-world-state-and-command-receipts",
|
||||
"width": 80,
|
||||
"lines": [
|
||||
"EveTui eve.world-smoke",
|
||||
"surface eve.world-smoke.surface",
|
||||
"command eve.world-smoke.commands",
|
||||
"receipt eve.world_smoke.command_receipt.v1",
|
||||
"surface eve.world-smoke.root",
|
||||
" card eve.world-smoke.summary World Contract Control",
|
||||
" text eve.world-smoke.summary.text A tiny provider-owned world surface use...",
|
||||
" metric eve.world-smoke.tick Tick",
|
||||
" world eve.world-smoke.field Contract Field",
|
||||
" card eve.world-smoke.commands Command Boundary",
|
||||
" command eve.world-smoke.command.focus Focus Beacon",
|
||||
" text eve.world-smoke.authority The provider owns accepted state and recei..."
|
||||
],
|
||||
"embeddedDocumentSlots": [],
|
||||
"pluginProjections": [],
|
||||
"lossiness": "terminal-grid-command-surface; summarizes provider-authored world surface without owning provider state",
|
||||
"artifactKind": "json-grid",
|
||||
"captureKind": "terminal-cell-grid",
|
||||
"sourceAdvertisementPath": "E:\\Projects\\Eve\\web\\fixtures\\eve-world-smoke.provider-advertisement.json",
|
||||
"sourceCapabilityManifestPath": "E:\\Projects\\Eve\\runtimes\\incubating\\eve-tui\\eve-runtime-capability.json",
|
||||
"issuedAtUtc": "latest"
|
||||
}
|
||||
195
eve-runtime-capability.json
Normal file
195
eve-runtime-capability.json
Normal file
|
|
@ -0,0 +1,195 @@
|
|||
{
|
||||
"schema": "gamecult.eve.runtime_capability.v1",
|
||||
"runtimeId": "tui",
|
||||
"title": "Compact TUI",
|
||||
"owner": "EveTui",
|
||||
"incubation": {
|
||||
"status": "graduated",
|
||||
"ownerRepo": "EveTui",
|
||||
"currentHostRepo": "EveTui",
|
||||
"splitTarget": "EveTui",
|
||||
"graduationTrigger": "Graduated: EveTui owns package, tests, terminal lowering, command transport, lifecycle, and capture evidence."
|
||||
},
|
||||
"supportedFeatures": [
|
||||
"providerAdvertisements",
|
||||
"commandTransport",
|
||||
"terminalGridSummary",
|
||||
"terminalGridLowering",
|
||||
"embeddedDocuments",
|
||||
"pluginProjection"
|
||||
],
|
||||
"supportedPlugins": [
|
||||
{
|
||||
"pluginId": "sai.vn",
|
||||
"capabilities": [
|
||||
"vn.stage",
|
||||
"story.choose",
|
||||
"story.continue",
|
||||
"story.jump"
|
||||
],
|
||||
"projectionAdapter": "EveTuiShell.buildPluginProjection",
|
||||
"lossiness": "terminal-summary"
|
||||
},
|
||||
{
|
||||
"pluginId": "norn.graph",
|
||||
"capabilities": [
|
||||
"embed.norn"
|
||||
],
|
||||
"projectionAdapter": "EveTuiShell.buildPluginProjection",
|
||||
"lossiness": "terminal-outline"
|
||||
},
|
||||
{
|
||||
"pluginId": "tex.math",
|
||||
"capabilities": [
|
||||
"embed.tex",
|
||||
"tex.inline",
|
||||
"tex.block"
|
||||
],
|
||||
"projectionAdapter": "EveTuiShell.buildPluginProjection",
|
||||
"lossiness": "source-text"
|
||||
}
|
||||
],
|
||||
"unsupportedPlugins": [],
|
||||
"worldSurfaceLowering": [
|
||||
{
|
||||
"targetId": "tui",
|
||||
"surfaceKinds": ["interactive-world", "interactive-world-editor"],
|
||||
"projectionKinds": ["provider-authored-world-surface", "provider-authored-world-editor-surface"],
|
||||
"supportLevel": "terminal-grid-command-surface",
|
||||
"ownership": "runtime-lowers-provider-world-surface-without-owning-world-state",
|
||||
"lossiness": "terminal-grid-summary-only",
|
||||
"evidencePaths": [
|
||||
"src/eve-tui-shell.mjs",
|
||||
"test/eve-tui-shell.test.mjs",
|
||||
"../Eve/web/fixtures/sai-vn-surface.json",
|
||||
"../Aetheria/conformance/eve/aetheria.provider-advertisement.json",
|
||||
"../Eve/web/fixtures/eve-world-smoke.provider-advertisement.json"
|
||||
]
|
||||
}
|
||||
],
|
||||
"commandTransport": {
|
||||
"schema": "gamecult.eve.command_invocation.v1",
|
||||
"status": "provider-shell-contract-skeleton",
|
||||
"emitter": "EveTuiShell.createCommandIntent",
|
||||
"clientId": "tui",
|
||||
"authority": "TUI emits command intents through provider-advertised command boundaries; providers accept, deny, and publish receipts."
|
||||
},
|
||||
"lifecycle": {
|
||||
"release": {
|
||||
"ownerRepo": "EveTui",
|
||||
"status": "owner-npm-package",
|
||||
"releaseContract": {
|
||||
"ownerRepo": "EveTui",
|
||||
"packageName": "eve-tui",
|
||||
"packageRoot": ".",
|
||||
"versionSource": "package.json",
|
||||
"tagPattern": "evetui-v{version}",
|
||||
"artifactKind": "terminal-runtime",
|
||||
"requestSchema": "gamecult.eve.runtime_release_request.v1",
|
||||
"requestBuilder": "tools/evetui/evetui-release-contract.mjs",
|
||||
"artifactPattern": "artifacts/evetui-release/{version}/eve-tui-{version}.tgz",
|
||||
"publishProof": "Terminal runtime release is created from the EveTui repo and references the provider-agnostic TUI package root."
|
||||
},
|
||||
"evidencePaths": [
|
||||
"package.json",
|
||||
"src/eve-tui-shell.mjs",
|
||||
"tools/evetui/evetui-release-contract.mjs",
|
||||
"tools/evetui/evetui-release-contract.test.mjs",
|
||||
"scripts/run-evetui-release-contract-smoke.ps1"
|
||||
],
|
||||
"pendingProofs": []
|
||||
},
|
||||
"test": {
|
||||
"ownerRepo": "EveTui",
|
||||
"status": "provider-shell-contract-skeleton",
|
||||
"testContract": {
|
||||
"ownerRepo": "EveTui",
|
||||
"runnerKind": "terminal-provider-shell-smoke",
|
||||
"runnerScript": "scripts/run-evetui-provider-shell-smoke.ps1",
|
||||
"consumerProject": "../Aetheria",
|
||||
"packageName": "eve-tui",
|
||||
"testAssembly": "eve-tui-shell.test.mjs",
|
||||
"testPlatform": "Node terminal-grid contract skeleton",
|
||||
"resultsArtifact": "artifacts/evetui-provider-shell/{stamp}/provider-shell-results.json",
|
||||
"logArtifact": "artifacts/evetui-provider-shell/{stamp}/provider-shell.log",
|
||||
"manifestMutation": "None; the TUI consumes provider advertisements and must not mutate provider product manifests."
|
||||
},
|
||||
"evidencePaths": [
|
||||
"package.json",
|
||||
"src/eve-tui-shell.mjs",
|
||||
"test/eve-tui-shell.test.mjs",
|
||||
"scripts/run-evetui-provider-shell-smoke.ps1",
|
||||
"conformance/captures/aetheria/tui-grid.json",
|
||||
"conformance/captures/world-smoke/tui-grid.json"
|
||||
],
|
||||
"pendingProofs": []
|
||||
},
|
||||
"capture": {
|
||||
"ownerRepo": "EveTui",
|
||||
"status": "cell-grid-json-capture",
|
||||
"captureContract": {
|
||||
"ownerRepo": "EveTui",
|
||||
"runtimeId": "tui",
|
||||
"targetId": "tui",
|
||||
"requestSchema": "gamecult.eve.runtime_capture_request.v1",
|
||||
"requestBuilder": "tools/evetui/evetui-capture-contract.mjs",
|
||||
"advertisementPath": "../Aetheria/conformance/eve/aetheria.provider-advertisement.json",
|
||||
"captureKind": "terminal-cell-grid",
|
||||
"artifactKind": "json-grid",
|
||||
"artifactPattern": "artifacts/evetui-capture/{stamp}/tui-grid.json",
|
||||
"conformanceAttachment": "runtime.captureArtifacts[]",
|
||||
"requiredProvider": "aetheria",
|
||||
"requiredSurface": "aetheria.daemon.game",
|
||||
"additionalProviderSurfaces": [
|
||||
{
|
||||
"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.",
|
||||
"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",
|
||||
"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",
|
||||
"conformance/captures/aetheria/tui-grid.json",
|
||||
"conformance/captures/world-smoke/tui-grid.json"
|
||||
],
|
||||
"pendingProofs": []
|
||||
}
|
||||
},
|
||||
"capture": {
|
||||
"status": "json-grid",
|
||||
"note": "EveTui publishes provider-advertisement-backed gamecult.eve.tui_grid.v1 release captures.",
|
||||
"artifacts": [
|
||||
{
|
||||
"kind": "json-grid",
|
||||
"schema": "gamecult.eve.tui_grid.v1",
|
||||
"path": "conformance/captures/aetheria/tui-grid.json",
|
||||
"providerId": "aetheria",
|
||||
"surfaceId": "aetheria.daemon.game",
|
||||
"sourceSurfacePath": "../Aetheria/conformance/eve/aetheria-world-surface.json",
|
||||
"requestPath": "conformance/captures/aetheria/capture-request.json"
|
||||
},
|
||||
{
|
||||
"kind": "json-grid",
|
||||
"schema": "gamecult.eve.tui_grid.v1",
|
||||
"path": "conformance/captures/world-smoke/tui-grid.json",
|
||||
"providerId": "eve.world-smoke",
|
||||
"surfaceId": "eve.world-smoke.surface",
|
||||
"sourceSurfacePath": "../Eve/web/fixtures/eve-world-smoke-surface.json",
|
||||
"requestPath": "conformance/captures/world-smoke/capture-request.json"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
18
package.json
Normal file
18
package.json
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
{
|
||||
"name": "eve-tui",
|
||||
"version": "0.1.0",
|
||||
"type": "module",
|
||||
"description": "Provider-agnostic compact TUI shell skeleton for Eve surfaces.",
|
||||
"main": "src/eve-tui-shell.mjs",
|
||||
"scripts": {
|
||||
"test": "C:\\Progra~1\\nodejs\\node.exe --test test/*.test.mjs",
|
||||
"test:all": "C:\\Progra~1\\nodejs\\node.exe --test test/*.test.mjs tools/evetui/*.test.mjs"
|
||||
},
|
||||
"keywords": [
|
||||
"Eve",
|
||||
"TUI",
|
||||
"CultUI",
|
||||
"provider-advertisement"
|
||||
],
|
||||
"license": "MPL-2.0"
|
||||
}
|
||||
99
scripts/run-evetui-capture-contract-smoke.ps1
Normal file
99
scripts/run-evetui-capture-contract-smoke.ps1
Normal file
|
|
@ -0,0 +1,99 @@
|
|||
param(
|
||||
[string] $CapabilityManifestPath = "eve-runtime-capability.json",
|
||||
[string] $AdvertisementPath = "..\Aetheria\conformance\eve\aetheria.provider-advertisement.json",
|
||||
[string] $OutputPath = "artifacts\evetui-capture-contract\latest\capture-request.json",
|
||||
[string] $Stamp = "smoke"
|
||||
)
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
|
||||
$projectRoot = Split-Path -Parent $PSScriptRoot
|
||||
$absoluteCapabilityPath = if ([System.IO.Path]::IsPathRooted($CapabilityManifestPath)) {
|
||||
$CapabilityManifestPath
|
||||
} else {
|
||||
Join-Path $projectRoot $CapabilityManifestPath
|
||||
}
|
||||
$absoluteAdvertisementPath = if ([System.IO.Path]::IsPathRooted($AdvertisementPath)) {
|
||||
$AdvertisementPath
|
||||
} else {
|
||||
Join-Path $projectRoot $AdvertisementPath
|
||||
}
|
||||
$absoluteOutputPath = if ([System.IO.Path]::IsPathRooted($OutputPath)) {
|
||||
$OutputPath
|
||||
} else {
|
||||
Join-Path $projectRoot $OutputPath
|
||||
}
|
||||
|
||||
foreach ($path in @($absoluteCapabilityPath, $absoluteAdvertisementPath)) {
|
||||
if (-not (Test-Path -LiteralPath $path)) {
|
||||
throw "EveTui capture contract input not found: $path"
|
||||
}
|
||||
}
|
||||
|
||||
$manifest = Get-Content -LiteralPath $absoluteCapabilityPath -Raw | ConvertFrom-Json
|
||||
$captureContract = $manifest.lifecycle.capture.captureContract
|
||||
if ($null -eq $captureContract) {
|
||||
throw "EveTui capture contract missing lifecycle.capture.captureContract"
|
||||
}
|
||||
foreach ($field in @("ownerRepo", "runtimeId", "targetId", "requestSchema", "requestBuilder", "advertisementPath", "captureKind", "artifactKind", "artifactPattern", "conformanceAttachment", "requiredProvider", "requiredSurface", "authority", "publishProof")) {
|
||||
if (-not $captureContract.$field) {
|
||||
throw "EveTui capture contract missing $field"
|
||||
}
|
||||
}
|
||||
if ($captureContract.ownerRepo -ne "EveTui") {
|
||||
throw "Unexpected EveTui capture owner: $($captureContract.ownerRepo)"
|
||||
}
|
||||
if ($captureContract.runtimeId -ne "tui") {
|
||||
throw "Unexpected EveTui capture runtime: $($captureContract.runtimeId)"
|
||||
}
|
||||
if ($captureContract.targetId -ne "tui") {
|
||||
throw "Unexpected EveTui capture target: $($captureContract.targetId)"
|
||||
}
|
||||
if ($captureContract.requestSchema -ne "gamecult.eve.runtime_capture_request.v1") {
|
||||
throw "Unexpected EveTui capture request schema: $($captureContract.requestSchema)"
|
||||
}
|
||||
|
||||
$builderPath = Join-Path $projectRoot $captureContract.requestBuilder
|
||||
if (-not (Test-Path -LiteralPath $builderPath)) {
|
||||
throw "EveTui capture request builder not found: $($captureContract.requestBuilder)"
|
||||
}
|
||||
if ((Join-Path $projectRoot $captureContract.advertisementPath) -ne $absoluteAdvertisementPath) {
|
||||
throw "EveTui capture advertisement path mismatch: $($captureContract.advertisementPath) vs $AdvertisementPath"
|
||||
}
|
||||
|
||||
node --test (Join-Path $projectRoot "tools\evetui\evetui-capture-contract.test.mjs")
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
throw "EveTui capture contract tests failed with exit code $LASTEXITCODE"
|
||||
}
|
||||
|
||||
node $builderPath --capability $absoluteCapabilityPath --advertisement $absoluteAdvertisementPath --output $absoluteOutputPath --stamp $Stamp
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
throw "EveTui capture request builder failed with exit code $LASTEXITCODE"
|
||||
}
|
||||
if (-not (Test-Path -LiteralPath $absoluteOutputPath)) {
|
||||
throw "EveTui capture request was not written: $absoluteOutputPath"
|
||||
}
|
||||
|
||||
$request = Get-Content -LiteralPath $absoluteOutputPath -Raw | ConvertFrom-Json
|
||||
foreach ($expectation in @(
|
||||
@{ field = "schema"; value = "gamecult.eve.runtime_capture_request.v1" },
|
||||
@{ field = "runtimeId"; value = "tui" },
|
||||
@{ field = "ownerRepo"; value = "EveTui" },
|
||||
@{ field = "providerId"; value = "aetheria" },
|
||||
@{ field = "surfaceId"; value = "aetheria.daemon.game" },
|
||||
@{ field = "targetId"; value = "tui" },
|
||||
@{ field = "commandBoundary"; value = "aetheria.daemon.commands" },
|
||||
@{ field = "receiptSchema"; value = "aetheria.eve_command_acceptance_status.v1" },
|
||||
@{ 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-grid.json") {
|
||||
throw "Unexpected EveTui capture artifact path: $($request.artifactPath)"
|
||||
}
|
||||
|
||||
Write-Host "EveTui capture contract smoke passed: $absoluteOutputPath"
|
||||
77
scripts/run-evetui-capture-smoke.ps1
Normal file
77
scripts/run-evetui-capture-smoke.ps1
Normal file
|
|
@ -0,0 +1,77 @@
|
|||
param(
|
||||
[string] $CapabilityManifestPath = "eve-runtime-capability.json",
|
||||
[string] $AdvertisementPath = "..\Aetheria\conformance\eve\aetheria.provider-advertisement.json",
|
||||
[string] $SurfacePath = "..\Aetheria\conformance\eve\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",
|
||||
[string] $ExpectedProviderId = "aetheria",
|
||||
[string] $ExpectedSurfaceId = "aetheria.daemon.game",
|
||||
[string] $ExpectedCommandBoundary = "aetheria.daemon.commands",
|
||||
[string] $ExpectedReceiptSchema = "aetheria.eve_command_acceptance_status.v1"
|
||||
)
|
||||
|
||||
$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 = $ExpectedProviderId },
|
||||
@{ field = "surfaceId"; value = $ExpectedSurfaceId },
|
||||
@{ field = "commandBoundary"; value = $ExpectedCommandBoundary },
|
||||
@{ field = "receiptSchema"; value = $ExpectedReceiptSchema },
|
||||
@{ 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"
|
||||
195
scripts/run-evetui-lifecycle-smoke.ps1
Normal file
195
scripts/run-evetui-lifecycle-smoke.ps1
Normal file
|
|
@ -0,0 +1,195 @@
|
|||
param(
|
||||
[string] $CapabilityManifestPath = "eve-runtime-capability.json"
|
||||
)
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
|
||||
$projectRoot = Split-Path -Parent $PSScriptRoot
|
||||
$absoluteManifestPath = if ([System.IO.Path]::IsPathRooted($CapabilityManifestPath)) {
|
||||
$CapabilityManifestPath
|
||||
} else {
|
||||
Join-Path $projectRoot $CapabilityManifestPath
|
||||
}
|
||||
|
||||
if (-not (Test-Path -LiteralPath $absoluteManifestPath)) {
|
||||
throw "EveTui runtime capability manifest not found: $absoluteManifestPath"
|
||||
}
|
||||
|
||||
$manifest = Get-Content -LiteralPath $absoluteManifestPath -Raw | ConvertFrom-Json
|
||||
|
||||
if ($manifest.schema -ne "gamecult.eve.runtime_capability.v1") {
|
||||
throw "Unexpected EveTui manifest schema: $($manifest.schema)"
|
||||
}
|
||||
if ($manifest.runtimeId -ne "tui") {
|
||||
throw "Unexpected EveTui runtime id: $($manifest.runtimeId)"
|
||||
}
|
||||
if ($manifest.owner -ne "EveTui") {
|
||||
throw "Unexpected EveTui owner: $($manifest.owner)"
|
||||
}
|
||||
if ($manifest.incubation.ownerRepo -ne "EveTui") {
|
||||
throw "Unexpected EveTui incubation owner: $($manifest.incubation.ownerRepo)"
|
||||
}
|
||||
if ($manifest.incubation.currentHostRepo -ne "EveTui") {
|
||||
throw "Unexpected EveTui current host repo: $($manifest.incubation.currentHostRepo)"
|
||||
}
|
||||
if ($manifest.incubation.splitTarget -ne "EveTui") {
|
||||
throw "Unexpected EveTui split target: $($manifest.incubation.splitTarget)"
|
||||
}
|
||||
|
||||
foreach ($feature in @("providerAdvertisements", "commandTransport", "terminalGridSummary", "terminalGridLowering", "embeddedDocuments", "pluginProjection")) {
|
||||
if (-not (@($manifest.supportedFeatures) -contains $feature)) {
|
||||
throw "EveTui manifest missing provider-shell feature: $feature"
|
||||
}
|
||||
}
|
||||
|
||||
$expectedPlugins = @{
|
||||
"sai.vn" = @("vn.stage", "story.choose", "story.continue", "story.jump")
|
||||
"norn.graph" = @("embed.norn")
|
||||
"tex.math" = @("embed.tex", "tex.inline", "tex.block")
|
||||
}
|
||||
foreach ($pluginId in $expectedPlugins.Keys) {
|
||||
$supported = @($manifest.supportedPlugins) | Where-Object { $_.pluginId -eq $pluginId } | Select-Object -First 1
|
||||
if (-not $supported) {
|
||||
throw "EveTui manifest missing supported plugin declaration: $pluginId"
|
||||
}
|
||||
foreach ($capability in $expectedPlugins[$pluginId]) {
|
||||
if (-not (@($supported.capabilities) -contains $capability)) {
|
||||
throw "EveTui supported plugin $pluginId missing capability: $capability"
|
||||
}
|
||||
}
|
||||
if ($supported.projectionAdapter -ne "EveTuiShell.buildPluginProjection") {
|
||||
throw "EveTui supported plugin $pluginId has unexpected projection adapter: $($supported.projectionAdapter)"
|
||||
}
|
||||
}
|
||||
|
||||
$worldSurfaceLoweringClaims = @()
|
||||
if ($null -ne $manifest.worldSurfaceLowering) {
|
||||
$worldSurfaceLoweringClaims = @($manifest.worldSurfaceLowering)
|
||||
}
|
||||
if ($worldSurfaceLoweringClaims.Count -ne 1) {
|
||||
throw "EveTui must claim exactly one world-surface lowering target"
|
||||
}
|
||||
$worldSurfaceLoweringClaim = $worldSurfaceLoweringClaims | Where-Object { $_.targetId -eq "tui" } | Select-Object -First 1
|
||||
if (-not $worldSurfaceLoweringClaim) {
|
||||
throw "EveTui manifest missing tui world-surface lowering claim"
|
||||
}
|
||||
if ($worldSurfaceLoweringClaim.supportLevel -ne "terminal-grid-command-surface") {
|
||||
throw "Unexpected EveTui world-surface support level: $($worldSurfaceLoweringClaim.supportLevel)"
|
||||
}
|
||||
if ($worldSurfaceLoweringClaim.ownership -ne "runtime-lowers-provider-world-surface-without-owning-world-state") {
|
||||
throw "Unexpected EveTui world-surface ownership: $($worldSurfaceLoweringClaim.ownership)"
|
||||
}
|
||||
foreach ($evidencePath in @($worldSurfaceLoweringClaim.evidencePaths)) {
|
||||
$absoluteEvidencePath = Join-Path $projectRoot $evidencePath
|
||||
if (-not (Test-Path -LiteralPath $absoluteEvidencePath)) {
|
||||
throw "EveTui world-surface claim missing evidence path: $evidencePath"
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($pluginId in $expectedPlugins.Keys) {
|
||||
$unsupported = @($manifest.unsupportedPlugins) | Where-Object { $_.pluginId -eq $pluginId } | Select-Object -First 1
|
||||
if ($unsupported) {
|
||||
throw "EveTui manifest must not report $pluginId unsupported while terminal fallback projection is declared"
|
||||
}
|
||||
}
|
||||
|
||||
if ($manifest.commandTransport.schema -ne "gamecult.eve.command_invocation.v1") {
|
||||
throw "Unexpected EveTui command schema: $($manifest.commandTransport.schema)"
|
||||
}
|
||||
if ($manifest.commandTransport.status -ne "provider-shell-contract-skeleton") {
|
||||
throw "Unexpected EveTui command transport status: $($manifest.commandTransport.status)"
|
||||
}
|
||||
|
||||
foreach ($stage in @("release", "test", "capture")) {
|
||||
$stageDocument = $manifest.lifecycle.$stage
|
||||
if ($null -eq $stageDocument) {
|
||||
throw "EveTui lifecycle missing stage: $stage"
|
||||
}
|
||||
if ($stageDocument.ownerRepo -ne "EveTui") {
|
||||
throw "EveTui lifecycle stage $stage has unexpected owner: $($stageDocument.ownerRepo)"
|
||||
}
|
||||
if (-not $stageDocument.status) {
|
||||
throw "EveTui lifecycle stage $stage missing status"
|
||||
}
|
||||
foreach ($evidencePath in @($stageDocument.evidencePaths)) {
|
||||
$absoluteEvidencePath = Join-Path $projectRoot $evidencePath
|
||||
if (-not (Test-Path -LiteralPath $absoluteEvidencePath)) {
|
||||
throw "EveTui lifecycle stage $stage missing evidence path: $evidencePath"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$releaseContract = $manifest.lifecycle.release.releaseContract
|
||||
foreach ($field in @("ownerRepo", "packageName", "packageRoot", "versionSource", "tagPattern", "artifactKind", "requestSchema", "requestBuilder", "artifactPattern", "publishProof")) {
|
||||
if (-not $releaseContract.$field) {
|
||||
throw "EveTui release contract missing $field"
|
||||
}
|
||||
}
|
||||
if ($releaseContract.requestSchema -ne "gamecult.eve.runtime_release_request.v1") {
|
||||
throw "EveTui release contract has unexpected request schema: $($releaseContract.requestSchema)"
|
||||
}
|
||||
foreach ($pathProperty in @("packageRoot", "versionSource")) {
|
||||
$relativePath = $releaseContract.$pathProperty
|
||||
$absolutePath = Join-Path $projectRoot $relativePath
|
||||
if (-not (Test-Path -LiteralPath $absolutePath)) {
|
||||
throw "EveTui release contract $pathProperty does not exist: $relativePath"
|
||||
}
|
||||
}
|
||||
foreach ($pathProperty in @("requestBuilder")) {
|
||||
$relativePath = $releaseContract.$pathProperty
|
||||
$absolutePath = Join-Path $projectRoot $relativePath
|
||||
if (-not (Test-Path -LiteralPath $absolutePath)) {
|
||||
throw "EveTui release contract $pathProperty does not exist: $relativePath"
|
||||
}
|
||||
}
|
||||
|
||||
$testContract = $manifest.lifecycle.test.testContract
|
||||
foreach ($field in @("ownerRepo", "runnerKind", "runnerScript", "consumerProject", "packageName", "testAssembly", "testPlatform", "resultsArtifact", "logArtifact", "manifestMutation")) {
|
||||
if (-not $testContract.$field) {
|
||||
throw "EveTui test contract missing $field"
|
||||
}
|
||||
}
|
||||
foreach ($pathProperty in @("runnerScript", "consumerProject")) {
|
||||
$candidate = $testContract.$pathProperty
|
||||
$absolutePath = if ([System.IO.Path]::IsPathRooted($candidate)) {
|
||||
$candidate
|
||||
} else {
|
||||
Join-Path $projectRoot $candidate
|
||||
}
|
||||
if (-not (Test-Path -LiteralPath $absolutePath)) {
|
||||
throw "EveTui test contract $pathProperty does not exist: $candidate"
|
||||
}
|
||||
}
|
||||
|
||||
$captureContract = $manifest.lifecycle.capture.captureContract
|
||||
foreach ($field in @("ownerRepo", "runtimeId", "targetId", "requestSchema", "requestBuilder", "advertisementPath", "captureKind", "artifactKind", "artifactPattern", "conformanceAttachment", "requiredProvider", "requiredSurface", "authority", "publishProof")) {
|
||||
if (-not $captureContract.$field) {
|
||||
throw "EveTui capture contract missing $field"
|
||||
}
|
||||
}
|
||||
if ($captureContract.runtimeId -ne "tui") {
|
||||
throw "EveTui capture contract has unexpected runtime: $($captureContract.runtimeId)"
|
||||
}
|
||||
if ($captureContract.targetId -ne "tui") {
|
||||
throw "EveTui capture contract has unexpected target: $($captureContract.targetId)"
|
||||
}
|
||||
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-cell-grid") {
|
||||
throw "EveTui capture contract has unexpected capture kind: $($captureContract.captureKind)"
|
||||
}
|
||||
foreach ($pathProperty in @("requestBuilder", "advertisementPath")) {
|
||||
$relativePath = $captureContract.$pathProperty
|
||||
$absolutePath = Join-Path $projectRoot $relativePath
|
||||
if (-not (Test-Path -LiteralPath $absolutePath)) {
|
||||
throw "EveTui capture contract $pathProperty does not exist: $relativePath"
|
||||
}
|
||||
}
|
||||
|
||||
& (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"
|
||||
111
scripts/run-evetui-provider-shell-smoke.ps1
Normal file
111
scripts/run-evetui-provider-shell-smoke.ps1
Normal file
|
|
@ -0,0 +1,111 @@
|
|||
param(
|
||||
[string] $CapabilityManifestPath = "eve-runtime-capability.json"
|
||||
)
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
|
||||
$projectRoot = Split-Path -Parent $PSScriptRoot
|
||||
$absoluteManifestPath = if ([System.IO.Path]::IsPathRooted($CapabilityManifestPath)) {
|
||||
$CapabilityManifestPath
|
||||
} else {
|
||||
Join-Path $projectRoot $CapabilityManifestPath
|
||||
}
|
||||
|
||||
if (-not (Test-Path -LiteralPath $absoluteManifestPath)) {
|
||||
throw "EveTui runtime capability manifest not found: $absoluteManifestPath"
|
||||
}
|
||||
|
||||
$manifest = Get-Content -LiteralPath $absoluteManifestPath -Raw | ConvertFrom-Json
|
||||
|
||||
if ($manifest.runtimeId -ne "tui") {
|
||||
throw "Unexpected EveTui runtime id: $($manifest.runtimeId)"
|
||||
}
|
||||
if ($manifest.lifecycle.test.status -ne "provider-shell-contract-skeleton") {
|
||||
throw "Unexpected EveTui provider shell status: $($manifest.lifecycle.test.status)"
|
||||
}
|
||||
|
||||
foreach ($feature in @("providerAdvertisements", "commandTransport", "terminalGridSummary", "terminalGridLowering", "pluginProjection")) {
|
||||
if (-not (@($manifest.supportedFeatures) -contains $feature)) {
|
||||
throw "EveTui provider shell missing supported feature: $feature"
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($pluginId in @("sai.vn", "norn.graph", "tex.math")) {
|
||||
$supported = @($manifest.supportedPlugins) | Where-Object { $_.pluginId -eq $pluginId } | Select-Object -First 1
|
||||
if (-not $supported) {
|
||||
throw "EveTui provider shell missing supported plugin projection: $pluginId"
|
||||
}
|
||||
}
|
||||
|
||||
$worldSurfaceLoweringClaims = @()
|
||||
if ($null -ne $manifest.worldSurfaceLowering) {
|
||||
$worldSurfaceLoweringClaims = @($manifest.worldSurfaceLowering)
|
||||
}
|
||||
if ($worldSurfaceLoweringClaims.Count -ne 1) {
|
||||
throw "EveTui must claim exactly one provider world-surface lowering target"
|
||||
}
|
||||
$worldSurfaceLoweringClaim = $worldSurfaceLoweringClaims | Where-Object { $_.targetId -eq "tui" } | Select-Object -First 1
|
||||
if (-not $worldSurfaceLoweringClaim) {
|
||||
throw "EveTui missing tui world-surface lowering claim"
|
||||
}
|
||||
foreach ($surfaceKind in @("interactive-world", "interactive-world-editor")) {
|
||||
if (-not (@($worldSurfaceLoweringClaim.surfaceKinds) -contains $surfaceKind)) {
|
||||
throw "EveTui world-surface claim missing surface kind: $surfaceKind"
|
||||
}
|
||||
}
|
||||
foreach ($projectionKind in @("provider-authored-world-surface", "provider-authored-world-editor-surface")) {
|
||||
if (-not (@($worldSurfaceLoweringClaim.projectionKinds) -contains $projectionKind)) {
|
||||
throw "EveTui world-surface claim missing projection kind: $projectionKind"
|
||||
}
|
||||
}
|
||||
if ($worldSurfaceLoweringClaim.supportLevel -ne "terminal-grid-command-surface") {
|
||||
throw "Unexpected EveTui support level: $($worldSurfaceLoweringClaim.supportLevel)"
|
||||
}
|
||||
if ($worldSurfaceLoweringClaim.ownership -ne "runtime-lowers-provider-world-surface-without-owning-world-state") {
|
||||
throw "Unexpected EveTui ownership: $($worldSurfaceLoweringClaim.ownership)"
|
||||
}
|
||||
foreach ($evidencePath in @($worldSurfaceLoweringClaim.evidencePaths)) {
|
||||
$absoluteEvidencePath = Join-Path $projectRoot $evidencePath
|
||||
if (-not (Test-Path -LiteralPath $absoluteEvidencePath)) {
|
||||
throw "EveTui world-surface claim missing evidence path: $evidencePath"
|
||||
}
|
||||
}
|
||||
|
||||
$expectedFiles = @(
|
||||
"package.json",
|
||||
"src\eve-tui-shell.mjs",
|
||||
"test\eve-tui-shell.test.mjs"
|
||||
)
|
||||
|
||||
foreach ($relativePath in $expectedFiles) {
|
||||
$absolutePath = Join-Path $projectRoot $relativePath
|
||||
if (-not (Test-Path -LiteralPath $absolutePath)) {
|
||||
throw "EveTui provider shell missing file: $relativePath"
|
||||
}
|
||||
}
|
||||
|
||||
$shellSource = Get-Content -LiteralPath (Join-Path $projectRoot "src\eve-tui-shell.mjs") -Raw
|
||||
foreach ($symbol in @("EveTuiShell", "selectSurface", "lowerSurface", "normalizeSurfaceDocument", "buildComponentLines", "buildPluginProjection", "collectPluginProjections", "terminalElementKind", "sai.vn", "norn.graph", "tex.math", "sidecar-advertised-plugin-abi", "createCommandIntent", "renderSummary", "gamecult.eve.tui_grid.v1", "commandBoundary", "receiptSchema")) {
|
||||
if (-not $shellSource.Contains($symbol)) {
|
||||
throw "EveTui provider shell source missing symbol: $symbol"
|
||||
}
|
||||
}
|
||||
|
||||
$testSource = Get-Content -LiteralPath (Join-Path $projectRoot "test\eve-tui-shell.test.mjs") -Raw
|
||||
foreach ($symbol in @("selects advertised provider surfaces", "command intents carry provider-advertised boundaries", "summary grid is explicitly lossy", "lowers provider surface trees into a terminal grid", "lowers Sai, Norn, and TeX plugin surfaces into compact terminal fallbacks", "pluginProjections", "sai-vn-terminal-stage-summary", "norn-graph-terminal-outline", "tex-math-terminal-block-source", "rejects surface documents that do not match the advertised TUI target", "aetheria.daemon.commands")) {
|
||||
if (-not $testSource.Contains($symbol)) {
|
||||
throw "EveTui provider shell test missing symbol: $symbol"
|
||||
}
|
||||
}
|
||||
|
||||
Push-Location $projectRoot
|
||||
try {
|
||||
npm test
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
throw "EveTui provider shell tests failed with exit code $LASTEXITCODE"
|
||||
}
|
||||
} finally {
|
||||
Pop-Location
|
||||
}
|
||||
|
||||
Write-Host "EveTui provider shell smoke passed: $absoluteManifestPath"
|
||||
98
scripts/run-evetui-release-contract-smoke.ps1
Normal file
98
scripts/run-evetui-release-contract-smoke.ps1
Normal file
|
|
@ -0,0 +1,98 @@
|
|||
param(
|
||||
[string] $CapabilityManifestPath = "eve-runtime-capability.json",
|
||||
[string] $OutputPath = "artifacts\evetui-release-contract\latest\release-request.json"
|
||||
)
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
|
||||
$projectRoot = Split-Path -Parent $PSScriptRoot
|
||||
$absoluteCapabilityPath = if ([System.IO.Path]::IsPathRooted($CapabilityManifestPath)) {
|
||||
$CapabilityManifestPath
|
||||
} else {
|
||||
Join-Path $projectRoot $CapabilityManifestPath
|
||||
}
|
||||
$absoluteOutputPath = if ([System.IO.Path]::IsPathRooted($OutputPath)) {
|
||||
$OutputPath
|
||||
} else {
|
||||
Join-Path $projectRoot $OutputPath
|
||||
}
|
||||
|
||||
if (-not (Test-Path -LiteralPath $absoluteCapabilityPath)) {
|
||||
throw "EveTui release contract input not found: $absoluteCapabilityPath"
|
||||
}
|
||||
|
||||
$manifest = Get-Content -LiteralPath $absoluteCapabilityPath -Raw | ConvertFrom-Json
|
||||
$releaseContract = $manifest.lifecycle.release.releaseContract
|
||||
if ($null -eq $releaseContract) {
|
||||
throw "EveTui release contract missing lifecycle.release.releaseContract"
|
||||
}
|
||||
foreach ($field in @("ownerRepo", "packageName", "packageRoot", "versionSource", "tagPattern", "artifactKind", "requestSchema", "requestBuilder", "artifactPattern", "publishProof")) {
|
||||
if (-not $releaseContract.$field) {
|
||||
throw "EveTui release contract missing $field"
|
||||
}
|
||||
}
|
||||
if ($releaseContract.ownerRepo -ne "EveTui") {
|
||||
throw "Unexpected EveTui release owner: $($releaseContract.ownerRepo)"
|
||||
}
|
||||
if ($releaseContract.packageName -ne "eve-tui") {
|
||||
throw "Unexpected EveTui package name: $($releaseContract.packageName)"
|
||||
}
|
||||
if ($releaseContract.requestSchema -ne "gamecult.eve.runtime_release_request.v1") {
|
||||
throw "Unexpected EveTui release request schema: $($releaseContract.requestSchema)"
|
||||
}
|
||||
if ($releaseContract.artifactKind -ne "terminal-runtime") {
|
||||
throw "Unexpected EveTui release artifact kind: $($releaseContract.artifactKind)"
|
||||
}
|
||||
|
||||
$builderPath = Join-Path $projectRoot $releaseContract.requestBuilder
|
||||
$packagePath = Join-Path $projectRoot $releaseContract.versionSource
|
||||
$packageRoot = Join-Path $projectRoot $releaseContract.packageRoot
|
||||
foreach ($path in @($builderPath, $packagePath, $packageRoot)) {
|
||||
if (-not (Test-Path -LiteralPath $path)) {
|
||||
throw "EveTui release contract path not found: $path"
|
||||
}
|
||||
}
|
||||
|
||||
node --test (Join-Path $projectRoot "tools\evetui\evetui-release-contract.test.mjs")
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
throw "EveTui release contract tests failed with exit code $LASTEXITCODE"
|
||||
}
|
||||
|
||||
node $builderPath --capability $absoluteCapabilityPath --package $packagePath --output $absoluteOutputPath
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
throw "EveTui release request builder failed with exit code $LASTEXITCODE"
|
||||
}
|
||||
if (-not (Test-Path -LiteralPath $absoluteOutputPath)) {
|
||||
throw "EveTui release request was not written: $absoluteOutputPath"
|
||||
}
|
||||
|
||||
$request = Get-Content -LiteralPath $absoluteOutputPath -Raw | ConvertFrom-Json
|
||||
$packageManifest = Get-Content -LiteralPath $packagePath -Raw | ConvertFrom-Json
|
||||
foreach ($expectation in @(
|
||||
@{ field = "schema"; value = "gamecult.eve.runtime_release_request.v1" },
|
||||
@{ field = "ownerRepo"; value = "EveTui" },
|
||||
@{ field = "repository"; value = "GameCult/EveTui" },
|
||||
@{ field = "packageName"; value = "eve-tui" },
|
||||
@{ field = "version"; value = $packageManifest.version },
|
||||
@{ field = "packageRoot"; value = "." },
|
||||
@{ field = "versionSource"; value = "package.json" },
|
||||
@{ field = "artifactKind"; value = "terminal-runtime" }
|
||||
)) {
|
||||
if ($request.($expectation.field) -ne $expectation.value) {
|
||||
throw "EveTui release request $($expectation.field) expected $($expectation.value) got $($request.($expectation.field))"
|
||||
}
|
||||
}
|
||||
|
||||
$expectedTagName = $releaseContract.tagPattern.Replace("{version}", $packageManifest.version)
|
||||
if ($request.tagName -ne $expectedTagName) {
|
||||
throw "Unexpected EveTui release tag: $($request.tagName)"
|
||||
}
|
||||
$expectedArtifactPath = $releaseContract.artifactPattern.Replace("{version}", $packageManifest.version)
|
||||
if ($request.artifactPath -ne $expectedArtifactPath) {
|
||||
throw "Unexpected EveTui release artifact path: $($request.artifactPath)"
|
||||
}
|
||||
if ($request.main -ne $packageManifest.main) {
|
||||
throw "EveTui release request lost package main entry"
|
||||
}
|
||||
|
||||
Write-Host "EveTui release contract smoke passed: $absoluteOutputPath"
|
||||
297
src/eve-tui-shell.mjs
Normal file
297
src/eve-tui-shell.mjs
Normal file
|
|
@ -0,0 +1,297 @@
|
|||
export class EveTuiShell {
|
||||
constructor({ clientId = "tui", width = 80 } = {}) {
|
||||
this.clientId = clientId;
|
||||
this.width = Math.max(20, Number(width) || 80);
|
||||
}
|
||||
|
||||
selectSurface(providerAdvertisement, requestedSurfaceId = "") {
|
||||
const provider = normalizeProviderAdvertisement(providerAdvertisement);
|
||||
const surface = requestedSurfaceId
|
||||
? provider.surfaces.find(candidate => candidate.surfaceId === requestedSurfaceId)
|
||||
: provider.surfaces[0];
|
||||
if (!surface) {
|
||||
throw new Error(`Provider ${provider.providerId} does not advertise surface ${requestedSurfaceId || "(first)"}`);
|
||||
}
|
||||
return {
|
||||
providerId: provider.providerId,
|
||||
surfaceId: surface.surfaceId,
|
||||
title: surface.title || provider.title || surface.surfaceId,
|
||||
transport: surface.transport || "",
|
||||
url: surface.url || surface.key || "",
|
||||
worldInteraction: normalizeWorldInteraction(surface.worldInteraction),
|
||||
};
|
||||
}
|
||||
|
||||
createCommandIntent(providerAdvertisement, requestedSurfaceId, command, payload = {}) {
|
||||
if (!command) throw new Error("Command is required.");
|
||||
const selected = this.selectSurface(providerAdvertisement, requestedSurfaceId);
|
||||
const action = objectValue(payload.action);
|
||||
const commandBoundary = firstString(
|
||||
selected.worldInteraction.commandBoundary,
|
||||
payload.commandBoundary,
|
||||
action.commandBoundary,
|
||||
action.target,
|
||||
);
|
||||
const receiptSchema = firstString(
|
||||
selected.worldInteraction.receiptSchema,
|
||||
payload.receiptSchema,
|
||||
action.receiptSchema,
|
||||
);
|
||||
const intent = {
|
||||
type: "surface-command",
|
||||
schema: "gamecult.eve.command_invocation.v1",
|
||||
providerId: selected.providerId,
|
||||
surfaceId: selected.surfaceId,
|
||||
command,
|
||||
payload: {
|
||||
...payload,
|
||||
transport: payload.transport ?? null,
|
||||
},
|
||||
issuedAt: new Date().toISOString(),
|
||||
clientId: this.clientId,
|
||||
};
|
||||
if (commandBoundary) intent.commandBoundary = commandBoundary;
|
||||
if (receiptSchema) intent.receiptSchema = receiptSchema;
|
||||
return intent;
|
||||
}
|
||||
|
||||
lowerSurface(surfaceDocument, providerAdvertisement, requestedSurfaceId = "") {
|
||||
const selected = this.selectSurface(providerAdvertisement, requestedSurfaceId);
|
||||
const document = normalizeSurfaceDocument(surfaceDocument);
|
||||
if (document.surfaceId !== selected.surfaceId) {
|
||||
throw new Error(`Surface document ${document.surfaceId} does not match advertised TUI surface ${selected.surfaceId}.`);
|
||||
}
|
||||
|
||||
const lines = [
|
||||
fitLine(`EveTui ${selected.providerId}`, this.width),
|
||||
fitLine(`surface ${selected.surfaceId}`, this.width),
|
||||
fitLine(`command ${selected.worldInteraction.commandBoundary || "unadvertised"}`, this.width),
|
||||
fitLine(`receipt ${selected.worldInteraction.receiptSchema || "unadvertised"}`, this.width),
|
||||
...buildComponentLines(document.root, this.width),
|
||||
];
|
||||
|
||||
return {
|
||||
schema: "gamecult.eve.tui_grid.v1",
|
||||
runtimeId: "tui",
|
||||
providerId: selected.providerId,
|
||||
surfaceId: selected.surfaceId,
|
||||
projectionKind: selected.worldInteraction.projectionKind,
|
||||
commandBoundary: selected.worldInteraction.commandBoundary,
|
||||
receiptSchema: selected.worldInteraction.receiptSchema,
|
||||
ownership: selected.worldInteraction.ownership,
|
||||
width: this.width,
|
||||
lines,
|
||||
embeddedDocumentSlots: collectEmbeddedDocumentSlots(document.root),
|
||||
pluginProjections: collectPluginProjections(document.root),
|
||||
lossiness: "terminal-grid-command-surface; summarizes provider-authored world surface without owning provider state",
|
||||
};
|
||||
}
|
||||
|
||||
renderSummary(providerAdvertisement, requestedSurfaceId = "") {
|
||||
const selected = this.selectSurface(providerAdvertisement, requestedSurfaceId);
|
||||
const lines = [
|
||||
fitLine(`EveTui ${selected.providerId}`, this.width),
|
||||
fitLine(`surface ${selected.surfaceId}`, this.width),
|
||||
fitLine(`transport ${selected.transport || "unknown"}`, this.width),
|
||||
fitLine(`command ${selected.worldInteraction.commandBoundary || "unadvertised"}`, this.width),
|
||||
fitLine(`receipt ${selected.worldInteraction.receiptSchema || "unadvertised"}`, this.width),
|
||||
];
|
||||
return {
|
||||
schema: "gamecult.eve.tui_grid.v1",
|
||||
runtimeId: "tui",
|
||||
providerId: selected.providerId,
|
||||
surfaceId: selected.surfaceId,
|
||||
width: this.width,
|
||||
lines,
|
||||
lossiness: "provider-shell-summary-only; use lowerSurface for TUI world-surface lowering",
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export function normalizeProviderAdvertisement(providerAdvertisement) {
|
||||
if (!providerAdvertisement || typeof providerAdvertisement !== "object") {
|
||||
throw new Error("Provider advertisement is required.");
|
||||
}
|
||||
const providerId = String(providerAdvertisement.providerId || "");
|
||||
if (!providerId) throw new Error("Provider advertisement missing providerId.");
|
||||
const surfaces = Array.isArray(providerAdvertisement.surfaces)
|
||||
? providerAdvertisement.surfaces
|
||||
.filter(surface => surface && typeof surface === "object" && surface.surfaceId)
|
||||
.map(surface => ({ ...surface, surfaceId: String(surface.surfaceId) }))
|
||||
: [];
|
||||
if (!surfaces.length) throw new Error(`Provider ${providerId} advertises no surfaces.`);
|
||||
return { ...providerAdvertisement, providerId, surfaces };
|
||||
}
|
||||
|
||||
export function normalizeSurfaceDocument(surfaceDocument) {
|
||||
if (!surfaceDocument || typeof surfaceDocument !== "object") {
|
||||
throw new Error("Surface document is required.");
|
||||
}
|
||||
if (surfaceDocument.schema !== "gamecult.eve.surface.v1") {
|
||||
throw new Error(`Unexpected surface schema: ${surfaceDocument.schema || ""}`);
|
||||
}
|
||||
const surface = objectValue(surfaceDocument.surface);
|
||||
const surfaceId = firstString(surface.id);
|
||||
if (!surfaceId) throw new Error("Surface document missing surface.id.");
|
||||
const root = objectValue(surface.root);
|
||||
if (!root.id) throw new Error(`Surface document ${surfaceId} missing surface.root.`);
|
||||
return { ...surfaceDocument, surfaceId, root };
|
||||
}
|
||||
|
||||
function normalizeWorldInteraction(value) {
|
||||
const source = objectValue(value);
|
||||
return {
|
||||
projectionKind: firstString(source.projectionKind),
|
||||
commandBoundary: firstString(source.commandBoundary),
|
||||
receiptSchema: firstString(source.receiptSchema),
|
||||
ownership: firstString(source.ownership),
|
||||
};
|
||||
}
|
||||
|
||||
function buildComponentLines(component, width, depth = 0) {
|
||||
const source = objectValue(component);
|
||||
const kind = firstString(source.kind) || "component";
|
||||
const id = firstString(source.id) || "(anonymous)";
|
||||
const props = objectValue(source.props);
|
||||
const label = firstString(props.label, props.title, props.text, props.bind, props.command, props.fieldId);
|
||||
const indent = " ".repeat(Math.min(depth, 6));
|
||||
const pluginProjection = buildPluginProjection(kind, source);
|
||||
const pluginSuffix = pluginProjection
|
||||
? ` [${pluginProjection.pluginId}:${pluginProjection.semanticOwner}]`
|
||||
: "";
|
||||
const ownLine = fitLine(`${indent}${terminalElementKind(kind)} ${id}${label ? ` ${label}` : ""}${pluginSuffix}`, width);
|
||||
const children = Array.isArray(source.children) ? source.children : [];
|
||||
const embeddedDocuments = normalizeEmbeddedDocuments(source.embeddedDocuments);
|
||||
return [
|
||||
ownLine,
|
||||
...embeddedDocuments.map(document => fitLine(`${indent} slot ${document.slotId || "(slot)"} ${document.documentId || "(document)"}`, width)),
|
||||
...children.flatMap(child => buildComponentLines(child, width, depth + 1)),
|
||||
];
|
||||
}
|
||||
|
||||
function collectEmbeddedDocumentSlots(component, slots = []) {
|
||||
const source = objectValue(component);
|
||||
const ownerId = firstString(source.id);
|
||||
for (const document of normalizeEmbeddedDocuments(source.embeddedDocuments)) {
|
||||
slots.push({ ownerId, ...document });
|
||||
}
|
||||
for (const child of Array.isArray(source.children) ? source.children : []) {
|
||||
collectEmbeddedDocumentSlots(child, slots);
|
||||
}
|
||||
return slots;
|
||||
}
|
||||
|
||||
function collectPluginProjections(component, projections = []) {
|
||||
const source = objectValue(component);
|
||||
const projection = buildPluginProjection(firstString(source.kind), source);
|
||||
if (projection) {
|
||||
projections.push({
|
||||
ownerId: firstString(source.id),
|
||||
...projection,
|
||||
});
|
||||
}
|
||||
for (const child of Array.isArray(source.children) ? source.children : []) {
|
||||
collectPluginProjections(child, projections);
|
||||
}
|
||||
return projections;
|
||||
}
|
||||
|
||||
function normalizeEmbeddedDocuments(value) {
|
||||
return Array.isArray(value)
|
||||
? value
|
||||
.filter(document => document && typeof document === "object")
|
||||
.map(document => ({
|
||||
slotId: firstString(document.slotId, document.id),
|
||||
documentId: firstString(document.documentId, document.href, document.url),
|
||||
schemaId: firstString(document.schemaId, document.schema),
|
||||
presentationKind: firstString(document.presentationKind, document.kind),
|
||||
}))
|
||||
.filter(document => document.slotId || document.documentId)
|
||||
: [];
|
||||
}
|
||||
|
||||
function terminalElementKind(componentKind) {
|
||||
if (!componentKind) return "empty";
|
||||
if (componentKind === "vn.stage") return "sai-vn";
|
||||
if (componentKind === "embed.norn") return "norn";
|
||||
if (componentKind === "embed.tex") return "tex";
|
||||
if (componentKind.startsWith("control.")) return "command";
|
||||
if (componentKind.startsWith("embed.")) return "plugin";
|
||||
if (componentKind === "surface.slot") return "slot";
|
||||
if (componentKind.startsWith("field.") || componentKind.startsWith("world.")) return "world";
|
||||
if (componentKind.startsWith("text.") || componentKind === "text" || componentKind === "label") return "text";
|
||||
if (componentKind === "metric") return "metric";
|
||||
return componentKind;
|
||||
}
|
||||
|
||||
function buildPluginProjection(componentKind, component) {
|
||||
const props = objectValue(component.props);
|
||||
if (componentKind === "vn.stage") {
|
||||
return {
|
||||
pluginId: "sai.vn",
|
||||
projectionKind: "sai-vn-terminal-stage-summary",
|
||||
abiSchema: "gamecult.eve.plugin_abi.v1",
|
||||
commandBoundary: "sidecar-advertised-plugin-abi",
|
||||
capabilities: ["vn.stage", "story.choose", "story.continue", "story.jump"],
|
||||
documentId: firstString(props.storyId, component.id),
|
||||
semanticOwner: "Sai",
|
||||
availability: "required",
|
||||
fallbackKind: "terminal-summary",
|
||||
};
|
||||
}
|
||||
|
||||
if (componentKind === "embed.norn") {
|
||||
return {
|
||||
pluginId: "norn.graph",
|
||||
projectionKind: "norn-graph-terminal-outline",
|
||||
abiSchema: "gamecult.eve.plugin_abi.v1",
|
||||
commandBoundary: "sidecar-advertised-plugin-abi",
|
||||
capabilities: ["embed.norn"],
|
||||
documentId: firstString(props.sourceUri, props.documentId, component.id),
|
||||
semanticOwner: "Norn",
|
||||
availability: "optional-nested",
|
||||
fallbackKind: "terminal-outline",
|
||||
};
|
||||
}
|
||||
|
||||
if (componentKind === "embed.tex") {
|
||||
return {
|
||||
pluginId: "tex.math",
|
||||
projectionKind: texProjectionKind(props),
|
||||
abiSchema: "gamecult.eve.plugin_abi.v1",
|
||||
commandBoundary: "sidecar-advertised-plugin-abi",
|
||||
capabilities: ["embed.tex", "tex.inline", "tex.block"],
|
||||
documentId: firstString(props.sourceUri, props.source, component.id),
|
||||
semanticOwner: "EvePlugins",
|
||||
availability: "optional-nested",
|
||||
fallbackKind: "source-text",
|
||||
};
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
function texProjectionKind(props) {
|
||||
const display = firstString(props.display, "inline");
|
||||
if (display === "block") return "tex-math-terminal-block-source";
|
||||
if (display === "page") return "tex-math-terminal-page-source";
|
||||
return "tex-math-terminal-inline-source";
|
||||
}
|
||||
|
||||
function fitLine(value, width) {
|
||||
const text = String(value || "");
|
||||
if (text.length <= width) return text;
|
||||
return `${text.slice(0, Math.max(0, width - 3))}...`;
|
||||
}
|
||||
|
||||
function objectValue(value) {
|
||||
return value && typeof value === "object" && !Array.isArray(value) ? value : {};
|
||||
}
|
||||
|
||||
function firstString(...values) {
|
||||
for (const value of values) {
|
||||
if (typeof value === "string" && value.trim()) return value;
|
||||
if (typeof value === "number" && Number.isFinite(value)) return String(value);
|
||||
}
|
||||
return "";
|
||||
}
|
||||
71
test-support/schema-subset.mjs
Normal file
71
test-support/schema-subset.mjs
Normal file
|
|
@ -0,0 +1,71 @@
|
|||
export function validateSchemaSubset(schema, value, pointer = "$", rootSchema = schema) {
|
||||
const errors = [];
|
||||
if (!schema || typeof schema !== "object") return errors;
|
||||
|
||||
if (schema.$ref) {
|
||||
const resolved = resolveSchemaReference(rootSchema, schema.$ref);
|
||||
return resolved
|
||||
? validateSchemaSubset(resolved, value, pointer, rootSchema)
|
||||
: [`${pointer}:unresolved-ref:${schema.$ref}`];
|
||||
}
|
||||
|
||||
if (schema.const !== undefined && value !== schema.const) {
|
||||
errors.push(`${pointer}:const:${schema.const}`);
|
||||
}
|
||||
if (schema.enum && !schema.enum.includes(value)) {
|
||||
errors.push(`${pointer}:enum:${schema.enum.join("|")}`);
|
||||
}
|
||||
if (schema.type && !matchesSchemaType(value, schema.type)) {
|
||||
errors.push(`${pointer}:type:${schema.type}`);
|
||||
return errors;
|
||||
}
|
||||
if (schema.minLength !== undefined && typeof value === "string" && value.length < schema.minLength) {
|
||||
errors.push(`${pointer}:minLength:${schema.minLength}`);
|
||||
}
|
||||
if (schema.minItems !== undefined && Array.isArray(value) && value.length < schema.minItems) {
|
||||
errors.push(`${pointer}:minItems:${schema.minItems}`);
|
||||
}
|
||||
if (schema.required && value && typeof value === "object" && !Array.isArray(value)) {
|
||||
for (const key of schema.required) {
|
||||
if (value[key] === undefined) errors.push(`${pointer}.${key}:required`);
|
||||
}
|
||||
}
|
||||
if (schema.properties && value && typeof value === "object" && !Array.isArray(value)) {
|
||||
for (const [key, childSchema] of Object.entries(schema.properties)) {
|
||||
if (value[key] !== undefined) {
|
||||
errors.push(...validateSchemaSubset(childSchema, value[key], `${pointer}.${key}`, rootSchema));
|
||||
}
|
||||
}
|
||||
}
|
||||
if (schema.items && Array.isArray(value)) {
|
||||
value.forEach((item, index) => {
|
||||
errors.push(...validateSchemaSubset(schema.items, item, `${pointer}[${index}]`, rootSchema));
|
||||
});
|
||||
}
|
||||
return errors;
|
||||
}
|
||||
|
||||
function resolveSchemaReference(rootSchema, reference) {
|
||||
if (!reference.startsWith("#/")) return null;
|
||||
return reference.slice(2).split("/").reduce((current, segment) => {
|
||||
if (!current || typeof current !== "object") return null;
|
||||
return current[segment.replaceAll("~1", "/").replaceAll("~0", "~")];
|
||||
}, rootSchema);
|
||||
}
|
||||
|
||||
function matchesSchemaType(value, type) {
|
||||
switch (type) {
|
||||
case "array":
|
||||
return Array.isArray(value);
|
||||
case "object":
|
||||
return value !== null && typeof value === "object" && !Array.isArray(value);
|
||||
case "string":
|
||||
return typeof value === "string";
|
||||
case "number":
|
||||
return typeof value === "number";
|
||||
case "boolean":
|
||||
return typeof value === "boolean";
|
||||
default:
|
||||
return true;
|
||||
}
|
||||
}
|
||||
256
test/eve-tui-shell.test.mjs
Normal file
256
test/eve-tui-shell.test.mjs
Normal file
|
|
@ -0,0 +1,256 @@
|
|||
import assert from "node:assert/strict";
|
||||
import { readFileSync } from "node:fs";
|
||||
import path from "node:path";
|
||||
import test from "node:test";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import { validateSchemaSubset } from "../test-support/schema-subset.mjs";
|
||||
import { EveTuiShell } from "../src/eve-tui-shell.mjs";
|
||||
|
||||
const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../../Eve");
|
||||
const tuiGridSchema = JSON.parse(readFileSync(
|
||||
path.join(repoRoot, "schemas/gamecult.eve.tui_grid.v1.schema.json"),
|
||||
"utf8",
|
||||
));
|
||||
|
||||
test("selects advertised provider surfaces for compact projection", () => {
|
||||
const shell = new EveTuiShell({ width: 48 });
|
||||
const selected = shell.selectSurface(advertisement(), "aetheria.daemon.editor");
|
||||
|
||||
assert.equal(selected.providerId, "aetheria");
|
||||
assert.equal(selected.surfaceId, "aetheria.daemon.editor");
|
||||
assert.equal(selected.worldInteraction.commandBoundary, "aetheria.daemon.commands");
|
||||
assert.equal(selected.worldInteraction.receiptSchema, "aetheria.eve_command_acceptance_status.v1");
|
||||
});
|
||||
|
||||
test("command intents carry provider-advertised boundaries", () => {
|
||||
const shell = new EveTuiShell();
|
||||
const intent = shell.createCommandIntent(
|
||||
advertisement(),
|
||||
"aetheria.daemon.game",
|
||||
"aetheria.daemon.commands",
|
||||
{
|
||||
action: {
|
||||
commandBoundary: "component.fallback.boundary",
|
||||
receiptSchema: "component.fallback.receipt.v1",
|
||||
target: "component.fallback.target",
|
||||
},
|
||||
commandId: "aetheria.daemon.focus",
|
||||
},
|
||||
);
|
||||
|
||||
assert.equal(intent.schema, "gamecult.eve.command_invocation.v1");
|
||||
assert.equal(intent.providerId, "aetheria");
|
||||
assert.equal(intent.surfaceId, "aetheria.daemon.game");
|
||||
assert.equal(intent.commandBoundary, "aetheria.daemon.commands");
|
||||
assert.equal(intent.receiptSchema, "aetheria.eve_command_acceptance_status.v1");
|
||||
assert.equal(intent.clientId, "tui");
|
||||
});
|
||||
|
||||
test("summary grid is explicitly lossy provider-shell evidence", () => {
|
||||
const shell = new EveTuiShell({ width: 36 });
|
||||
const grid = shell.renderSummary(advertisement(), "aetheria.daemon.game");
|
||||
|
||||
assert.deepEqual(validateSchemaSubset(tuiGridSchema, grid), []);
|
||||
assert.equal(grid.schema, "gamecult.eve.tui_grid.v1");
|
||||
assert.equal(grid.runtimeId, "tui");
|
||||
assert.equal(grid.surfaceId, "aetheria.daemon.game");
|
||||
assert.match(grid.lossiness, /use lowerSurface/);
|
||||
assert.ok(grid.lines.every(line => line.length <= 36));
|
||||
});
|
||||
|
||||
test("lowers provider surface trees into a terminal grid", () => {
|
||||
const shell = new EveTuiShell({ width: 44 });
|
||||
const grid = shell.lowerSurface(surfaceDocument(), advertisement(), "aetheria.daemon.game");
|
||||
|
||||
assert.deepEqual(validateSchemaSubset(tuiGridSchema, grid), []);
|
||||
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.projectionKind, "provider-authored-world-surface");
|
||||
assert.equal(grid.commandBoundary, "aetheria.daemon.commands");
|
||||
assert.equal(grid.receiptSchema, "aetheria.eve_command_acceptance_status.v1");
|
||||
assert.match(grid.lossiness, /terminal-grid-command-surface/);
|
||||
assert.ok(grid.lines.every(line => line.length <= 44));
|
||||
assert.ok(grid.lines.some(line => line.includes("world aetheria.daemon.game.gravity")));
|
||||
assert.ok(grid.lines.some(line => line.includes("command aetheria.daemon.game.focus")));
|
||||
assert.ok(grid.lines.some(line => line.includes("norn aetheria.daemon.game.norn")));
|
||||
assert.ok(grid.lines.some(line => line.includes("slot norn.map")));
|
||||
assert.deepEqual(grid.pluginProjections, [
|
||||
{
|
||||
ownerId: "aetheria.daemon.game.norn",
|
||||
pluginId: "norn.graph",
|
||||
projectionKind: "norn-graph-terminal-outline",
|
||||
abiSchema: "gamecult.eve.plugin_abi.v1",
|
||||
commandBoundary: "sidecar-advertised-plugin-abi",
|
||||
capabilities: ["embed.norn"],
|
||||
documentId: "aetheria.daemon.game.norn",
|
||||
semanticOwner: "Norn",
|
||||
availability: "optional-nested",
|
||||
fallbackKind: "terminal-outline",
|
||||
},
|
||||
]);
|
||||
assert.deepEqual(grid.embeddedDocumentSlots, [
|
||||
{
|
||||
ownerId: "aetheria.daemon.game.norn",
|
||||
slotId: "norn.map",
|
||||
documentId: "cultmesh://aetheria/norn/map",
|
||||
schemaId: "gamecult.eve.surface.v1",
|
||||
presentationKind: "terminal-grid-slot",
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
test("rejects surface documents that do not match the advertised TUI target", () => {
|
||||
const shell = new EveTuiShell();
|
||||
assert.throws(
|
||||
() => shell.lowerSurface(surfaceDocument("aetheria.daemon.game"), advertisement(), "aetheria.daemon.editor"),
|
||||
/does not match advertised TUI surface/,
|
||||
);
|
||||
});
|
||||
|
||||
test("lowers Sai, Norn, and TeX plugin surfaces into compact terminal fallbacks", () => {
|
||||
const shell = new EveTuiShell({ width: 72 });
|
||||
const saiSurface = JSON.parse(readFileSync(path.join(repoRoot, "web/fixtures/sai-vn-surface.json"), "utf8"));
|
||||
const grid = shell.lowerSurface(saiSurface, saiAdvertisement(), "sai.visual_novel.surface");
|
||||
|
||||
assert.deepEqual(validateSchemaSubset(tuiGridSchema, grid), []);
|
||||
assert.equal(grid.providerId, "gamecult.home.vn");
|
||||
assert.equal(grid.surfaceId, "sai.visual_novel.surface");
|
||||
assert.ok(grid.lines.every(line => line.length <= 72));
|
||||
assert.ok(grid.lines.some(line => line.includes("sai-vn sai.root")));
|
||||
assert.ok(grid.lines.some(line => line.includes("norn sai.graph")));
|
||||
assert.ok(grid.lines.some(line => line.includes("tex sai.tex.log-power")));
|
||||
|
||||
const byPlugin = new Map(grid.pluginProjections.map(projection => [projection.pluginId, projection]));
|
||||
assert.equal(byPlugin.get("sai.vn").semanticOwner, "Sai");
|
||||
assert.equal(byPlugin.get("sai.vn").availability, "required");
|
||||
assert.equal(byPlugin.get("sai.vn").projectionKind, "sai-vn-terminal-stage-summary");
|
||||
assert.deepEqual(byPlugin.get("sai.vn").capabilities, [
|
||||
"vn.stage",
|
||||
"story.choose",
|
||||
"story.continue",
|
||||
"story.jump",
|
||||
]);
|
||||
assert.equal(byPlugin.get("norn.graph").semanticOwner, "Norn");
|
||||
assert.equal(byPlugin.get("norn.graph").availability, "optional-nested");
|
||||
assert.equal(byPlugin.get("norn.graph").fallbackKind, "terminal-outline");
|
||||
assert.equal(byPlugin.get("tex.math").semanticOwner, "EvePlugins");
|
||||
assert.equal(byPlugin.get("tex.math").availability, "optional-nested");
|
||||
assert.equal(byPlugin.get("tex.math").projectionKind, "tex-math-terminal-block-source");
|
||||
assert.equal(byPlugin.get("tex.math").fallbackKind, "source-text");
|
||||
assert.equal(byPlugin.get("tex.math").documentId, "\\\\mathrm{votes}(p)=1+\\\\lfloor\\\\log_b(1+p)\\\\rfloor");
|
||||
});
|
||||
|
||||
function advertisement() {
|
||||
return {
|
||||
schema: "gamecult.eve.provider_advertisement.v1",
|
||||
providerId: "aetheria",
|
||||
title: "Aetheria",
|
||||
surfaces: [
|
||||
{
|
||||
surfaceId: "aetheria.daemon.game",
|
||||
transport: "local-json",
|
||||
url: "conformance/eve/aetheria-world-surface.json",
|
||||
worldInteraction: {
|
||||
projectionKind: "provider-authored-world-surface",
|
||||
commandBoundary: "aetheria.daemon.commands",
|
||||
receiptSchema: "aetheria.eve_command_acceptance_status.v1",
|
||||
ownership: "provider-owns-world-state-assets-command-acceptance-and-receipts",
|
||||
},
|
||||
},
|
||||
{
|
||||
surfaceId: "aetheria.daemon.editor",
|
||||
transport: "local-json",
|
||||
url: "conformance/eve/aetheria-world-surface.json",
|
||||
worldInteraction: {
|
||||
projectionKind: "provider-authored-world-editor-surface",
|
||||
commandBoundary: "aetheria.daemon.commands",
|
||||
receiptSchema: "aetheria.eve_command_acceptance_status.v1",
|
||||
ownership: "provider-owns-editor-state-assets-command-acceptance-and-receipts",
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
function saiAdvertisement() {
|
||||
return {
|
||||
schema: "gamecult.eve.provider_advertisement.v1",
|
||||
providerId: "gamecult.home.vn",
|
||||
title: "GameCult Compound VN",
|
||||
surfaces: [
|
||||
{
|
||||
surfaceId: "sai.visual_novel.surface",
|
||||
transport: "local-json",
|
||||
url: "web/fixtures/sai-vn-surface.json",
|
||||
worldInteraction: {
|
||||
projectionKind: "provider-authored-world-surface",
|
||||
commandBoundary: "sai.vn.plugin.commands",
|
||||
receiptSchema: "gamecult.eve.command_receipt.v1",
|
||||
ownership: "provider-owns-story-state-plugin-sidecars-own-nested-semantics",
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
function surfaceDocument(surfaceId = "aetheria.daemon.game") {
|
||||
return {
|
||||
type: "surface-state",
|
||||
schema: "gamecult.eve.surface.v1",
|
||||
providerId: "aetheria",
|
||||
providerKind: "game.runtime",
|
||||
title: "Aetheria world",
|
||||
version: 1,
|
||||
updatedAtUtc: "2026-07-09T00:00:00Z",
|
||||
surface: {
|
||||
id: surfaceId,
|
||||
root: {
|
||||
id: `${surfaceId}.root`,
|
||||
kind: "surface",
|
||||
props: {},
|
||||
children: [
|
||||
{
|
||||
id: `${surfaceId}.gravity`,
|
||||
kind: "field.surface2d",
|
||||
props: {
|
||||
label: "Current Zone Gravity",
|
||||
bind: "aetheria.daemon.soaView.gravityField",
|
||||
fieldId: "aetheria.zone.gravity",
|
||||
},
|
||||
children: [],
|
||||
},
|
||||
{
|
||||
id: `${surfaceId}.focus`,
|
||||
kind: "control.button",
|
||||
props: {
|
||||
label: "Focus Relay",
|
||||
command: "aetheria.daemon.commands",
|
||||
commandId: "aetheria.daemon.focus",
|
||||
},
|
||||
children: [],
|
||||
},
|
||||
{
|
||||
id: `${surfaceId}.norn`,
|
||||
kind: "embed.norn",
|
||||
props: {
|
||||
label: "Norn tactical map",
|
||||
},
|
||||
embeddedDocuments: [
|
||||
{
|
||||
slotId: "norn.map",
|
||||
documentId: "cultmesh://aetheria/norn/map",
|
||||
schemaId: "gamecult.eve.surface.v1",
|
||||
presentationKind: "terminal-grid-slot",
|
||||
},
|
||||
],
|
||||
children: [],
|
||||
},
|
||||
],
|
||||
},
|
||||
styles: [],
|
||||
},
|
||||
commands: [],
|
||||
};
|
||||
}
|
||||
118
tools/evetui/evetui-capture-artifact.mjs
Normal file
118
tools/evetui/evetui-capture-artifact.mjs
Normal file
|
|
@ -0,0 +1,118 @@
|
|||
import { mkdirSync, writeFileSync } from "node:fs";
|
||||
import path from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import { EveTuiShell } from "../../src/eve-tui-shell.mjs";
|
||||
import { buildTuiCaptureRequest, loadJsonFile, writeCaptureRequest } from "./evetui-capture-contract.mjs";
|
||||
|
||||
export function buildTuiCaptureArtifact({
|
||||
advertisement,
|
||||
capabilityManifest,
|
||||
surfaceDocument,
|
||||
advertisementPath = "",
|
||||
capabilityManifestPath = "",
|
||||
captureSurfaceId = "",
|
||||
stamp = "latest",
|
||||
width = 80,
|
||||
} = {}) {
|
||||
const request = buildTuiCaptureRequest({
|
||||
advertisement,
|
||||
capabilityManifest,
|
||||
advertisementPath,
|
||||
capabilityManifestPath,
|
||||
captureSurfaceId,
|
||||
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,
|
||||
captureSurfaceId: args["surface-id"] || "",
|
||||
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();
|
||||
}
|
||||
57
tools/evetui/evetui-capture-artifact.test.mjs
Normal file
57
tools/evetui/evetui-capture-artifact.test.mjs
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
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("../../../Aetheria/conformance/eve/aetheria.provider-advertisement.json", import.meta.url), "utf8"));
|
||||
const capabilityManifest = JSON.parse(readFileSync(new URL("../../eve-runtime-capability.json", import.meta.url), "utf8"));
|
||||
const surfaceDocument = JSON.parse(readFileSync(new URL("../../../Aetheria/conformance/eve/aetheria-world-surface.json", import.meta.url), "utf8"));
|
||||
const worldSmokeAdvertisement = JSON.parse(readFileSync(new URL("../../../Eve/web/fixtures/eve-world-smoke.provider-advertisement.json", import.meta.url), "utf8"));
|
||||
const worldSmokeSurfaceDocument = JSON.parse(readFileSync(new URL("../../../Eve/web/fixtures/eve-world-smoke-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: "conformance/eve/aetheria.provider-advertisement.json",
|
||||
capabilityManifestPath: "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")));
|
||||
});
|
||||
|
||||
test("builds TUI JSON grid capture artifact from a generic world provider", () => {
|
||||
const { grid } = buildTuiCaptureArtifact({
|
||||
advertisement: worldSmokeAdvertisement,
|
||||
capabilityManifest,
|
||||
surfaceDocument: worldSmokeSurfaceDocument,
|
||||
advertisementPath: "web/fixtures/eve-world-smoke.provider-advertisement.json",
|
||||
capabilityManifestPath: "eve-runtime-capability.json",
|
||||
stamp: "smoke",
|
||||
width: 72,
|
||||
});
|
||||
|
||||
assert.equal(grid.schema, "gamecult.eve.tui_grid.v1");
|
||||
assert.equal(grid.runtimeId, "tui");
|
||||
assert.equal(grid.providerId, "eve.world-smoke");
|
||||
assert.equal(grid.surfaceId, "eve.world-smoke.surface");
|
||||
assert.equal(grid.commandBoundary, "eve.world-smoke.commands");
|
||||
assert.equal(grid.receiptSchema, "eve.world_smoke.command_receipt.v1");
|
||||
assert.equal(grid.artifactKind, "json-grid");
|
||||
assert.equal(grid.captureKind, "terminal-cell-grid");
|
||||
assert.ok(grid.lines.some(line => line.includes("eve.world-smoke.surface")));
|
||||
});
|
||||
160
tools/evetui/evetui-capture-contract.mjs
Normal file
160
tools/evetui/evetui-capture-contract.mjs
Normal file
|
|
@ -0,0 +1,160 @@
|
|||
import { mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
||||
import path from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
|
||||
export function buildTuiCaptureRequest({
|
||||
advertisement,
|
||||
capabilityManifest,
|
||||
advertisementPath = "",
|
||||
capabilityManifestPath = "",
|
||||
captureSurfaceId = "",
|
||||
stamp = new Date().toISOString().replace(/[:.]/g, "-"),
|
||||
} = {}) {
|
||||
if (!advertisement || typeof advertisement !== "object") {
|
||||
throw new Error("advertisement is required");
|
||||
}
|
||||
if (!capabilityManifest || typeof capabilityManifest !== "object") {
|
||||
throw new Error("capabilityManifest is required");
|
||||
}
|
||||
|
||||
const captureContract = capabilityManifest.lifecycle?.capture?.captureContract;
|
||||
if (!captureContract) {
|
||||
throw new Error("capability manifest is missing lifecycle.capture.captureContract");
|
||||
}
|
||||
|
||||
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"}`,
|
||||
);
|
||||
}
|
||||
|
||||
const targetId = captureContract.targetId || captureContract.runtimeId;
|
||||
const surface = (advertisement.surfaces || []).find(candidate => candidate.surfaceId === captureSurface.surfaceId);
|
||||
if (!surface) {
|
||||
throw new Error(`capture surface not advertised: ${captureSurface.surfaceId}`);
|
||||
}
|
||||
|
||||
const loweringTargets = surface.worldInteraction?.loweringTargets || [];
|
||||
if (!loweringTargets.includes(targetId)) {
|
||||
throw new Error(`surface ${surface.surfaceId} does not advertise lowering target ${targetId}`);
|
||||
}
|
||||
|
||||
const artifactPath = replaceStamp(captureContract.artifactPattern, stamp);
|
||||
|
||||
return {
|
||||
schema: captureContract.requestSchema || "gamecult.eve.runtime_capture_request.v1",
|
||||
runtimeId: capabilityManifest.runtimeId,
|
||||
ownerRepo: captureContract.ownerRepo,
|
||||
providerId: advertisement.providerId,
|
||||
surfaceId: surface.surfaceId,
|
||||
surfaceKind: surface.surfaceKind || "",
|
||||
targetId,
|
||||
projectionKind: surface.worldInteraction?.projectionKind || "",
|
||||
commandBoundary: surface.worldInteraction?.commandBoundary || "",
|
||||
receiptSchema: surface.worldInteraction?.receiptSchema || "",
|
||||
captureKind: captureContract.captureKind,
|
||||
artifactKind: captureContract.artifactKind,
|
||||
artifactPath,
|
||||
conformanceAttachment: captureContract.conformanceAttachment,
|
||||
authority: captureContract.authority,
|
||||
sourceAdvertisementPath: advertisementPath,
|
||||
sourceCapabilityManifestPath: capabilityManifestPath,
|
||||
issuedAtUtc: stamp,
|
||||
};
|
||||
}
|
||||
|
||||
export function loadJsonFile(filePath) {
|
||||
return JSON.parse(readFileSync(filePath, "utf8"));
|
||||
}
|
||||
|
||||
export function writeCaptureRequest(request, outputPath) {
|
||||
mkdirSync(path.dirname(outputPath), { recursive: true });
|
||||
writeFileSync(outputPath, `${JSON.stringify(request, null, 2)}\n`, "utf8");
|
||||
}
|
||||
|
||||
function replaceStamp(pattern, stamp) {
|
||||
if (!pattern || !pattern.includes("{stamp}")) {
|
||||
throw new Error(`capture artifact pattern must include {stamp}: ${pattern || "missing"}`);
|
||||
}
|
||||
return pattern.replace("{stamp}", stamp);
|
||||
}
|
||||
|
||||
function 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 [
|
||||
{
|
||||
providerId: captureContract.requiredProvider || "",
|
||||
surfaceId: captureContract.requiredSurface || "",
|
||||
},
|
||||
...(Array.isArray(captureContract.additionalProviderSurfaces) ? captureContract.additionalProviderSurfaces : []),
|
||||
]
|
||||
.filter(claim => claim && typeof claim === "object")
|
||||
.map(claim => ({
|
||||
providerId: String(claim.providerId || ""),
|
||||
surfaceId: String(claim.surfaceId || ""),
|
||||
}))
|
||||
.filter(claim => claim.providerId && claim.surfaceId);
|
||||
}
|
||||
|
||||
function parseArgs(argv) {
|
||||
const args = {};
|
||||
for (let index = 0; index < argv.length; index += 1) {
|
||||
const value = argv[index];
|
||||
if (!value.startsWith("--")) continue;
|
||||
const key = value.slice(2);
|
||||
const next = argv[index + 1];
|
||||
if (!next || next.startsWith("--")) {
|
||||
args[key] = true;
|
||||
} else {
|
||||
args[key] = next;
|
||||
index += 1;
|
||||
}
|
||||
}
|
||||
return args;
|
||||
}
|
||||
|
||||
function runCli() {
|
||||
const args = parseArgs(process.argv.slice(2));
|
||||
for (const required of ["advertisement", "capability", "output"]) {
|
||||
if (!args[required]) {
|
||||
throw new Error(`missing required --${required}`);
|
||||
}
|
||||
}
|
||||
|
||||
const stamp = args.stamp || new Date().toISOString().replace(/[:.]/g, "-");
|
||||
const request = buildTuiCaptureRequest({
|
||||
advertisement: loadJsonFile(args.advertisement),
|
||||
capabilityManifest: loadJsonFile(args.capability),
|
||||
advertisementPath: args.advertisement,
|
||||
capabilityManifestPath: args.capability,
|
||||
captureSurfaceId: args["surface-id"] || "",
|
||||
stamp,
|
||||
});
|
||||
writeCaptureRequest(request, args.output);
|
||||
process.stdout.write(`${args.output}\n`);
|
||||
}
|
||||
|
||||
const invokedPath = process.argv[1] ? path.resolve(process.argv[1]) : "";
|
||||
const modulePath = fileURLToPath(import.meta.url);
|
||||
if (invokedPath === modulePath) {
|
||||
runCli();
|
||||
}
|
||||
78
tools/evetui/evetui-capture-contract.test.mjs
Normal file
78
tools/evetui/evetui-capture-contract.test.mjs
Normal file
|
|
@ -0,0 +1,78 @@
|
|||
import assert from "node:assert/strict";
|
||||
import { readFileSync } from "node:fs";
|
||||
import test from "node:test";
|
||||
import { buildTuiCaptureRequest } from "./evetui-capture-contract.mjs";
|
||||
|
||||
const advertisement = JSON.parse(readFileSync(new URL("../../../Aetheria/conformance/eve/aetheria.provider-advertisement.json", import.meta.url), "utf8"));
|
||||
const worldSmokeAdvertisement = JSON.parse(readFileSync(new URL("../../../Eve/web/fixtures/eve-world-smoke.provider-advertisement.json", import.meta.url), "utf8"));
|
||||
const capabilityManifest = JSON.parse(readFileSync(new URL("../../eve-runtime-capability.json", import.meta.url), "utf8"));
|
||||
|
||||
test("builds TUI capture request from provider advertisement", () => {
|
||||
const request = buildTuiCaptureRequest({
|
||||
advertisement,
|
||||
capabilityManifest,
|
||||
advertisementPath: "conformance/eve/aetheria.provider-advertisement.json",
|
||||
capabilityManifestPath: "eve-runtime-capability.json",
|
||||
stamp: "smoke",
|
||||
});
|
||||
|
||||
assert.equal(request.schema, "gamecult.eve.runtime_capture_request.v1");
|
||||
assert.equal(request.runtimeId, "tui");
|
||||
assert.equal(request.ownerRepo, "EveTui");
|
||||
assert.equal(request.providerId, "aetheria");
|
||||
assert.equal(request.surfaceId, "aetheria.daemon.game");
|
||||
assert.equal(request.targetId, "tui");
|
||||
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-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/);
|
||||
});
|
||||
|
||||
test("builds TUI capture request for Aetheria editor surface", () => {
|
||||
const request = buildTuiCaptureRequest({
|
||||
advertisement,
|
||||
capabilityManifest,
|
||||
captureSurfaceId: "aetheria.daemon.editor",
|
||||
advertisementPath: "conformance/eve/aetheria.provider-advertisement.json",
|
||||
capabilityManifestPath: "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,
|
||||
capabilityManifest,
|
||||
advertisementPath: "web/fixtures/eve-world-smoke.provider-advertisement.json",
|
||||
capabilityManifestPath: "eve-runtime-capability.json",
|
||||
stamp: "smoke",
|
||||
});
|
||||
|
||||
assert.equal(request.runtimeId, "tui");
|
||||
assert.equal(request.providerId, "eve.world-smoke");
|
||||
assert.equal(request.surfaceId, "eve.world-smoke.surface");
|
||||
assert.equal(request.commandBoundary, "eve.world-smoke.commands");
|
||||
assert.equal(request.receiptSchema, "eve.world_smoke.command_receipt.v1");
|
||||
});
|
||||
|
||||
test("rejects capture request when the surface does not advertise TUI", () => {
|
||||
const withoutTarget = structuredClone(advertisement);
|
||||
const surface = withoutTarget.surfaces.find(candidate => candidate.surfaceId === "aetheria.daemon.game");
|
||||
surface.worldInteraction.loweringTargets = surface.worldInteraction.loweringTargets.filter(target => target !== "tui");
|
||||
|
||||
assert.throws(
|
||||
() => buildTuiCaptureRequest({ advertisement: withoutTarget, capabilityManifest, stamp: "smoke" }),
|
||||
/does not advertise lowering target tui/,
|
||||
);
|
||||
});
|
||||
114
tools/evetui/evetui-release-contract.mjs
Normal file
114
tools/evetui/evetui-release-contract.mjs
Normal file
|
|
@ -0,0 +1,114 @@
|
|||
import { mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
||||
import path from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
|
||||
export function buildTuiReleaseRequest({
|
||||
capabilityManifest,
|
||||
packageManifest,
|
||||
capabilityManifestPath = "",
|
||||
packageManifestPath = "",
|
||||
repository = "GameCult/EveTui",
|
||||
} = {}) {
|
||||
if (!capabilityManifest || typeof capabilityManifest !== "object") {
|
||||
throw new Error("capabilityManifest is required");
|
||||
}
|
||||
if (!packageManifest || typeof packageManifest !== "object") {
|
||||
throw new Error("packageManifest is required");
|
||||
}
|
||||
|
||||
const releaseContract = capabilityManifest.lifecycle?.release?.releaseContract;
|
||||
if (!releaseContract) {
|
||||
throw new Error("capability manifest is missing lifecycle.release.releaseContract");
|
||||
}
|
||||
|
||||
if (packageManifest.name !== releaseContract.packageName) {
|
||||
throw new Error(`package name mismatch: expected ${releaseContract.packageName} got ${packageManifest.name || "missing"}`);
|
||||
}
|
||||
if (!packageManifest.version || !/^\d+\.\d+\.\d+(?:[-+][0-9A-Za-z.-]+)?$/.test(packageManifest.version)) {
|
||||
throw new Error(`package version is not a SemVer-compatible release version: ${packageManifest.version || "missing"}`);
|
||||
}
|
||||
|
||||
const tagName = replaceVersion(releaseContract.tagPattern, packageManifest.version);
|
||||
const artifactPath = replaceVersion(
|
||||
releaseContract.artifactPattern || `artifacts/evetui-release/{version}/${releaseContract.packageName}-{version}.tgz`,
|
||||
packageManifest.version,
|
||||
);
|
||||
|
||||
return {
|
||||
schema: releaseContract.requestSchema || "gamecult.eve.runtime_release_request.v1",
|
||||
ownerRepo: releaseContract.ownerRepo,
|
||||
repository,
|
||||
packageName: releaseContract.packageName,
|
||||
version: packageManifest.version,
|
||||
packageRoot: releaseContract.packageRoot,
|
||||
versionSource: releaseContract.versionSource,
|
||||
tagName,
|
||||
artifactKind: releaseContract.artifactKind,
|
||||
artifactPath,
|
||||
publishProof: releaseContract.publishProof,
|
||||
sourceCapabilityManifestPath: capabilityManifestPath,
|
||||
sourcePackageManifestPath: packageManifestPath,
|
||||
main: packageManifest.main || "",
|
||||
scripts: packageManifest.scripts || {},
|
||||
dependencies: packageManifest.dependencies || {},
|
||||
devDependencies: packageManifest.devDependencies || {},
|
||||
};
|
||||
}
|
||||
|
||||
export function loadJsonFile(filePath) {
|
||||
return JSON.parse(readFileSync(filePath, "utf8"));
|
||||
}
|
||||
|
||||
export function writeReleaseRequest(request, outputPath) {
|
||||
mkdirSync(path.dirname(outputPath), { recursive: true });
|
||||
writeFileSync(outputPath, `${JSON.stringify(request, null, 2)}\n`, "utf8");
|
||||
}
|
||||
|
||||
function replaceVersion(pattern, version) {
|
||||
if (!pattern || !pattern.includes("{version}")) {
|
||||
throw new Error(`release pattern must include {version}: ${pattern || "missing"}`);
|
||||
}
|
||||
return pattern.replaceAll("{version}", version);
|
||||
}
|
||||
|
||||
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 ["capability", "package", "output"]) {
|
||||
if (!args[required]) {
|
||||
throw new Error(`missing required --${required}`);
|
||||
}
|
||||
}
|
||||
|
||||
const request = buildTuiReleaseRequest({
|
||||
capabilityManifest: loadJsonFile(args.capability),
|
||||
packageManifest: loadJsonFile(args.package),
|
||||
capabilityManifestPath: args.capability,
|
||||
packageManifestPath: args.package,
|
||||
repository: args.repository || "GameCult/EveTui",
|
||||
});
|
||||
writeReleaseRequest(request, args.output);
|
||||
process.stdout.write(`${args.output}\n`);
|
||||
}
|
||||
|
||||
const invokedPath = process.argv[1] ? path.resolve(process.argv[1]) : "";
|
||||
const modulePath = fileURLToPath(import.meta.url);
|
||||
if (invokedPath === modulePath) {
|
||||
runCli();
|
||||
}
|
||||
37
tools/evetui/evetui-release-contract.test.mjs
Normal file
37
tools/evetui/evetui-release-contract.test.mjs
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
import assert from "node:assert/strict";
|
||||
import { readFileSync } from "node:fs";
|
||||
import test from "node:test";
|
||||
import { buildTuiReleaseRequest } from "./evetui-release-contract.mjs";
|
||||
|
||||
const capabilityManifest = JSON.parse(readFileSync(new URL("../../eve-runtime-capability.json", import.meta.url), "utf8"));
|
||||
const packageManifest = JSON.parse(readFileSync(new URL("../../package.json", import.meta.url), "utf8"));
|
||||
|
||||
test("builds EveTui release request from package manifest", () => {
|
||||
const request = buildTuiReleaseRequest({
|
||||
capabilityManifest,
|
||||
packageManifest,
|
||||
capabilityManifestPath: "eve-runtime-capability.json",
|
||||
packageManifestPath: "package.json",
|
||||
});
|
||||
|
||||
assert.equal(request.schema, "gamecult.eve.runtime_release_request.v1");
|
||||
assert.equal(request.ownerRepo, "EveTui");
|
||||
assert.equal(request.repository, "GameCult/EveTui");
|
||||
assert.equal(request.packageName, "eve-tui");
|
||||
assert.equal(request.version, "0.1.0");
|
||||
assert.equal(request.packageRoot, ".");
|
||||
assert.equal(request.versionSource, "package.json");
|
||||
assert.equal(request.tagName, "evetui-v0.1.0");
|
||||
assert.equal(request.artifactKind, "terminal-runtime");
|
||||
assert.equal(request.artifactPath, "artifacts/evetui-release/0.1.0/eve-tui-0.1.0.tgz");
|
||||
assert.equal(request.main, "src/eve-tui-shell.mjs");
|
||||
});
|
||||
|
||||
test("rejects package manifests that do not match the TUI release contract", () => {
|
||||
const wrongPackage = { ...packageManifest, name: "eve-wrong" };
|
||||
|
||||
assert.throws(
|
||||
() => buildTuiReleaseRequest({ capabilityManifest, packageManifest: wrongPackage }),
|
||||
/package name mismatch/,
|
||||
);
|
||||
});
|
||||
Reference in a new issue