Preserve advertised metadata in web provider fallback
This commit is contained in:
parent
4bee5c8504
commit
46de0bd309
9 changed files with 103 additions and 16 deletions
|
|
@ -980,6 +980,12 @@ Recently cut:
|
|||
capability claims; the local schema subset validators now resolve `$ref` and
|
||||
enforce enum values so missing availability cannot hide in recursive runtime
|
||||
capture trees.
|
||||
- Browser reference fallback discovery now merges local catalog transport/URL
|
||||
stubs with provider advertisement surface metadata instead of letting the
|
||||
local stub erase semantics. `web/provider-advertisements.mjs` preserves
|
||||
advertised `worldInteraction` and `requiresPlugins` for Aetheria and Sai
|
||||
while keeping fixture-local loading paths, and both `web/surface.js` and the
|
||||
layout probe consume that shared merge path.
|
||||
- Sai, Norn, and TeX now have plugin ABI fixtures for describe, validate,
|
||||
project, lower, measure, and apply behavior. The parity harness validates
|
||||
those fixtures against plugin manifests, so Eve proves the ABI shape without
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ const fs = require('fs');
|
|||
const path = require('path');
|
||||
const root = process.argv[2];
|
||||
const port = Number(process.argv[3]);
|
||||
const types = {'.html':'text/html; charset=utf-8','.js':'text/javascript; charset=utf-8','.css':'text/css; charset=utf-8','.json':'application/json; charset=utf-8','.eve':'text/plain; charset=utf-8'};
|
||||
const types = {'.html':'text/html; charset=utf-8','.js':'text/javascript; charset=utf-8','.mjs':'text/javascript; charset=utf-8','.css':'text/css; charset=utf-8','.json':'application/json; charset=utf-8','.eve':'text/plain; charset=utf-8'};
|
||||
http.createServer((req, res) => {
|
||||
const urlPath = decodeURIComponent(new URL(req.url, 'http://localhost').pathname);
|
||||
const clean = urlPath === '/' ? '/index.html' : urlPath;
|
||||
|
|
|
|||
|
|
@ -1145,7 +1145,15 @@
|
|||
"expectedSourceSymbols": [
|
||||
{
|
||||
"path": "web/surface.js",
|
||||
"contains": ["loadLocalProviderCatalog", "loadProviderAdvertisement", "publishCommandIntent", "browser.reference"]
|
||||
"contains": ["loadLocalProviderCatalog", "loadProviderAdvertisement", "mergeProviderAdvertisement", "publishCommandIntent", "browser.reference"]
|
||||
},
|
||||
{
|
||||
"path": "web/provider-advertisements.mjs",
|
||||
"contains": ["mergeProviderAdvertisement", "mergeProviderSurface", "findProviderCatalogEntry"]
|
||||
},
|
||||
{
|
||||
"path": "web/provider-advertisements.test.mjs",
|
||||
"contains": ["preserves Sai required and optional nested plugin requirements from advertisement", "merges local fixture transport with advertised Aetheria world interaction", "requiresPlugins", "worldInteraction", "optional-nested", "aetheria.daemon.commands"]
|
||||
},
|
||||
{
|
||||
"path": "web/local-provider-catalog.json",
|
||||
|
|
@ -1181,6 +1189,7 @@
|
|||
"evidencePaths": [
|
||||
"web/index.html",
|
||||
"web/surface.js",
|
||||
"web/provider-advertisements.mjs",
|
||||
"packages/eve-browser-lowering/src/index.ts"
|
||||
],
|
||||
"pendingProofs": [
|
||||
|
|
@ -1193,6 +1202,7 @@
|
|||
"command": "node --test web/eve-dsl.test.mjs && npm --prefix packages/eve-browser-lowering test && powershell -ExecutionPolicy Bypass -File .\\scripts\\run-parity-harness.ps1",
|
||||
"evidencePaths": [
|
||||
"web/eve-dsl.test.mjs",
|
||||
"web/provider-advertisements.test.mjs",
|
||||
"packages/eve-browser-lowering/test/browser-lowering.test.mjs",
|
||||
"tools/parity/parity-manifest.json",
|
||||
"scripts/run-parity-harness.ps1"
|
||||
|
|
|
|||
|
|
@ -41,6 +41,7 @@ function createStaticServer(root) {
|
|||
const types = {
|
||||
".html": "text/html; charset=utf-8",
|
||||
".js": "text/javascript; charset=utf-8",
|
||||
".mjs": "text/javascript; charset=utf-8",
|
||||
".css": "text/css; charset=utf-8",
|
||||
".json": "application/json; charset=utf-8",
|
||||
".eve": "text/plain; charset=utf-8",
|
||||
|
|
|
|||
|
|
@ -69,6 +69,7 @@
|
|||
"evidencePaths": [
|
||||
"web/index.html",
|
||||
"web/surface.js",
|
||||
"web/provider-advertisements.mjs",
|
||||
"packages/eve-browser-lowering/src/index.ts"
|
||||
],
|
||||
"pendingProofs": [
|
||||
|
|
@ -81,6 +82,7 @@
|
|||
"command": "node --test web/eve-dsl.test.mjs && npm --prefix packages/eve-browser-lowering test && powershell -ExecutionPolicy Bypass -File .\\scripts\\run-parity-harness.ps1",
|
||||
"evidencePaths": [
|
||||
"web/eve-dsl.test.mjs",
|
||||
"web/provider-advertisements.test.mjs",
|
||||
"packages/eve-browser-lowering/test/browser-lowering.test.mjs",
|
||||
"tools/parity/parity-manifest.json",
|
||||
"scripts/run-parity-harness.ps1"
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import { renderEveSurface } from "../packages/eve-browser-lowering/dist/index.js";
|
||||
import { compileEveDsl } from "./eve-dsl.js";
|
||||
import { findProviderCatalogEntry, mergeProviderAdvertisement } from "./provider-advertisements.mjs";
|
||||
|
||||
const app = document.querySelector("#app");
|
||||
const output = document.querySelector("#eve-layout-probe-output");
|
||||
|
|
@ -40,17 +41,11 @@ try {
|
|||
async function loadProvider(requestedProviderId) {
|
||||
const response = await fetch("./local-provider-catalog.json", { cache: "no-store" });
|
||||
const catalog = await response.json();
|
||||
const provider = (catalog.providers || []).find(candidate =>
|
||||
candidate.providerId === requestedProviderId || candidate.aliases?.includes(requestedProviderId));
|
||||
const provider = findProviderCatalogEntry(catalog, requestedProviderId);
|
||||
if (!provider) throw new Error(`Unknown local provider: ${requestedProviderId}`);
|
||||
if (!provider.advertisement) return provider;
|
||||
const advertisement = await (await fetch(provider.advertisement, { cache: "no-store" })).json();
|
||||
return {
|
||||
...advertisement,
|
||||
...provider,
|
||||
surfaces: provider.surfaces || advertisement.surfaces || [],
|
||||
localAdvertisement: advertisement,
|
||||
};
|
||||
return mergeProviderAdvertisement(provider, advertisement);
|
||||
}
|
||||
|
||||
async function loadSurface(surface) {
|
||||
|
|
|
|||
34
web/provider-advertisements.mjs
Normal file
34
web/provider-advertisements.mjs
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
export function mergeProviderAdvertisement(catalogProvider, advertisement = undefined) {
|
||||
if (!advertisement || typeof advertisement !== "object") {
|
||||
return catalogProvider;
|
||||
}
|
||||
|
||||
const catalogSurfaces = Array.isArray(catalogProvider?.surfaces) ? catalogProvider.surfaces : [];
|
||||
const advertisedSurfaces = Array.isArray(advertisement.surfaces) ? advertisement.surfaces : [];
|
||||
const surfaces = catalogSurfaces.length
|
||||
? catalogSurfaces.map(surface => mergeProviderSurface(surface, advertisedSurfaces.find(candidate => candidate.surfaceId === surface.surfaceId)))
|
||||
: advertisedSurfaces;
|
||||
|
||||
return {
|
||||
...advertisement,
|
||||
...catalogProvider,
|
||||
surfaces,
|
||||
localAdvertisement: advertisement,
|
||||
};
|
||||
}
|
||||
|
||||
export function mergeProviderSurface(catalogSurface, advertisedSurface = undefined) {
|
||||
if (!advertisedSurface || typeof advertisedSurface !== "object") {
|
||||
return catalogSurface;
|
||||
}
|
||||
return {
|
||||
...advertisedSurface,
|
||||
...catalogSurface,
|
||||
};
|
||||
}
|
||||
|
||||
export function findProviderCatalogEntry(catalog, requestedProviderId) {
|
||||
const providers = Array.isArray(catalog?.providers) ? catalog.providers : [];
|
||||
return providers.find(candidate =>
|
||||
candidate.providerId === requestedProviderId || candidate.aliases?.includes(requestedProviderId));
|
||||
}
|
||||
43
web/provider-advertisements.test.mjs
Normal file
43
web/provider-advertisements.test.mjs
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
import assert from "node:assert/strict";
|
||||
import { readFileSync } from "node:fs";
|
||||
import test from "node:test";
|
||||
import { findProviderCatalogEntry, mergeProviderAdvertisement } from "./provider-advertisements.mjs";
|
||||
|
||||
const catalog = JSON.parse(readFileSync(new URL("./local-provider-catalog.json", import.meta.url), "utf8"));
|
||||
const aetheriaAdvertisement = JSON.parse(readFileSync(new URL("./fixtures/aetheria.provider-advertisement.json", import.meta.url), "utf8"));
|
||||
const saiAdvertisement = JSON.parse(readFileSync(new URL("./fixtures/sai-vn.provider-advertisement.json", import.meta.url), "utf8"));
|
||||
|
||||
test("merges local fixture transport with advertised Aetheria world interaction", () => {
|
||||
const catalogProvider = findProviderCatalogEntry(catalog, "aetheria");
|
||||
const provider = mergeProviderAdvertisement(catalogProvider, aetheriaAdvertisement);
|
||||
const surface = provider.surfaces.find(candidate => candidate.surfaceId === "aetheria.daemon.game");
|
||||
|
||||
assert.equal(surface.transport, "local-json");
|
||||
assert.equal(surface.url, "./fixtures/aetheria-world-surface.json");
|
||||
assert.equal(surface.surfaceKind, "interactive-world");
|
||||
assert.equal(surface.worldInteraction.projectionKind, "provider-authored-world-surface");
|
||||
assert.equal(surface.worldInteraction.commandBoundary, "aetheria.daemon.commands");
|
||||
assert.equal(surface.worldInteraction.receiptSchema, "aetheria.eve_command_acceptance_status.v1");
|
||||
assert.equal(provider.localAdvertisement.providerId, "aetheria");
|
||||
});
|
||||
|
||||
test("preserves Sai required and optional nested plugin requirements from advertisement", () => {
|
||||
const catalogProvider = findProviderCatalogEntry(catalog, "gamecult.home.vn");
|
||||
const provider = mergeProviderAdvertisement(catalogProvider, saiAdvertisement);
|
||||
const surface = provider.surfaces.find(candidate => candidate.surfaceId === "sai.visual_novel.surface");
|
||||
const byPlugin = new Map(surface.requiresPlugins.map(requirement => [requirement.pluginId, requirement]));
|
||||
|
||||
assert.equal(surface.transport, "local-json");
|
||||
assert.equal(surface.url, "./fixtures/sai-vn-surface.json");
|
||||
assert.equal(byPlugin.get("sai.vn").availability, "required");
|
||||
assert.deepEqual(byPlugin.get("sai.vn").requiredCapabilities, [
|
||||
"vn.stage",
|
||||
"story.choose",
|
||||
"story.continue",
|
||||
"story.jump",
|
||||
]);
|
||||
assert.equal(byPlugin.get("norn.graph").availability, "optional-nested");
|
||||
assert.deepEqual(byPlugin.get("norn.graph").optionalCapabilities, ["embed.norn"]);
|
||||
assert.equal(byPlugin.get("tex.math").availability, "optional-nested");
|
||||
assert.deepEqual(byPlugin.get("tex.math").optionalCapabilities, ["embed.tex"]);
|
||||
});
|
||||
|
|
@ -4,6 +4,7 @@ import {
|
|||
renderEveSurface,
|
||||
} from "../packages/eve-browser-lowering/dist/index.js";
|
||||
import { compileEveDsl } from "./eve-dsl.js";
|
||||
import { mergeProviderAdvertisement } from "./provider-advertisements.mjs";
|
||||
|
||||
const statusEl = document.querySelector("#status");
|
||||
const app = document.querySelector("#app");
|
||||
|
|
@ -89,12 +90,7 @@ async function loadProviderAdvertisement(provider) {
|
|||
if (!provider.advertisement) return provider;
|
||||
const response = await fetch(provider.advertisement);
|
||||
const advertisement = await response.json();
|
||||
return {
|
||||
...advertisement,
|
||||
...provider,
|
||||
surfaces: provider.surfaces || advertisement.surfaces || [],
|
||||
localAdvertisement: advertisement,
|
||||
};
|
||||
return mergeProviderAdvertisement(provider, advertisement);
|
||||
}
|
||||
|
||||
async function openProvider(provider) {
|
||||
|
|
|
|||
Loading…
Reference in a new issue