Schema-back shell projection outputs
This commit is contained in:
parent
3f47ac0f31
commit
d4e101c225
12 changed files with 251 additions and 1 deletions
|
|
@ -437,6 +437,11 @@ Recently cut:
|
|||
wrapped and validated through the same schema, and the conformance export
|
||||
carries `lifecycleManifestPath` plus `lifecycleErrors` so runtime owners can
|
||||
consume lifecycle status without guessing from prose.
|
||||
- Electron and TUI provider-shell outputs are now schema-backed as
|
||||
`gamecult.eve.electron_shell_projection.v1` and
|
||||
`gamecult.eve.tui_grid.v1`. Their runtime tests load the schema documents and
|
||||
validate actual lowered projection objects, so Electron and TUI cannot claim
|
||||
generic provider-shell lowering by string constant alone.
|
||||
- EveElectron now has a pending split target and handoff at
|
||||
`runtimes/incubating/eve-electron/eveelectron-split-handoff.json`. It does
|
||||
claim a provider-agnostic Electron shell surface-tree projection and command
|
||||
|
|
|
|||
|
|
@ -228,6 +228,10 @@ Runtime lifecycle records use `gamecult.eve.runtime_lifecycle.v1`. Standalone
|
|||
lifecycle documents such as `flutter/eve_parity/eveflutter-lifecycle.json` and
|
||||
embedded runtime capability lifecycle blocks validate against the same stage
|
||||
shape, and exported runtime records carry `lifecycleErrors`.
|
||||
Runtime shell output contracts are schema-backed too:
|
||||
`gamecult.eve.electron_shell_projection.v1` covers the Electron shell tree, and
|
||||
`gamecult.eve.tui_grid.v1` covers the compact terminal grid. The Electron and
|
||||
TUI runtime tests validate real lowered objects against those schemas.
|
||||
|
||||
After generating the parity report, `run-parity-harness.ps1` copies
|
||||
`artifacts/conformance/latest` into
|
||||
|
|
|
|||
|
|
@ -1,7 +1,17 @@
|
|||
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 { EveElectronShell } from "../src/eve-electron-shell.mjs";
|
||||
|
||||
const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../../../..");
|
||||
const electronProjectionSchema = JSON.parse(readFileSync(
|
||||
path.join(repoRoot, "schemas/gamecult.eve.electron_shell_projection.v1.schema.json"),
|
||||
"utf8",
|
||||
));
|
||||
|
||||
test("selects the active advertised provider surface", () => {
|
||||
const shell = new EveElectronShell();
|
||||
const selected = shell.selectSurface(advertisement(), "aetheria.daemon.editor");
|
||||
|
|
@ -40,6 +50,7 @@ test("lowers provider surface trees into an Electron shell projection", () => {
|
|||
const shell = new EveElectronShell();
|
||||
const projection = shell.lowerSurface(surfaceDocument(), advertisement(), "aetheria.daemon.game");
|
||||
|
||||
assert.deepEqual(validateSchemaSubset(electronProjectionSchema, projection), []);
|
||||
assert.equal(projection.schema, "gamecult.eve.electron_shell_projection.v1");
|
||||
assert.equal(projection.providerId, "aetheria");
|
||||
assert.equal(projection.surfaceId, "aetheria.daemon.game");
|
||||
|
|
|
|||
|
|
@ -1,7 +1,17 @@
|
|||
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)), "../../../..");
|
||||
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");
|
||||
|
|
@ -40,6 +50,7 @@ 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");
|
||||
|
|
@ -51,6 +62,7 @@ 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");
|
||||
|
|
|
|||
53
runtimes/incubating/test-support/schema-subset.mjs
Normal file
53
runtimes/incubating/test-support/schema-subset.mjs
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
export function validateSchemaSubset(schema, value, pointer = "$") {
|
||||
const errors = [];
|
||||
if (!schema || typeof schema !== "object") return errors;
|
||||
|
||||
if (schema.const !== undefined && value !== schema.const) {
|
||||
errors.push(`${pointer}:const:${schema.const}`);
|
||||
}
|
||||
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}`));
|
||||
}
|
||||
}
|
||||
}
|
||||
if (schema.items && Array.isArray(value)) {
|
||||
value.forEach((item, index) => {
|
||||
errors.push(...validateSchemaSubset(schema.items, item, `${pointer}[${index}]`));
|
||||
});
|
||||
}
|
||||
return errors;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,93 @@
|
|||
{
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"$id": "gamecult.eve.electron_shell_projection.v1",
|
||||
"title": "Eve Electron Shell Projection",
|
||||
"type": "object",
|
||||
"required": [
|
||||
"type",
|
||||
"schema",
|
||||
"providerId",
|
||||
"surfaceId",
|
||||
"projectionKind",
|
||||
"commandBoundary",
|
||||
"receiptSchema",
|
||||
"ownership",
|
||||
"root"
|
||||
],
|
||||
"properties": {
|
||||
"type": {
|
||||
"const": "electron-shell-projection"
|
||||
},
|
||||
"schema": {
|
||||
"const": "gamecult.eve.electron_shell_projection.v1"
|
||||
},
|
||||
"providerId": {
|
||||
"type": "string",
|
||||
"minLength": 1
|
||||
},
|
||||
"surfaceId": {
|
||||
"type": "string",
|
||||
"minLength": 1
|
||||
},
|
||||
"projectionKind": {
|
||||
"type": "string",
|
||||
"minLength": 1
|
||||
},
|
||||
"commandBoundary": {
|
||||
"type": "string",
|
||||
"minLength": 1
|
||||
},
|
||||
"receiptSchema": {
|
||||
"type": "string",
|
||||
"minLength": 1
|
||||
},
|
||||
"ownership": {
|
||||
"type": "string",
|
||||
"minLength": 1
|
||||
},
|
||||
"root": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"id",
|
||||
"componentKind",
|
||||
"shellElementKind",
|
||||
"stateBindingCount",
|
||||
"embeddedDocumentCount",
|
||||
"children"
|
||||
],
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string",
|
||||
"minLength": 1
|
||||
},
|
||||
"componentKind": {
|
||||
"type": "string"
|
||||
},
|
||||
"shellElementKind": {
|
||||
"type": "string",
|
||||
"minLength": 1
|
||||
},
|
||||
"props": {
|
||||
"type": "object"
|
||||
},
|
||||
"layout": {
|
||||
"type": "object"
|
||||
},
|
||||
"style": {
|
||||
"type": "object"
|
||||
},
|
||||
"stateBindingCount": {
|
||||
"type": "number"
|
||||
},
|
||||
"embeddedDocumentCount": {
|
||||
"type": "number"
|
||||
},
|
||||
"children": {
|
||||
"type": "array"
|
||||
}
|
||||
},
|
||||
"additionalProperties": true
|
||||
}
|
||||
},
|
||||
"additionalProperties": true
|
||||
}
|
||||
58
schemas/gamecult.eve.tui_grid.v1.schema.json
Normal file
58
schemas/gamecult.eve.tui_grid.v1.schema.json
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
{
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"$id": "gamecult.eve.tui_grid.v1",
|
||||
"title": "Eve TUI Grid",
|
||||
"type": "object",
|
||||
"required": [
|
||||
"schema",
|
||||
"runtimeId",
|
||||
"providerId",
|
||||
"surfaceId",
|
||||
"width",
|
||||
"lines",
|
||||
"lossiness"
|
||||
],
|
||||
"properties": {
|
||||
"schema": {
|
||||
"const": "gamecult.eve.tui_grid.v1"
|
||||
},
|
||||
"runtimeId": {
|
||||
"const": "tui"
|
||||
},
|
||||
"providerId": {
|
||||
"type": "string",
|
||||
"minLength": 1
|
||||
},
|
||||
"surfaceId": {
|
||||
"type": "string",
|
||||
"minLength": 1
|
||||
},
|
||||
"projectionKind": {
|
||||
"type": "string"
|
||||
},
|
||||
"commandBoundary": {
|
||||
"type": "string"
|
||||
},
|
||||
"receiptSchema": {
|
||||
"type": "string"
|
||||
},
|
||||
"ownership": {
|
||||
"type": "string"
|
||||
},
|
||||
"width": {
|
||||
"type": "number"
|
||||
},
|
||||
"lines": {
|
||||
"type": "array",
|
||||
"minItems": 1,
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"lossiness": {
|
||||
"type": "string",
|
||||
"minLength": 1
|
||||
}
|
||||
},
|
||||
"additionalProperties": true
|
||||
}
|
||||
|
|
@ -45,6 +45,8 @@ try {
|
|||
--expect-schema gamecult.eve.runtime_split_handoff.v1 `
|
||||
--expect-schema gamecult.eve.plugin_handoff.v1 `
|
||||
--expect-schema gamecult.eve.provider_handoff.v1 `
|
||||
--expect-schema gamecult.eve.electron_shell_projection.v1 `
|
||||
--expect-schema gamecult.eve.tui_grid.v1 `
|
||||
--expect-capability-gap runtime:Fensalir:direct2d:capture:missing `
|
||||
--expect-world-lowering-coverage aetheria:aetheria.daemon.game:web-reference:claimed:Eve:web `
|
||||
--expect-world-lowering-coverage aetheria:aetheria.daemon.game:unity-uitoolkit:claimed:EveUnity:unity-uitoolkit `
|
||||
|
|
|
|||
|
|
@ -90,6 +90,8 @@ foreach ($contract in @(
|
|||
"gamecult.eve.runtime_capture_request.v1",
|
||||
"gamecult.eve.provider_advertisement.v1",
|
||||
"gamecult.eve.provider_handoff.v1",
|
||||
"gamecult.eve.electron_shell_projection.v1",
|
||||
"gamecult.eve.tui_grid.v1",
|
||||
"gamecult.eve.runtime_capability.v1",
|
||||
"gamecult.eve.runtime_lifecycle.v1",
|
||||
"gamecult.eve.runtime_split_handoff.v1"
|
||||
|
|
@ -124,6 +126,8 @@ if (Test-Path -LiteralPath (Join-Path $absoluteExportDirectory "index.json")) {
|
|||
"gamecult.eve.provider_scenario.v1",
|
||||
"gamecult.eve.provider_advertisement.v1",
|
||||
"gamecult.eve.provider_handoff.v1",
|
||||
"gamecult.eve.electron_shell_projection.v1",
|
||||
"gamecult.eve.tui_grid.v1",
|
||||
"gamecult.eve.runtime_capability.v1",
|
||||
"gamecult.eve.runtime_lifecycle.v1",
|
||||
"gamecult.eve.runtime_split_handoff.v1",
|
||||
|
|
|
|||
|
|
@ -45,6 +45,8 @@ node $consumerScript $consumerExport `
|
|||
--expect-schema gamecult.eve.runtime_capture_request.v1 `
|
||||
--expect-schema gamecult.eve.runtime_lifecycle.v1 `
|
||||
--expect-schema gamecult.eve.runtime_split_handoff.v1 `
|
||||
--expect-schema gamecult.eve.electron_shell_projection.v1 `
|
||||
--expect-schema gamecult.eve.tui_grid.v1 `
|
||||
--expect-runtime-status web:active `
|
||||
--expect-runtime-status windows-flutter:active `
|
||||
--expect-runtime-status linux-flutter:active `
|
||||
|
|
|
|||
|
|
@ -26,6 +26,8 @@
|
|||
"schemas/gamecult.eve.provider_scenario.v1.schema.json",
|
||||
"schemas/gamecult.eve.provider_advertisement.v1.schema.json",
|
||||
"schemas/gamecult.eve.provider_handoff.v1.schema.json",
|
||||
"schemas/gamecult.eve.electron_shell_projection.v1.schema.json",
|
||||
"schemas/gamecult.eve.tui_grid.v1.schema.json",
|
||||
"schemas/gamecult.eve.runtime_capability.v1.schema.json",
|
||||
"schemas/gamecult.eve.runtime_lifecycle.v1.schema.json",
|
||||
"schemas/gamecult.eve.runtime_split_handoff.v1.schema.json",
|
||||
|
|
@ -88,6 +90,8 @@
|
|||
"gamecult.eve.runtime_capture_request.v1",
|
||||
"gamecult.eve.provider_advertisement.v1",
|
||||
"gamecult.eve.provider_handoff.v1",
|
||||
"gamecult.eve.electron_shell_projection.v1",
|
||||
"gamecult.eve.tui_grid.v1",
|
||||
"gamecult.eve.runtime_capability.v1",
|
||||
"gamecult.eve.runtime_lifecycle.v1",
|
||||
"gamecult.eve.runtime_split_handoff.v1"
|
||||
|
|
|
|||
|
|
@ -27,7 +27,9 @@
|
|||
"gamecult.eve.provider_scenario.v1": "schemas/gamecult.eve.provider_scenario.v1.schema.json",
|
||||
"gamecult.eve.provider_advertisement.v1": "schemas/gamecult.eve.provider_advertisement.v1.schema.json",
|
||||
"gamecult.eve.provider_handoff.v1": "schemas/gamecult.eve.provider_handoff.v1.schema.json",
|
||||
"gamecult.eve.conformance_handoff.v1": "schemas/gamecult.eve.conformance_handoff.v1.schema.json"
|
||||
"gamecult.eve.conformance_handoff.v1": "schemas/gamecult.eve.conformance_handoff.v1.schema.json",
|
||||
"gamecult.eve.electron_shell_projection.v1": "schemas/gamecult.eve.electron_shell_projection.v1.schema.json",
|
||||
"gamecult.eve.tui_grid.v1": "schemas/gamecult.eve.tui_grid.v1.schema.json"
|
||||
},
|
||||
"conformancePacks": [
|
||||
{
|
||||
|
|
|
|||
Loading…
Reference in a new issue