diff --git a/packages/eve-contracts/test/contracts.test.mjs b/packages/eve-contracts/test/contracts.test.mjs index e49cc74..2847c1a 100644 --- a/packages/eve-contracts/test/contracts.test.mjs +++ b/packages/eve-contracts/test/contracts.test.mjs @@ -178,3 +178,18 @@ test("validates an existing canonical Eve surface fixture", async () => { const fixture = JSON.parse(await readFile(fixturePath, "utf8")); assert.equal(parseEveSurfaceDocument(fixture).providerId, "eve.world-smoke"); }); + +test("validates the Epiphany Model Atlas advertisement and canonical surface", async () => { + const fixtureRoot = resolve(import.meta.dirname, "..", "..", "..", "web", "fixtures"); + const advertisement = JSON.parse(await readFile( + resolve(fixtureRoot, "epiphany-model-atlas.provider-advertisement.json"), + "utf8", + )); + const surfaceFixture = JSON.parse(await readFile( + resolve(fixtureRoot, "epiphany-model-atlas-surface.json"), + "utf8", + )); + + assert.equal(parseEveProviderAdvertisement(advertisement).providerId, "epiphany.model-atlas"); + assert.equal(parseEveSurfaceDocument(surfaceFixture).surface.id, "epiphany.model-atlas.surface"); +}); diff --git a/web/fixtures/epiphany-model-atlas-surface.conformance.json b/web/fixtures/epiphany-model-atlas-surface.conformance.json new file mode 100644 index 0000000..db6f576 --- /dev/null +++ b/web/fixtures/epiphany-model-atlas-surface.conformance.json @@ -0,0 +1,27 @@ +{ + "schema": "gamecult.eve.conformance_fixture.v1", + "fixtureId": "epiphany-model-atlas", + "title": "Epiphany Model Atlas", + "pack": "provider", + "ownerRepo": "EpiphanyAgent", + "surface": { + "transport": "local-json", + "path": "web/fixtures/epiphany-model-atlas-surface.json" + }, + "purpose": "Proves an Epiphany-owned entanglement projection can publish one attention-first Eve surface for GUI and TUI lowering without moving modeled truth into a renderer.", + "exitCriteria": "Move live projection state and authored product scenarios to EpiphanyAgent; Eve retains only the minimal cross-runtime contract witness while this boundary is incubating.", + "asserts": [ + "attention list is the default view", + "selected entanglement drilldown", + "secondary graph with linear fallback", + "CultMesh bindings use gamecult.model.entanglement_projection.v0", + "GUI and TUI consume one surface id", + "selection and filter commands are presentation-only", + "no dashboard compatibility state or renderer-owned domain truth" + ], + "loweringTargets": [ + "gui", + "tui" + ], + "splitTarget": "EpiphanyAgent" +} diff --git a/web/fixtures/epiphany-model-atlas-surface.json b/web/fixtures/epiphany-model-atlas-surface.json new file mode 100644 index 0000000..cd84872 --- /dev/null +++ b/web/fixtures/epiphany-model-atlas-surface.json @@ -0,0 +1,392 @@ +{ + "type": "surface-state", + "schema": "gamecult.eve.surface.v1", + "providerId": "epiphany.model-atlas", + "providerKind": "modeling.projection", + "title": "Epiphany Model Atlas", + "version": 1, + "updatedAtUtc": "2026-08-14T00:00:00Z", + "surface": { + "id": "epiphany.model-atlas.surface", + "root": { + "id": "epiphany.model-atlas.root", + "kind": "surface", + "props": { + "defaultView": "attention", + "stateAuthority": "gamecult.model.entanglement_projection.v0", + "presentationState": "provider-owned-ephemeral", + "viewOrder": [ + "attention", + "drilldown", + "graph" + ] + }, + "children": [ + { + "id": "epiphany.model-atlas.attention", + "kind": "panel", + "props": { + "title": "Attention", + "viewId": "attention", + "role": "primary", + "default": true + }, + "children": [ + { + "id": "epiphany.model-atlas.attention.controls", + "kind": "rail", + "props": { + "role": "presentation-controls" + }, + "children": [ + { + "id": "epiphany.model-atlas.attention.filter", + "kind": "control.select", + "props": { + "label": "Filter", + "value": "needs-attention", + "options": [ + "needs-attention", + "degraded", + "disputed", + "stale", + "all" + ], + "command": "epiphany.model-atlas.presentation.filter", + "commandId": "epiphany.model-atlas.presentation.filter", + "presentationOnly": true, + "domainStateEffects": "none" + }, + "children": [] + }, + { + "id": "epiphany.model-atlas.attention.select", + "kind": "control.select", + "props": { + "label": "Selected entanglement", + "value": "entanglement.epiphany-eve.surface", + "options": [ + "entanglement.epiphany-eve.surface", + "entanglement.epiphany-cultlib.state", + "entanglement.odin-eve.discovery" + ], + "command": "epiphany.model-atlas.presentation.select", + "commandId": "epiphany.model-atlas.presentation.select", + "presentationOnly": true, + "domainStateEffects": "none" + }, + "children": [] + } + ] + }, + { + "id": "epiphany.model-atlas.attention.items", + "kind": "list", + "props": { + "title": "Relationships requiring attention", + "items": [ + { + "label": "EpiphanyAgent -> Eve", + "status": "disputed", + "detail": "Consumer requires Model Atlas parity; provider surface contract is awaiting conformance.", + "badges": [ + "presentation", + "cross-repo", + "high" + ] + }, + { + "label": "EpiphanyAgent -> CultLib", + "status": "stale", + "detail": "Typed projection witness has exceeded its declared freshness window.", + "badges": [ + "state", + "freshness" + ] + } + ] + }, + "stateBindings": [ + { + "targetProp": "items", + "pointerId": "epiphany.model-atlas.attention.items", + "sourceId": "cultmesh://gamecult.swarm/epiphany/modeling/entanglements#attention", + "schemaId": "gamecult.model.entanglement_projection.v0", + "routeKind": "network", + "routeDescription": "CultMesh projection of entanglements currently requiring operator or owner attention." + } + ], + "children": [] + } + ] + }, + { + "id": "epiphany.model-atlas.drilldown", + "kind": "panel", + "props": { + "title": "Selected entanglement", + "viewId": "drilldown", + "role": "detail" + }, + "stateBindings": [ + { + "targetProp": "title", + "pointerId": "epiphany.model-atlas.selected.title", + "sourceId": "cultmesh://gamecult.swarm/epiphany/modeling/entanglements#selected.title", + "schemaId": "gamecult.model.entanglement_projection.v0", + "routeKind": "network", + "routeDescription": "Provider-authored title for the selected entanglement." + } + ], + "children": [ + { + "id": "epiphany.model-atlas.drilldown.summary", + "kind": "text", + "props": { + "text": "EpiphanyAgent consumes Eve's retained surface contract. The consumer claim and provider surface agree; GUI/TUI conformance remains the live proof." + }, + "stateBindings": [ + { + "targetProp": "text", + "pointerId": "epiphany.model-atlas.selected.summary", + "sourceId": "cultmesh://gamecult.swarm/epiphany/modeling/entanglements#selected.summary", + "schemaId": "gamecult.model.entanglement_projection.v0", + "routeKind": "network", + "routeDescription": "Provider-authored relationship summary." + } + ], + "children": [] + }, + { + "id": "epiphany.model-atlas.drilldown.status", + "kind": "metric", + "props": { + "label": "State", + "value": "disputed" + }, + "stateBindings": [ + { + "targetProp": "value", + "pointerId": "epiphany.model-atlas.selected.state", + "sourceId": "cultmesh://gamecult.swarm/epiphany/modeling/entanglements#selected.state", + "schemaId": "gamecult.model.entanglement_projection.v0", + "routeKind": "network", + "routeDescription": "Derived entanglement state owned by Epiphany Modeling." + } + ], + "children": [] + }, + { + "id": "epiphany.model-atlas.drilldown.claims", + "kind": "list", + "props": { + "title": "Owned claims", + "items": [ + { + "label": "Consumer claim", + "status": "declared", + "detail": "EpiphanyAgent requires one retained tree consumable by GUI and TUI.", + "badges": [ + "EpiphanyAgent" + ] + }, + { + "label": "Provider claim", + "status": "available", + "detail": "Eve publishes gamecult.eve.surface.v1 and presentation-only command descriptors.", + "badges": [ + "Eve" + ] + } + ] + }, + "stateBindings": [ + { + "targetProp": "items", + "pointerId": "epiphany.model-atlas.selected.claims", + "sourceId": "cultmesh://gamecult.swarm/epiphany/modeling/entanglements#selected.claims", + "schemaId": "gamecult.model.entanglement_projection.v0", + "routeKind": "network", + "routeDescription": "Endpoint-owned claims retained without merging their authority." + } + ], + "children": [] + }, + { + "id": "epiphany.model-atlas.drilldown.evidence", + "kind": "list", + "props": { + "title": "Verification", + "items": [ + { + "label": "Surface contract", + "status": "verified", + "detail": "Canonical gamecult.eve.surface.v1 retained tree validates.", + "badges": [ + "Soul" + ] + }, + { + "label": "Runtime parity", + "status": "pending", + "detail": "GUI and TUI owners must lower this same surface identity.", + "badges": [ + "conformance" + ] + } + ] + }, + "stateBindings": [ + { + "targetProp": "items", + "pointerId": "epiphany.model-atlas.selected.evidence", + "sourceId": "cultmesh://gamecult.swarm/epiphany/modeling/entanglements#selected.evidence", + "schemaId": "gamecult.model.entanglement_projection.v0", + "routeKind": "network", + "routeDescription": "Verification evidence projected by Epiphany Modeling." + } + ], + "children": [] + } + ] + }, + { + "id": "epiphany.model-atlas.graph", + "kind": "graph", + "props": { + "title": "Swarm topology", + "viewId": "graph", + "role": "secondary", + "presentationFallback": "children", + "nodes": [ + { + "id": "repo.epiphany-agent", + "label": "EpiphanyAgent", + "owner": "EpiphanyAgent" + }, + { + "id": "repo.eve", + "label": "Eve", + "owner": "Eve" + }, + { + "id": "repo.cultlib", + "label": "CultLib", + "owner": "CultLib" + } + ], + "edges": [ + { + "id": "entanglement.epiphany-eve.surface", + "from": "repo.epiphany-agent", + "to": "repo.eve", + "state": "disputed" + }, + { + "id": "entanglement.epiphany-cultlib.state", + "from": "repo.epiphany-agent", + "to": "repo.cultlib", + "state": "stale" + } + ] + }, + "stateBindings": [ + { + "targetProp": "nodes", + "pointerId": "epiphany.model-atlas.graph.nodes", + "sourceId": "cultmesh://gamecult.swarm/epiphany/modeling/entanglements#graph.nodes", + "schemaId": "gamecult.model.entanglement_projection.v0", + "routeKind": "network", + "routeDescription": "Derived repository nodes for graphical lowering." + }, + { + "targetProp": "edges", + "pointerId": "epiphany.model-atlas.graph.edges", + "sourceId": "cultmesh://gamecult.swarm/epiphany/modeling/entanglements#graph.edges", + "schemaId": "gamecult.model.entanglement_projection.v0", + "routeKind": "network", + "routeDescription": "Derived entanglement edges for graphical lowering." + } + ], + "children": [ + { + "id": "epiphany.model-atlas.graph.linear", + "kind": "list", + "props": { + "title": "Topology", + "role": "linear-fallback", + "items": [ + { + "label": "EpiphanyAgent -> Eve", + "status": "disputed", + "detail": "surface contract", + "badges": [ + "consumer-to-provider" + ] + }, + { + "label": "EpiphanyAgent -> CultLib", + "status": "stale", + "detail": "typed state projection", + "badges": [ + "consumer-to-provider" + ] + } + ] + }, + "stateBindings": [ + { + "targetProp": "items", + "pointerId": "epiphany.model-atlas.graph.linear", + "sourceId": "cultmesh://gamecult.swarm/epiphany/modeling/entanglements#graph.linear", + "schemaId": "gamecult.model.entanglement_projection.v0", + "routeKind": "network", + "routeDescription": "Linear projection of the same graph for compact and TUI lowerings." + } + ], + "children": [] + } + ] + } + ] + }, + "styles": { + "tokens": { + "colorBackground": "#0A0E12", + "colorPanel": "#151C23", + "colorPanelAlt": "#202A33", + "colorText": "#F0F4F1", + "colorMuted": "#9DA9A2", + "colorAccent": "#78D6A3", + "colorWarning": "#D6B24A", + "colorDanger": "#C95D63", + "fontBody": "Ubuntu Sans, Ubuntu, sans-serif", + "fontTitle": "Montserrat, Zen Kaku Gothic New, Ubuntu Sans, sans-serif" + } + } + }, + "commands": [ + { + "schema": "gamecult.eve.command.v1", + "command": "epiphany.model-atlas.presentation.select", + "label": "Select entanglement", + "surfaceId": "epiphany.model-atlas.surface", + "transport": "cultmesh-command", + "authority": "epiphany.model-atlas.presentation", + "result": "surface-republished-or-denied", + "presentationOnly": true, + "domainStateEffects": "none" + }, + { + "schema": "gamecult.eve.command.v1", + "command": "epiphany.model-atlas.presentation.filter", + "label": "Filter attention", + "surfaceId": "epiphany.model-atlas.surface", + "transport": "cultmesh-command", + "authority": "epiphany.model-atlas.presentation", + "result": "surface-republished-or-denied", + "presentationOnly": true, + "domainStateEffects": "none" + } + ] +} diff --git a/web/fixtures/epiphany-model-atlas.provider-advertisement.json b/web/fixtures/epiphany-model-atlas.provider-advertisement.json new file mode 100644 index 0000000..16c2721 --- /dev/null +++ b/web/fixtures/epiphany-model-atlas.provider-advertisement.json @@ -0,0 +1,72 @@ +{ + "schema": "gamecult.eve.provider_advertisement.v1", + "providerId": "epiphany.model-atlas", + "serviceId": "epiphany.modeling", + "verseId": "gamecult.swarm", + "rootVerse": "gamecult", + "canonicalService": "gamecult.epiphany.modeling", + "locatedService": "gamecult.swarm.epiphany.modeling", + "cultMeshAddress": "cultmesh://gamecult.swarm/epiphany/modeling", + "title": "Epiphany Model Atlas", + "kind": "modeling.projection", + "updatedAtUtc": "2026-08-14T00:00:00Z", + "freshness": { + "state": "fixture", + "lastSeenAtUtc": "2026-08-14T00:00:00Z", + "maxAgeMs": 15000 + }, + "schemas": [ + "gamecult.model.entanglement_projection.v0", + "gamecult.eve.surface.v1", + "gamecult.eve.command.v1" + ], + "witnesses": [ + { + "kind": "fixture", + "ref": "web/fixtures/epiphany-model-atlas-surface.json", + "summary": "Eve conformance witness for an Epiphany-owned cross-repo Modeling projection. The fixture is presentation, not entanglement authority." + } + ], + "surfaces": [ + { + "schema": "gamecult.eve.surface.v1", + "surfaceId": "epiphany.model-atlas.surface", + "key": "epiphany:surface:model-atlas", + "transport": "cultmesh-record", + "status": "available", + "audience": "operator", + "mode": "interactive", + "surfaceKind": "model-atlas", + "interactionModel": "provider-presentation-commands", + "loweringTargets": [ + "gui", + "tui" + ], + "stateSchemas": [ + "gamecult.model.entanglement_projection.v0" + ], + "ownership": "epiphany-owns-entanglement-projection-eve-renders-one-retained-tree", + "summary": "Attention-first cross-repo Modeling view with drilldown and a secondary graph from one typed CultMesh projection." + } + ], + "commands": [ + { + "command": "epiphany.model-atlas.presentation.select", + "schema": "gamecult.eve.command.v1", + "transport": "cultmesh-command", + "authority": "epiphany.model-atlas.presentation", + "presentationOnly": true, + "domainStateEffects": "none", + "summary": "Selects an entanglement for presentation without changing the modeled relationship." + }, + { + "command": "epiphany.model-atlas.presentation.filter", + "schema": "gamecult.eve.command.v1", + "transport": "cultmesh-command", + "authority": "epiphany.model-atlas.presentation", + "presentationOnly": true, + "domainStateEffects": "none", + "summary": "Changes the attention view filter without changing the entanglement projection." + } + ] +} diff --git a/web/local-provider-catalog.json b/web/local-provider-catalog.json index d8c4ac6..e4695d4 100644 --- a/web/local-provider-catalog.json +++ b/web/local-provider-catalog.json @@ -2,6 +2,19 @@ "schema": "gamecult.eve.local_provider_catalog.v1", "purpose": "Browser reference fallback catalog for local provider advertisements and fixture surfaces. This is not live discovery truth.", "providers": [ + { + "providerId": "epiphany.model-atlas", + "title": "Epiphany Model Atlas", + "kind": "modeling.projection", + "advertisement": "./fixtures/epiphany-model-atlas.provider-advertisement.json", + "surfaces": [ + { + "transport": "local-json", + "surfaceId": "epiphany.model-atlas.surface", + "url": "./fixtures/epiphany-model-atlas-surface.json" + } + ] + }, { "providerId": "eve.world-smoke", "title": "Generic Interactive World Smoke", diff --git a/web/model-atlas-conformance.test.mjs b/web/model-atlas-conformance.test.mjs new file mode 100644 index 0000000..1ccc4a1 --- /dev/null +++ b/web/model-atlas-conformance.test.mjs @@ -0,0 +1,94 @@ +import assert from "node:assert/strict"; +import { readFileSync } from "node:fs"; +import test from "node:test"; +import { findProviderCatalogEntry, mergeProviderAdvertisement } from "./provider-advertisements.mjs"; + +const projectionSchema = "gamecult.model.entanglement_projection.v0"; +const catalog = readJson("./local-provider-catalog.json"); +const advertisement = readJson("./fixtures/epiphany-model-atlas.provider-advertisement.json"); +const surface = readJson("./fixtures/epiphany-model-atlas-surface.json"); +const conformance = readJson("./fixtures/epiphany-model-atlas-surface.conformance.json"); + +function readJson(relativeUrl) { + return JSON.parse(readFileSync(new URL(relativeUrl, import.meta.url), "utf8")); +} + +function* walk(component) { + yield component; + for (const child of component.children || []) yield* walk(child); +} + +function component(id) { + return [...walk(surface.surface.root)].find(candidate => candidate.id === id); +} + +test("advertises one canonical Model Atlas surface to GUI and TUI consumers", () => { + const catalogProvider = findProviderCatalogEntry(catalog, "epiphany.model-atlas"); + const merged = mergeProviderAdvertisement(catalogProvider, advertisement); + + assert.equal(merged.surfaces.length, 1); + assert.equal(merged.surfaces[0].surfaceId, surface.surface.id); + assert.equal(merged.surfaces[0].url, "./fixtures/epiphany-model-atlas-surface.json"); + assert.deepEqual(merged.surfaces[0].loweringTargets, ["gui", "tui"]); + assert.deepEqual(conformance.loweringTargets, ["gui", "tui"]); + assert.equal(surface.surface.root.kind, "surface"); + assert.equal("nodes" in surface, false); + assert.equal("selectedNodeId" in surface, false); +}); + +test("keeps attention primary while retaining drilldown and a secondary graph", () => { + const root = surface.surface.root; + assert.equal(root.props.defaultView, "attention"); + assert.deepEqual(root.props.viewOrder, ["attention", "drilldown", "graph"]); + assert.equal(root.children[0].id, "epiphany.model-atlas.attention"); + assert.equal(root.children[0].props.default, true); + + const attention = component("epiphany.model-atlas.attention.items"); + const drilldown = component("epiphany.model-atlas.drilldown"); + const graph = component("epiphany.model-atlas.graph"); + const linearGraph = component("epiphany.model-atlas.graph.linear"); + + assert.equal(attention.kind, "list"); + assert.equal(drilldown.kind, "panel"); + assert.equal(graph.kind, "graph"); + assert.equal(graph.props.role, "secondary"); + assert.equal(graph.props.presentationFallback, "children"); + assert.equal(linearGraph.kind, "list"); + assert.equal(linearGraph.props.role, "linear-fallback"); + assert.ok(graph.children.includes(linearGraph)); +}); + +test("binds every modeled value to the Epiphany-owned CultMesh projection", () => { + const bindings = [...walk(surface.surface.root)].flatMap(candidate => candidate.stateBindings || []); + + assert.ok(bindings.length >= 9); + assert.ok(bindings.every(binding => binding.schemaId === projectionSchema)); + assert.ok(bindings.every(binding => binding.sourceId.startsWith("cultmesh://gamecult.swarm/epiphany/modeling/entanglements#"))); + assert.ok(bindings.every(binding => binding.routeKind === "network")); + assert.deepEqual( + component("epiphany.model-atlas.graph").stateBindings.map(binding => binding.targetProp), + ["nodes", "edges"], + ); + assert.equal( + component("epiphany.model-atlas.graph.linear").stateBindings[0].schemaId, + component("epiphany.model-atlas.graph").stateBindings[1].schemaId, + ); +}); + +test("limits select and filter operations to provider-owned presentation state", () => { + const advertisedCommands = new Map(advertisement.commands.map(command => [command.command, command])); + const surfaceCommands = new Map(surface.commands.map(command => [command.command, command])); + const controlCommands = [...walk(surface.surface.root)] + .map(candidate => candidate.props?.command) + .filter(Boolean); + + assert.deepEqual([...surfaceCommands.keys()].sort(), [...advertisedCommands.keys()].sort()); + assert.deepEqual(controlCommands.sort(), [...surfaceCommands.keys()].sort()); + for (const command of [...advertisement.commands, ...surface.commands]) { + assert.equal(command.presentationOnly, true); + assert.equal(command.domainStateEffects, "none"); + assert.equal(command.transport, "cultmesh-command"); + assert.equal(command.authority, "epiphany.model-atlas.presentation"); + assert.notEqual(command.payloadSchema, projectionSchema); + } +});