diff --git a/docs/eve-migration-roadmap.md b/docs/eve-migration-roadmap.md index 8f92113..bb6c3b5 100644 --- a/docs/eve-migration-roadmap.md +++ b/docs/eve-migration-roadmap.md @@ -1,1458 +1,143 @@ # Eve Migration Roadmap -Date: 2026-07-08 - -The great Eve migration is the move from a useful pile of renderer proofs, -fixtures, provider demos, and platform experiments into a kernel-shaped system: -Eve core owns contracts, plugins own semantics, runtime repos own native -projection, and provider repos own live truth. - -This is not a repo cleanup campaign. It is an authority migration. The split is -complete only when the old in-repo convenience paths can no longer decide -provider truth, plugin semantics, or runtime behavior by accident. - ## Objective -Make Eve the stable interactive language for CultMesh surfaces without turning -`E:\Projects\Eve` into a permanent warehouse. - -The target shape: - -- Eve core: surface contracts, CultUI DSL, command/binding descriptors, - provider/plugin advertisement specs, browser reference, and conformance - harness incubation. -- EvePlugins: first-party reusable semantic plugins while young. -- Owner repos: Sai owns VN/Ink, Norn owns graph semantics, providers such as - Aetheria own live state and authored product surfaces. -- Runtime repos: EveUnity, EveFlutter, EveAndroid, EveIOS, EveGodot, EveElectron, - Fensalir/Direct2D, and future bodies own projection, build glue, plugin - projection adapters, capture, and runtime tests. -- EveConformance: fixture corpus, parity runner, capability matrix, - screenshot/report artifacts, plugin packs, and CI once the harness is stable - enough to leave Eve core. - -## Current Authority Map - -Owner: - -- Eve owns `gamecult.eve.surface.v1`, `gamecult.eve.command.v1`, CultUI, - provider/plugin advertisement contracts, plugin ABI shape, browser reference - behavior, and parity/conformance policy. - -Inputs: - -- Local fixtures under `web/fixtures`. -- Incubating plugin manifests under `plugins/incubating`. -- Runtime claims in `tools/parity/parity-manifest.json`. -- Provider advertisements such as Repixelizer's local fixture. -- Hermodr/Odin/CultMesh live provider surfaces when available. - -Outputs: - -- Browser reference lowering. -- Runtime capability reports. -- Plugin capability gaps. -- Parity reports under `artifacts/parity`. -- Split targets and graduation triggers in the parity manifest. - -Derived state: - -- Local provider lists are fixture advertisements, not discovery truth. -- Runtime capability status is diagnostic, not provider authority. -- Screenshot/golden artifacts are evidence, not portable state. -- In-repo runtime/plugin bodies are incubation, not permanent ownership. - -Forbidden writers: - -- Runtime lowerers must not import provider internals to recover missing - semantics. -- Plugins must not mutate provider truth or accept provider commands. -- Product demos must not become Eve core fixtures once they carry product state, - assets, receipts, or deployment assumptions. -- Compatibility HTTP routes and browser tabs must not replace provider - advertisements. -- Passing parity tests must not preserve a body that cannot name its owner, - inputs, outputs, and split trigger. - -Shared paths: - -- Every runtime claim flows through `tools/parity/parity-manifest.json`. -- Every fixture belongs to a conformance pack: `core`, `plugin`, `provider`, or - `runtime`. -- Every plugin claim names owner repo, split target, capabilities, required - fixtures, and graduation trigger. -- Every runtime claim names owner repo, repo role, split target, supported - features, plugin support, and graduation trigger. - -Deletion line: - -- Cut hardcoded provider tabs before adding new live providers. -- Cut renderer-specific product knowledge before claiming generic Eve support. -- Cut in-repo runtime/plugin bodies once their contract and release lifecycle - stop changing. - -## Phase 0: Ledger And Contract Grounding - -Status: underway. - -Purpose: make ownership visible before moving code. - -Work: - -- Keep `docs/repo-strategy.md` as the repo boundary doctrine. -- Keep this roadmap as the migration sequence. -- Keep `tools/parity/parity-manifest.json` as the machine-readable ownership - ledger. -- Keep `docs/surface-contract-v1.md`, - `docs/provider-advertisement-contract.md`, and - `docs/plugin-architecture.md` as the contract source of truth. -- Keep browser reference and parity harness green after every migration cut. - -Exit criteria: - -- All fixtures declare a conformance pack. -- All incubating plugins declare owner, split target, capabilities, and required - fixtures. -- All runtimes declare owner, repo role, supported features, plugin claims, and - graduation trigger. -- Harness reports missing ownership metadata and plugin gaps explicitly. - -Current proof: - -- `node --test web\eve-dsl.test.mjs` -- `powershell -ExecutionPolicy Bypass -File .\scripts\run-parity-harness.ps1` - -## Phase 1: Stabilize Core Surface Contract - -Purpose: make the surface contract boring enough that runtimes and providers can -leave the repo without dragging hidden behavior with them. - -Work: - -- Normalize `surface.root`, `surface.styles.tokens`, `stateBindings`, - `embeddedDocuments`, command templates, and operation invocation descriptors. -- Keep compatibility fields such as `nodes`, `selectedNodeId`, raw `command`, - and string payloads as projections only. -- Add explicit authority/staleness witness UI in the browser reference for - stale, missing, predicted, denied, accepted, and reconciled values. -- Add provider advertisement fixture checks for local and live discovery paths. - First cut is in place for Repixelizer and Aetheria provider advertisements. -- Add plugin advertisement fixture checks before adding more plugin semantics. - -Exit criteria: - -- Browser reference can open all local fixtures through advertisement-shaped - provider entries. -- New renderer APIs construct commands from CultMesh operation descriptors, not - raw strings. -- New binding APIs carry CultMesh state binding descriptors, not renderer-local - stores. -- Parity harness fails if embedded slots, required bindings, command descriptors, - or provider advertisement fields disappear. - -## Phase 2: Split Conformance Shape From Runtime Bodies - -Purpose: make the evidence path portable before runtime repos leave. - -Work: - -- Promote `tools/parity` from a local script into a conformance pack layout: - fixture metadata, expected capability matrix, runtime adapters, plugin packs, - and report schema. -- Separate core fixtures from plugin/provider/runtime fixtures. -- Add screenshot comparison metrics for structure, token colors, bounding boxes, - text presence, and capability-gap overlays without pretending byte-identical - pixels are the goal. -- Keep generated reports under `artifacts/parity` as evidence only. - -Exit criteria: - -- A runtime repo can run conformance against Eve fixtures without importing Eve - source internals. -- Plugin conformance packs can be added without editing core harness logic. -- `EveConformance` has a clean split plan, or stays incubating only because the - report schema is still changing. - -## Phase 3: Graduate Plugins - -Purpose: move semantic ownership out of Eve core. - -Work: - -- Consume Sai, Norn, and TeX manifests, advertisements, ABI fixtures, and - executable witnesses from their owner repos. Eve keeps only the ABI schemas, - provider composition fixtures, and capability-gap policy. -- Define `gamecult.eve.plugin.v1` and - `gamecult.eve.plugin_advertisement.v1` as explicit schemas. First-cut JSON - schemas live under `schemas/` and the parity harness validates incubating - plugin manifests and advertisements against them. -- Add plugin ABI fixtures for `describe`, `validate`, `project`, `apply`, - `lower`, and `measure` where relevant. -- Move `sai.vn` to Sai when VN/Ink story state, visual manifest, story commands, - and projection fixtures are stable. -- Move `norn.graph` to Norn when graph layout, interaction, fallback, and - conformance fixtures are stable. -- Keep `tex.math` in EvePlugins. Its retained sidecar owns KaTeX parsing, - typeset render results, cache behavior, and source fallback while providers - retain source-state authority. -- Treat nested plugin placement as composition, not custody. A Sai surface may - deploy with nested Norn or TeX surfaces when those independent plugin - sidecars are available, but Sai does not depend on them for VN authority and - does not own their graph or math semantics. Norn and TeX keep their own ABI - manifests, capability claims, and conformance packs. - -Exit criteria: - -- Eve core knows plugin manifests and capability gaps, not plugin internals. -- Providers invoke plugins through the ABI object model. -- Runtimes declare plugin support through capability manifests. -- Unsupported required plugin capabilities appear as visible gaps. -- Optional nested plugin gaps degrade the nested surface; they do not transfer - graph, math, or renderer semantics into the parent plugin. -- A provider may mark a nested plugin slot as optional or required for a - specific surface, but that availability policy is provider composition, not - Sai ownership of Norn or TeX. - -## Phase 4: Graduate Runtime Bodies - -Purpose: let runtime repos own native projection without losing conformance. - -Work: - -- EveFlutter: provider picker, command transport, plugin projection adapters, - Android/iOS/desktop build flow, native capture, and parity reports. -- EveUnity: UPM package release, plugin projection adapter behavior, - resolver-backed embedded documents, Unity test/capture lifecycle, and Aetheria - consumption. -- EveAndroid: Kotlin device edge, generic surface traversal, sensor publishing, - Android-specific CI, and explicit discovery input. -- EveIOS: UIKit renderer, sensor edge release flow, fixed-device and simulator - capture, and generic surface lowering. -- Fensalir/Direct2D: Eve-to-`AquariumUiDocument` adapter, DirectWrite/Direct2D - token lowering, embedded slot preservation, and frame capture. -- EveWeb: stays in Eve while it is the behavior oracle; splits only if it becomes - an independently deployed runtime product. - -Exit criteria: - -- Runtime can build, test, capture, and report conformance without sharing Eve's - dirty worktree. -- Runtime consumes surface documents, plugin manifests, and provider - advertisements through contracts. -- Runtime has no provider-specific renderer brain unless it is explicitly a - provider-owned app. - -## Phase 5: Move Product Fixtures To Provider Repos - -Purpose: prevent demos from becoming quiet product authority inside Eve. - -Work: - -- Repixelizer owns job/session/artifact/operator fixtures once they carry real - product state or receipts. -- Aetheria owns RTS/ARPG world surfaces, daemon-published asset manifest - records, generated level state, command receipts, and scenario conformance. -- Huginn owns `.cc` and Persona-state inspection fixture packs when they depend - on runtime stewardship details. -- Mimir owns sensor/media/control surfaces that depend on live stream timing or - room state. - -Exit criteria: - -- Provider repo publishes provider advertisements and fixture/conformance packs. -- Eve core keeps only minimal generic fixtures and references provider packs by - contract. -- Product assets and receipts are not stored as Eve core truth. - -## Phase 6: Cut Compatibility Paths - -Purpose: make old routes unable to reassert authority. - -Work: - -- Remove hardcoded browser provider tabs once live/local advertisements cover - the same flows. -- Remove direct Mimir deck probing from generic Eve paths. -- Remove renderer-private style shortcuts once token lowering is complete. -- Remove raw command construction APIs from new public runtime paths. -- Remove provider-specific renderer fallbacks from generic lowerers. - -Exit criteria: - -- The only normal path is provider advertisement -> surface document -> runtime - lowering -> command intent -> provider receipt -> republished state. -- Compatibility routes exist only as named lowerings or debug probes. -- Parity and runtime diagnostics report the same layer of reality the user sees. - -## Active Work Queue - -1. Split `EveConformance` when owner runtimes can consume its schema catalog, - packs, witness attachments, and runner without Eve source-layout knowledge. - -Recently cut: - -- Aetheria now owns its Eve provider conformance pack: advertisement, daemon - game/editor surfaces, fixture metadata, command replay scenario, and Unity - asset manifest. Eve's fixture/provider ledger resolves that pack through an - owner `sourceRoot`; browser reference keeps only the generic world-smoke - oracle. The duplicated Aetheria fixtures, local browser entry, provider - handoff, and handoff smoke are deleted from Eve. - -- `GameCult/EveFlutter` now owns the Flutter app, Windows/Linux/Android/iOS - platform glue, provider picker, command-intent lowering, tests, golden - captures, and capture runners. Its owner tests use generic provider documents - and pass across phone, tablet, and desktop. The runtime no longer hardcodes - Repixelizer asset filenames or reads Aetheria/Eve fixture paths. Eve prepares - contract fixtures, invokes the owner smoke, and reads the owner lifecycle; - the in-repo Flutter body and platform scripts are deleted. - -- `GameCult/EvePlugins` now owns `tex.math`. The owner sidecar uses KaTeX for - parsing and typesetting and implements `describe`, `validate`, `project`, - `lower`, `measure`, and `apply` over one retained `stdio-ndjson` process. - Its conformance witness proves render-result caching does not mutate provider - source state. Eve consumes the owner manifest, advertisement, ABI fixture, - executable, and witness; the incubating copies and handoff machinery are - deleted. - -- EveUnity now owns `org.gamecult.eve.surface`, - `org.gamecult.eve.unity-uitoolkit`, and `org.gamecult.eve.unity-scene` along - with their package build, test, and capture-request entry points. Aetheria - consumes all three directly from EveUnity. Eve's duplicate package bodies, - split handoff, release proxies, and semantic JSON capture proxy are deleted; - native UI Toolkit PNG capture remains an explicit owner-repo lifecycle gap. - -- EveElectron now produces native offscreen-painted 1280x800 PNG captures for - both `eve.world-smoke.surface` and `aetheria.daemon.game`. The owner runner - emits generic runtime witnesses with dimensions, byte counts, SHA-256 hashes, - provider/surface assertions, and runtime-owned authority. Eve's parity - harness regenerates the captures, attaches both witnesses to conformance, - and downstream consumers require them. The generic runtime-witness contract - no longer requires Unity-specific movement, targeting, action, or receipt - fields; those remain valid profile assertions when a gameplay witness has - them. EveElectron is now reported as graduated rather than "ready to split." - -- `GameCult/EveElectron` now owns the generic Electron runtime package. Its - shell lowers provider-advertised surfaces and command intents; its window - host owns secure `BrowserWindow` construction and controls; its preload - helper exposes the narrow `window.eveProvider` transport. Aetheria consumes - those package APIs while retaining daemon launch, CultMesh decoding, asset - protocol, authored renderer assets, and product smoke assertions. The live - Stage 7C Electron daemon witness passes through that path. Eve parity reads - the owner capability manifest directly, and the incubating runtime body, - split handoff, release tools, capture tools, and duplicate lifecycle smokes - have been deleted from Eve. Owner-produced PNG attachment is the remaining - Electron deliverable. - -- Sai now owns a live runtime-independent Ink playback sidecar. One retained - `stdio-ndjson` process opens a compiled story, applies a branch choice, - projects the resulting `gamecult.eve.surface.v1`, and jumps to another knot. - Its owner pack declares Norn and TeX only as optional nested plugins; the Sai - process imports neither implementation. Eve parity consumes Sai's manifest, - advertisement, ABI fixture, and executable from `../Sai`, and the obsolete - incubating copies and handoff document have been deleted from Eve. The Sai - and Norn witnesses both survive conformance export and consumer validation. - -- CultLib now owns `CultMeshSnapshotSession`, a retained endpoint connection - for ordered bulk snapshot transfer. RUDP ACK packets are sequence-neutral, - fragmented data is acknowledged as it arrives, and sequenced control packets - release ordered frames they unblock. This removes the unreliable-control - gaps and per-chunk reconnect churn that previously made CDN transfer stall. -- EveUnity uses the retained session only for CultMesh CDN manifests/chunks; - live surface and receipt snapshots retain their scoped authority. It batches - two 24 KiB chunks per request, verifies the completed bundle, and caches it - by SHA-256. A cold-cache Aetheria witness transferred 384 chunks and passed - in 165 seconds; the corresponding warm-cache witness passed in under a - minute including CultLib rebuild and Unity startup. -- The generic Unity witness now proves movement, target acquisition, and fire - action through the advertised world commands and authoritative receipts. - Aetheria's daemon-owned `ProjectileWeapon` state is now the fire-capability - authority for group zero, and player firing consumes the submitted daemon - intent instead of relying on legacy loadout groups. Runtime camera framing - uses aggregate authored-renderer bounds; the resulting frame is attached at - `E:\Projects\EveUnity\artifacts\aetheria-daemon\aetheria-daemon-world.png`. -- Eve's browser lowerer now owns `EveBrowserProviderHost`: provider - advertisement selection, surface polling, lowering, command submission, and - command-triggered refresh. Aetheria Electron publishes its provider - advertisement over preload IPC and supplies a narrow CultMesh adapter. Its - renderer no longer selects `eve:surface:aetheria.daemon.game` or owns the - refresh loop; the product launcher configures the desired advertised surface. - Eve parity and generated-binding checks pass. The Stage 7C Electron live - smoke discovers the provider advertisement, verifies `aetheria.game`, lowers - the provider surface into the renderer, reads field documents and daemon - authority/session state, and receives an accepted Eve command receipt. -- TypeScript and C# RUDP now share one sequence invariant: ACK packets are - sequence-neutral receipt metadata, every data fragment is acknowledged, and - sequenced control can release ordered frames it unblocks. The previous TS ACK - allocator consumed a sequence number that C# correctly ignored, leaving the - next ordered snapshot request buffered forever. CultNet TS unit tests, the - CultMesh TS suite, Aetheria's local Stage 7C verifier, and its full Electron - verifier all pass with the repaired protocol. -- The browser lowerer now gives `world.scene3d` / `world.entity3d` a generic - tactical projection. It normalizes provider-authored X/Z poses into a stable - viewport, distinguishes controlled and faction markers, and maps target, - movement, focus, and action input into provider-routed Eve command intents. - It does not advance poses or simulate combat locally. Aetheria's Electron - witness requires the Eve-owned world scene, provider entity markers, and one - controlled marker in the rendered DOM, so surrounding HUD text cannot stand - in for interactive-world lowering proof. -- Eve now defines `gamecult.eve.runtime_witness.v1`, and EveUnity's live - Aetheria runner emits owner-repo witnesses with cache mode, wall/test timing, - provider advertisement and asset assertions, movement distance, initial and - final surface versions, terminal movement/target/action receipts, screenshot - dimensions/hash/size, and hashed test results. The conformance attachment - tool validates the schema, copies declared artifacts into export-relative - paths, and indexes the witness without retaining EveUnity source paths. - Export consumption passed for both `eveunity.aetheria.game.warm` (68.27 s - wall, 7.72 s test) and `eveunity.aetheria.game.cold` (138.90 s wall, - 111.52 s test), including all three reconciled command receipts. -- Embedded document resolution is now provider-neutral in the Aetheria - renderer. The browser forwards only document/schema/slot identity plus - viewport presentation context through `eveDocument`; Aetheria's main-process - transport adapter owns its gravity, render-splat, object, and nested-surface - query routing. Static verification rejects those Aetheria schema ids in the - renderer bundle, while the live Electron witness still proves the resolved - field surface. Eve core documents this as the host/plugin decoding boundary, - not as an Aetheria-specific exception. -- Norn now has live runtime-independent sidecar proof, not only projection - declarations. The owner-repo `norn-eve-plugin` stays alive across correlated - `describe`, `validate`, `project`, and solver-backed `measure` requests over - advertised `stdio-ndjson`; all four operations satisfy Norn's ABI fixture. - Eve's generic sidecar witness launcher validates advertisement/fixture - agreement and response correlation, emits `gamecult.eve.plugin_witness.v1`, - and the conformance attachment path exports that witness without Norn source - paths. The first live run also removed a UTF-8 BOM framing fault at Norn's - NDJSON boundary. This proves Norn independently; it does not count as Sai - playback proof. - -- The clean EveUnity PlayMode client now consumes Aetheria's independently - running daemon without importing Aetheria code. Aetheria builds its authored - prefabs into a provider-owned Unity AssetBundle, advertises an Eve core asset - catalog, and serves the immutable bundle through standard CultMesh CDN - manifest/chunk documents. EveUnity selects the platform variant, verifies - size and SHA-256, caches it by content hash, loads the prefab hierarchy, owns - entity pose on a wrapper root, submits movement, observes the authoritative - receipt and later surface version, and captures - `E:\Projects\EveUnity\artifacts\aetheria-daemon\aetheria-daemon-world.png`. - Cold- and warm-cache witnesses pass through the retained CDN session. The - current capture framing is diagnostic rather than a presentation-quality - gameplay camera. - -- Eve core now publishes typed CultCache wire documents for provider - advertisement, command invocation, and provider receipt. Command descriptors - remain `gamecult.eve.command.v1`; executable intents are the distinct - `gamecult.eve.command_invocation.v1` contract. Receipts carry the provider's - source version, and EveUnity withholds terminal receipts until that version - is actually lowered. -- EveUnity now owns a minimal clean `TestProject` that installs CultLib, Eve - surface contracts, and the EveUnity scene package without Aetheria code. Its - generic `eve.world-smoke` PlayMode test lowers a world, routes a command - intent, and emits a real 640x360 PNG. Conformance records this as a valid - control artifact while keeping the Aetheria daemon product capture blocked. -- CultLib now owns generation of `org.gamecult.cultlib`, the explicit managed - assembly closure consumed by clean Unity clients. Eve declares CultLib as a - surface-package dependency; provider repos no longer need to vendor Brokkr's - private DLL layout to compile the generic client. -- Eve no longer contains `runtimes/incubating/eve-unity-scene`. Runtime - capability, source-symbol, lifecycle, and package evidence now resolve from - `../EveUnity` through the parity runtime's `sourceRoot`. Eve-local scene - release, capture, lifecycle, split-handoff, and EditMode runner scripts were - deleted. `unity-scene` is active in parity; its real frame capture remains a - visible lifecycle gap rather than a JSON projection standing in for pixels. -- EveUnity now owns typed release and capture request builders alongside its - UPM pack and consumer-test scripts. The release builder describes a package - request; the capture builder consumes a provider advertisement and describes - the requested frame. Neither claims a release tag or PNG exists before the - corresponding owner-repo artifact is produced. -- `GameCult/EveUnity` now exists as the runtime owner repo. It owns - `packages/org.gamecult.eve.unity-scene`, the generic GameObject playable-world - host, camera/input components, plugin projection shells, package tests, UPM - packing, and its authority map. Commit `9341d44` established the repo and - commit `862d2bd` made its Aetheria consumer runner resolve the package from - the owner path. -- Aetheria now consumes - `file:../EveUnity/packages/org.gamecult.eve.unity-scene` rather than Eve's - incubating runtime directory. Its provider-owned adapter remains under - `org.gamecult.aetheria.eve-runtime`; the generic lowerer no longer arrives - from the provider repo or Eve core. A consumer run through the owner path - passed all 21 `GameCult.Eve.UnityScene.Tests` tests, with results under - `E:\Projects\EveUnity\artifacts\aetheria-consumer\20260710T022930`. -- The web reference now has a schema-backed layout probe for the - `embedded-surface` and `sai-vn` fixtures. - `scripts/run-web-reference-layout-probe-smoke.ps1` builds the browser lowerer, - runs Chrome headless through - `tools/web-reference/run-layout-probe.mjs`, writes - `gamecult.eve.web_layout_probe.v1` evidence under - `artifacts/web-reference-layout-probe/latest/`, and parity consumes those - artifacts so `web:embedded-surface:bounding-boxes` and - `web:sai-vn:bounding-boxes` are measured runtime evidence instead of - `pending-runtime-probe`. This proves the browser oracle can measure both core - embedded slots and a Sai surface that optionally composes independent Norn - and TeX plugin surfaces without taking custody of their semantics. -- The conformance export now carries `screenshotComparisonMetrics[]` records. - Runtime owners get structure, color-token, bounding-box, and text-presence - metrics per runtime/fixture pair without relying on byte-identical screenshots - or Eve-local prose. Structure and token metrics are surface-contract evidence; - text presence is authored text evidence; bounding boxes stay - `pending-runtime-probe` or `pending-capture` until a runtime-owned capture or - layout probe attaches real visual evidence. -- Runtime capability manifests now declare world-surface lowering targets. - `web/eve-runtime-capability.json` claims `web-reference`, and - `packages/org.gamecult.eve.unity-uitoolkit/eve-runtime-capability.json` - claims `unity-uitoolkit`, each with supported world surface kinds, - projection kinds, support level, evidence paths, and the rule that runtimes - lower provider-owned world state without owning it. Parity compares those - claims against the ledger, exports them through runtime records and - `packs/runtime.json`, the runtime-owner smoke asserts them, and the generic - conformance smoke now treats `unity-scene`, `electron-shell`, and `tui` as - narrow runtime-owned command-surface claims while capture and release remain - split blockers. -- Missing provider-advertised world-surface lowering targets are still assigned - to their pending runtime owner when one exists. Eve only owns a world-lowering - gap when no runtime target has been declared. -- The conformance export now carries `worldSurfaceLoweringGaps[]` as typed - data in addition to the generic `capabilityGaps[]` ledger. Runtime owners can - consume provider id, surface id, target id, owner repo, runtime id, split - target, and runtime status without scraping prose or gap strings. -- The conformance export also carries `worldSurfaceLoweringCoverage[]`, a full - provider-surface-to-target ledger that marks each advertised lowering target - as `claimed`, `missing-claim`, or `missing-runtime`. The generic conformance - consumer now asserts Aetheria's claimed web, UI Toolkit, Unity scene graph, - Electron shell, and TUI terminal-grid targets from the same typed surface. -- The conformance export carries `commandBoundaryCoverage[]`, joining each - provider-owned interactive world surface target to its runtime owner, - provider command boundary, receipt schema, and runtime command envelope. Web, - Unity UI Toolkit, Unity scene, Electron shell, and TUI are `covered` because - they advertise `gamecult.eve.command.v1`. -- The browser reference lowerer now carries the active provider surface's - advertised `worldInteraction.commandBoundary` and - `worldInteraction.receiptSchema` into each `gamecult.eve.command.v1` intent. - It can use component action metadata as a fallback, but the provider - advertisement is the authority when present. -- The browser lowerer command intent path now passes the active surface id - explicitly and tests that a provider with multiple world surfaces uses the - active surface advertisement instead of the provider's first advertised - surface. This keeps Aetheria game/editor command boundaries from collapsing - inside the generic web reference. -- Unity scene now has a provider-shell contract skeleton under - `runtimes/incubating/eve-unity-scene`. The skeleton consumes an advertised - `worldInteraction` boundary, creates `gamecult.eve.command.v1` requests with - command boundary and receipt schema fields, builds a provider-agnostic Unity - scene graph DTO from the surface tree, and claims only the `unity-scene` - graph command-surface target. It still does not own provider simulation, - plugin projection adapters, release, or capture evidence. -- EveElectron now has a provider-shell contract skeleton under - `runtimes/incubating/eve-electron`. The shell selects advertised provider - surfaces, emits `gamecult.eve.command.v1` intents with provider command - boundary and receipt schema fields, and has a runnable Node smoke without - claiming embedded document rendering, packaged Electron lifecycle, or capture. -- EveTui now has a provider-shell contract skeleton under - `runtimes/incubating/eve-tui`. The shell selects advertised provider - surfaces, emits `gamecult.eve.command.v1` intents with provider command - boundary and receipt schema fields, lowers Eve surface trees into an - explicitly lossy `gamecult.eve.tui_grid.v1` terminal grid, and claims the - `tui` command-surface target without owning provider truth or transcript - capture. -- The conformance export carries `splitHandoffMoveCoverage[]`, joining runtime - split handoff move sets to source path status. EveElectron now exposes the - existing Aetheria `Aetheria.Rts.Web/Electron` and command transport paths as - `observed-provider` sources that must be replaced by a provider-agnostic - shell; Unity scene now exposes current incubation paths for its generic scene - graph body, world-surface lowering claim, and command transport, while scene - capture remains replacement-required. -- The conformance export schema and consumer smoke now require minimum typed - item shapes for plugins, providers, runtimes, and split targets. Owner repos - can rely on exported ids, owners, statuses, split targets, manifest or - advertisement paths, feature/plugin arrays, and blocker/proof arrays instead - of treating those sections as untyped bags. -- Plugin ABI fixture operations are now exported as - `plugins[].abiOperationContracts`. Sai and Norn can consume operation-level - `input` and `expect` contracts for `describe`, `validate`, `project`, - `lower`, `measure`, and `apply` without reading Eve-local fixture files. -- Plugin ABI operation coverage is now exported as root - `pluginAbiOperationCoverage[]` records. The plugin-owner smoke asserts Sai - Norn, and TeX `describe`, `lower`, and `apply` operations as `contracted`, so - plugin owners can consume the coverage ledger without walking Eve's nested - plugin records. -- Plugin handoff move/source coverage is now exported as root - `pluginHandoffMoveCoverage[]` records. The plugin-owner smoke asserts Sai, - Norn, and TeX manifest and ABI-conformance move sets as current existing - sources, so owner repos can see what must leave Eve incubation without - reading Eve's parity manifest or plugin handoff files directly. -- Provider/plugin requirement coverage is now exported as root - `providerPluginRequirementCoverage[]` records with availability. The - plugin-owner smoke asserts `sai.vn` as the required VN plugin and - `norn.graph` / `tex.math` as `optional-satisfied` nested plugins owned by - Norn and EvePlugins. A Sai surface can embed Norn or TeX when those plugins - are available, but Sai does not own their graph or math semantics. -- Runtime/plugin projection claims are now exported as root - `runtimePluginProjectionCoverage[]` records. Runtime-owner smokes assert - Unity UI Toolkit support for `sai.vn`, `norn.graph`, and `tex.math`, Unity - scene support for the same three plugin capabilities, and Electron shell - and TUI support for the same sidecar plugin projection shells without - importing plugin semantics. -- Provider/runtime/plugin projection coverage is now exported as - `providerRuntimePluginProjectionCoverage[]`. The runtime-owner smoke proves - the Sai VN provider surface lowers fully in web, lowers in Unity UI Toolkit - and Android Flutter with optional independent Norn and TeX nested plugin - projection when those plugins are available, and does not let UIKit's generic - embedded-document path impersonate required Sai VN, optional Norn graph, or - optional TeX math projection. -- Runtime/plugin projection gaps are now exported as - `runtimePluginProjectionGaps[]`. Device-edge runtimes no longer consume the - Sai fixture as failed projection clients; missing Sai, Norn, or TeX support is - recorded only on runtimes that actually claim plugin projection work, so plugin - semantics do not drift into Eve core or Aetheria product code. -- Split target blockers are now exported as `splitTargetBlockers[]` and as - per-target `blockerRecords[]` with blocker kind, subject, owner, and source - text. The split-target consumer smoke asserts Unity scene status/plugin - blockers, Electron feature blockers, and TUI command-transport blockers - without scraping the human `blockers[]` strings. -- EveUnity now has a pending Unity scene handoff at - `runtimes/incubating/eve-unity-scene/eveunity-scene-split-handoff.json`. - The active `unity-uitoolkit` runtime remains a semantic UI surface lowerer; - it is not treated as full scene/world projection. The pending `unity-scene` - runtime now names current scene graph and command-transport source paths, - while release, capture, non-Aetheria provider proof, and runtime graduation - remain EveUnity blockers. -- Unity scene now has a pending runtime capability manifest at - `runtimes/incubating/eve-unity-scene/eve-runtime-capability.json` and a - lifecycle smoke at `scripts/run-eveunity-scene-lifecycle-smoke.ps1`. The - manifest claims a provider-agnostic scene graph command surface for - `unity-scene`, types the package release, provider-advertisement scene smoke, - command boundary, and scene capture contracts. The release contract now uses - the Unity scene package manifest as version source and reuses the shared - EveUnity release request builder through - `scripts/run-eveunity-scene-release-contract-smoke.ps1`. It also reuses the - shared EveUnity capture request builder through - `scripts/run-eveunity-scene-capture-contract-smoke.ps1`, while the actual - Unity scene PNG/frame artifact remains pending. The scene graph now preserves - embedded document slot identity for resolver-backed child surfaces without - owning the nested plugin semantics. Sai, Norn, and TeX scene projection - adapters now exist as runtime-owned shells; TeX parsing, typesetting, - baseline metrics, and render-cache semantics remain plugin-owned. -- Unity scene now has a direct split handoff smoke at - `scripts/run-eveunity-scene-split-handoff-smoke.ps1`. The smoke verifies the - scene runtime's current source paths for runtime body, scene graph lowering, - Sai/Norn/TeX plugin projection, and command transport, does not treat - Aetheria product paths, Sai story state, Norn implementation state, or TeX - implementation state as generic runtime source, names the required Eve - contracts, and keeps capture as an external proof until EveUnity owns it. -- Runtime split handoff documents now have a catalog-backed schema, - `gamecult.eve.runtime_split_handoff.v1`. EveConformance exports that schema - so runtime owner repos can consume split target, move set, contract input, - forbidden import, and external proof shape without trusting only ad hoc smoke - script checks. -- Plugin, provider, and conformance handoff documents are now schema-backed as - `gamecult.eve.plugin_handoff.v1`, `gamecult.eve.provider_handoff.v1`, and - `gamecult.eve.conformance_handoff.v1`. The parity runner validates the - documents, exports the schema catalog entries, and owner-consumer smokes now - demand those contracts explicitly, so Sai, Norn, EvePlugins, Aetheria, and - EveConformance can consume handoff shape without depending on Eve's local - smoke script folklore. -- Runtime lifecycle shape is now schema-backed as - `gamecult.eve.runtime_lifecycle.v1`. Flutter's standalone - `eveflutter-lifecycle.json` validates against that contract, embedded - release/test/capture lifecycle blocks in runtime capability manifests are - 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 - surface with Sai, Norn, and TeX sidecar plugin projection shells, but not - packaged rendering or captured window artifacts. The handoff draws the line - between the generic shell and Aetheria's Starbridge RTS product client, and - the split-target consumer smoke keeps release, packaged rendering, and - capture artifact proofs visible as blockers. -- EveElectron now has a direct split handoff smoke at - `scripts/run-eveelectron-split-handoff-smoke.ps1`. The smoke verifies the - current generic shell, command, and world-surface lowering paths, the observed - Aetheria Electron/client paths that must be replaced, the required Eve - contract inputs, and the pending external proofs without treating Aetheria's - product client as the generic Electron runtime. -- EveElectron now has a pending runtime capability manifest at - `runtimes/incubating/eve-electron/eve-runtime-capability.json` and a - lifecycle smoke at `scripts/run-eveelectron-lifecycle-smoke.ps1`. The - manifest declares provider advertisement consumption, command transport, and - surface-tree/plugin projection, claims `electron-shell` world/editor - lowering, and now carries executable release and capture request contracts. - Release uses - `tools/eveelectron/eveelectron-release-contract.mjs` and - `scripts/run-eveelectron-release-contract-smoke.ps1`; capture uses - `tools/eveelectron/eveelectron-capture-contract.mjs` and - `scripts/run-eveelectron-capture-contract-smoke.ps1`, plus a deterministic - `gamecult.eve.electron_shell_projection.v1` JSON projection artifact through - `tools/eveelectron/eveelectron-capture-artifact.mjs` and - `scripts/run-eveelectron-capture-smoke.ps1`. The packaged Electron app, - packaged window rendering, and actual Electron window PNG artifacts remain - pending EveElectron work. -- Electron pending lifecycle details and TUI active-incubation lifecycle details - now live in their runtime capability manifests rather than being duplicated in - `tools/parity/parity-manifest.json`. The parity ledger points at the - capability documents; the conformance export reads command/lifecycle contracts - from those documents for runtime targets. -- EveTui now has an active incubating runtime target and a split handoff at - `runtimes/incubating/eve-tui/evetui-split-handoff.json`. It claims the - advertised `tui` world-surface lowering target as a lossy terminal-grid - command surface and carries compact Sai, Norn, and TeX sidecar plugin - fallback shells. The handoff draws the line between the provider-agnostic - terminal/grid lowerer, plugin-owned semantics, and provider-owned TUI - surfaces, while the split-target consumer smoke keeps package release and - owner-repo capture production visible as blockers. -- EveTui now has a direct split handoff smoke at - `scripts/run-evetui-split-handoff-smoke.ps1`, an active runtime capability - manifest at `runtimes/incubating/eve-tui/eve-runtime-capability.json`, and a - lifecycle smoke at `scripts/run-evetui-lifecycle-smoke.ps1`. The manifest - declares provider advertisement consumption, command transport, terminal-grid - summary, terminal-grid lowering, sidecar plugin fallback projection, and the - `tui` world/editor lowering claim; it now carries an executable terminal - capture request contract through - `tools/evetui/evetui-capture-contract.mjs` and - `scripts/run-evetui-capture-contract-smoke.ps1`, plus a deterministic - `gamecult.eve.tui_grid.v1` JSON capture artifact through - `tools/evetui/evetui-capture-artifact.mjs` and - `scripts/run-evetui-capture-smoke.ps1`. Terminal package release and - owner-repo production of the capture path remain pending. -- EveTui release lifecycle now carries a structured pending release request - contract. The runtime capability manifest reads version truth from - `runtimes/incubating/eve-tui/package.json`, names the - `gamecult.eve.runtime_release_request.v1` request schema, and proves the - `evetui-v{version}` tag plus terminal runtime artifact path through - `tools/evetui/evetui-release-contract.mjs` and - `scripts/run-evetui-release-contract-smoke.ps1`. This proves the request - shape without pretending the EveTui package has been published. -- Aetheria interactive world surfaces now carry a structured provider-owned - `worldInteraction` contract. The provider advertisement names projection - kind, provider state schemas, daemon command boundary, receipt schema, - lowering targets, and ownership rules for `aetheria.daemon.game` and - `aetheria.daemon.editor`. The provider scenario requires the daemon game - world-surface contract, parity validates it against the advertisement, and the - conformance export carries both provider `surfaceContracts` and root - `interactiveWorldSurfaces[]` records. The Aetheria consumer smoke asserts the - advertised web, Unity UI Toolkit, and Unity scene lowering targets without - importing Aetheria or Eve internals. -- EveUnity test lifecycle now carries structured Unity EditMode runner - contracts. `packages/org.gamecult.eve.unity-uitoolkit/eve-runtime-capability.json` - and `runtimes/incubating/eve-unity-scene/eve-runtime-capability.json` name - runner scripts, the Aetheria consumer Unity project, package names, test - assemblies, platform, result artifacts, and temporary `testables` manifest - mutation. The shared runner restores Aetheria's `Packages/manifest.json` - after each run, while `scripts/run-aetheria-unity-scene-editmode-tests.ps1` - selects `org.gamecult.eve.unity-scene` and - `GameCult.Eve.UnityScene.Tests`. Lifecycle smokes validate the runner scripts - and Aetheria project, parity compares the contracts against the ledger, and - runtime-owner consumer smoke asserts exported test fields. UI Toolkit still - carries the CultLib-managed precompiled assembly dependency proof; Unity - Scene now carries a generic playable-world EditMode proof through the real - Aetheria consumer project. -- EveUnity release lifecycle now carries a structured UPM release contract. - `packages/org.gamecult.eve.unity-uitoolkit/eve-runtime-capability.json` - names the package root, version source, tag pattern, artifact kind, and - owner-side publish proof for `org.gamecult.eve.unity-uitoolkit`. - `tools/eveunity/eveunity-release-contract.mjs` now builds a - `gamecult.eve.runtime_release_request.v1` request from the runtime capability - manifest and UPM package manifest, deriving the `eveunity-uitoolkit-v{version}` - tag, package dependency set, required package dependency owners, and UPM - artifact path. `tools/eveunity/eveunity-release-artifact.mjs` consumes that - request and runs `npm pack` against the declared package root, producing the - declared UPM `.tgz` artifact as local smoke evidence. The lifecycle smoke - validates that contract against the package manifest, parity compares it - against the ledger, and the runtime-owner consumer smoke asserts the exported - release fields without pretending the tagged EveUnity release already exists. -- EveUnity capture lifecycle now carries a structured pending capture contract. - `captureContract` names the Unity UI Toolkit runtime, capture kind, PNG - artifact pattern, conformance attachment point, required Aetheria provider - surface, request schema, request builder, advertisement input, and authority - rule. `tools/eveunity/eveunity-capture-contract.mjs` now builds a - `gamecult.eve.runtime_capture_request.v1` request from the runtime capability - manifest and provider advertisement, and - `scripts/run-eveunity-capture-contract-smoke.ps1` proves that request path - without pretending a Unity PNG has been captured. The actual capture artifact - remains a split blocker for EveUnity. -- Unity UI Toolkit now also has schema-backed semantic projection evidence. - `tools/eveunity/eveunity-uitoolkit-capture-artifact.mjs` lowers - Aetheria's owner-pack world surface into - `gamecult.eve.unity_uitoolkit_projection.v1`, writes - `artifacts/eveunity-uitoolkit-capture/latest/unity-uitoolkit-projection.json`, - and exports it as `runtime.captureArtifacts[]`. The builder also proves Sai, - Norn, and TeX stay sidecar plugin projections when a visual-novel surface is - lowered through UI Toolkit. The remaining EveUnity capture blocker is an - owner-repo Unity editor or batchmode PNG, not absence of a typed projection - artifact. -- Unity scene capture now has the same semantic artifact boundary as Electron - without pretending it is a rendered Unity frame. `tools/eveunity/eveunity-scene-capture-artifact.mjs` - lowers Aetheria's owner-pack world surface into - `gamecult.eve.unity_scene_projection.v1`, writes - `artifacts/eveunity-scene-capture/latest/unity-scene-projection.json`, and - exports it as `runtime.captureArtifacts[]`. The builder also proves Sai, - Norn, and TeX remain sidecar plugin projections when a visual-novel surface - is lowered. The remaining EveUnity blocker is an owner-repo Unity screenshot - or frame-capture PNG, not absence of semantic scene projection evidence. -- Unity scene lowering now extracts a provider-authored playable ARPG world - model from the Aetheria daemon game surface without importing Aetheria runtime - types. The Aetheria fixture carries generic `world.scene3d`, - `world.entity3d`, asset-manifest, input-profile, camera, movement, focus, and - target command affordances; `EveUnitySceneSurfaceLowerer` exposes them as a - `PlayableWorld` projection, and the JSON capture artifact exports the same - `playableWorld` shape under `gamecult.eve.unity_scene_projection.v1`. - `EveUnitySceneClientSession` now consumes provider surface snapshots and emits - movement/focus/target/action intents through the advertised command boundary, - while `EveUnitySceneProviderConnection` defines the generic surface source - and command sink ports that a CultMesh/CultNet adapter must implement. - `IEveUnitySceneProviderSurfaceDocumentSource`, - `EveUnitySceneProviderSurfaceDocument`, and - `EveUnitySceneProviderSurfaceDocumentSource` now add the typed provider - document boundary in front of those snapshots, so live CultMesh/CultCache - readers can publish daemon-owned surface documents without teaching EveUnity - Aetheria types. - `EveUnityPlayableWorldLiveClient` now composes the provider connection with - the playable-world presenter, so provider snapshots immediately drive scene - presentation and Unity input still exits through the provider command sink - rather than local simulation. The same live client now observes provider - command receipts through `IEveUnitySceneCommandReceiptSource`: pending - receipts remain status only, while accepted or reconciled receipts refresh - from the provider surface source instead of moving entities locally. - `EveUnityPlayableWorldRuntime` now owns the generic Unity playable-world - composition point: provider surface documents, provider asset manifest - documents, command sink, optional receipt source, asset manifest cache, - presenter, and scene sink are wired without Aetheria imports. - `EveUnityPlayableWorldPresenter` now maps provider-authored entity rows and - asset refs into scene sink operations, including removal of entities absent - from later provider snapshots, without importing Aetheria entity/prefab - classes. `EveUnityGameObjectPlayableWorldSceneSink` now provides the first - Unity-native sink body: generic `GameObject` creation/update/removal, - transform/radius application, entity markers, and a swappable asset-provider - hook. `EveUnityPlayableWorldAssetManifest` and - `EveUnityManifestGameObjectAssetProvider` map provider-authored asset refs and - entity kinds to Unity resource/prefab keys through data, not Aetheria code. - `IEveUnityPlayableWorldAssetManifestSource` and - `EveUnityPlayableWorldAssetManifestCache` key live manifest updates by the - `playableWorld.AssetManifest` pointer, so the future CultMesh/CultCache - reader can update Unity asset bindings without changing scene lowering. - `gamecult.eve.unity_playable_world_asset_manifest.v1` and - `EveUnityPlayableWorldAssetManifestDocumentSource` now define the typed - manifest document loading boundary and feed that cache without importing - Aetheria asset classes. `EveUnityLivePlayableWorldAssetProvider` connects - that cache to the active world pointer for GameObject scenes. The Unity - client boundary is generic before it learns live transport. - `EveUnityPlayableWorldClientHost`, - `EveUnityPlayableWorldClientBootstrap`, - `EveUnityPlayableWorldInputDriver`, and `EveUnityPlayableWorldCameraRig` now - form the first generic Unity client body: a provider component that implements - the Eve interfaces can be mounted without Aetheria-specific client code, - provider-authored entities are instantiated under a Unity scene root, camera - and movement input are runtime-owned, and movement exits through the - provider-advertised `gamecult.eve.command.v1` boundary. - `EveUnitySceneLiveProviderBridge` now names the runtime-owned live transport - port: a CultMesh/CultNet transport supplies provider surface documents, asset - manifest documents, command submission, receipts, and refresh requests through - `IEveUnitySceneLiveProviderTransport`, while the bridge exposes the existing - scene runtime source/sink/receipt interfaces without importing provider - internals. The Unity scene EditMode proof now includes - `LiveProviderBridgeFeedsPlayableWorldRuntimeThroughTransportPorts`, proving - the generic playable-world runtime can consume that transport-shaped boundary. - Aetheria now provides the owner-repo proof by exposing - `AetheriaEveUnitySceneProviderBridge` as an - `IEveUnitySceneLiveProviderTransport`; the generic Eve live bridge consumes - that transport and drives `EveUnityPlayableWorldRuntime` without the runtime - importing Aetheria scene classes. Aetheria's provider advertisement and - surface catalog now publish the world interaction metadata the Unity scene - document needs: surface kind, projection kind, command boundary, receipt - schema, ownership, lowering targets, state schemas, and record pointer. The - Aetheria Unity bridge now selects that provider-owned advertisement instead of - fabricating the Unity scene metadata locally. The asset manifest leg is now a - first-class live transport proof rather than a local catalog projection: - Aetheria client state exposes `AssetManifest`, the provider bridge reads - `runtimeState.AssetManifest.Latest()`, and the Unity manifest document points - at `AetheriaRuntimeVerseRecordKeys.DaemonAssetManifest`. EveUnity still - consumes only the generic provider/plugin contracts and sidecar transport - ports; Aetheria keeps the gameplay and asset authority. The remaining blocker - has narrowed again: Aetheria now mounts - `AetheriaEveUnitySceneProviderComponent` through the generic - `EveUnityPlayableWorldClientHost`, instantiates provider-authored daemon - entities under a real Unity `GameObject` scene root, and drives the generic - camera rig from the advertised player entity without Aetheria-specific Unity - client code. The remaining blocker is owner-repo PNG/frame capture from the - future EveUnity repo, with provider receipts plus daemon snapshots driving - frames rather than renderer-local simulation. -- EveElectron capture lifecycle now carries the same kind of structured pending - capture contract. `captureContract` names the Electron shell runtime, capture - kind, PNG artifact pattern, conformance attachment point, required Aetheria - provider surface, request schema, request builder, advertisement input, and - authority rule. `tools/eveelectron/eveelectron-capture-contract.mjs` builds a - `gamecult.eve.runtime_capture_request.v1` request from the runtime capability - manifest and provider advertisement, and - `scripts/run-eveelectron-capture-contract-smoke.ps1` proves that request path. - `tools/eveelectron/eveelectron-capture-artifact.mjs` now also lowers the - Aetheria world surface through `EveElectronShell`, writes - `artifacts/eveelectron-capture/latest/electron-shell-projection.json`, and - exports it as `runtime.captureArtifacts[]`. The actual packaged window PNG - remains a split blocker for EveElectron. -- Runtime capture probe coverage is now exported as - `runtimeCaptureProbeCoverage[]`. It records the lifecycle capture contract, - capture owner, requested artifact kind, current semantic/cell-grid artifact, - request path, pending proofs, and validation errors for each runtime with - capture evidence. The consumer smokes assert that Unity UI Toolkit, Unity - scene, and Electron currently have `json-projection` semantic artifacts while - their contracted `png` captures remain owner-repo work; TUI's current - `json-grid` artifact satisfies its contract but still needs EveTui ownership. -- EveTui capture lifecycle now attaches a typed terminal-grid artifact instead - of stopping at a request contract. `tools/evetui/evetui-capture-artifact.mjs` - lowers Aetheria's owner-pack world surface through `EveTuiShell`, - writes `artifacts/evetui-capture/latest/tui-grid.json`, and stores the paired - capture request beside it. Parity validates the artifact schema, provider, - surface, and linked request path, then exports it as `runtime.captureArtifacts[]`. - The split blocker is no longer "no artifact"; it is "EveTui must own this - capture path outside Eve incubation." -- EveElectron release lifecycle now carries a structured pending release - request contract. `releaseContract` names - `gamecult.eve.runtime_release_request.v1`, the - `tools/eveelectron/eveelectron-release-contract.mjs` request builder, - `runtimes/incubating/eve-electron/package.json` as the version source, and - the `eveelectron-v{version}` tag/artifact pattern. - `scripts/run-eveelectron-release-contract-smoke.ps1` proves the request - without pretending a packaged Electron app has been published. -- Unity scene capture now uses that same request-contract shape. The - `unity-scene` runtime capability manifest names - `gamecult.eve.runtime_capture_request.v1`, the shared - `tools/eveunity/eveunity-capture-contract.mjs` request builder, the Aetheria - provider advertisement input, and the scene PNG artifact pattern. - `scripts/run-eveunity-scene-capture-contract-smoke.ps1` proves the request - without pretending a Unity scene frame has been captured. The actual frame or - screenshot artifact remains a split blocker for EveUnity. -- Unity scene release now uses the same UPM release request contract shape as - Unity UI Toolkit. The `unity-scene` runtime capability manifest names - `gamecult.eve.runtime_release_request.v1`, the shared - `tools/eveunity/eveunity-release-contract.mjs` request builder, - `tools/eveunity/eveunity-release-artifact.mjs` artifact builder, - `runtimes/incubating/eve-unity-scene/package.json` as the version source, and - the `eveunity-scene-v{version}` tag/artifact pattern. - `scripts/run-eveunity-scene-release-artifact-smoke.ps1` now produces the - declared Unity scene UPM `.tgz` as local smoke evidence without pretending the - tagged UPM package has been published from EveUnity. -- EveTui capture lifecycle now carries a structured terminal cell-grid capture - contract and typed artifact. `captureContract` names the TUI runtime, - `terminal-cell-grid` capture kind, `json-grid` artifact kind, conformance - attachment point, required Aetheria provider surface, request schema, request - builder, advertisement input, and authority rule. - `tools/evetui/evetui-capture-contract.mjs` builds a - `gamecult.eve.runtime_capture_request.v1` request from the runtime capability - manifest and provider advertisement, and - `tools/evetui/evetui-capture-artifact.mjs` writes - `gamecult.eve.tui_grid.v1` evidence. The remaining blocker is owner-repo - production of the same capture path, not absence of a cell-grid artifact. -- Runtime-owner conformance consumption now asserts EveUnity lifecycle stage - evidence directly. The generic consumer supports - `--expect-runtime-lifecycle-status` and - `--expect-runtime-lifecycle-pending`; the runtime-owner smoke verifies the - Unity UI Toolkit release/test/capture statuses plus the tagged UPM release, - batchmode runner, and capture artifact pending proofs from the exported - runtime record. -- Plugin manifests and advertisements now carry a typed runtime-independent - sidecar ABI boundary. Sai, Norn, and TeX declare `invocationModel: - executable-sidecar`, `gamecult.eve.plugin_abi.v1`, CultMesh/stdio transports, - renderer independence, no provider-state mutation, provider-owned command - acceptance, and `runtime.sidecar` fields for process kind, protocol, - request/response schemas, operations, command envelope, receipt schema, and - state authority. The parity harness validates manifest/advertisement - agreement, checks sidecar operations against ABI fixture operations, exports - the runtime boundary, and the plugin-owner consumer smoke asserts the Sai and - Norn sidecar contracts from the conformance pack. -- The plugin-owner conformance consumer now asserts independent nested plugin - composition directly. `--expect-independent-nested-plugin` verifies that the - Sai VN provider surface requires `sai.vn` while `norn.graph` and `tex.math` - remain `optional-nested`, separately owned, sidecar-provided capabilities. - That keeps a Sai surface free to deploy with Norn or TeX when available - without turning either plugin into a Sai dependency or runtime-hosted - semantic authority. -- The split-target conformance consumer now asserts EveUnity's generic - interactive-world capture controls. `unity-uitoolkit` and `unity-scene` must - export `runtime.captureArtifacts[]` for both Aetheria and the non-product - `eve.world-smoke` provider, proving the Unity lowerers are generic runtime - clients before the remaining blocker narrows to owner-repo PNG/frame capture - and release lifecycle. -- The same split-target consumer now asserts EveElectron's generic - interactive-world shell controls. `electron-shell` must export - `runtime.captureArtifacts[]` for both Aetheria and `eve.world-smoke`, so the - Aetheria RTS web client remains provider pressure while the generic Electron - shell proof stays runtime-owned. -- EveTui split-target consumption now follows the same rule. `tui` must export - `runtime.captureArtifacts[]` for both Aetheria and `eve.world-smoke`, proving - the compact terminal-grid lowerer is a provider-agnostic runtime client while - package release and owner-repo capture production remain explicit blockers. -- The Aetheria provider consumer now asserts the runtime artifacts it relies on: - Unity UI Toolkit, Unity Scene, Electron shell, and TUI must all export - Aetheria `runtime.captureArtifacts[]` for `aetheria.daemon.game`. That makes - the provider-side proof consume generic lowering clients instead of stopping - at advertised target coverage. -- Runtime capture requests now select a declared provider surface, not only a - provider id. Unity UI Toolkit, Electron shell, and TUI declare - `aetheria.daemon.editor` as an additional capture surface, while Unity Scene - rejects the editor surface because Aetheria does not advertise `unity-scene` - for editor lowering. This keeps game/editor capture authority explicit. -- Conformance export now carries first-class `capabilityGaps[]` records derived - from plugin errors, provider advertisement/scenario errors, runtime missing - evidence, declared unsupported plugin projection, capture gaps, and split - blockers. The generic consumer can assert named gaps with - `--expect-capability-gap`; the default smoke now proves Direct2D capture and - EveUnity release ownership remain visible blockers instead of hiding in - Markdown prose. -- Conformance export now carries a first-class - `gamecult.eve.capability_matrix.v1` projection. The matrix summarizes pack - health, plugin ABI/capability claims, provider surfaces/commands/receipts, - runtime feature/capture/command coverage, split readiness, and handoff counts - from the exported evidence. The generic consumer smoke asserts it with - `--expect-capability-matrix`, so owner repos do not need to scrape the - Markdown report or rebuild Eve's summary logic to understand coverage. -- The capability matrix projection now has a catalog-backed schema document at - `schemas/gamecult.eve.capability_matrix.v1.schema.json`, and the generic - conformance consumer asserts that schema from the exported pack. -- Conformance export now copies handoff documents into `handoffs/*.json` rather - than exporting Eve-local source paths only. The export carries handoff export - paths for EveConformance, Sai/Norn plugins, Aetheria, and EveUnity; the - generic consumer verifies those copied documents for conformance, plugin, - provider, and runtime handoff assertions. Owner repos can inspect the - boundary map from the exported pack itself. -- EveConformance now has a machine-readable handoff at - `tools/conformance/eveconformance-handoff.json` and a verifier at - `scripts/run-eveconformance-handoff-smoke.ps1`. The handoff names the - conformance runner, schemas, consumer smokes, fixture corpus, contract inputs, - forbidden imports, and external proofs that leave Eve when the harness - graduates. The conformance export carries `conformanceHandoffPath`, and the - generic consumer smoke asserts it so EveConformance split readiness is not - trapped in Eve's parity manifest. -- Sai, Norn, and TeX have crossed their handoff boundaries. Eve consumes each - owner manifest, advertisement, ABI fixture, and executable sidecar directly; - conformance attaches owner-produced witnesses instead of exporting migration - handoff documents as if migration were still pending. -- Norn has crossed that handoff boundary. `Norn/plugins` now publishes the - `norn.graph` manifest, advertisement, and ABI fixture, while - `Norn/crates/norn-eve-plugin` runs the first executable sidecar over bounded - NDJSON stdio. Eve consumes those owner paths directly and no longer carries - a duplicate Norn manifest or handoff document. The plugin currently owns - `describe`, `validate`, `project`, and solver-backed `measure`; runtime - lowering and provider command acceptance remain outside Norn. -- Aetheria crossed its provider handoff boundary. Its owner pack under - `Aetheria/conformance/eve` is validated in place and consumed directly by - Eve; no provider handoff document remains in the kernel. -- EveUnity now has a machine-readable split handoff at - `packages/org.gamecult.eve.unity-uitoolkit/eveunity-split-handoff.json` and - a verifier at `scripts/run-eveunity-split-handoff-smoke.ps1`. The handoff - names the UPM package body, Unity test lifecycle, Unity capture lifecycle, - contract inputs, forbidden imports, and required external proofs that must - move to EveUnity. It is wired into the runtime capability manifest and - lifecycle smoke, so the remaining split blockers are release/test/capture - ownership rather than an undocumented migration shape. -- Split-target conformance consumption now verifies graduation state instead of - only split-target presence. The generic consumer supports - `--expect-split-target-status`, `--expect-split-target-blocker`, and - `--expect-split-target-proof`; the parity harness runs - `scripts/run-split-target-conformance-consumer-smoke.ps1`; and EveFlutter / - EveUnity consumers can now prove that the targets remain incubating for named - blockers while their current split-readiness proofs stay passed. -- Runtime owner conformance consumption now verifies runtime target health - instead of only runtime presence. The generic consumer supports - `--expect-runtime-status`, `--expect-runtime-feature`, - `--expect-runtime-command-schema`, and `--expect-runtime-capture-status`; the - export carries top-level `captureStatus`; and the parity harness runs a - runtime-owner smoke that proves active Flutter/Unity/web targets while keeping - Fensalir Direct2D explicitly demoted as `external-adapter-spike` with missing - capture. -- Provider owner conformance consumption now verifies the interactive-world - contract instead of only provider presence. The generic conformance consumer - supports `--expect-provider-surface`, `--expect-provider-surface-kind`, - `--expect-provider-command`, and `--expect-provider-receipt-state`; the - export now carries provider receipt states and surface-kind claims; and - Aetheria's consumer smoke asserts the daemon game/editor surfaces, - interactive world surface roles, daemon command boundary, and - accepted/reconciled receipts from the exported provider pack. -- Plugin owner conformance consumption now has a smoke runner at - `scripts/run-plugin-owner-conformance-consumer-smoke.ps1`. The generic - conformance consumer can assert plugin ABI operations and capabilities with - `--expect-plugin-operation` and `--expect-plugin-capability`, and the export - now carries plugin manifest paths, advertisement paths, split targets, - graduation triggers, and optional nested composition claims. Sai and Norn can - prove the plugin contract from the conformance export without reading Eve's - parity manifest. -- EveUnity now has a split lifecycle smoke at - `scripts/run-eveunity-lifecycle-smoke.ps1`. It validates the existing - `packages/org.gamecult.eve.unity-uitoolkit/eve-runtime-capability.json` - lifecycle claims directly instead of duplicating them, checks evidence paths, - and runs the Aetheria Unity package consumer-build smoke. `-RunUnityEditMode` - is available for the heavier Unity batchmode path. EveUnity remains - incubating until tagged UPM release, batchmode runner ownership, and capture - artifacts live under the `EveUnity` repo. -- EveFlutter now has explicit lifecycle incubation evidence at - `EveFlutter/eveflutter-lifecycle.json` and an owner smoke runner at - `scripts/run-eveflutter-owner-smoke.ps1`. The smoke validates the - lifecycle document, checks release/test/capture evidence paths, runs Dart - analysis from the bundled SDK, and consumes the exported conformance pack from - inside the owner repo. The remaining lifecycle blocker is narrower: - tagged release, test runner ownership, and capture runner ownership must move - to `EveFlutter`, rather than pretending the lifecycle shape is still unknown. -- EveFlutter now has a provider-catalog picker primitive. `EveProviderCatalog` - parses conformance-export provider entries, including separate Sai, Norn, and - TeX plugin requirements, and `EveProviderPicker` selects those provider ids without - reading web fixture paths. The Flutter smoke test proves provider selection - from an export-shaped provider list, so the split-readiness report records the - provider-picker proof as passed while leaving release/capture lifecycle - ownership as the remaining EveFlutter blocker. -- EveFlutter now has a conformance consumer smoke at - `EveFlutter/scripts/run-eveflutter-conformance-consumer-smoke.ps1`. It runs from - the owner repo, consumes the exported conformance pack, and asserts the - runtime pack, Sai provider fixture, Flutter runtimes, and `EveFlutter` split - target without reading Eve's parity manifest. The split-readiness report now - records that as a passed proof instead of leaving it as a permanent pending - blocker. -- Fensalir now has a recorded runtime-pack - `gamecult.eve.surface.v1` adapter fixture at - `web/fixtures/fensalir-direct2d-surface.json`. The browser local catalog uses - that contract-shaped surface instead of the older dashboard-state sketch, and - the parity harness validates embedded-slot preservation, command descriptors, - runtime authority witnesses, and style tokens. Direct2D capture and - runtime-owned command smoke remain the activation blockers. -- UIKit and Kotlin device-edge runtimes no longer consume the Sai plugin fixture - as projection acceptance targets. They remain generic embedded-document and - device-edge proofs; plugin projection authority belongs to EveFlutter, - EveUnity, EveElectron, EveTui, or future runtime-specific adapters when those - bodies earn the claim. -- Android Kotlin now maps surface style tokens into its lightweight parity - fixture typography path. `MainActivity.kt` uses token-aware text roles, - parses future text-size/scale tokens, and maps `fontBody`, `fontTitle`, and - `fontMono` to Android typefaces while keeping Kotlin device-edge scope limited - to generic embedded documents, style tokens, and sensor/uplink work. It still - does not claim Sai/Norn/TeX plugin projection acceptance. -- The web reference no longer hardcodes the local provider list in - `web/surface.js`. It loads `web/local-provider-catalog.json`, then opens - advertisement-backed entries for Aetheria, Repixelizer, and Sai through the - same `loadProviderAdvertisement` path used by the provider picker. Fixture-only - surfaces remain explicit local catalog entries until they gain provider - advertisements or move into their owner packs. The fallback catalog is now - schema-backed as `gamecult.eve.local_provider_catalog.v1`, copied into the - conformance export, and indexed as `localProviderCatalogs[]` so external - consumers can verify which provider advertisements the browser reference uses - without importing Eve's source layout. -- Provider advertisements can now name sidecar plugin requirements per surface - with `surfaces[].requiresPlugins[]`. The parity harness validates those - requirements against known plugin manifests, exports them in the conformance - provider list, and reports missing plugins or capabilities as provider - capability gaps. The Sai VN fixture now has an advertisement-shaped provider - entry for `gamecult.home.vn`, so Sai, Norn, and TeX requirements sit at - separate provider/plugin boundaries instead of hiding only in fixture - metadata. -- The conformance runtime pack now exports `runtimeTargets` in - `artifacts/conformance/latest/packs/runtime.json`. Runtime repos can consume - runtime status, feature/plugin claims, command schema, lifecycle, capture - status, capability manifest path, and missing evidence without depending on - Eve's parity manifest layout. -- Sai, Norn, and TeX plugin ABI fixtures now cover `lower` and `measure` in - addition to `describe`, `validate`, `project`, and `apply`. Eve validates the - operation shape, preserved component kinds, measurement outputs, and provider - authority flag without importing plugin internals or deciding plugin - semantics. -- The web reference now publishes a runtime capability manifest at - `web/eve-runtime-capability.json`, and the parity harness validates it through - the same runtime capability path as Unity. Web remains Eve's behavior oracle, - not an EveWeb split target, unless it gains independent deployment lifecycle. - Its manifest explicitly claims provider advertisement consumption, - embedded-document lowering, state bindings, style tokens, authority witnesses, - plugin projection support, command intent emission, and Chrome headless - capture evidence. -- Unity UI Toolkit now declares runtime lifecycle evidence in - `packages/org.gamecult.eve.unity-uitoolkit/eve-runtime-capability.json`. The - parity harness validates release, test, and capture lifecycle claims against - `tools/parity/parity-manifest.json`, checks declared evidence paths, and - exports lifecycle status through the conformance index. Current proof covers - incubating UPM package identity, package EditMode tests, Aetheria - consumer-build smoke, and an Eve-owned batchmode EditMode run through the - Aetheria Unity project; tagged EveUnity release, moving the batchmode runner - into EveUnity, and editor or batchmode capture remain explicit split - blockers. -- Aetheria now has repeatable Unity EditMode evidence for the Eve UI Toolkit - runtime. `scripts/run-aetheria-unity-editmode-tests.ps1` temporarily adds - `org.gamecult.eve.unity-uitoolkit` to Aetheria's Unity `testables`, runs - `GameCult.Eve.UnityUIToolkit.Tests` in Unity batchmode, writes XML and log - artifacts under `artifacts/aetheria-unity-editmode`, and restores - `Packages/manifest.json` before returning. The Brokkr/CultMesh DLL references - in the Unity test asmdef are now mirrored by - `testContract.managedAssemblyDependencies`, with CultLib named as the - NuGet/package owner. EveUnity's split blocker is proving how the Unity package - resolves those assemblies after it leaves Eve incubation, not inventing a - dependency system for CultLib. This proves the incubating test lifecycle; it - does not make Eve own Unity's final runtime lifecycle. -- Aetheria now has the same repeatable Unity EditMode evidence for the - EveUnity scene runtime. `scripts/run-aetheria-unity-scene-editmode-tests.ps1` - temporarily adds `org.gamecult.eve.unity-scene` to Aetheria's Unity - `testables`, runs `GameCult.Eve.UnityScene.Tests`, writes XML and log - artifacts under `artifacts/aetheria-unity-scene-editmode`, and restores - `Packages/manifest.json`. This proves the generic playable-world host, - bootstrap, input, camera, scene sink, and command-envelope tests compile and - run through the Aetheria consumer project without making the Unity client - Aetheria-specific. -- Aetheria now also carries the real provider-side proof for the target shape: - a generic `EveUnityPlayableWorldRuntime` lowers the daemon-published 3D ARPG - world surface through the Aetheria Eve provider bridge, resolves - provider-owned playable-world asset refs through the daemon-published - `AetheriaRuntimeVerseRecordKeys.DaemonAssetManifest` record, submits movement - through the advertised `gamecult.eve.command.v1` boundary, observes - provider-owned receipts, and refreshes from daemon snapshots without - importing Aetheria product authority into EveUnity. The generic Unity - document-source wrapper now connects live provider sources through - `IEveUnitySceneProviderSurfaceDocumentConnection`, delegates provider refresh - through `IEveUnityProviderRefreshSource`, `Connect()` is idempotent for the - current snapshot, and `EveUnitySceneLiveProviderBridge` forwards receipts - while `EveUnityPlayableWorldLiveClient` owns refresh-on-terminal receipt - policy. Aetheria also proves the - generic Unity scene body, not only a recording sink: - `GenericEveUnityClientHostInstantiatesAetheriaDaemonWorldThroughProviderComponent` - mounts the public provider component through `EveUnityPlayableWorldClientHost`, - creates provider-authored `EveUnityPlayableWorldEntityMarker` GameObjects, and - applies `EveUnityPlayableWorldCameraRig` against the advertised player. - Eve records this as the `aetheria-playable-world-consumer-proof` - observed-provider move set in - `runtimes/incubating/eve-unity-scene/eveunity-scene-split-handoff.json`, and - the split-target plus Aetheria consumer smokes assert it from the conformance - export. The remaining blocker is owner-repo PNG/frame capture from the future - EveUnity repo, not whether the Unity client is generic. -- Aetheria's Unity package manifest consumes the surface, UI Toolkit, and scene - packages from EveUnity. Eve's owner smoke verifies those references and asks - EveUnity to build the UI Toolkit package. UI Toolkit capture is reported as - missing until EveUnity emits a native PNG; an Eve-generated semantic JSON - projection is no longer allowed to impersonate runtime evidence. -- Unity UI Toolkit now has a first-party `norn.graph` projection-adapter proof - for `embed.norn`. `NornGraphUiToolkitProjectionAdapter` owns the native - embedded graph shell and graph command emission path, while the Norn sidecar - plugin keeps graph layout and graph semantics. UI Toolkit split-readiness is now - blocked on runtime-owned release, test, and capture lifecycle evidence rather - than missing required Sai VN or optional Norn graph projection support. -- Unity scene now has the same kind of bounded `norn.graph` projection proof - for `embed.norn`. `NornGraphUnitySceneProjectionAdapter` emits a - scene-projection shell with the sidecar ABI, command boundary, document id, - and Norn semantic owner preserved; it does not calculate graph layout or make - graph-state decisions. -- Unity UI Toolkit now has a first-party `sai.vn` projection-adapter proof. The - runtime owns `IEveUiToolkitPluginProjectionAdapter`, registers - `SaiVisualNovelUiToolkitProjectionAdapter` through - `EveUiToolkitSurfaceOptions`, and declares support for `vn.stage`, - `story.choose`, `story.continue`, and `story.jump` in its runtime capability - manifest. Sai still owns story state and command semantics; Unity only lowers - the visual stage/dialogue/action surface and emits Eve command requests. -- Unity UI Toolkit now publishes a runtime-owned capability manifest at - `packages/org.gamecult.eve.unity-uitoolkit/eve-runtime-capability.json`. The - parity harness validates that manifest against - `gamecult.eve.runtime_capability.v1`, cross-checks supported features, - supported Sai, Norn, and TeX plugin projection declarations, command transport - schema, and incubation metadata, then exports the manifest path and validation - errors through the conformance report. Unity owns source-fallback projection - shells only; TeX parsing, typesetting, baseline metrics, and cached render - semantics remain EvePlugins-owned. -- Aetheria now consumes the exported conformance pack from its own working - directory through `scripts/run-aetheria-conformance-consumer-smoke.ps1`. The - smoke asserts the provider pack, `aetheria-world` fixture, `aetheria` - provider entry, and `aetheria-world-command-replay` scenario without reading - Eve's parity manifest or dirtying the Aetheria worktree. -- The Aetheria consumer smoke now also asserts frontend extraction boundaries: - web, Unity UI Toolkit, Unity scene, and Electron shell are covered generic - lowerings. Observed Aetheria Unity and Electron product paths remain - provider-owned split-handoff evidence to replace with provider-agnostic - EveUnity/EveElectron bodies where runtime code still lives in the wrong - place. -- The parity harness now runs a conformance consumer smoke against a copied - `artifacts/conformance/latest` layout, proving the export can be consumed - through `index.json` and `packs/*.json` without reading Eve's parity manifest. -- The parity harness now emits an `EveConformance`-shaped export under - `artifacts/conformance/latest`, with a top-level index and per-pack JSON - files for core, plugin, provider, and runtime consumers. -- Unity, Electron, and TUI plugin projection capability gaps are now explicitly - demoted. UI Toolkit, Unity scene, Electron, and compact TUI runtimes own - generic projection and command requests, while Sai, Norn, and TeX semantics - remain sidecar-plugin responsibilities. Runtimes only add projection shells or - lossy terminal fallbacks for capabilities a plugin advertises through Eve. -- Flutter and Unity now have runtime command-transport smoke evidence in the - parity report. Flutter emits `gamecult.eve.command.v1` intents from lowered - controls, and Unity command requests now carry the same command schema. -- Aetheria now has a provider scenario replay fixture advertised through its - provider advertisement. The parity harness validates advertised surfaces, - command boundary consumption, command intents, and provider-owned receipt - states without importing Aetheria internals into Eve. -- Aetheria's provider scenario now covers both `aetheria.daemon.game` and - `aetheria.daemon.editor` as provider-owned interactive world surfaces. The - Aetheria consumer smoke asserts editor ownership and the claimed TUI - terminal-grid lowering target instead of letting editor support hide behind - the game-surface scenario. -- The surface contract now states nested Norn and TeX placement as optional - composition, not Sai custody. The plugin-owner consumer can assert - provider-plugin requirement availability, so `sai.vn` remains required while - `norn.graph` and `tex.math` stay optional nested sidecar plugins. The browser - fallback catalog only advertises the Sai provider surface; independent Norn - and TeX availability is discovered through that provider's plugin - requirements and the separate plugin advertisements. -- Electron shell and compact TUI projection artifacts now carry the same - availability split as Unity projection artifacts: `sai.vn` projection is - required for the VN surface, while `norn.graph` and `tex.math` are - `optional-nested` independent sidecar plugin projections. This keeps - runtime projection support from becoming an implied Sai dependency story. -- Runtime projection schemas now type that boundary. Electron, TUI, Unity - scene, and Unity UI Toolkit projection schemas require plugin projections to - declare `availability`, `semanticOwner`, ABI schema, command boundary, and - 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. -- EveUnity's managed dependency story is now explicit machine-readable - lifecycle data instead of only prose. Each CultLib-owned Unity precompiled - dependency under `testContract.managedAssemblyDependencies[]` carries a - nested `dependencyContract` naming CultLib as source authority, NuGet as the - package source, the package/assembly identity, version policy, Unity - incubation resolution, and the EveUnity split handoff requirement. The - runtime capability schema, parity comparator, lifecycle smoke, and - runtime-owner conformance smoke all verify that boundary. -- Eve now has a non-Aetheria interactive world control fixture at - `web/fixtures/eve-world-smoke-surface.json` with an advertisement at - `web/fixtures/eve-world-smoke.provider-advertisement.json`. The local - provider catalog, web advertisement merge test, web layout probe, generic - conformance consumer, and runtime-owner consumer all assert that - `web-reference`, `unity-uitoolkit`, `unity-scene`, `electron-shell`, and - `tui` lower the same provider-owned world/command boundary without importing - Aetheria product state. -- That generic world fixture now has its own provider scenario at - `web/fixtures/eve-world-smoke-scenario.json`. The scenario advertises - accepted and denied command receipts through - `eve.world_smoke.command_receipt.v1`, so replay proof is no longer only an - Aetheria product concern. -- Runtime semantic capture now uses the same generic world control. UI Toolkit, - Unity scene, Electron shell, and TUI capture contracts keep Aetheria as the - primary pressure fixture while declaring `eve.world-smoke` as an additional - accepted provider surface. The parity harness emits separate - `eve-world-smoke` JSON projection/grid artifacts for those runtimes, and the - runtime-owner conformance smoke asserts them from the exported pack. -- 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 - owning plugin semantics. -- Plugin sidecar request/response schema names are now backed by Eve contract - documents. `gamecult.eve.plugin_abi.request.v1` and - `gamecult.eve.plugin_abi.response.v1` live under `schemas/`; Sai, Norn, and - TeX ABI fixtures must name those same schemas, and parity fails if a sidecar - advertises a request/response schema that is missing from the contract - catalog. -- Plugin sidecar receipt schema names are now backed by Eve contract - documents. `gamecult.eve.command_receipt.v1` is the generic command receipt - envelope for provider-owned acceptance/reconciliation, while - `gamecult.eve.plugin_receipt.v1` is the plugin-runtime diagnostic/proposed - state receipt envelope. Plugin ABI fixtures must match the sidecar-advertised - receipt schema, and parity fails if that schema is missing from the contract - catalog. -- Runtime release/capture request schema names are now backed by Eve contract - documents. `gamecult.eve.runtime_release_request.v1` and - `gamecult.eve.runtime_capture_request.v1` live under `schemas/`; runtime - lifecycle manifests must name catalog-backed request schemas, so release and - capture status cannot hide a missing wire contract. -- Conformance export now carries a root `schemaCatalog` and copies schema - documents under `schemas/` in the export. Plugin owners can assert - `gamecult.eve.plugin_abi.request.v1` and - `gamecult.eve.plugin_abi.response.v1` from the exported pack instead of - reading Eve's parity manifest to discover contract paths. -- EveConformance handoff now treats the exported schema catalog as part of the - split boundary. The handoff schema move set includes plugin, provider, - runtime, conformance, and ABI wire schemas, and the handoff smoke verifies the - latest conformance export copied those schema documents. -- Split-readiness reporting now exists for `EveFlutter` and `EveUnity`. The - parity report lists member runtime health, required plugin capability claims, - pending proofs, and blockers before either repo can graduate out of Eve - incubation. -- Direct2D is now recorded as an external Fensalir adapter spike, not an active - Eve runtime. Activation requires recorded `gamecult.eve.surface.v1` fixture - replay, command emission through the advertised boundary, and Direct2D frame - capture evidence from Fensalir. -- Fixture conformance metadata now lives beside each fixture and is validated by - the parity harness, so the corpus can move toward `EveConformance` without - relying only on Eve's local manifest layout. -- The browser reference now has an authority/staleness witness fixture. The - parity report verifies visible `fresh`, `accepted`, `stale`, `missing`, - `pending`, `denied`, `predicted`, and `reconciled` states, with provider and - renderer ownership kept separate. -- Command descriptor validation now runs for strict fixtures. Sai proves - `story.*`/`style.patch`; Aetheria proves the daemon command boundary; the web - lowerer resolves authored `action.command` through the shared Eve command - intent path. -- Provider plugin requirements now distinguish required parent semantics from - optional nested sidecar affordances at the contract layer. A Sai surface may - require `sai.vn`, but nested `norn.graph` and `tex.math` entries must keep - `requiredCapabilities` empty and advertise their nested affordances through - `optionalCapabilities`, preserving Norn and TeX as independent sidecar - plugins. -- Provider advertisement validation now runs in the parity harness. -- Repixelizer and Aetheria provider advertisements are checked against - `gamecult.eve.provider_advertisement.v1`. -- Aetheria has a minimal provider-owned interactive world fixture in Eve so the - browser oracle can prove the surface path without owning Aetheria truth. - -## Live Unity Gameplay Gate - -The extracted EveUnity package now proves provider-agnostic lowering against a -separately running Aetheria daemon. `EveUnityCultMeshLiveProviderTransport` -knows only Eve core documents and CultLib: it discovers the provider -advertisement, fetches the advertised world surface, forwards command puts to -the provider primary, and reconciles provider receipts. Aetheria-specific DTOs, -record-key constants, and client bridges are absent from the runtime body. - -Completion requires one EveUnity-owned PlayMode client and one separately -running Aetheria daemon to prove the whole path: - -1. Discover or configure the daemon CultMesh endpoint from provider data. -2. Subscribe to the provider advertisement, `aetheria.daemon.game`, asset - manifest, and receipt records without importing Aetheria code into EveUnity. -3. Render the initial world in a minimal EveUnity-owned Unity project. -4. Submit movement and action intents through - `gamecult.eve.command_invocation.v1`. -5. Observe a separately published provider receipt and a later surface version. -6. Reconcile the Unity scene to that version without local simulation authority. -7. Capture PNG evidence for both Aetheria and the generic world-smoke provider. - -Evidence on 2026-07-10: - -- `E:\Projects\EveUnity\scripts\run-aetheria-daemon-world-witness.ps1` - starts an isolated Aetheria daemon, runs the EveUnity-owned PlayMode client, - and tears both bodies down. -- The client lowered `aetheria.game`, instantiated provider-authored entities, - submitted an idempotent move-vector invocation, received the separately - published reconciled receipt, observed a later surface version and visible - player displacement, and captured - `artifacts/aetheria-daemon/aetheria-daemon-world.png`. -- The same EveUnity project retains the non-Aetheria `eve.world-smoke` capture, - proving the runtime body is generic rather than an extracted Aetheria client. -- Aetheria no longer overrides `EveSurfaceDocument` metadata with its private - runtime surface serializer. Core surface wire fields use CultMesh transport - records; provider-private surface documents remain separate authority. - -This gate is complete: cold and warm runtime-owned witnesses attach their PNG, -test result, receipt, version, movement, timing, and screenshot evidence to a -consumer-verifiable conformance export. - -## Stop Conditions - -Stop adding features and update the map when: - -- a runtime needs product knowledge to render a generic surface; -- a plugin fixture needs provider state to prove plugin behavior; -- a provider fixture becomes necessary for core Eve tests; -- a compatibility route becomes easier to use than a provider advertisement; -- a generated report says "active" while listing missing source symbols, - features, metadata, or plugin support; -- a split target exists but the owner, inputs, outputs, and release lifecycle - are still awkward to explain. - -If the sentence "X owns Y so that Z remains true" cannot be written cleanly, the -migration step is not ready. The machine may still be useful. It is not yet -coherent. +Eve is the contract kernel for a runtime-independent meta-game engine. A game +daemon publishes typed surfaces, commands, state bindings, assets, and plugin +requirements. Generic runtime clients lower those documents without knowing +the game. Semantic plugins run as sidecar daemons behind the Eve plugin ABI and +remain independent of rendering engines. + +The proving scenario is Aetheria: its daemon publishes a 3D ARPG world surface +that a generic EveUnity client lowers into a playable game without importing +Aetheria code or assets as runtime authority. + +## Authority Map + +| Owner | Authority | +| --- | --- | +| Eve | Surface, command, provider, plugin, runtime, and receipt contracts; CultUI DSL; browser reference lowerer; minimal core fixtures | +| EveConformance | Fixture matrix, parity runner, capability ledger, witness attachment, portable exports, and cross-repo CI orchestration | +| EvePlugins / plugin owner repos | Runtime-independent semantic sidecars, domain fixtures, and plugin witnesses | +| EveUnity, EveElectron, EveFlutter, future runtime repos | Native projection, plugin transport, platform lifecycle, captures, and runtime tests | +| Aetheria and other providers | Live state, authored surfaces, assets, command handling, receipts, and product scenarios | + +Boundary rule: Eve owns contracts. Plugins own semantics. Runtimes own native +projection. Providers own live truth. EveConformance owns proof. + +No runtime may import provider or plugin internals. Provider/plugin +advertisements and typed command/receipt documents are the boundary. + +## Live Data Flow + +1. A provider daemon advertises surfaces, schemas, commands, assets, and plugin + requirements through CultMesh/CultNet documents. +2. A generic runtime selects a surface and resolves required plugin + advertisements. +3. Plugin operations execute through the runtime-independent sidecar ABI. +4. The runtime lowers the resulting Eve surface into native presentation and + emits typed command invocations. +5. The provider remains the sole writer of game state and returns typed + receipts/state updates. +6. EveConformance joins owner manifests and witnesses into a portable report; + it never becomes provider or runtime truth. + +## Completed Migration + +- EveUnity owns the Unity scene and UI Toolkit packages. Aetheria consumes those + packages directly. +- The generic EveUnity playable-world client lowers the separately running + Aetheria daemon's 3D ARPG world, resolves provider-owned assets, and exercises + movement, targeting, and fire command receipts. +- EveElectron owns its shell, security lifecycle, captures, and runtime witness. +- EveFlutter owns the generic Flutter clients and platform lifecycle. +- Sai and Norn publish independent sidecar plugins. A Sai surface may request + nested Norn or TeX capabilities when those plugins are available. +- EvePlugins owns the TeX sidecar and KaTeX-backed semantic witness. +- Aetheria owns its provider advertisement, world surface, scenario, asset + manifest, and conformance pack. +- EveConformance owns the parity manifest, runner, fixture exporter, witness + attachment, export consumer, and report artifacts. +- Eve retains the browser reference as the behavior oracle and a minimal generic + interactive-world fixture. + +## Remaining Work + +### Runtime Graduation + +- Extract EveTui when its process lifecycle and release surface are stable. +- Move Android Kotlin and iOS UIKit lowerers into runtime owner repos or retire + them when Flutter is the intended native owner. +- Tag and publish EveUnity UPM packages; prove installation from a released + package rather than sibling source paths. +- Tag EveFlutter releases and remove remaining incubation claims. + +### Generic Game Lowering + +- Replace local sibling-path discovery with CultMesh discovery for Aetheria, + plugins, schemas, and assets. +- Prove cold-start Unity play from published packages plus daemon + advertisements, with no Aetheria checkout available to the client. +- Expand world lowering only through generic surface contracts: entities, + transforms, animation state, collision/navigation intent, cameras, effects, + interaction prompts, and command boundaries. +- Keep simulation, AI, inventory, combat resolution, and authored world truth in + Aetheria. + +### Plugin Architecture + +- Package CultLib for NuGet and use its typed CultCache/CultNet/CultMesh APIs in + C# runtimes. +- Replace development `stdio-ndjson` transport where appropriate with the + published CultNet/CultMesh sidecar transport while preserving the same plugin + ABI operations. +- Graduate graph semantics fully to Norn and VN/Ink semantics fully to Sai once + no Eve fixture is their semantic owner. +- Keep nested plugin availability explicit and non-transitive: Sai does not own + Norn or TeX. + +### Conformance + +- Move remaining fixture metadata that describes product/plugin semantics to + its owner pack; Eve keeps only minimal contract fixtures. +- Run EveConformance CI against released runtime/plugin artifacts, not only + sibling development checkouts. +- Publish immutable conformance exports and capability matrices per release. +- Remove obsolete split-handoff schemas and checks after every named owner repo + has a released consumer path. + +## Graduation Criteria + +- A runtime splits when it has its own build/deploy lifecycle or platform SDK + pressure. +- A plugin splits when it owns domain semantics, fixtures, and conformance cases. +- A product fixture splits when it carries product state, assets, flows, or + receipts. +- Conformance remains independent when runtimes/plugins consume its export + without depending on Eve's checkout layout. + +## Acceptance + +The migration is complete when: + +1. A clean generic EveUnity client installed from published packages discovers + Aetheria through the Verse and plays its 3D world without Aetheria code. +2. Sai, Norn, and TeX execute as independent runtime-agnostic sidecars and can be + composed through advertisements. +3. Browser, Unity, Electron, Flutter, and TUI/native owners consume the same + provider contracts and report honest capability gaps. +4. Eve answers "what is the contract?" without carrying product state, plugin + semantics, platform build bodies, or conformance orchestration. +5. EveConformance can verify released providers, plugins, and runtimes from + public artifacts and typed witnesses. + +## Verification + +```powershell +node --test web/eve-dsl.test.mjs +npm --prefix packages/eve-browser-lowering test +powershell -ExecutionPolicy Bypass -File .\scripts\run-parity-harness.ps1 +``` + +`run-parity-harness.ps1` is an Eve compatibility entry point. The matrix and +runner it invokes are owned by the sibling EveConformance repository. diff --git a/docs/repo-strategy.md b/docs/repo-strategy.md index 403055f..88126d3 100644 --- a/docs/repo-strategy.md +++ b/docs/repo-strategy.md @@ -1,67 +1,30 @@ -# Eve Repo Strategy +# Eve Repository Strategy -Eve is a kernel, not a forever warehouse. +Eve is a kernel, not a warehouse. -The boundary rule is deliberately blunt: Eve core owns contracts. Plugins own -semantics. Runtime repos own native projection. Provider repos own live truth. +## Ownership -## Kernel Scope +- `Eve` owns contracts, CultUI, the browser reference oracle, and minimal core + fixtures. +- `EveConformance` owns the parity matrix, runner, exported packs, witnesses, + and CI orchestration. +- `EvePlugins` incubates reusable first-party semantic sidecars; Sai, Norn, and + other domain owners take stable semantics into their own repos. +- Runtime repos own native lowering, platform builds, plugin transport, tests, + and captures. +- Provider repos own state, authored surfaces, assets, commands, receipts, and + product scenarios. -`E:\Projects\Eve` may own: +Repositories communicate through schemas, advertisements, CultNet/CultMesh +documents, packages, and conformance exports. A sibling checkout may support +development, but it is never an authority boundary. -- `gamecult.eve.surface.v1`, command descriptors, binding descriptors, embedded - document slots, provider advertisements, and plugin advertisements; -- CultUI DSL syntax, compiler behavior, and style contract; -- the browser reference lowerer while it remains the behavior oracle; -- conformance harness incubation and minimal core fixtures; -- first-party runtime or plugin proofs only while their contracts are still - forming. +## Graduation -Anything else is guilty until it can explain why it protects a core invariant. +- Split a runtime when it has an independent build/deploy lifecycle. +- Split a plugin when it owns domain semantics and conformance fixtures. +- Move a fixture when it contains product-specific truth. +- Publish a package when consumers should no longer depend on source layout. -## Graduation Rules - -- A runtime splits when it has its own build/deploy lifecycle, platform SDK pain, - plugin projection behavior, capture pipeline, or release cadence. -- A plugin splits when it owns domain semantics, fixtures, and conformance cases. -- A product demo splits when it carries product-specific state, assets, flows, - receipts, or deployment authority. -- Conformance splits when runtimes/plugins need to consume it without depending - on Eve source layout. - -## Target Repos - -- `Eve`: kernel contracts, specs, browser reference, and conformance incubation. -- `EvePlugins`: first-party reusable plugins while young, such as fields, - world 2D/3D, and TeX. These plugins are independent sidecar capabilities, not - hidden features of Sai or any other provider. -- `Sai`: owner for the VN/Ink plugin once `sai.vn` graduates. -- `Norn`: owner for graph plugin semantics once `norn.graph` graduates. -- `EveUnity`, `EveGodot`, `EveFlutter`, `EveElectron`, and similar runtime - repos: native projection, platform build glue, plugin projection adapters, - captures, and runtime tests. Runtime plugin projection adapters lower - sidecar-advertised output; they do not host Sai, Norn, TeX, or any other - plugin's semantic authority. -- `EveConformance`: fixture corpus, parity runner, capability matrix, - screenshot/report artifacts, plugin packs, and CI orchestration once the - harness stops thrashing. -- Provider repos such as `Aetheria`: daemon state, authored surfaces, assets, - provider fixtures, receipts, and real app scenarios. - -## Incubation Metadata - -`tools/parity/parity-manifest.json` is the current ownership ledger. Fixtures -declare a conformance pack. Plugins declare owner repo, split target, capability -claim, required fixtures, and graduation trigger. Runtimes declare owner repo, -repo role, split target, supported features, and graduation trigger. - -The parity harness treats missing ownership metadata as a failure mode. That is -intentional. A renderer or plugin that cannot name its owner is not mature; it -is just code sharing a worktree. - -## Forbidden Shortcut - -Provider and plugin advertisements are the boundary between repos. A renderer -must not import provider or plugin internals because they happen to be nearby. -If a runtime needs an Aetheria concept, either Eve lacks a generic primitive or -Aetheria is leaking. The friction is useful. Keep it. +The useful friction is deliberate: if a generic runtime needs an Aetheria +internal, either Eve lacks a generic contract or Aetheria is leaking authority. diff --git a/scripts/run-aetheria-conformance-consumer-smoke.ps1 b/scripts/run-aetheria-conformance-consumer-smoke.ps1 index 00e9afc..3ea670b 100644 --- a/scripts/run-aetheria-conformance-consumer-smoke.ps1 +++ b/scripts/run-aetheria-conformance-consumer-smoke.ps1 @@ -32,7 +32,8 @@ if (Test-Path $consumerExport) { New-Item -ItemType Directory -Force -Path $consumerRoot | Out-Null Copy-Item -LiteralPath $sourceExport -Destination $consumerExport -Recurse -$consumerScript = Join-Path $projectRoot "tools\conformance\consume-export.mjs" +$conformanceRoot = if ($env:EVE_CONFORMANCE_ROOT) { $env:EVE_CONFORMANCE_ROOT } else { "E:\Projects\EveConformance" } +$consumerScript = Join-Path $conformanceRoot "tools\conformance\consume-export.mjs" Push-Location $AetheriaRoot try { @@ -93,8 +94,7 @@ try { --expect-provider-command aetheria:aetheria.daemon.commands ` --expect-provider-receipt-state aetheria:accepted ` --expect-provider-receipt-state aetheria:pending ` - --expect-provider-receipt-state aetheria:reconciled ` - --expect-provider-handoff-move aetheria:provider-scenario:current:exists:run-aetheria-conformance-consumer-smoke.ps1 + --expect-provider-receipt-state aetheria:reconciled if ($LASTEXITCODE -ne 0) { throw "Aetheria conformance consumer smoke failed with exit code $LASTEXITCODE" } diff --git a/scripts/run-conformance-consumer-smoke.ps1 b/scripts/run-conformance-consumer-smoke.ps1 index efb0806..f92f97c 100644 --- a/scripts/run-conformance-consumer-smoke.ps1 +++ b/scripts/run-conformance-consumer-smoke.ps1 @@ -6,6 +6,8 @@ param( $ErrorActionPreference = "Stop" $projectRoot = Split-Path -Parent $PSScriptRoot +$conformanceRoot = if ($env:EVE_CONFORMANCE_ROOT) { $env:EVE_CONFORMANCE_ROOT } else { "E:\Projects\EveConformance" } +$consumerScript = Join-Path $conformanceRoot "tools\conformance\consume-export.mjs" $sourceExport = if ([System.IO.Path]::IsPathRooted($ExportDirectory)) { $ExportDirectory } else { @@ -30,10 +32,9 @@ Copy-Item -LiteralPath $sourceExport -Destination $consumerExport -Recurse Push-Location $projectRoot try { - node .\tools\conformance\consume-export.mjs $consumerExport ` + node $consumerScript $consumerExport ` --expect-capability-matrix ` --expect-schema gamecult.eve.conformance_export.v1 ` - --expect-schema gamecult.eve.conformance_handoff.v1 ` --expect-schema gamecult.eve.capability_matrix.v1 ` --expect-schema gamecult.eve.command_receipt.v1 ` --expect-schema gamecult.eve.plugin_receipt.v1 ` @@ -104,8 +105,7 @@ try { --expect-runtime-witness electron-shell:eve.world-smoke:eve.world-smoke.surface:cold:pass ` --expect-runtime-witness electron-shell:aetheria:aetheria.daemon.game:cold:pass ` --expect-runtime-capture-probe tui:contract-artifact-present:json-grid:EveTui ` - --expect-capability-gap "split-target:EveUnity:EveUnity:proof:Tagged UPM release" ` - --expect-conformance-handoff + --expect-capability-gap "split-target:EveUnity:EveUnity:proof:Tagged UPM release" if ($LASTEXITCODE -ne 0) { throw "Conformance consumer smoke failed with exit code $LASTEXITCODE" } diff --git a/scripts/run-eveconformance-handoff-smoke.ps1 b/scripts/run-eveconformance-handoff-smoke.ps1 deleted file mode 100644 index 7d9c863..0000000 --- a/scripts/run-eveconformance-handoff-smoke.ps1 +++ /dev/null @@ -1,170 +0,0 @@ -param( - [string] $HandoffPath = "tools\conformance\eveconformance-handoff.json", - [string] $ManifestPath = "tools\parity\parity-manifest.json", - [string] $ExportDirectory = "artifacts\conformance\latest" -) - -$ErrorActionPreference = "Stop" - -$projectRoot = Split-Path -Parent $PSScriptRoot -$absoluteHandoffPath = if ([System.IO.Path]::IsPathRooted($HandoffPath)) { - $HandoffPath -} else { - Join-Path $projectRoot $HandoffPath -} -$absoluteManifestPath = if ([System.IO.Path]::IsPathRooted($ManifestPath)) { - $ManifestPath -} else { - Join-Path $projectRoot $ManifestPath -} -$absoluteExportDirectory = if ([System.IO.Path]::IsPathRooted($ExportDirectory)) { - $ExportDirectory -} else { - Join-Path $projectRoot $ExportDirectory -} - -if (-not (Test-Path -LiteralPath $absoluteHandoffPath)) { - throw "EveConformance handoff manifest not found: $absoluteHandoffPath" -} -if (-not (Test-Path -LiteralPath $absoluteManifestPath)) { - throw "Parity manifest not found: $absoluteManifestPath" -} - -$handoff = Get-Content -LiteralPath $absoluteHandoffPath -Raw | ConvertFrom-Json -$manifest = Get-Content -LiteralPath $absoluteManifestPath -Raw | ConvertFrom-Json - -if ($handoff.schema -ne "gamecult.eve.conformance_handoff.v1") { - throw "Unexpected EveConformance handoff schema: $($handoff.schema)" -} -if ($handoff.splitTarget -ne "EveConformance") { - throw "Unexpected EveConformance handoff split target: $($handoff.splitTarget)" -} -if ($handoff.ownerRepo -ne "EveConformance") { - throw "Unexpected EveConformance handoff owner: $($handoff.ownerRepo)" -} -if ($handoff.currentHostRepo -ne "Eve") { - throw "Unexpected EveConformance handoff host: $($handoff.currentHostRepo)" -} - -$expectedPath = $HandoffPath.Replace("\", "/") -if ($manifest.repoStrategy.conformanceHandoffPath -ne $expectedPath) { - throw "Parity manifest does not point at EveConformance handoff. Expected $expectedPath got $($manifest.repoStrategy.conformanceHandoffPath)" -} - -foreach ($id in @("conformance-runner", "conformance-schemas", "consumer-smokes", "fixture-corpus")) { - $moveSet = @($handoff.moveSets) | Where-Object { $_.id -eq $id } | Select-Object -First 1 - if (-not $moveSet) { - throw "EveConformance handoff missing move set: $id" - } - if ($moveSet.destinationOwner -ne "EveConformance") { - throw "EveConformance move set $id has unexpected destination owner: $($moveSet.destinationOwner)" - } - if (-not $moveSet.replacementProof) { - throw "EveConformance move set $id missing replacement proof" - } - foreach ($relativePath in @($moveSet.currentPaths)) { - $absolutePath = Join-Path $projectRoot $relativePath - if (-not (Test-Path -LiteralPath $absolutePath)) { - throw "EveConformance move set $id references missing current path: $relativePath" - } - } -} - -foreach ($contract in @( - "gamecult.eve.surface.v1", - "gamecult.eve.command.v1", - "gamecult.eve.command_receipt.v1", - "gamecult.eve.conformance_export.v1", - "gamecult.eve.conformance_handoff.v1", - "gamecult.eve.capability_matrix.v1", - "gamecult.eve.conformance_fixture.v1", - "gamecult.eve.provider_scenario.v1", - "gamecult.eve.plugin.v1", - "gamecult.eve.plugin_receipt.v1", - "gamecult.eve.plugin_advertisement.v1", - "gamecult.eve.plugin_handoff.v1", - "gamecult.eve.plugin_abi_fixture.v1", - "gamecult.eve.plugin_abi.request.v1", - "gamecult.eve.plugin_abi.response.v1", - "gamecult.eve.runtime_release_request.v1", - "gamecult.eve.runtime_capture_request.v1", - "gamecult.eve.provider_advertisement.v1", - "gamecult.eve.provider_handoff.v1", - "gamecult.eve.electron_shell_projection.v1", - "gamecult.eve.unity_scene_projection.v1", - "gamecult.eve.unity_playable_world_asset_manifest.v1", - "gamecult.eve.tui_grid.v1", - "gamecult.eve.web_layout_probe.v1", - "gamecult.eve.runtime_capability.v1", - "gamecult.eve.runtime_lifecycle.v1", - "gamecult.eve.runtime_split_handoff.v1" -)) { - if (-not (@($handoff.contractInputs) -contains $contract)) { - throw "EveConformance handoff missing contract input: $contract" - } -} - -if (Test-Path -LiteralPath (Join-Path $absoluteExportDirectory "index.json")) { - $export = Get-Content -LiteralPath (Join-Path $absoluteExportDirectory "index.json") -Raw | ConvertFrom-Json - if ($export.conformanceHandoffPath -ne $expectedPath) { - throw "Conformance export does not point at EveConformance handoff. Expected $expectedPath got $($export.conformanceHandoffPath)" - } - if (-not $export.conformanceHandoffExportPath) { - throw "Conformance export missing EveConformance handoff export path" - } - if (-not (Test-Path -LiteralPath (Join-Path $absoluteExportDirectory $export.conformanceHandoffExportPath))) { - throw "Conformance export missing EveConformance handoff document: $($export.conformanceHandoffExportPath)" - } - foreach ($packId in @("core", "plugin", "provider", "runtime")) { - $pack = @($export.packs) | Where-Object { $_.id -eq $packId } | Select-Object -First 1 - if (-not $pack) { - throw "Conformance export missing pack: $packId" - } - } - foreach ($schemaId in @( - "gamecult.eve.conformance_export.v1", - "gamecult.eve.conformance_handoff.v1", - "gamecult.eve.capability_matrix.v1", - "gamecult.eve.conformance_fixture.v1", - "gamecult.eve.provider_scenario.v1", - "gamecult.eve.provider_advertisement.v1", - "gamecult.eve.provider_handoff.v1", - "gamecult.eve.electron_shell_projection.v1", - "gamecult.eve.unity_scene_projection.v1", - "gamecult.eve.unity_playable_world_asset_manifest.v1", - "gamecult.eve.tui_grid.v1", - "gamecult.eve.web_layout_probe.v1", - "gamecult.eve.runtime_capability.v1", - "gamecult.eve.runtime_lifecycle.v1", - "gamecult.eve.runtime_split_handoff.v1", - "gamecult.eve.command_receipt.v1", - "gamecult.eve.plugin.v1", - "gamecult.eve.plugin_receipt.v1", - "gamecult.eve.plugin_advertisement.v1", - "gamecult.eve.plugin_handoff.v1", - "gamecult.eve.plugin_abi_fixture.v1", - "gamecult.eve.plugin_abi.request.v1", - "gamecult.eve.plugin_abi.response.v1", - "gamecult.eve.runtime_release_request.v1", - "gamecult.eve.runtime_capture_request.v1" - )) { - $schema = @($export.schemaCatalog) | Where-Object { $_.schemaId -eq $schemaId } | Select-Object -First 1 - if (-not $schema) { - throw "Conformance export schema catalog missing schema: $schemaId" - } - if (-not $schema.exportPath) { - throw "Conformance export schema catalog missing export path for schema: $schemaId" - } - if (-not (Test-Path -LiteralPath (Join-Path $absoluteExportDirectory $schema.exportPath))) { - throw "Conformance export missing copied schema document for $($schemaId): $($schema.exportPath)" - } - } -} - -foreach ($proof in @($handoff.requiredExternalProofs)) { - if (-not $proof) { - throw "EveConformance handoff contains an empty required external proof" - } -} - -Write-Host "EveConformance handoff smoke passed: $absoluteHandoffPath" diff --git a/scripts/run-eveflutter-owner-smoke.ps1 b/scripts/run-eveflutter-owner-smoke.ps1 index 84ceeaa..59caa60 100644 --- a/scripts/run-eveflutter-owner-smoke.ps1 +++ b/scripts/run-eveflutter-owner-smoke.ps1 @@ -25,7 +25,9 @@ if ($runtimeSources -match "aetheria|repixelizer|gamecult\.home") { } New-Item -ItemType Directory -Force -Path $fontDirectory | Out-Null -node (Join-Path (Split-Path -Parent $PSScriptRoot) "tools\parity\export-fixture.mjs") cultui-inspector $surfacePath +$conformanceRoot = if ($env:EVE_CONFORMANCE_ROOT) { $env:EVE_CONFORMANCE_ROOT } else { "E:\Projects\EveConformance" } +$env:EVE_KERNEL_ROOT = Split-Path -Parent $PSScriptRoot +node (Join-Path $conformanceRoot "tools\parity\export-fixture.mjs") cultui-inspector $surfacePath if ($LASTEXITCODE -ne 0) { throw "Eve fixture export for EveFlutter failed." } Copy-Item -LiteralPath (Join-Path $FlutterRoot "bin\cache\artifacts\material_fonts\roboto-regular.ttf") -Destination (Join-Path $fontDirectory "Roboto-Regular.ttf") -Force Copy-Item -LiteralPath (Join-Path $FlutterRoot "bin\cache\artifacts\material_fonts\roboto-bold.ttf") -Destination (Join-Path $fontDirectory "Roboto-Bold.ttf") -Force diff --git a/scripts/run-parity-harness.ps1 b/scripts/run-parity-harness.ps1 index 7dbf713..79281a2 100644 --- a/scripts/run-parity-harness.ps1 +++ b/scripts/run-parity-harness.ps1 @@ -5,6 +5,10 @@ param( $ErrorActionPreference = "Stop" $projectRoot = Split-Path -Parent $PSScriptRoot +$conformanceRoot = if ($env:EVE_CONFORMANCE_ROOT) { $env:EVE_CONFORMANCE_ROOT } else { "E:\Projects\EveConformance" } +if (-not (Test-Path (Join-Path $conformanceRoot "tools\parity\run-parity.mjs"))) { + throw "EveConformance checkout not found: $conformanceRoot" +} $env:EVE_PARITY_OUTPUT = if ([System.IO.Path]::IsPathRooted($OutputDirectory)) { $OutputDirectory } else { @@ -39,17 +43,18 @@ try { -ExpectedSurfaceId "eve.world-smoke.surface" ` -ExpectedCommandBoundary "eve.world-smoke.commands" ` -ExpectedReceiptSchema "eve.world_smoke.command_receipt.v1" - node .\tools\parity\run-parity.mjs - node .\tools\conformance\attach-runtime-witness.mjs ` + $env:EVE_KERNEL_ROOT = $projectRoot + $env:EVE_CONFORMANCE_OUTPUT = Join-Path $projectRoot "artifacts\conformance" + node (Join-Path $conformanceRoot "tools\parity\run-parity.mjs") + node (Join-Path $conformanceRoot "tools\conformance\attach-runtime-witness.mjs") ` "E:\Projects\EveElectron\artifacts\capture\generic-world\runtime-witness.json" ` ".\artifacts\conformance\latest" - node .\tools\conformance\attach-runtime-witness.mjs ` + node (Join-Path $conformanceRoot "tools\conformance\attach-runtime-witness.mjs") ` "E:\Projects\EveElectron\artifacts\capture\aetheria-world\runtime-witness.json" ` ".\artifacts\conformance\latest" - node .\tools\conformance\attach-plugin-witness.mjs ` + node (Join-Path $conformanceRoot "tools\conformance\attach-plugin-witness.mjs") ` "E:\Projects\EvePlugins\artifacts\tex-math\runtime-witness.json" ` ".\artifacts\conformance\latest" - & (Join-Path $PSScriptRoot "run-eveconformance-handoff-smoke.ps1") & (Join-Path $PSScriptRoot "run-eveelectron-owner-smoke.ps1") & (Join-Path $PSScriptRoot "run-evetui-split-handoff-smoke.ps1") & (Join-Path $PSScriptRoot "run-evetui-lifecycle-smoke.ps1") diff --git a/scripts/run-parity-smoke.ps1 b/scripts/run-parity-smoke.ps1 index cc6a704..d3ec8ce 100644 --- a/scripts/run-parity-smoke.ps1 +++ b/scripts/run-parity-smoke.ps1 @@ -11,7 +11,9 @@ $projectRoot = Split-Path -Parent $PSScriptRoot $stamp = Get-Date -Format "yyyyMMdd-HHmmss" $runRoot = Join-Path $projectRoot (Join-Path $OutputDirectory $stamp) New-Item -ItemType Directory -Force $runRoot | Out-Null -$manifest = Get-Content (Join-Path $projectRoot "tools\parity\parity-manifest.json") | ConvertFrom-Json +$conformanceRoot = if ($env:EVE_CONFORMANCE_ROOT) { $env:EVE_CONFORMANCE_ROOT } else { "E:\Projects\EveConformance" } +$manifest = Get-Content (Join-Path $conformanceRoot "tools\parity\parity-manifest.json") | ConvertFrom-Json +$env:EVE_KERNEL_ROOT = $projectRoot $responsiveCases = @($manifest.responsiveCases) $androidDeviceCases = @($manifest.androidDeviceCases) @@ -82,7 +84,7 @@ try { throw "Semantic parity harness failed with exit code $LASTEXITCODE" } $flutterSurface = Join-Path $runRoot "flutter-current-surface.json" - node .\tools\parity\export-fixture.mjs $ProviderId $flutterSurface | Out-Host + node (Join-Path $conformanceRoot "tools\parity\export-fixture.mjs") $ProviderId $flutterSurface | Out-Host if ($LASTEXITCODE -ne 0) { throw "Flutter fixture export failed with exit code $LASTEXITCODE" } foreach ($case in $responsiveCases) { @@ -97,7 +99,7 @@ try { Invoke-Capture "ios" { $iosFixture = Join-Path $runRoot "ios-current-surface.json" - node .\tools\parity\export-fixture.mjs $ProviderId $iosFixture | Out-Host + node (Join-Path $conformanceRoot "tools\parity\export-fixture.mjs") $ProviderId $iosFixture | Out-Host powershell -ExecutionPolicy Bypass -File .\scripts\capture-eve-screenshot.ps1 ` -Target $IosSshTarget ` -OutputDirectory $runRoot ` diff --git a/scripts/run-plugin-owner-conformance-consumer-smoke.ps1 b/scripts/run-plugin-owner-conformance-consumer-smoke.ps1 index 44fa4b5..11a434f 100644 --- a/scripts/run-plugin-owner-conformance-consumer-smoke.ps1 +++ b/scripts/run-plugin-owner-conformance-consumer-smoke.ps1 @@ -28,7 +28,8 @@ if (Test-Path $consumerExport) { New-Item -ItemType Directory -Force -Path $consumerRoot | Out-Null Copy-Item -LiteralPath $sourceExport -Destination $consumerExport -Recurse -$consumerScript = Join-Path $projectRoot "tools\conformance\consume-export.mjs" +$conformanceRoot = if ($env:EVE_CONFORMANCE_ROOT) { $env:EVE_CONFORMANCE_ROOT } else { "E:\Projects\EveConformance" } +$consumerScript = Join-Path $conformanceRoot "tools\conformance\consume-export.mjs" node $consumerScript $consumerExport ` --expect-pack plugin ` diff --git a/scripts/run-runtime-owner-conformance-consumer-smoke.ps1 b/scripts/run-runtime-owner-conformance-consumer-smoke.ps1 index 664a161..362ea26 100644 --- a/scripts/run-runtime-owner-conformance-consumer-smoke.ps1 +++ b/scripts/run-runtime-owner-conformance-consumer-smoke.ps1 @@ -28,7 +28,8 @@ if (Test-Path $consumerExport) { New-Item -ItemType Directory -Force -Path $consumerRoot | Out-Null Copy-Item -LiteralPath $sourceExport -Destination $consumerExport -Recurse -$consumerScript = Join-Path $projectRoot "tools\conformance\consume-export.mjs" +$conformanceRoot = if ($env:EVE_CONFORMANCE_ROOT) { $env:EVE_CONFORMANCE_ROOT } else { "E:\Projects\EveConformance" } +$consumerScript = Join-Path $conformanceRoot "tools\conformance\consume-export.mjs" node $consumerScript $consumerExport ` --expect-pack runtime ` diff --git a/scripts/run-split-target-conformance-consumer-smoke.ps1 b/scripts/run-split-target-conformance-consumer-smoke.ps1 index 81cf265..68cfd47 100644 --- a/scripts/run-split-target-conformance-consumer-smoke.ps1 +++ b/scripts/run-split-target-conformance-consumer-smoke.ps1 @@ -6,6 +6,8 @@ param( $ErrorActionPreference = "Stop" $projectRoot = Split-Path -Parent $PSScriptRoot +$conformanceRoot = if ($env:EVE_CONFORMANCE_ROOT) { $env:EVE_CONFORMANCE_ROOT } else { "E:\Projects\EveConformance" } +$consumerScript = Join-Path $conformanceRoot "tools\conformance\consume-export.mjs" $sourcePath = if ([System.IO.Path]::IsPathRooted($SourceDirectory)) { $SourceDirectory } else { @@ -30,7 +32,7 @@ Copy-Item -LiteralPath $sourcePath -Destination $exportPath -Recurse Push-Location $projectRoot try { - node .\tools\conformance\consume-export.mjs $exportPath ` + node $consumerScript $exportPath ` --expect-split-target EveFlutter ` --expect-split-target-status EveFlutter:incubating ` --expect-split-target-proof "EveFlutter:Provider picker consumes provider advertisements" ` diff --git a/tools/conformance/attach-plugin-witness.mjs b/tools/conformance/attach-plugin-witness.mjs deleted file mode 100644 index 58faec0..0000000 --- a/tools/conformance/attach-plugin-witness.mjs +++ /dev/null @@ -1,44 +0,0 @@ -import { mkdir, readFile, writeFile } from "node:fs/promises"; -import path from "node:path"; -import { fileURLToPath } from "node:url"; -import { validateSchemaSubset } from "../../runtimes/incubating/test-support/schema-subset.mjs"; - -const [witnessArgument, exportArgument] = process.argv.slice(2); -if (!witnessArgument || !exportArgument) { - console.error("Usage: node tools/conformance/attach-plugin-witness.mjs "); - process.exit(2); -} -const witnessPath = path.resolve(witnessArgument); -const exportDirectory = path.resolve(exportArgument); -const indexPath = path.join(exportDirectory, "index.json"); -const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../.."); -const witness = parseJson(await readFile(witnessPath, "utf8")); -const schema = parseJson(await readFile(path.join(repoRoot, "schemas/gamecult.eve.plugin_witness.v1.schema.json"), "utf8")); -const schemaErrors = validateSchemaSubset(schema, witness); -if (schemaErrors.length) throw new Error(`Plugin witness schema errors: ${schemaErrors.join("; ")}`); - -const attachmentRoot = path.join(exportDirectory, "attachments", "plugin", safeSegment(witness.witnessId)); -await mkdir(attachmentRoot, { recursive: true }); -const exportedWitnessPath = path.join(attachmentRoot, "witness.json"); -await writeFile(exportedWitnessPath, `${JSON.stringify(witness, null, 2)}\n`); - -const index = parseJson(await readFile(indexPath, "utf8")); -const record = { - witnessId: witness.witnessId, - pluginId: witness.pluginId, - ownerRepo: witness.ownerRepo, - status: witness.status, - transport: witness.transport, - operationCount: witness.operations.length, - witnessExportPath: slash(path.relative(exportDirectory, exportedWitnessPath)), -}; -index.pluginWitnesses = [ - ...(Array.isArray(index.pluginWitnesses) ? index.pluginWitnesses : []).filter(candidate => candidate.witnessId !== record.witnessId), - record, -].sort((left, right) => left.witnessId.localeCompare(right.witnessId)); -await writeFile(indexPath, `${JSON.stringify(index, null, 2)}\n`); -console.log(exportedWitnessPath); - -function safeSegment(value) { return String(value).replace(/[^a-zA-Z0-9._-]+/g, "-"); } -function slash(value) { return value.replaceAll("\\", "/"); } -function parseJson(value) { return JSON.parse(value.replace(/^\uFEFF/, "")); } diff --git a/tools/conformance/attach-runtime-witness.mjs b/tools/conformance/attach-runtime-witness.mjs deleted file mode 100644 index c42adad..0000000 --- a/tools/conformance/attach-runtime-witness.mjs +++ /dev/null @@ -1,78 +0,0 @@ -import { copyFile, mkdir, readFile, writeFile } from "node:fs/promises"; -import path from "node:path"; -import { fileURLToPath } from "node:url"; -import { validateSchemaSubset } from "../../runtimes/incubating/test-support/schema-subset.mjs"; - -const [witnessArgument, exportArgument] = process.argv.slice(2); -if (!witnessArgument || !exportArgument) { - console.error("Usage: node tools/conformance/attach-runtime-witness.mjs "); - process.exit(2); -} - -const witnessPath = path.resolve(witnessArgument); -const exportDirectory = path.resolve(exportArgument); -const indexPath = path.join(exportDirectory, "index.json"); -const witness = parseJson(await readFile(witnessPath, "utf8")); -validateWitness(witness); -const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../.."); -const witnessSchema = parseJson(await readFile(path.join(repoRoot, "schemas/gamecult.eve.runtime_witness.v1.schema.json"), "utf8")); -const schemaErrors = validateSchemaSubset(witnessSchema, witness); -if (schemaErrors.length) throw new Error(`Runtime witness schema errors: ${schemaErrors.join("; ")}`); - -const attachmentRoot = path.join(exportDirectory, "attachments", "runtime", safeSegment(witness.witnessId)); -await mkdir(attachmentRoot, { recursive: true }); -const artifacts = []; -for (const artifact of witness.artifacts) { - const sourcePath = path.resolve(path.dirname(witnessPath), artifact.path); - const fileName = path.basename(sourcePath); - const destinationPath = path.join(attachmentRoot, fileName); - await copyFile(sourcePath, destinationPath); - artifacts.push({ - ...artifact, - exportPath: slash(path.relative(exportDirectory, destinationPath)), - }); -} - -const exportedWitness = { ...witness, artifacts }; -const exportedWitnessPath = path.join(attachmentRoot, "witness.json"); -await writeFile(exportedWitnessPath, `${JSON.stringify(exportedWitness, null, 2)}\n`); - -const index = parseJson(await readFile(indexPath, "utf8")); -const record = { - witnessId: witness.witnessId, - runtimeId: witness.runtimeId, - runtimeOwnerRepo: witness.runtimeOwnerRepo, - providerId: witness.providerId, - surfaceId: witness.surfaceId, - projectionKind: witness.projectionKind, - status: witness.status, - cacheState: witness.execution.cacheState, - witnessExportPath: slash(path.relative(exportDirectory, exportedWitnessPath)), - artifactCount: artifacts.length, -}; -index.runtimeWitnesses = [ - ...(Array.isArray(index.runtimeWitnesses) ? index.runtimeWitnesses : []).filter(candidate => candidate.witnessId !== record.witnessId), - record, -].sort((left, right) => left.witnessId.localeCompare(right.witnessId)); -await writeFile(indexPath, `${JSON.stringify(index, null, 2)}\n`); -console.log(exportedWitnessPath); - -function validateWitness(value) { - for (const field of ["witnessId", "runtimeId", "runtimeOwnerRepo", "providerId", "surfaceId", "projectionKind", "status", "generatedAtUtc", "authority"]) { - if (!value?.[field]) throw new Error(`Runtime witness missing ${field}.`); - } - if (value.schema !== "gamecult.eve.runtime_witness.v1") throw new Error(`Unexpected runtime witness schema: ${value.schema || ""}`); - if (!value.execution?.cacheState || !Number.isFinite(value.execution?.durationMs)) throw new Error("Runtime witness execution is incomplete."); - if (!value.assertions || typeof value.assertions !== "object") throw new Error("Runtime witness assertions are missing."); - if (value.receipts !== undefined && !Array.isArray(value.receipts)) throw new Error("Runtime witness receipts must be an array when present."); - if (!Array.isArray(value.artifacts) || !value.artifacts.length) throw new Error("Runtime witness artifacts are missing."); - for (const artifact of value.artifacts) { - if (!artifact.path || !/^[a-f0-9]{64}$/i.test(artifact.sha256 || "") || !(artifact.sizeBytes > 0)) { - throw new Error(`Runtime witness artifact is invalid: ${artifact.path || "unknown"}`); - } - } -} - -function safeSegment(value) { return String(value).replace(/[^a-zA-Z0-9._-]+/g, "-"); } -function slash(value) { return value.replaceAll("\\", "/"); } -function parseJson(value) { return JSON.parse(value.replace(/^\uFEFF/, "")); } diff --git a/tools/conformance/consume-export.mjs b/tools/conformance/consume-export.mjs deleted file mode 100644 index 140c97b..0000000 --- a/tools/conformance/consume-export.mjs +++ /dev/null @@ -1,2100 +0,0 @@ -import { readFile } from "node:fs/promises"; -import { existsSync } from "node:fs"; -import { readFileSync } from "node:fs"; -import path from "node:path"; - -const { exportDirectory, expectations } = parseArguments(process.argv.slice(2)); -if (!exportDirectory) { - console.error([ - "Usage: node tools/conformance/consume-export.mjs [expectations]", - "", - "Expectations:", - " --expect-pack ", - " --expect-fixture ", - " --expect-plugin ", - " --expect-provider ", - " --expect-runtime ", - " --expect-scenario ", - " --expect-split-target ", - " --expect-capability-matrix", - " --expect-schema ", - " --expect-capability-gap ", - " --expect-runtime-plugin-projection ", - " --expect-provider-runtime-plugin-projection ", - " --expect-runtime-plugin-gap ", - " --expect-local-provider-catalog ", - " --expect-local-provider-catalog-provider ", - " --expect-local-provider-catalog-advertisement ", - " --expect-world-lowering-coverage ", - " --expect-command-boundary-coverage ", - " --expect-world-lowering-gap ", - " --expect-screenshot-metric ", - " --expect-runtime-capture-probe ", - " --expect-conformance-handoff", - " --expect-plugin-operation ", - " --expect-plugin-capability ", - " --expect-plugin-runtime ", - " --expect-plugin-runtime-transport ", - " --expect-plugin-runtime-authority ", - " --expect-plugin-runtime-field ", - " --expect-plugin-abi-field ", - " --expect-plugin-abi-operation-coverage ", - " --expect-plugin-handoff ", - " --expect-plugin-witness ", - " --expect-plugin-handoff-move ", - " --expect-provider-handoff-move ", - " --expect-provider-surface ", - " --expect-provider-surface-kind ", - " --expect-provider-surface-field ", - " --expect-interactive-world-surface ", - " --expect-provider-plugin-requirement ", - " --expect-independent-nested-plugin ", - " --expect-provider-command ", - " --expect-provider-receipt-state ", - " --expect-provider-handoff ", - " --expect-runtime-status ", - " --expect-runtime-feature ", - " --expect-runtime-world-target ", - " --expect-runtime-world-field ", - " --expect-runtime-handoff ", - " --expect-runtime-command-schema ", - " --expect-runtime-capture-status ", - " --expect-runtime-capture-artifact ", - " --expect-runtime-witness ", - " --expect-runtime-lifecycle-status ", - " --expect-runtime-lifecycle-pending ", - " --expect-runtime-lifecycle-field ", - " --expect-split-target-status ", - " --expect-split-target-blocker ", - " --expect-split-target-blocker-record ", - " --expect-split-handoff-move ", - " --expect-split-target-proof ", - ].join("\n")); - process.exit(2); -} - -const indexPath = path.join(exportDirectory, "index.json"); -const errors = []; - -if (!existsSync(indexPath)) { - errors.push(`index.json:missing:${indexPath}`); -} else { - const index = JSON.parse(await readFile(indexPath, "utf8")); - validateIndex(index, exportDirectory, expectations, errors); -} - -for (const error of errors) console.error(`Conformance consumer error: ${error}`); -if (errors.length) process.exit(1); - -console.log(`Conformance consumer smoke passed: ${exportDirectory}`); - -function validateIndex(index, directory, expectations, errors) { - if (index.schema !== "gamecult.eve.conformance_export.v1") { - errors.push(`schema:expected gamecult.eve.conformance_export.v1 got ${index.schema || ""}`); - } - - const packs = Array.isArray(index.packs) ? index.packs : []; - const packIds = new Set(packs.map(pack => pack.id)); - const fixtureIds = new Set(); - const exportedRuntimeTargets = []; - validateSchemaCatalog(index.schemaCatalog, directory, expectations.schemas, errors); - for (const requiredPack of ["core", "plugin", "provider", "runtime"]) { - if (!packIds.has(requiredPack)) errors.push(`pack:${requiredPack}:missing`); - } - for (const expectedPack of expectations.packs) { - if (!packIds.has(expectedPack)) errors.push(`pack:${expectedPack}:missing`); - } - - for (const pack of packs) { - const packPath = path.join(directory, "packs", `${pack.id}.json`); - if (!existsSync(packPath)) { - errors.push(`pack-file:${pack.id}:missing`); - continue; - } - - const packDocument = JSON.parse(readFileSyncUtf8(packPath)); - if (packDocument.id !== pack.id) { - errors.push(`pack-file:${pack.id}:id:expected ${pack.id} got ${packDocument.id || ""}`); - } - if ((packDocument.fixtures || []).length !== (pack.fixtures || []).length) { - errors.push(`pack-file:${pack.id}:fixture-count:expected ${(pack.fixtures || []).length} got ${(packDocument.fixtures || []).length}`); - } - - for (const fixture of packDocument.fixtures || []) { - if (fixture.fixtureId) fixtureIds.add(fixture.fixtureId); - if (!fixture.fixtureId) errors.push(`pack-file:${pack.id}:fixture:fixtureId:missing`); - if (!fixture.status) errors.push(`pack-file:${pack.id}:fixture:${fixture.fixtureId || "unknown"}:status:missing`); - if (!fixture.surface?.path) errors.push(`pack-file:${pack.id}:fixture:${fixture.fixtureId || "unknown"}:surface.path:missing`); - if (!fixture.metadataPath) errors.push(`pack-file:${pack.id}:fixture:${fixture.fixtureId || "unknown"}:metadataPath:missing`); - } - - if (pack.id === "runtime") { - const packRuntimeTargets = Array.isArray(packDocument.runtimeTargets) ? packDocument.runtimeTargets : []; - if (!packRuntimeTargets.length) errors.push("pack-file:runtime:runtimeTargets:missing"); - for (const runtime of packRuntimeTargets) { - exportedRuntimeTargets.push(runtime); - if (!runtime.runtimeId) errors.push("pack-file:runtime:runtimeTarget:runtimeId:missing"); - if (!runtime.status) errors.push(`pack-file:runtime:runtimeTarget:${runtime.runtimeId || "unknown"}:status:missing`); - if (!runtime.ownerRepo) errors.push(`pack-file:runtime:runtimeTarget:${runtime.runtimeId || "unknown"}:ownerRepo:missing`); - } - } - } - - const plugins = Array.isArray(index.plugins) ? index.plugins : []; - const pluginWitnesses = Array.isArray(index.pluginWitnesses) ? index.pluginWitnesses : []; - const providers = Array.isArray(index.providers) ? index.providers : []; - const runtimes = mergeRuntimeRecords(Array.isArray(index.runtimes) ? index.runtimes : [], exportedRuntimeTargets); - const splitTargets = Array.isArray(index.splitTargets) ? index.splitTargets : []; - const pluginAbiOperationCoverage = Array.isArray(index.pluginAbiOperationCoverage) ? index.pluginAbiOperationCoverage : []; - const providerPluginRequirementCoverage = Array.isArray(index.providerPluginRequirementCoverage) ? index.providerPluginRequirementCoverage : []; - const capabilityGaps = Array.isArray(index.capabilityGaps) ? index.capabilityGaps : []; - const runtimePluginProjectionCoverage = Array.isArray(index.runtimePluginProjectionCoverage) ? index.runtimePluginProjectionCoverage : []; - const providerRuntimePluginProjectionCoverage = Array.isArray(index.providerRuntimePluginProjectionCoverage) ? index.providerRuntimePluginProjectionCoverage : []; - const runtimePluginProjectionGaps = Array.isArray(index.runtimePluginProjectionGaps) ? index.runtimePluginProjectionGaps : []; - const localProviderCatalogs = Array.isArray(index.localProviderCatalogs) ? index.localProviderCatalogs : []; - const interactiveWorldSurfaces = Array.isArray(index.interactiveWorldSurfaces) ? index.interactiveWorldSurfaces : []; - const worldSurfaceLoweringCoverage = Array.isArray(index.worldSurfaceLoweringCoverage) ? index.worldSurfaceLoweringCoverage : []; - const commandBoundaryCoverage = Array.isArray(index.commandBoundaryCoverage) ? index.commandBoundaryCoverage : []; - const screenshotComparisonMetrics = Array.isArray(index.screenshotComparisonMetrics) ? index.screenshotComparisonMetrics : []; - const runtimeCaptureProbeCoverage = Array.isArray(index.runtimeCaptureProbeCoverage) ? index.runtimeCaptureProbeCoverage : []; - const runtimeWitnesses = Array.isArray(index.runtimeWitnesses) ? index.runtimeWitnesses : []; - const splitTargetBlockers = Array.isArray(index.splitTargetBlockers) ? index.splitTargetBlockers : []; - const splitHandoffMoveCoverage = Array.isArray(index.splitHandoffMoveCoverage) ? index.splitHandoffMoveCoverage : []; - const pluginHandoffMoveCoverage = Array.isArray(index.pluginHandoffMoveCoverage) ? index.pluginHandoffMoveCoverage : []; - const providerHandoffMoveCoverage = Array.isArray(index.providerHandoffMoveCoverage) ? index.providerHandoffMoveCoverage : []; - const worldSurfaceLoweringGaps = Array.isArray(index.worldSurfaceLoweringGaps) ? index.worldSurfaceLoweringGaps : []; - - validatePluginRecords(plugins, errors); - validatePluginWitnesses(pluginWitnesses, directory, errors); - validatePluginAbiOperationCoverage(index.pluginAbiOperationCoverage, errors); - validateProviderRecords(providers, errors); - validateProviderPluginRequirementCoverage(index.providerPluginRequirementCoverage, errors); - validateRuntimeRecords(runtimes, errors); - validateSplitTargetRecords(splitTargets, errors); - validateInteractiveWorldSurfaces(index.interactiveWorldSurfaces, errors); - - if (expectations.conformanceHandoff && !index.conformanceHandoffPath) { - errors.push("conformanceHandoffPath:missing"); - } - if (expectations.conformanceHandoff) { - validateExportedHandoff(index.conformanceHandoffExportPath, directory, "conformanceHandoff", errors); - } - if (expectations.capabilityMatrix) { - validateCapabilityMatrix(index.capabilityMatrix, packs, plugins, providers, runtimes, splitTargets, errors); - } - validateCapabilityGaps(index.capabilityGaps, errors); - validateRuntimePluginProjectionCoverage(index.runtimePluginProjectionCoverage, errors); - validateProviderRuntimePluginProjectionCoverage(index.providerRuntimePluginProjectionCoverage, errors); - validateRuntimePluginProjectionGaps(index.runtimePluginProjectionGaps, errors); - validateLocalProviderCatalogs(index.localProviderCatalogs, directory, errors); - validateSplitTargetBlockers(index.splitTargetBlockers, errors); - validateSplitHandoffMoveCoverage(index.splitHandoffMoveCoverage, errors); - validatePluginHandoffMoveCoverage(index.pluginHandoffMoveCoverage, errors); - validateProviderHandoffMoveCoverage(index.providerHandoffMoveCoverage, errors); - validateWorldSurfaceLoweringCoverage(index.worldSurfaceLoweringCoverage, errors); - validateCommandBoundaryCoverage(index.commandBoundaryCoverage, errors); - validateScreenshotComparisonMetrics(index.screenshotComparisonMetrics, errors); - validateRuntimeCaptureProbeCoverage(index.runtimeCaptureProbeCoverage, errors); - validateRuntimeWitnesses(runtimeWitnesses, directory, errors); - validateWorldSurfaceLoweringGaps(index.worldSurfaceLoweringGaps, errors); - for (const expectedGap of expectations.capabilityGaps) { - if (!capabilityGaps.some(gap => capabilityGapText(gap).includes(expectedGap))) { - errors.push(`capabilityGaps:${expectedGap}:missing`); - } - } - for (const expectation of expectations.runtimePluginProjectionCoverage) { - const record = runtimePluginProjectionCoverage.find(candidate => - candidate.runtimeId === expectation.runtimeId && - candidate.pluginId === expectation.pluginId); - if (!record) { - errors.push(`runtimePluginProjectionCoverage:${expectation.runtimeId}:${expectation.pluginId}:missing`); - continue; - } - if (record.status !== expectation.status) { - errors.push(`runtimePluginProjectionCoverage:${expectation.runtimeId}:${expectation.pluginId}:status:expected ${expectation.status} got ${record.status || ""}`); - } - if (record.runtimeOwnerRepo !== expectation.ownerRepo) { - errors.push(`runtimePluginProjectionCoverage:${expectation.runtimeId}:${expectation.pluginId}:runtimeOwnerRepo:expected ${expectation.ownerRepo} got ${record.runtimeOwnerRepo || ""}`); - } - } - for (const expectation of expectations.runtimeWitnesses) { - const witness = runtimeWitnesses.find(candidate => - candidate.runtimeId === expectation.runtimeId && - candidate.providerId === expectation.providerId && - candidate.surfaceId === expectation.surfaceId && - candidate.cacheState === expectation.cacheState); - if (!witness) { - errors.push(`runtimeWitnesses:${expectation.runtimeId}:${expectation.providerId}:${expectation.surfaceId}:${expectation.cacheState}:missing`); - continue; - } - if (witness.status !== expectation.status) { - errors.push(`runtimeWitnesses:${witness.witnessId}:status:expected ${expectation.status} got ${witness.status || ""}`); - } - } - for (const expectation of expectations.pluginWitnesses) { - const witness = pluginWitnesses.find(candidate => - candidate.pluginId === expectation.pluginId && candidate.transport === expectation.transport); - if (!witness) { - errors.push(`pluginWitnesses:${expectation.pluginId}:${expectation.transport}:missing`); - continue; - } - if (witness.status !== expectation.status) errors.push(`pluginWitnesses:${expectation.pluginId}:status:expected ${expectation.status} got ${witness.status || ""}`); - } - for (const expectation of expectations.providerRuntimePluginProjectionCoverage) { - const record = providerRuntimePluginProjectionCoverage.find(candidate => - candidate.providerId === expectation.providerId && - candidate.surfaceId === expectation.surfaceId && - candidate.runtimeId === expectation.runtimeId && - candidate.pluginId === expectation.pluginId); - if (!record) { - errors.push(`providerRuntimePluginProjectionCoverage:${expectation.providerId}:${expectation.surfaceId}:${expectation.runtimeId}:${expectation.pluginId}:missing`); - continue; - } - if (record.status !== expectation.status) { - errors.push(`providerRuntimePluginProjectionCoverage:${expectation.providerId}:${expectation.surfaceId}:${expectation.runtimeId}:${expectation.pluginId}:status:expected ${expectation.status} got ${record.status || ""}`); - } - if (record.runtimeOwnerRepo !== expectation.runtimeOwnerRepo) { - errors.push(`providerRuntimePluginProjectionCoverage:${expectation.providerId}:${expectation.surfaceId}:${expectation.runtimeId}:${expectation.pluginId}:runtimeOwnerRepo:expected ${expectation.runtimeOwnerRepo} got ${record.runtimeOwnerRepo || ""}`); - } - } - for (const expectation of expectations.runtimePluginGaps) { - const gap = runtimePluginProjectionGaps.find(candidate => - candidate.runtimeId === expectation.runtimeId && - candidate.pluginId === expectation.pluginId); - if (!gap) { - errors.push(`runtimePluginProjectionGaps:${expectation.runtimeId}:${expectation.pluginId}:missing`); - continue; - } - if (gap.ownerRepo !== expectation.ownerRepo) { - errors.push(`runtimePluginProjectionGaps:${expectation.runtimeId}:${expectation.pluginId}:ownerRepo:expected ${expectation.ownerRepo} got ${gap.ownerRepo || ""}`); - } - if (!gap.reason) { - errors.push(`runtimePluginProjectionGaps:${expectation.runtimeId}:${expectation.pluginId}:reason:missing`); - } - } - for (const expectation of expectations.localProviderCatalogs) { - const catalog = localProviderCatalogs.find(candidate => candidate.runtimeId === expectation.runtimeId); - if (!catalog) { - errors.push(`localProviderCatalogs:${expectation.runtimeId}:missing`); - continue; - } - if (catalog.status !== expectation.status) { - errors.push(`localProviderCatalogs:${expectation.runtimeId}:status:expected ${expectation.status} got ${catalog.status || ""}`); - } - if (!catalog.catalogPath?.includes(expectation.pathSubstring) && !catalog.catalogExportPath?.includes(expectation.pathSubstring)) { - errors.push(`localProviderCatalogs:${expectation.runtimeId}:path:expected substring ${expectation.pathSubstring}`); - } - } - for (const expectation of expectations.localProviderCatalogProviders) { - const catalog = localProviderCatalogs.find(candidate => candidate.runtimeId === expectation.runtimeId); - if (!catalog) { - errors.push(`localProviderCatalogs:${expectation.runtimeId}:missing`); - continue; - } - if (!Array.isArray(catalog.providerIds) || !catalog.providerIds.includes(expectation.providerId)) { - errors.push(`localProviderCatalogs:${expectation.runtimeId}:provider:${expectation.providerId}:missing`); - } - } - for (const expectation of expectations.localProviderCatalogAdvertisements) { - const catalog = localProviderCatalogs.find(candidate => candidate.runtimeId === expectation.runtimeId); - if (!catalog) { - errors.push(`localProviderCatalogs:${expectation.runtimeId}:missing`); - continue; - } - if (!Array.isArray(catalog.advertisementPaths) || !catalog.advertisementPaths.some(candidate => candidate.includes(expectation.pathSubstring))) { - errors.push(`localProviderCatalogs:${expectation.runtimeId}:advertisement:${expectation.pathSubstring}:missing`); - } - } - for (const expectation of expectations.worldLoweringCoverage) { - const coverage = worldSurfaceLoweringCoverage.find(candidate => - candidate.providerId === expectation.providerId && - candidate.surfaceId === expectation.surfaceId && - candidate.targetId === expectation.targetId); - if (!coverage) { - errors.push(`worldSurfaceLoweringCoverage:${expectation.providerId}:${expectation.surfaceId}:${expectation.targetId}:missing`); - continue; - } - if (coverage.status !== expectation.status) { - errors.push(`worldSurfaceLoweringCoverage:${expectation.providerId}:${expectation.surfaceId}:${expectation.targetId}:status:expected ${expectation.status} got ${coverage.status || ""}`); - } - if (coverage.runtimeOwnerRepo !== expectation.ownerRepo) { - errors.push(`worldSurfaceLoweringCoverage:${expectation.providerId}:${expectation.surfaceId}:${expectation.targetId}:runtimeOwnerRepo:expected ${expectation.ownerRepo} got ${coverage.runtimeOwnerRepo || ""}`); - } - if (coverage.runtimeId !== expectation.runtimeId) { - errors.push(`worldSurfaceLoweringCoverage:${expectation.providerId}:${expectation.surfaceId}:${expectation.targetId}:runtimeId:expected ${expectation.runtimeId} got ${coverage.runtimeId || ""}`); - } - } - for (const expectation of expectations.commandBoundaryCoverage) { - const coverage = commandBoundaryCoverage.find(candidate => - candidate.providerId === expectation.providerId && - candidate.surfaceId === expectation.surfaceId && - candidate.targetId === expectation.targetId); - if (!coverage) { - errors.push(`commandBoundaryCoverage:${expectation.providerId}:${expectation.surfaceId}:${expectation.targetId}:missing`); - continue; - } - if (coverage.status !== expectation.status) { - errors.push(`commandBoundaryCoverage:${expectation.providerId}:${expectation.surfaceId}:${expectation.targetId}:status:expected ${expectation.status} got ${coverage.status || ""}`); - } - if (coverage.runtimeOwnerRepo !== expectation.ownerRepo) { - errors.push(`commandBoundaryCoverage:${expectation.providerId}:${expectation.surfaceId}:${expectation.targetId}:runtimeOwnerRepo:expected ${expectation.ownerRepo} got ${coverage.runtimeOwnerRepo || ""}`); - } - if (coverage.runtimeId !== expectation.runtimeId) { - errors.push(`commandBoundaryCoverage:${expectation.providerId}:${expectation.surfaceId}:${expectation.targetId}:runtimeId:expected ${expectation.runtimeId} got ${coverage.runtimeId || ""}`); - } - } - for (const expectation of expectations.worldLoweringGaps) { - const gap = worldSurfaceLoweringGaps.find(candidate => - candidate.providerId === expectation.providerId && - candidate.surfaceId === expectation.surfaceId && - candidate.targetId === expectation.targetId); - if (!gap) { - errors.push(`worldSurfaceLoweringGaps:${expectation.providerId}:${expectation.surfaceId}:${expectation.targetId}:missing`); - continue; - } - if (gap.ownerRepo !== expectation.ownerRepo) { - errors.push(`worldSurfaceLoweringGaps:${expectation.providerId}:${expectation.surfaceId}:${expectation.targetId}:ownerRepo:expected ${expectation.ownerRepo} got ${gap.ownerRepo || ""}`); - } - if (gap.runtimeId !== expectation.runtimeId) { - errors.push(`worldSurfaceLoweringGaps:${expectation.providerId}:${expectation.surfaceId}:${expectation.targetId}:runtimeId:expected ${expectation.runtimeId} got ${gap.runtimeId || ""}`); - } - } - for (const expectation of expectations.screenshotMetrics) { - const metric = screenshotComparisonMetrics.find(candidate => - candidate.runtimeId === expectation.runtimeId && - candidate.fixtureId === expectation.fixtureId && - candidate.metricKind === expectation.metricKind); - if (!metric) { - errors.push(`screenshotComparisonMetrics:${expectation.runtimeId}:${expectation.fixtureId}:${expectation.metricKind}:missing`); - continue; - } - if (metric.status !== expectation.status) { - errors.push(`screenshotComparisonMetrics:${expectation.runtimeId}:${expectation.fixtureId}:${expectation.metricKind}:status:expected ${expectation.status} got ${metric.status || ""}`); - } - } - for (const expectation of expectations.runtimeCaptureProbes) { - const record = runtimeCaptureProbeCoverage.find(candidate => candidate.runtimeId === expectation.runtimeId); - if (!record) { - errors.push(`runtimeCaptureProbeCoverage:${expectation.runtimeId}:missing`); - continue; - } - if (record.status !== expectation.status) { - errors.push(`runtimeCaptureProbeCoverage:${expectation.runtimeId}:status:expected ${expectation.status} got ${record.status || ""}`); - } - if (record.contractArtifactKind !== expectation.contractArtifactKind) { - errors.push(`runtimeCaptureProbeCoverage:${expectation.runtimeId}:contractArtifactKind:expected ${expectation.contractArtifactKind} got ${record.contractArtifactKind || ""}`); - } - if (record.captureOwnerRepo !== expectation.captureOwnerRepo) { - errors.push(`runtimeCaptureProbeCoverage:${expectation.runtimeId}:captureOwnerRepo:expected ${expectation.captureOwnerRepo} got ${record.captureOwnerRepo || ""}`); - } - } - - for (const expectedFixture of expectations.fixtures) { - if (!fixtureIds.has(expectedFixture)) errors.push(`fixture:${expectedFixture}:missing`); - } - for (const expectedPlugin of expectations.plugins) { - if (!plugins.some(plugin => plugin.pluginId === expectedPlugin)) errors.push(`plugins:${expectedPlugin}:missing`); - } - for (const expectation of expectations.pluginOperations) { - const plugin = plugins.find(candidate => candidate.pluginId === expectation.pluginId); - if (!plugin) { - errors.push(`plugins:${expectation.pluginId}:missing`); - continue; - } - if (!Array.isArray(plugin.abiOperations) || !plugin.abiOperations.includes(expectation.operation)) { - errors.push(`plugins:${expectation.pluginId}:operation:${expectation.operation}:missing`); - } - } - for (const expectation of expectations.pluginCapabilities) { - const plugin = plugins.find(candidate => candidate.pluginId === expectation.pluginId); - if (!plugin) { - errors.push(`plugins:${expectation.pluginId}:missing`); - continue; - } - if (!Array.isArray(plugin.capabilities) || !plugin.capabilities.includes(expectation.capability)) { - errors.push(`plugins:${expectation.pluginId}:capability:${expectation.capability}:missing`); - } - } - for (const expectation of expectations.pluginRuntimes) { - const plugin = plugins.find(candidate => candidate.pluginId === expectation.pluginId); - if (!plugin) { - errors.push(`plugins:${expectation.pluginId}:missing`); - continue; - } - if (plugin.runtimeBoundary?.invocationModel !== expectation.invocationModel) { - errors.push(`plugins:${expectation.pluginId}:runtime:expected ${expectation.invocationModel} got ${plugin.runtimeBoundary?.invocationModel || ""}`); - } - } - for (const expectation of expectations.pluginRuntimeTransports) { - const plugin = plugins.find(candidate => candidate.pluginId === expectation.pluginId); - if (!plugin) { - errors.push(`plugins:${expectation.pluginId}:missing`); - continue; - } - if (!Array.isArray(plugin.runtimeBoundary?.transports) || !plugin.runtimeBoundary.transports.includes(expectation.transport)) { - errors.push(`plugins:${expectation.pluginId}:runtime.transport:${expectation.transport}:missing`); - } - } - for (const expectation of expectations.pluginRuntimeAuthorities) { - const plugin = plugins.find(candidate => candidate.pluginId === expectation.pluginId); - if (!plugin) { - errors.push(`plugins:${expectation.pluginId}:missing`); - continue; - } - if (!Array.isArray(plugin.runtimeBoundary?.authority) || !plugin.runtimeBoundary.authority.includes(expectation.authority)) { - errors.push(`plugins:${expectation.pluginId}:runtime.authority:${expectation.authority}:missing`); - } - } - for (const expectation of expectations.pluginRuntimeFields) { - const plugin = plugins.find(candidate => candidate.pluginId === expectation.pluginId); - if (!plugin) { - errors.push(`plugins:${expectation.pluginId}:missing`); - continue; - } - const actual = readNestedField(plugin.runtimeBoundary, expectation.fieldPath); - if (actual !== expectation.value) { - errors.push(`plugins:${expectation.pluginId}:runtime.${expectation.fieldPath}:expected ${expectation.value} got ${actual || ""}`); - } - } - for (const expectation of expectations.pluginAbiFields) { - const plugin = plugins.find(candidate => candidate.pluginId === expectation.pluginId); - if (!plugin) { - errors.push(`plugins:${expectation.pluginId}:missing`); - continue; - } - const contracts = Array.isArray(plugin.abiOperationContracts) ? plugin.abiOperationContracts : []; - const contract = contracts.find(candidate => candidate.operation === expectation.operation); - if (!contract) { - errors.push(`plugins:${expectation.pluginId}:abiOperation:${expectation.operation}:missing`); - continue; - } - const actual = readNestedField(contract, expectation.fieldPath); - if (actual !== expectation.value) { - errors.push(`plugins:${expectation.pluginId}:abiOperation:${expectation.operation}.${expectation.fieldPath}:expected ${expectation.value} got ${actual || ""}`); - } - } - for (const expectation of expectations.pluginAbiOperationCoverage) { - const record = pluginAbiOperationCoverage.find(candidate => - candidate.pluginId === expectation.pluginId && - candidate.operation === expectation.operation); - if (!record) { - errors.push(`pluginAbiOperationCoverage:${expectation.pluginId}:${expectation.operation}:missing`); - continue; - } - if (record.status !== expectation.status) { - errors.push(`pluginAbiOperationCoverage:${expectation.pluginId}:${expectation.operation}:status:expected ${expectation.status} got ${record.status || ""}`); - } - if (record.ownerRepo !== expectation.ownerRepo) { - errors.push(`pluginAbiOperationCoverage:${expectation.pluginId}:${expectation.operation}:ownerRepo:expected ${expectation.ownerRepo} got ${record.ownerRepo || ""}`); - } - } - for (const expectedPlugin of expectations.pluginHandoffs) { - const plugin = plugins.find(candidate => candidate.pluginId === expectedPlugin); - if (!plugin) { - errors.push(`plugins:${expectedPlugin}:missing`); - continue; - } - if (!plugin.handoffPath) { - errors.push(`plugins:${expectedPlugin}:handoffPath:missing`); - } - validateExportedHandoff(plugin.handoffExportPath, directory, `plugins:${expectedPlugin}:handoff`, errors); - } - for (const expectedProvider of expectations.providers) { - if (!providers.some(provider => provider.providerId === expectedProvider)) errors.push(`providers:${expectedProvider}:missing`); - } - for (const expectation of expectations.providerSurfaces) { - const provider = providers.find(candidate => candidate.providerId === expectation.providerId); - if (!provider) { - errors.push(`providers:${expectation.providerId}:missing`); - continue; - } - if (!Array.isArray(provider.surfaces) || !provider.surfaces.includes(expectation.surfaceId)) { - errors.push(`providers:${expectation.providerId}:surface:${expectation.surfaceId}:missing`); - } - } - for (const expectation of expectations.providerSurfaceKinds) { - const provider = providers.find(candidate => candidate.providerId === expectation.providerId); - if (!provider) { - errors.push(`providers:${expectation.providerId}:missing`); - continue; - } - const surfaceKinds = Array.isArray(provider.surfaceKinds) ? provider.surfaceKinds : []; - const surface = surfaceKinds.find(candidate => candidate.surfaceId === expectation.surfaceId); - if (!surface) { - errors.push(`providers:${expectation.providerId}:surfaceKind:${expectation.surfaceId}:missing`); - } else if (surface.surfaceKind !== expectation.surfaceKind) { - errors.push(`providers:${expectation.providerId}:surfaceKind:${expectation.surfaceId}:expected ${expectation.surfaceKind} got ${surface.surfaceKind || ""}`); - } - } - for (const expectation of expectations.providerSurfaceFields) { - const provider = providers.find(candidate => candidate.providerId === expectation.providerId); - if (!provider) { - errors.push(`providers:${expectation.providerId}:missing`); - continue; - } - const surfaceContracts = Array.isArray(provider.surfaceContracts) ? provider.surfaceContracts : []; - const surface = surfaceContracts.find(candidate => candidate.surfaceId === expectation.surfaceId); - if (!surface) { - errors.push(`providers:${expectation.providerId}:surfaceContract:${expectation.surfaceId}:missing`); - continue; - } - const actual = readNestedField(surface, expectation.fieldPath); - if (actual !== expectation.value) { - errors.push(`providers:${expectation.providerId}:surfaceContract:${expectation.surfaceId}.${expectation.fieldPath}:expected ${expectation.value} got ${actual || ""}`); - } - } - for (const expectation of expectations.interactiveWorldSurfaces) { - const surface = interactiveWorldSurfaces.find(candidate => - candidate.providerId === expectation.providerId && - candidate.surfaceId === expectation.surfaceId); - if (!surface) { - errors.push(`interactiveWorldSurfaces:${expectation.providerId}:${expectation.surfaceId}:missing`); - continue; - } - if (surface.ownerRepo !== expectation.ownerRepo) { - errors.push(`interactiveWorldSurfaces:${expectation.providerId}:${expectation.surfaceId}:ownerRepo:expected ${expectation.ownerRepo} got ${surface.ownerRepo || ""}`); - } - if (!Array.isArray(surface.loweringTargets) || !surface.loweringTargets.includes(expectation.targetId)) { - errors.push(`interactiveWorldSurfaces:${expectation.providerId}:${expectation.surfaceId}:target:${expectation.targetId}:missing`); - } - for (const field of ["projectionKind", "commandBoundary", "receiptSchema", "ownership"]) { - if (!surface[field]) errors.push(`interactiveWorldSurfaces:${expectation.providerId}:${expectation.surfaceId}:${field}:missing`); - } - } - for (const expectation of expectations.providerPluginRequirements) { - const requirement = providerPluginRequirementCoverage.find(candidate => - candidate.providerId === expectation.providerId && - candidate.surfaceId === expectation.surfaceId && - candidate.pluginId === expectation.pluginId); - if (!requirement) { - errors.push(`providerPluginRequirementCoverage:${expectation.providerId}:${expectation.surfaceId}:${expectation.pluginId}:missing`); - continue; - } - if (requirement.status !== expectation.status) { - errors.push(`providerPluginRequirementCoverage:${expectation.providerId}:${expectation.surfaceId}:${expectation.pluginId}:status:expected ${expectation.status} got ${requirement.status || ""}`); - } - if (requirement.pluginOwnerRepo !== expectation.pluginOwnerRepo) { - errors.push(`providerPluginRequirementCoverage:${expectation.providerId}:${expectation.surfaceId}:${expectation.pluginId}:pluginOwnerRepo:expected ${expectation.pluginOwnerRepo} got ${requirement.pluginOwnerRepo || ""}`); - } - if (expectation.availability && requirement.availability !== expectation.availability) { - errors.push(`providerPluginRequirementCoverage:${expectation.providerId}:${expectation.surfaceId}:${expectation.pluginId}:availability:expected ${expectation.availability} got ${requirement.availability || ""}`); - } - } - for (const expectation of expectations.independentNestedPlugins) { - const parentRequirement = providerPluginRequirementCoverage.find(candidate => - candidate.providerId === expectation.providerId && - candidate.surfaceId === expectation.surfaceId && - candidate.pluginId === expectation.parentPluginId); - const nestedRequirement = providerPluginRequirementCoverage.find(candidate => - candidate.providerId === expectation.providerId && - candidate.surfaceId === expectation.surfaceId && - candidate.pluginId === expectation.nestedPluginId); - if (!parentRequirement) { - errors.push(`independentNestedPlugin:${expectation.providerId}:${expectation.surfaceId}:${expectation.parentPluginId}:parent:missing`); - continue; - } - if (!nestedRequirement) { - errors.push(`independentNestedPlugin:${expectation.providerId}:${expectation.surfaceId}:${expectation.nestedPluginId}:nested:missing`); - continue; - } - if (parentRequirement.availability !== "required") { - errors.push(`independentNestedPlugin:${expectation.providerId}:${expectation.surfaceId}:${expectation.parentPluginId}:parent.availability:expected required got ${parentRequirement.availability || ""}`); - } - if (nestedRequirement.availability !== "optional-nested") { - errors.push(`independentNestedPlugin:${expectation.providerId}:${expectation.surfaceId}:${expectation.nestedPluginId}:nested.availability:expected optional-nested got ${nestedRequirement.availability || ""}`); - } - if (!["optional-satisfied", "optional-degraded"].includes(nestedRequirement.status)) { - errors.push(`independentNestedPlugin:${expectation.providerId}:${expectation.surfaceId}:${expectation.nestedPluginId}:nested.status:expected optional-satisfied or optional-degraded got ${nestedRequirement.status || ""}`); - } - if (nestedRequirement.pluginOwnerRepo !== expectation.nestedOwnerRepo) { - errors.push(`independentNestedPlugin:${expectation.providerId}:${expectation.surfaceId}:${expectation.nestedPluginId}:nested.pluginOwnerRepo:expected ${expectation.nestedOwnerRepo} got ${nestedRequirement.pluginOwnerRepo || ""}`); - } - if (nestedRequirement.pluginOwnerRepo === parentRequirement.pluginOwnerRepo) { - errors.push(`independentNestedPlugin:${expectation.providerId}:${expectation.surfaceId}:${expectation.nestedPluginId}:owner:expected owner distinct from parent ${parentRequirement.pluginOwnerRepo || ""}`); - } - if (Array.isArray(nestedRequirement.requiredCapabilities) && nestedRequirement.requiredCapabilities.length) { - errors.push(`independentNestedPlugin:${expectation.providerId}:${expectation.surfaceId}:${expectation.nestedPluginId}:requiredCapabilities:expected empty got ${nestedRequirement.requiredCapabilities.join(",")}`); - } - if (!Array.isArray(nestedRequirement.optionalCapabilities) || !nestedRequirement.optionalCapabilities.length) { - errors.push(`independentNestedPlugin:${expectation.providerId}:${expectation.surfaceId}:${expectation.nestedPluginId}:optionalCapabilities:missing`); - } - } - for (const expectation of expectations.providerCommands) { - const provider = providers.find(candidate => candidate.providerId === expectation.providerId); - if (!provider) { - errors.push(`providers:${expectation.providerId}:missing`); - continue; - } - if (!Array.isArray(provider.commands) || !provider.commands.includes(expectation.command)) { - errors.push(`providers:${expectation.providerId}:command:${expectation.command}:missing`); - } - } - for (const expectation of expectations.providerReceiptStates) { - const provider = providers.find(candidate => candidate.providerId === expectation.providerId); - if (!provider) { - errors.push(`providers:${expectation.providerId}:missing`); - continue; - } - if (!Array.isArray(provider.receiptStates) || !provider.receiptStates.includes(expectation.state)) { - errors.push(`providers:${expectation.providerId}:receiptState:${expectation.state}:missing`); - } - } - for (const expectedProvider of expectations.providerHandoffs) { - const provider = providers.find(candidate => candidate.providerId === expectedProvider); - if (!provider) { - errors.push(`providers:${expectedProvider}:missing`); - continue; - } - if (!provider.handoffPath) { - errors.push(`providers:${expectedProvider}:handoffPath:missing`); - } - validateExportedHandoff(provider.handoffExportPath, directory, `providers:${expectedProvider}:handoff`, errors); - } - for (const expectedRuntime of expectations.runtimes) { - if (!runtimes.some(runtime => runtime.runtimeId === expectedRuntime)) errors.push(`runtimes:${expectedRuntime}:missing`); - } - for (const expectation of expectations.runtimeStatuses) { - const runtime = runtimes.find(candidate => candidate.runtimeId === expectation.runtimeId); - if (!runtime) { - errors.push(`runtimes:${expectation.runtimeId}:missing`); - continue; - } - if (runtime.status !== expectation.status) { - errors.push(`runtimes:${expectation.runtimeId}:status:expected ${expectation.status} got ${runtime.status || ""}`); - } - } - for (const expectation of expectations.runtimeFeatures) { - const runtime = runtimes.find(candidate => candidate.runtimeId === expectation.runtimeId); - if (!runtime) { - errors.push(`runtimes:${expectation.runtimeId}:missing`); - continue; - } - if (!Array.isArray(runtime.supportedFeatures) || !runtime.supportedFeatures.includes(expectation.feature)) { - errors.push(`runtimes:${expectation.runtimeId}:feature:${expectation.feature}:missing`); - } - } - for (const expectation of expectations.runtimeWorldTargets) { - const runtime = runtimes.find(candidate => candidate.runtimeId === expectation.runtimeId); - if (!runtime) { - errors.push(`runtimes:${expectation.runtimeId}:missing`); - continue; - } - const worldTargets = Array.isArray(runtime.worldSurfaceLowering) ? runtime.worldSurfaceLowering : []; - if (!worldTargets.some(candidate => candidate.targetId === expectation.targetId)) { - errors.push(`runtimes:${expectation.runtimeId}:worldSurfaceLowering:${expectation.targetId}:missing`); - } - } - for (const expectation of expectations.runtimeWorldFields) { - const runtime = runtimes.find(candidate => candidate.runtimeId === expectation.runtimeId); - if (!runtime) { - errors.push(`runtimes:${expectation.runtimeId}:missing`); - continue; - } - const worldTargets = Array.isArray(runtime.worldSurfaceLowering) ? runtime.worldSurfaceLowering : []; - const target = worldTargets.find(candidate => candidate.targetId === expectation.targetId); - if (!target) { - errors.push(`runtimes:${expectation.runtimeId}:worldSurfaceLowering:${expectation.targetId}:missing`); - continue; - } - const actual = readNestedField(target, expectation.fieldPath); - if (actual !== expectation.value) { - errors.push(`runtimes:${expectation.runtimeId}:worldSurfaceLowering:${expectation.targetId}.${expectation.fieldPath}:expected ${expectation.value} got ${actual || ""}`); - } - } - for (const expectation of expectations.runtimeCommandSchemas) { - const runtime = runtimes.find(candidate => candidate.runtimeId === expectation.runtimeId); - if (!runtime) { - errors.push(`runtimes:${expectation.runtimeId}:missing`); - continue; - } - if (runtime.commandTransportSchema !== expectation.schema) { - errors.push(`runtimes:${expectation.runtimeId}:commandTransportSchema:expected ${expectation.schema} got ${runtime.commandTransportSchema || ""}`); - } - } - for (const expectation of expectations.runtimeCaptureStatuses) { - const runtime = runtimes.find(candidate => candidate.runtimeId === expectation.runtimeId); - if (!runtime) { - errors.push(`runtimes:${expectation.runtimeId}:missing`); - continue; - } - if (runtime.captureStatus !== expectation.status) { - errors.push(`runtimes:${expectation.runtimeId}:captureStatus:expected ${expectation.status} got ${runtime.captureStatus || ""}`); - } - } - for (const expectation of expectations.runtimeCaptureArtifacts) { - const runtime = runtimes.find(candidate => candidate.runtimeId === expectation.runtimeId); - if (!runtime) { - errors.push(`runtimes:${expectation.runtimeId}:missing`); - continue; - } - const artifacts = Array.isArray(runtime.captureArtifacts) ? runtime.captureArtifacts : []; - const artifact = artifacts.find(candidate => - candidate.kind === expectation.kind - && candidate.schema === expectation.schema - && candidate.providerId === expectation.providerId - && candidate.surfaceId === expectation.surfaceId); - if (!artifact) { - errors.push(`runtimes:${expectation.runtimeId}:captureArtifacts:${expectation.kind}:${expectation.schema}:${expectation.providerId}:${expectation.surfaceId}:missing`); - } - const artifactErrors = Array.isArray(runtime.captureArtifactErrors) ? runtime.captureArtifactErrors : []; - if (artifactErrors.length) { - errors.push(`runtimes:${expectation.runtimeId}:captureArtifactErrors:${artifactErrors.join(",")}`); - } - } - for (const expectation of expectations.runtimeLifecycleStatuses) { - const runtime = runtimes.find(candidate => candidate.runtimeId === expectation.runtimeId); - if (!runtime) { - errors.push(`runtimes:${expectation.runtimeId}:missing`); - continue; - } - const stage = runtime.lifecycle?.[expectation.stage]; - if (!stage) { - errors.push(`runtimes:${expectation.runtimeId}:lifecycle.${expectation.stage}:missing`); - } else if (stage.status !== expectation.status) { - errors.push(`runtimes:${expectation.runtimeId}:lifecycle.${expectation.stage}.status:expected ${expectation.status} got ${stage.status || ""}`); - } - } - for (const expectation of expectations.runtimeLifecyclePendingProofs) { - const runtime = runtimes.find(candidate => candidate.runtimeId === expectation.runtimeId); - if (!runtime) { - errors.push(`runtimes:${expectation.runtimeId}:missing`); - continue; - } - const stage = runtime.lifecycle?.[expectation.stage]; - const pendingProofs = Array.isArray(stage?.pendingProofs) ? stage.pendingProofs : []; - if (!pendingProofs.some(proof => proof.includes(expectation.pendingProof))) { - errors.push(`runtimes:${expectation.runtimeId}:lifecycle.${expectation.stage}.pendingProof:${expectation.pendingProof}:missing`); - } - } - for (const expectation of expectations.runtimeLifecycleFields) { - const runtime = runtimes.find(candidate => candidate.runtimeId === expectation.runtimeId); - if (!runtime) { - errors.push(`runtimes:${expectation.runtimeId}:missing`); - continue; - } - const actual = readNestedField(runtime.lifecycle?.[expectation.stage], expectation.fieldPath); - if (actual !== expectation.value) { - errors.push(`runtimes:${expectation.runtimeId}:lifecycle.${expectation.stage}.${expectation.fieldPath}:expected ${expectation.value} got ${actual || ""}`); - } - } - for (const expectedRuntime of expectations.runtimeHandoffs) { - const runtime = runtimes.find(candidate => candidate.runtimeId === expectedRuntime); - if (!runtime) { - errors.push(`runtimes:${expectedRuntime}:missing`); - continue; - } - if (!runtime.splitHandoffPath) { - errors.push(`runtimes:${expectedRuntime}:splitHandoffPath:missing`); - } - validateExportedHandoff(runtime.splitHandoffExportPath, directory, `runtimes:${expectedRuntime}:splitHandoff`, errors); - } - for (const expectedScenario of expectations.scenarios) { - if (!providers.some(provider => provider.scenarioId === expectedScenario)) errors.push(`providers:scenario:${expectedScenario}:missing`); - } - for (const expectedSplitTarget of expectations.splitTargets) { - if (!splitTargets.some(target => target.id === expectedSplitTarget)) errors.push(`splitTargets:${expectedSplitTarget}:missing`); - } - for (const expectation of expectations.splitTargetStatuses) { - const target = splitTargets.find(candidate => candidate.id === expectation.targetId); - if (!target) { - errors.push(`splitTargets:${expectation.targetId}:missing`); - continue; - } - if (target.status !== expectation.status) { - errors.push(`splitTargets:${expectation.targetId}:status:expected ${expectation.status} got ${target.status || ""}`); - } - } - for (const expectation of expectations.splitTargetBlockers) { - const target = splitTargets.find(candidate => candidate.id === expectation.targetId); - if (!target) { - errors.push(`splitTargets:${expectation.targetId}:missing`); - continue; - } - const blockers = Array.isArray(target.blockers) ? target.blockers : []; - if (!blockers.some(blocker => blocker.includes(expectation.blocker))) { - errors.push(`splitTargets:${expectation.targetId}:blocker:${expectation.blocker}:missing`); - } - } - for (const expectation of expectations.splitTargetBlockerRecords) { - const blocker = splitTargetBlockers.find(candidate => - candidate.targetId === expectation.targetId && - candidate.kind === expectation.kind && - candidate.subject === expectation.subject); - if (!blocker) { - errors.push(`splitTargetBlockers:${expectation.targetId}:${expectation.kind}:${expectation.subject}:missing`); - } - } - for (const expectation of expectations.splitHandoffMoves) { - const record = splitHandoffMoveCoverage.find(candidate => - candidate.splitTarget === expectation.splitTarget && - candidate.runtimeId === expectation.runtimeId && - candidate.moveSetId === expectation.moveSetId && - candidate.pathKind === expectation.pathKind && - candidate.status === expectation.status && - candidate.sourcePath?.includes(expectation.pathSubstring)); - if (!record) { - errors.push(`splitHandoffMoveCoverage:${expectation.splitTarget}:${expectation.runtimeId}:${expectation.moveSetId}:${expectation.pathKind}:${expectation.status}:${expectation.pathSubstring}:missing`); - } - } - for (const expectation of expectations.pluginHandoffMoves) { - const record = pluginHandoffMoveCoverage.find(candidate => - candidate.pluginId === expectation.pluginId && - candidate.moveSetId === expectation.moveSetId && - candidate.pathKind === expectation.pathKind && - candidate.status === expectation.status && - candidate.sourcePath?.includes(expectation.pathSubstring)); - if (!record) { - errors.push(`pluginHandoffMoveCoverage:${expectation.pluginId}:${expectation.moveSetId}:${expectation.pathKind}:${expectation.status}:${expectation.pathSubstring}:missing`); - } - } - for (const expectation of expectations.providerHandoffMoves) { - const record = providerHandoffMoveCoverage.find(candidate => - candidate.providerId === expectation.providerId && - candidate.moveSetId === expectation.moveSetId && - candidate.pathKind === expectation.pathKind && - candidate.status === expectation.status && - candidate.sourcePath?.includes(expectation.pathSubstring)); - if (!record) { - errors.push(`providerHandoffMoveCoverage:${expectation.providerId}:${expectation.moveSetId}:${expectation.pathKind}:${expectation.status}:${expectation.pathSubstring}:missing`); - } - } - for (const expectation of expectations.splitTargetProofs) { - const target = splitTargets.find(candidate => candidate.id === expectation.targetId); - if (!target) { - errors.push(`splitTargets:${expectation.targetId}:missing`); - continue; - } - const proofs = Array.isArray(target.proofs) ? target.proofs : []; - const proof = proofs.find(candidate => candidate.description?.includes(expectation.proof)); - if (!proof) { - errors.push(`splitTargets:${expectation.targetId}:proof:${expectation.proof}:missing`); - } else if (proof.status !== "passed") { - errors.push(`splitTargets:${expectation.targetId}:proof:${expectation.proof}:expected passed got ${proof.status || ""}`); - } - } - - if (!plugins.some(plugin => plugin.pluginId === "sai.vn" && plugin.abiFixturePath)) { - errors.push("plugins:sai.vn:abiFixturePath:missing"); - } - if (!providers.some(provider => provider.providerId === "aetheria" && provider.scenarioPath)) { - errors.push("providers:aetheria:scenarioPath:missing"); - } - if (!runtimes.some(runtime => runtime.runtimeId === "unity-uitoolkit" && runtime.commandTransportSchema === "gamecult.eve.command_invocation.v1")) { - errors.push("runtimes:unity-uitoolkit:commandTransportSchema:missing"); - } - if (!splitTargets.some(target => target.id === "EveUnity")) { - errors.push("splitTargets:EveUnity:missing"); - } -} - -function readFileSyncUtf8(filePath) { - return readFileSync(filePath, "utf8"); -} - -function validateExportedHandoff(exportPath, directory, label, errors) { - if (!exportPath) { - errors.push(`${label}:exportPath:missing`); - return; - } - if (!existsSync(path.join(directory, exportPath))) { - errors.push(`${label}:exportPath:${exportPath}:missing`); - } -} - -function validateCapabilityMatrix(matrix, packs, plugins, providers, runtimes, splitTargets, errors) { - if (!matrix) { - errors.push("capabilityMatrix:missing"); - return; - } - if (matrix.schema !== "gamecult.eve.capability_matrix.v1") { - errors.push(`capabilityMatrix:schema:expected gamecult.eve.capability_matrix.v1 got ${matrix.schema || ""}`); - } - if ((matrix.packs || []).length !== packs.length) { - errors.push(`capabilityMatrix:packs:expected ${packs.length} got ${(matrix.packs || []).length}`); - } - if ((matrix.plugins || []).length !== plugins.length) { - errors.push(`capabilityMatrix:plugins:expected ${plugins.length} got ${(matrix.plugins || []).length}`); - } - if ((matrix.providers || []).length !== providers.length) { - errors.push(`capabilityMatrix:providers:expected ${providers.length} got ${(matrix.providers || []).length}`); - } - if ((matrix.runtimes || []).length !== runtimes.length) { - errors.push(`capabilityMatrix:runtimes:expected ${runtimes.length} got ${(matrix.runtimes || []).length}`); - } - if ((matrix.splitTargets || []).length !== splitTargets.length) { - errors.push(`capabilityMatrix:splitTargets:expected ${splitTargets.length} got ${(matrix.splitTargets || []).length}`); - } - for (const packId of ["core", "plugin", "provider", "runtime"]) { - if (!(matrix.packs || []).some(pack => pack.packId === packId)) { - errors.push(`capabilityMatrix:packs:${packId}:missing`); - } - } - for (const plugin of plugins) { - if (!(matrix.plugins || []).some(candidate => candidate.pluginId === plugin.pluginId)) { - errors.push(`capabilityMatrix:plugins:${plugin.pluginId}:missing`); - } - } - for (const provider of providers) { - if (!(matrix.providers || []).some(candidate => candidate.providerId === provider.providerId)) { - errors.push(`capabilityMatrix:providers:${provider.providerId}:missing`); - } - } - for (const runtime of runtimes) { - if (!(matrix.runtimes || []).some(candidate => candidate.runtimeId === runtime.runtimeId)) { - errors.push(`capabilityMatrix:runtimes:${runtime.runtimeId}:missing`); - } - } -} - -function validateCapabilityGaps(gaps, errors) { - if (!Array.isArray(gaps)) { - errors.push("capabilityGaps:missing"); - return; - } - for (const [index, gap] of gaps.entries()) { - for (const field of ["kind", "subjectId", "gap", "severity"]) { - if (!gap?.[field]) errors.push(`capabilityGaps:${index}:${field}:missing`); - } - } -} - -function validateRuntimePluginProjectionCoverage(records, errors) { - if (!Array.isArray(records)) { - errors.push("runtimePluginProjectionCoverage:missing"); - return; - } - for (const [index, record] of records.entries()) { - for (const field of ["runtimeId", "runtimeStatus", "runtimeOwnerRepo", "pluginId", "pluginOwnerRepo", "status", "severity"]) { - if (!record?.[field]) errors.push(`runtimePluginProjectionCoverage:${index}:${field}:missing`); - } - for (const field of ["capabilities", "requiredFixtures", "pluginFixtures"]) { - if (!Array.isArray(record?.[field])) errors.push(`runtimePluginProjectionCoverage:${index}:${field}:expected array`); - } - if (record?.status === "supported" && !record.capabilities.length) { - errors.push(`runtimePluginProjectionCoverage:${index}:capabilities:missing`); - } - if (record?.status === "unsupported" && !record.reason) { - errors.push(`runtimePluginProjectionCoverage:${index}:reason:missing`); - } - } -} - -function validateProviderRuntimePluginProjectionCoverage(records, errors) { - if (!Array.isArray(records)) { - errors.push("providerRuntimePluginProjectionCoverage:missing"); - return; - } - for (const [index, record] of records.entries()) { - for (const field of ["providerId", "providerOwnerRepo", "surfaceId", "fixtureId", "runtimeId", "runtimeStatus", "runtimeOwnerRepo", "pluginId", "availability", "status", "severity"]) { - if (!record?.[field]) errors.push(`providerRuntimePluginProjectionCoverage:${index}:${field}:missing`); - } - for (const field of ["requiredCapabilities", "optionalCapabilities", "runtimeCapabilities", "missingRuntimeRequiredCapabilities", "missingRuntimeOptionalCapabilities"]) { - if (!Array.isArray(record?.[field])) errors.push(`providerRuntimePluginProjectionCoverage:${index}:${field}:expected array`); - } - if (record?.severity === "blocker" && record.availability !== "required") { - errors.push(`providerRuntimePluginProjectionCoverage:${index}:severity:optional entries cannot be blocker`); - } - } -} - -function validateRuntimePluginProjectionGaps(gaps, errors) { - if (!Array.isArray(gaps)) { - errors.push("runtimePluginProjectionGaps:missing"); - return; - } - for (const [index, gap] of gaps.entries()) { - for (const field of ["runtimeId", "ownerRepo", "pluginId", "severity", "reason"]) { - if (!gap?.[field]) errors.push(`runtimePluginProjectionGaps:${index}:${field}:missing`); - } - for (const field of ["requiredFixtures", "pluginFixtures"]) { - if (!Array.isArray(gap?.[field])) errors.push(`runtimePluginProjectionGaps:${index}:${field}:expected array`); - } - } -} - -function validateSplitTargetBlockers(blockers, errors) { - if (!Array.isArray(blockers)) { - errors.push("splitTargetBlockers:missing"); - return; - } - for (const [index, blocker] of blockers.entries()) { - for (const field of ["targetId", "ownerRepo", "kind", "severity", "subject", "text"]) { - if (!blocker?.[field]) errors.push(`splitTargetBlockers:${index}:${field}:missing`); - } - } -} - -function validateSplitHandoffMoveCoverage(records, errors) { - if (!Array.isArray(records)) { - errors.push("splitHandoffMoveCoverage:missing"); - return; - } - for (const [index, record] of records.entries()) { - for (const field of ["splitTarget", "runtimeId", "runtimeOwnerRepo", "handoffPath", "handoffExportPath", "moveSetId", "stage", "destinationOwner", "replacementProof", "pathKind", "sourcePath", "status", "severity"]) { - if (!record?.[field]) errors.push(`splitHandoffMoveCoverage:${index}:${field}:missing`); - } - if (typeof record?.pathExists !== "boolean") { - errors.push(`splitHandoffMoveCoverage:${index}:pathExists:expected boolean`); - } - } -} - -function validatePluginHandoffMoveCoverage(records, errors) { - if (!Array.isArray(records)) { - errors.push("pluginHandoffMoveCoverage:missing"); - return; - } - for (const [index, record] of records.entries()) { - for (const field of ["pluginId", "pluginOwnerRepo", "splitTarget", "handoffPath", "handoffExportPath", "moveSetId", "destinationOwner", "replacementProof", "pathKind", "sourcePath", "status", "severity"]) { - if (!record?.[field]) errors.push(`pluginHandoffMoveCoverage:${index}:${field}:missing`); - } - if (typeof record?.pathExists !== "boolean") { - errors.push(`pluginHandoffMoveCoverage:${index}:pathExists:expected boolean`); - } - } -} - -function validateProviderHandoffMoveCoverage(records, errors) { - if (!Array.isArray(records)) { - errors.push("providerHandoffMoveCoverage:missing"); - return; - } - for (const [index, record] of records.entries()) { - for (const field of ["providerId", "providerOwnerRepo", "handoffPath", "handoffExportPath", "moveSetId", "destinationOwner", "replacementProof", "pathKind", "sourcePath", "status", "severity"]) { - if (!record?.[field]) errors.push(`providerHandoffMoveCoverage:${index}:${field}:missing`); - } - if (typeof record?.pathExists !== "boolean") { - errors.push(`providerHandoffMoveCoverage:${index}:pathExists:expected boolean`); - } - } -} - -function validateWorldSurfaceLoweringCoverage(records, errors) { - if (!Array.isArray(records)) { - errors.push("worldSurfaceLoweringCoverage:missing"); - return; - } - for (const [index, record] of records.entries()) { - for (const field of ["providerId", "providerOwnerRepo", "surfaceId", "targetId", "status", "severity", "runtimeId", "runtimeOwnerRepo"]) { - if (!record?.[field]) errors.push(`worldSurfaceLoweringCoverage:${index}:${field}:missing`); - } - } -} - -function validateCommandBoundaryCoverage(records, errors) { - if (!Array.isArray(records)) { - errors.push("commandBoundaryCoverage:missing"); - return; - } - for (const [index, record] of records.entries()) { - for (const field of ["providerId", "providerOwnerRepo", "surfaceId", "targetId", "runtimeId", "runtimeOwnerRepo", "status", "severity", "commandBoundary", "receiptSchema"]) { - if (!record?.[field]) errors.push(`commandBoundaryCoverage:${index}:${field}:missing`); - } - if (record?.status === "covered" && record.runtimeCommandSchema !== "gamecult.eve.command_invocation.v1") { - errors.push(`commandBoundaryCoverage:${index}:runtimeCommandSchema:expected gamecult.eve.command_invocation.v1 got ${record.runtimeCommandSchema || ""}`); - } - } -} - -function validateScreenshotComparisonMetrics(records, errors) { - if (!Array.isArray(records)) { - errors.push("screenshotComparisonMetrics:missing"); - return; - } - if (!records.length) { - errors.push("screenshotComparisonMetrics:empty"); - return; - } - for (const [index, record] of records.entries()) { - for (const field of ["runtimeId", "runtimeOwnerRepo", "fixtureId", "metricKind", "status", "evidenceLayer", "detail"]) { - if (!record?.[field]) errors.push(`screenshotComparisonMetrics:${index}:${field}:missing`); - } - if (typeof record?.score !== "number") { - errors.push(`screenshotComparisonMetrics:${index}:score:expected number`); - } - if (!["structure", "color-tokens", "bounding-boxes", "text-presence"].includes(record?.metricKind)) { - errors.push(`screenshotComparisonMetrics:${index}:metricKind:unexpected ${record?.metricKind || ""}`); - } - } -} - -function validateRuntimeCaptureProbeCoverage(records, errors) { - if (!Array.isArray(records)) { - errors.push("runtimeCaptureProbeCoverage:missing"); - return; - } - if (!records.length) { - errors.push("runtimeCaptureProbeCoverage:empty"); - return; - } - for (const [index, record] of records.entries()) { - for (const field of ["runtimeId", "runtimeOwnerRepo", "captureOwnerRepo", "status", "severity", "detail"]) { - if (!record?.[field]) errors.push(`runtimeCaptureProbeCoverage:${index}:${field}:missing`); - } - for (const field of ["pendingProofs", "errors"]) { - if (!Array.isArray(record?.[field])) errors.push(`runtimeCaptureProbeCoverage:${index}:${field}:expected array`); - } - if (record?.currentArtifactPath && !record.currentArtifactKind) { - errors.push(`runtimeCaptureProbeCoverage:${index}:currentArtifactKind:missing`); - } - if (record?.contractArtifactKind && !record.contractRequestSchema) { - errors.push(`runtimeCaptureProbeCoverage:${index}:contractRequestSchema:missing`); - } - } -} - -function validateWorldSurfaceLoweringGaps(gaps, errors) { - if (!Array.isArray(gaps)) { - errors.push("worldSurfaceLoweringGaps:missing"); - return; - } - for (const [index, gap] of gaps.entries()) { - for (const field of ["providerId", "surfaceId", "targetId", "ownerRepo", "runtimeId", "severity"]) { - if (!gap?.[field]) errors.push(`worldSurfaceLoweringGaps:${index}:${field}:missing`); - } - } -} - -function validatePluginRecords(plugins, errors) { - for (const plugin of plugins) { - const label = `plugins:${plugin.pluginId || "unknown"}`; - for (const field of ["pluginId", "status", "ownerRepo", "manifestPath", "advertisementPath", "abiFixturePath"]) { - if (!plugin?.[field]) errors.push(`${label}:${field}:missing`); - } - if (plugin.status !== "active" && !plugin.splitTarget) errors.push(`${label}:splitTarget:missing`); - for (const field of ["capabilities", "abiOperations", "optionalPlugins"]) { - if (!Array.isArray(plugin?.[field])) errors.push(`${label}:${field}:expected array`); - } - if (!Array.isArray(plugin?.abiOperationContracts)) { - errors.push(`${label}:abiOperationContracts:expected array`); - } - for (const contract of plugin?.abiOperationContracts || []) { - if (!contract.operation) errors.push(`${label}:abiOperationContracts:operation:missing`); - if (!contract.input || typeof contract.input !== "object" || Array.isArray(contract.input)) { - errors.push(`${label}:abiOperationContracts:${contract.operation || "unknown"}:input:expected object`); - } - if (!contract.expect || typeof contract.expect !== "object" || Array.isArray(contract.expect)) { - errors.push(`${label}:abiOperationContracts:${contract.operation || "unknown"}:expect:expected object`); - } - } - } -} - -function validatePluginWitnesses(witnesses, directory, errors) { - if (!Array.isArray(witnesses)) return; - for (const [index, witness] of witnesses.entries()) { - for (const field of ["witnessId", "pluginId", "ownerRepo", "status", "transport", "operationCount", "witnessExportPath"]) { - if (witness?.[field] === undefined || witness?.[field] === "") errors.push(`pluginWitnesses:${index}:${field}:missing`); - } - const witnessPath = witness?.witnessExportPath ? path.join(directory, witness.witnessExportPath) : ""; - if (!witnessPath || !existsSync(witnessPath)) { - errors.push(`pluginWitnesses:${index}:witnessExportPath:missing`); - continue; - } - const document = JSON.parse(readFileSyncUtf8(witnessPath)); - if (document.schema !== "gamecult.eve.plugin_witness.v1") errors.push(`pluginWitnesses:${index}:schema:unexpected`); - if ((document.operations || []).length !== witness.operationCount) errors.push(`pluginWitnesses:${index}:operationCount:mismatch`); - } -} - -function validatePluginAbiOperationCoverage(records, errors) { - if (!Array.isArray(records)) { - errors.push("pluginAbiOperationCoverage:missing"); - return; - } - for (const [index, record] of records.entries()) { - for (const field of ["pluginId", "ownerRepo", "status", "operation", "abiFixturePath"]) { - if (!record?.[field]) errors.push(`pluginAbiOperationCoverage:${index}:${field}:missing`); - } - for (const field of ["inputKeys", "expectKeys"]) { - if (!Array.isArray(record?.[field])) errors.push(`pluginAbiOperationCoverage:${index}:${field}:expected array`); - } - } -} - -function validateProviderRecords(providers, errors) { - for (const provider of providers) { - const label = `providers:${provider.providerId || "unknown"}`; - for (const field of ["providerId", "status", "ownerRepo", "advertisementPath"]) { - if (!provider?.[field]) errors.push(`${label}:${field}:missing`); - } - for (const field of ["surfaces", "surfaceKinds", "surfaceContracts", "commands", "pluginRequirements", "receiptStates"]) { - if (!Array.isArray(provider?.[field])) errors.push(`${label}:${field}:expected array`); - } - } -} - -function validateProviderPluginRequirementCoverage(records, errors) { - if (!Array.isArray(records)) { - errors.push("providerPluginRequirementCoverage:missing"); - return; - } - for (const [index, record] of records.entries()) { - for (const field of ["providerId", "providerOwnerRepo", "surfaceId", "pluginId", "pluginStatus", "availability", "status"]) { - if (!record?.[field]) errors.push(`providerPluginRequirementCoverage:${index}:${field}:missing`); - } - for (const field of ["requiredCapabilities", "optionalCapabilities", "missingRequiredCapabilities", "missingOptionalCapabilities"]) { - if (!Array.isArray(record?.[field])) errors.push(`providerPluginRequirementCoverage:${index}:${field}:expected array`); - } - } -} - -function validateLocalProviderCatalogs(catalogs, directory, errors) { - if (!Array.isArray(catalogs)) { - errors.push("localProviderCatalogs:missing"); - return; - } - for (const [index, catalog] of catalogs.entries()) { - for (const field of ["runtimeId", "ownerRepo", "catalogPath", "catalogExportPath", "schema", "purpose", "status"]) { - if (!catalog?.[field]) errors.push(`localProviderCatalogs:${index}:${field}:missing`); - } - for (const field of ["providerIds", "advertisementPaths", "errors"]) { - if (!Array.isArray(catalog?.[field])) errors.push(`localProviderCatalogs:${index}:${field}:expected array`); - } - if (typeof catalog?.surfaceCount !== "number") errors.push(`localProviderCatalogs:${index}:surfaceCount:expected number`); - if (catalog?.catalogExportPath && !existsSync(path.join(directory, catalog.catalogExportPath))) { - errors.push(`localProviderCatalogs:${index}:catalogExportPath:${catalog.catalogExportPath}:missing`); - } - } -} - -function validateInteractiveWorldSurfaces(surfaces, errors) { - if (!Array.isArray(surfaces)) { - errors.push("interactiveWorldSurfaces:missing"); - return; - } - for (const [index, surface] of surfaces.entries()) { - for (const field of ["providerId", "ownerRepo", "surfaceId", "surfaceKind", "interactionModel", "projectionKind", "commandBoundary", "receiptSchema", "ownership"]) { - if (!surface?.[field]) errors.push(`interactiveWorldSurfaces:${index}:${field}:missing`); - } - for (const field of ["stateSchemas", "loweringTargets"]) { - if (!Array.isArray(surface?.[field])) errors.push(`interactiveWorldSurfaces:${index}:${field}:expected array`); - } - } -} - -function validateRuntimeRecords(runtimes, errors) { - for (const runtime of runtimes) { - const label = `runtimes:${runtime.runtimeId || "unknown"}`; - for (const field of ["runtimeId", "status", "ownerRepo", "splitTarget", "captureStatus"]) { - if (!runtime?.[field]) errors.push(`${label}:${field}:missing`); - } - for (const field of ["supportedFeatures", "supportedPlugins", "unsupportedPlugins", "capabilityManifestErrors", "lifecycleErrors", "worldSurfaceLowering"]) { - if (!Array.isArray(runtime?.[field])) errors.push(`${label}:${field}:expected array`); - } - } -} - -function validateRuntimeWitnesses(witnesses, directory, errors) { - if (!Array.isArray(witnesses)) return; - for (const [index, witness] of witnesses.entries()) { - for (const field of ["witnessId", "runtimeId", "runtimeOwnerRepo", "providerId", "surfaceId", "projectionKind", "status", "cacheState", "witnessExportPath", "artifactCount"]) { - if (witness?.[field] === undefined || witness?.[field] === "") errors.push(`runtimeWitnesses:${index}:${field}:missing`); - } - const witnessPath = witness?.witnessExportPath ? path.join(directory, witness.witnessExportPath) : ""; - if (!witnessPath || !existsSync(witnessPath)) { - errors.push(`runtimeWitnesses:${index}:witnessExportPath:missing`); - continue; - } - const document = JSON.parse(readFileSyncUtf8(witnessPath)); - if (document.schema !== "gamecult.eve.runtime_witness.v1") errors.push(`runtimeWitnesses:${index}:schema:unexpected`); - if ((document.artifacts || []).length !== witness.artifactCount) errors.push(`runtimeWitnesses:${index}:artifactCount:mismatch`); - for (const artifact of document.artifacts || []) { - if (!artifact.exportPath || !existsSync(path.join(directory, artifact.exportPath))) { - errors.push(`runtimeWitnesses:${index}:artifact:${artifact.kind || "unknown"}:missing`); - } - } - } -} - -function validateSplitTargetRecords(splitTargets, errors) { - for (const target of splitTargets) { - const label = `splitTargets:${target.id || "unknown"}`; - for (const field of ["id", "ownerRepo", "status", "runtimeStatuses"]) { - if (!target?.[field]) errors.push(`${label}:${field}:missing`); - } - for (const field of ["runtimes", "requiredRuntimeStatuses", "requiredFeatures", "requiredPlugins", "proofs", "pendingProofs", "blockers", "blockerRecords"]) { - if (!Array.isArray(target?.[field])) errors.push(`${label}:${field}:expected array`); - } - } -} - -function validateSchemaCatalog(schemaCatalog, directory, expectedSchemas, errors) { - if (!Array.isArray(schemaCatalog)) { - errors.push("schemaCatalog:missing"); - return; - } - for (const [index, schema] of schemaCatalog.entries()) { - const label = `schemaCatalog:${index}`; - for (const field of ["schemaId", "path", "exportPath"]) { - if (!schema?.[field]) errors.push(`${label}:${field}:missing`); - } - if (schema?.exportPath && !existsSync(path.join(directory, schema.exportPath))) { - errors.push(`${label}:exportPath:${schema.exportPath}:missing`); - } - } - for (const schemaId of expectedSchemas) { - const schema = schemaCatalog.find(candidate => candidate.schemaId === schemaId); - if (!schema) { - errors.push(`schemaCatalog:${schemaId}:missing`); - continue; - } - if (!schema.exportPath || !existsSync(path.join(directory, schema.exportPath))) { - errors.push(`schemaCatalog:${schemaId}:exportPath:missing`); - } - } -} - -function capabilityGapText(gap) { - return [ - gap.kind, - gap.ownerRepo, - gap.subjectId, - gap.gap, - gap.severity, - gap.detail, - ].filter(Boolean).join(":"); -} - -function parseArguments(args) { - const exportPath = args[0] ? path.resolve(args[0]) : ""; - const expectations = { - packs: [], - schemas: [], - fixtures: [], - plugins: [], - pluginOperations: [], - pluginCapabilities: [], - pluginRuntimes: [], - pluginRuntimeTransports: [], - pluginRuntimeAuthorities: [], - pluginRuntimeFields: [], - pluginAbiFields: [], - pluginAbiOperationCoverage: [], - pluginHandoffs: [], - pluginWitnesses: [], - pluginHandoffMoves: [], - providers: [], - providerSurfaces: [], - providerSurfaceKinds: [], - providerSurfaceFields: [], - interactiveWorldSurfaces: [], - providerPluginRequirements: [], - independentNestedPlugins: [], - providerCommands: [], - providerReceiptStates: [], - providerHandoffs: [], - providerHandoffMoves: [], - runtimes: [], - runtimeStatuses: [], - runtimeFeatures: [], - runtimeWorldTargets: [], - runtimeWorldFields: [], - runtimeHandoffs: [], - runtimeCommandSchemas: [], - runtimeCaptureStatuses: [], - runtimeCaptureArtifacts: [], - runtimeWitnesses: [], - runtimeLifecycleStatuses: [], - runtimeLifecyclePendingProofs: [], - runtimeLifecycleFields: [], - scenarios: [], - splitTargets: [], - splitTargetStatuses: [], - splitTargetBlockers: [], - splitTargetBlockerRecords: [], - splitHandoffMoves: [], - splitTargetProofs: [], - capabilityMatrix: false, - capabilityGaps: [], - runtimePluginProjectionCoverage: [], - providerRuntimePluginProjectionCoverage: [], - runtimePluginGaps: [], - localProviderCatalogs: [], - localProviderCatalogProviders: [], - localProviderCatalogAdvertisements: [], - worldLoweringCoverage: [], - commandBoundaryCoverage: [], - worldLoweringGaps: [], - screenshotMetrics: [], - runtimeCaptureProbes: [], - conformanceHandoff: false, - }; - const optionTargets = new Map([ - ["--expect-pack", expectations.packs], - ["--expect-schema", expectations.schemas], - ["--expect-fixture", expectations.fixtures], - ["--expect-plugin", expectations.plugins], - ["--expect-provider", expectations.providers], - ["--expect-runtime", expectations.runtimes], - ["--expect-scenario", expectations.scenarios], - ["--expect-split-target", expectations.splitTargets], - ["--expect-plugin-operation", expectations.pluginOperations], - ["--expect-plugin-capability", expectations.pluginCapabilities], - ["--expect-plugin-runtime", expectations.pluginRuntimes], - ["--expect-plugin-runtime-transport", expectations.pluginRuntimeTransports], - ["--expect-plugin-runtime-authority", expectations.pluginRuntimeAuthorities], - ["--expect-plugin-runtime-field", expectations.pluginRuntimeFields], - ["--expect-plugin-abi-field", expectations.pluginAbiFields], - ["--expect-plugin-abi-operation-coverage", expectations.pluginAbiOperationCoverage], - ["--expect-plugin-handoff", expectations.pluginHandoffs], - ["--expect-plugin-witness", expectations.pluginWitnesses], - ["--expect-plugin-handoff-move", expectations.pluginHandoffMoves], - ["--expect-provider-surface", expectations.providerSurfaces], - ["--expect-provider-surface-kind", expectations.providerSurfaceKinds], - ["--expect-provider-surface-field", expectations.providerSurfaceFields], - ["--expect-interactive-world-surface", expectations.interactiveWorldSurfaces], - ["--expect-provider-plugin-requirement", expectations.providerPluginRequirements], - ["--expect-independent-nested-plugin", expectations.independentNestedPlugins], - ["--expect-provider-command", expectations.providerCommands], - ["--expect-provider-receipt-state", expectations.providerReceiptStates], - ["--expect-provider-handoff", expectations.providerHandoffs], - ["--expect-provider-handoff-move", expectations.providerHandoffMoves], - ["--expect-runtime-status", expectations.runtimeStatuses], - ["--expect-runtime-feature", expectations.runtimeFeatures], - ["--expect-runtime-world-target", expectations.runtimeWorldTargets], - ["--expect-runtime-world-field", expectations.runtimeWorldFields], - ["--expect-runtime-handoff", expectations.runtimeHandoffs], - ["--expect-runtime-command-schema", expectations.runtimeCommandSchemas], - ["--expect-runtime-capture-status", expectations.runtimeCaptureStatuses], - ["--expect-runtime-capture-artifact", expectations.runtimeCaptureArtifacts], - ["--expect-runtime-witness", expectations.runtimeWitnesses], - ["--expect-runtime-lifecycle-status", expectations.runtimeLifecycleStatuses], - ["--expect-runtime-lifecycle-pending", expectations.runtimeLifecyclePendingProofs], - ["--expect-runtime-lifecycle-field", expectations.runtimeLifecycleFields], - ["--expect-split-target-status", expectations.splitTargetStatuses], - ["--expect-split-target-blocker", expectations.splitTargetBlockers], - ["--expect-split-target-blocker-record", expectations.splitTargetBlockerRecords], - ["--expect-split-handoff-move", expectations.splitHandoffMoves], - ["--expect-split-target-proof", expectations.splitTargetProofs], - ["--expect-capability-gap", expectations.capabilityGaps], - ["--expect-runtime-plugin-projection", expectations.runtimePluginProjectionCoverage], - ["--expect-provider-runtime-plugin-projection", expectations.providerRuntimePluginProjectionCoverage], - ["--expect-runtime-plugin-gap", expectations.runtimePluginGaps], - ["--expect-local-provider-catalog", expectations.localProviderCatalogs], - ["--expect-local-provider-catalog-provider", expectations.localProviderCatalogProviders], - ["--expect-local-provider-catalog-advertisement", expectations.localProviderCatalogAdvertisements], - ["--expect-world-lowering-coverage", expectations.worldLoweringCoverage], - ["--expect-command-boundary-coverage", expectations.commandBoundaryCoverage], - ["--expect-world-lowering-gap", expectations.worldLoweringGaps], - ["--expect-screenshot-metric", expectations.screenshotMetrics], - ["--expect-runtime-capture-probe", expectations.runtimeCaptureProbes], - ]); - - for (let index = 1; index < args.length; index += 1) { - const option = args[index]; - if (option === "--expect-capability-matrix") { - expectations.capabilityMatrix = true; - continue; - } - if (option === "--expect-conformance-handoff") { - expectations.conformanceHandoff = true; - continue; - } - const target = optionTargets.get(option); - if (!target) { - console.error(`Unknown option: ${option}`); - process.exit(2); - } - const value = args[index + 1]; - if (!value || value.startsWith("--")) { - console.error(`Missing value for option: ${option}`); - process.exit(2); - } - if (option === "--expect-plugin-operation") { - target.push(parsePluginExpectation(value, "operation")); - } else if (option === "--expect-plugin-capability") { - target.push(parsePluginExpectation(value, "capability")); - } else if (option === "--expect-plugin-runtime") { - target.push(parsePluginExpectation(value, "invocationModel")); - } else if (option === "--expect-plugin-runtime-transport") { - target.push(parsePluginExpectation(value, "transport")); - } else if (option === "--expect-plugin-runtime-authority") { - target.push(parsePluginExpectation(value, "authority")); - } else if (option === "--expect-plugin-runtime-field") { - target.push(parsePluginRuntimeFieldExpectation(value)); - } else if (option === "--expect-plugin-abi-field") { - target.push(parsePluginAbiFieldExpectation(value)); - } else if (option === "--expect-plugin-abi-operation-coverage") { - target.push(parsePluginAbiOperationCoverageExpectation(value)); - } else if (option === "--expect-plugin-handoff") { - target.push(value); - } else if (option === "--expect-plugin-witness") { - target.push(parsePluginWitnessExpectation(value)); - } else if (option === "--expect-plugin-handoff-move") { - target.push(parsePluginHandoffMoveExpectation(value)); - } else if (option === "--expect-provider-surface") { - target.push(parseProviderExpectation(value, "surfaceId")); - } else if (option === "--expect-provider-surface-kind") { - target.push(parseProviderSurfaceKindExpectation(value)); - } else if (option === "--expect-provider-surface-field") { - target.push(parseProviderSurfaceFieldExpectation(value)); - } else if (option === "--expect-interactive-world-surface") { - target.push(parseInteractiveWorldSurfaceExpectation(value)); - } else if (option === "--expect-provider-plugin-requirement") { - target.push(parseProviderPluginRequirementExpectation(value)); - } else if (option === "--expect-independent-nested-plugin") { - target.push(parseIndependentNestedPluginExpectation(value)); - } else if (option === "--expect-provider-command") { - target.push(parseProviderExpectation(value, "command")); - } else if (option === "--expect-provider-receipt-state") { - target.push(parseProviderExpectation(value, "state")); - } else if (option === "--expect-provider-handoff") { - target.push(value); - } else if (option === "--expect-provider-handoff-move") { - target.push(parseProviderHandoffMoveExpectation(value)); - } else if (option === "--expect-runtime-status") { - target.push(parseRuntimeExpectation(value, "status")); - } else if (option === "--expect-runtime-feature") { - target.push(parseRuntimeExpectation(value, "feature")); - } else if (option === "--expect-runtime-world-target") { - target.push(parseRuntimeWorldTargetExpectation(value)); - } else if (option === "--expect-runtime-world-field") { - target.push(parseRuntimeWorldFieldExpectation(value)); - } else if (option === "--expect-runtime-handoff") { - target.push(value); - } else if (option === "--expect-runtime-command-schema") { - target.push(parseRuntimeExpectation(value, "schema")); - } else if (option === "--expect-runtime-capture-status") { - target.push(parseRuntimeExpectation(value, "status")); - } else if (option === "--expect-runtime-capture-artifact") { - target.push(parseRuntimeCaptureArtifactExpectation(value)); - } else if (option === "--expect-runtime-witness") { - target.push(parseRuntimeWitnessExpectation(value)); - } else if (option === "--expect-runtime-lifecycle-status") { - target.push(parseRuntimeLifecycleExpectation(value, "status")); - } else if (option === "--expect-runtime-lifecycle-pending") { - target.push(parseRuntimeLifecycleExpectation(value, "pendingProof")); - } else if (option === "--expect-runtime-lifecycle-field") { - target.push(parseRuntimeLifecycleFieldExpectation(value)); - } else if (option === "--expect-split-target-status") { - target.push(parseSplitTargetExpectation(value, "status")); - } else if (option === "--expect-split-target-blocker") { - target.push(parseSplitTargetExpectation(value, "blocker")); - } else if (option === "--expect-split-target-blocker-record") { - target.push(parseSplitTargetBlockerRecordExpectation(value)); - } else if (option === "--expect-split-handoff-move") { - target.push(parseSplitHandoffMoveExpectation(value)); - } else if (option === "--expect-split-target-proof") { - target.push(parseSplitTargetExpectation(value, "proof")); - } else if (option === "--expect-world-lowering-gap") { - target.push(parseWorldLoweringGapExpectation(value)); - } else if (option === "--expect-world-lowering-coverage") { - target.push(parseWorldLoweringCoverageExpectation(value)); - } else if (option === "--expect-command-boundary-coverage") { - target.push(parseCommandBoundaryCoverageExpectation(value)); - } else if (option === "--expect-screenshot-metric") { - target.push(parseScreenshotMetricExpectation(value)); - } else if (option === "--expect-runtime-capture-probe") { - target.push(parseRuntimeCaptureProbeExpectation(value)); - } else if (option === "--expect-runtime-plugin-gap") { - target.push(parseRuntimePluginGapExpectation(value)); - } else if (option === "--expect-runtime-plugin-projection") { - target.push(parseRuntimePluginProjectionCoverageExpectation(value)); - } else if (option === "--expect-provider-runtime-plugin-projection") { - target.push(parseProviderRuntimePluginProjectionCoverageExpectation(value)); - } else if (option === "--expect-local-provider-catalog") { - target.push(parseLocalProviderCatalogExpectation(value)); - } else if (option === "--expect-local-provider-catalog-provider") { - target.push(parseLocalProviderCatalogProviderExpectation(value)); - } else if (option === "--expect-local-provider-catalog-advertisement") { - target.push(parseLocalProviderCatalogAdvertisementExpectation(value)); - } else { - target.push(value); - } - index += 1; - } - - return { exportDirectory: exportPath, expectations }; -} - -function parseScreenshotMetricExpectation(value) { - const parts = value.split(":"); - if (parts.length !== 4 || parts.some(part => !part)) { - console.error(`Expected screenshot metric in form, got: ${value}`); - process.exit(2); - } - return { - runtimeId: parts[0], - fixtureId: parts[1], - metricKind: parts[2], - status: parts[3], - }; -} - -function parseRuntimeCaptureProbeExpectation(value) { - const parts = value.split(":"); - if (parts.length !== 4 || parts.some(part => !part)) { - console.error(`Expected runtime capture probe in form, got: ${value}`); - process.exit(2); - } - return { - runtimeId: parts[0], - status: parts[1], - contractArtifactKind: parts[2], - captureOwnerRepo: parts[3], - }; -} - -function parseIndependentNestedPluginExpectation(value) { - const parts = value.split(":"); - if (parts.length !== 5 || parts.some(part => !part)) { - console.error(`Expected independent nested plugin in form, got: ${value}`); - process.exit(2); - } - return { - providerId: parts[0], - surfaceId: parts[1], - parentPluginId: parts[2], - nestedPluginId: parts[3], - nestedOwnerRepo: parts[4], - }; -} - -function parseLocalProviderCatalogExpectation(value) { - const parts = value.split(":"); - if (parts.length !== 3 || parts.some(part => !part)) { - console.error(`Expected local provider catalog in form, got: ${value}`); - process.exit(2); - } - return { - runtimeId: parts[0], - status: parts[1], - pathSubstring: parts[2], - }; -} - -function parseLocalProviderCatalogProviderExpectation(value) { - const parts = value.split(":"); - if (parts.length !== 2 || parts.some(part => !part)) { - console.error(`Expected local provider catalog provider in form, got: ${value}`); - process.exit(2); - } - return { - runtimeId: parts[0], - providerId: parts[1], - }; -} - -function parseLocalProviderCatalogAdvertisementExpectation(value) { - const parts = value.split(":"); - if (parts.length !== 2 || parts.some(part => !part)) { - console.error(`Expected local provider catalog advertisement in form, got: ${value}`); - process.exit(2); - } - return { - runtimeId: parts[0], - pathSubstring: parts[1], - }; -} - -function parseRuntimeCaptureArtifactExpectation(value) { - const parts = value.split(":"); - if (parts.length !== 5 || parts.some(part => !part)) { - console.error(`Expected runtime capture artifact in form, got: ${value}`); - process.exit(2); - } - return { - runtimeId: parts[0], - kind: parts[1], - schema: parts[2], - providerId: parts[3], - surfaceId: parts[4], - }; -} - -function parseWorldLoweringGapExpectation(value) { - const parts = value.split(":"); - if (parts.length !== 5 || parts.some(part => !part)) { - console.error(`Expected world lowering gap in form, got: ${value}`); - process.exit(2); - } - return { - providerId: parts[0], - surfaceId: parts[1], - targetId: parts[2], - ownerRepo: parts[3], - runtimeId: parts[4], - }; -} - -function parseWorldLoweringCoverageExpectation(value) { - const parts = value.split(":"); - if (parts.length !== 6 || parts.some(part => !part)) { - console.error(`Expected world lowering coverage in form, got: ${value}`); - process.exit(2); - } - return { - providerId: parts[0], - surfaceId: parts[1], - targetId: parts[2], - status: parts[3], - ownerRepo: parts[4], - runtimeId: parts[5], - }; -} - -function parseCommandBoundaryCoverageExpectation(value) { - const parts = value.split(":"); - if (parts.length !== 6 || parts.some(part => !part)) { - console.error(`Expected command boundary coverage in form, got: ${value}`); - process.exit(2); - } - return { - providerId: parts[0], - surfaceId: parts[1], - targetId: parts[2], - status: parts[3], - ownerRepo: parts[4], - runtimeId: parts[5], - }; -} - -function parseRuntimePluginGapExpectation(value) { - const parts = value.split(":"); - if (parts.length !== 3 || parts.some(part => !part)) { - console.error(`Expected runtime plugin projection gap in form, got: ${value}`); - process.exit(2); - } - return { - runtimeId: parts[0], - pluginId: parts[1], - ownerRepo: parts[2], - }; -} - -function parseRuntimePluginProjectionCoverageExpectation(value) { - const parts = value.split(":"); - if (parts.length !== 4 || parts.some(part => !part)) { - console.error(`Expected runtime plugin projection coverage in form, got: ${value}`); - process.exit(2); - } - return { - runtimeId: parts[0], - pluginId: parts[1], - status: parts[2], - ownerRepo: parts[3], - }; -} - -function parseProviderRuntimePluginProjectionCoverageExpectation(value) { - const parts = value.split(":"); - if (parts.length !== 6 || parts.some(part => !part)) { - console.error(`Expected provider runtime plugin projection coverage in form, got: ${value}`); - process.exit(2); - } - return { - providerId: parts[0], - surfaceId: parts[1], - runtimeId: parts[2], - pluginId: parts[3], - status: parts[4], - runtimeOwnerRepo: parts[5], - }; -} - -function parseSplitTargetBlockerRecordExpectation(value) { - const parts = value.split(":"); - if (parts.length < 3 || !parts[0] || !parts[1] || !parts.slice(2).join(":")) { - console.error(`Expected split target blocker record in form, got: ${value}`); - process.exit(2); - } - return { - targetId: parts[0], - kind: parts[1], - subject: parts.slice(2).join(":"), - }; -} - -function parseSplitHandoffMoveExpectation(value) { - const parts = value.split(":"); - if (parts.length < 6 || parts.slice(0, 5).some(part => !part) || !parts.slice(5).join(":")) { - console.error(`Expected split handoff move in form, got: ${value}`); - process.exit(2); - } - return { - splitTarget: parts[0], - runtimeId: parts[1], - moveSetId: parts[2], - pathKind: parts[3], - status: parts[4], - pathSubstring: parts.slice(5).join(":"), - }; -} - -function parsePluginHandoffMoveExpectation(value) { - const parts = value.split(":"); - if (parts.length < 5 || parts.slice(0, 4).some(part => !part) || !parts.slice(4).join(":")) { - console.error(`Expected plugin handoff move in form, got: ${value}`); - process.exit(2); - } - return { - pluginId: parts[0], - moveSetId: parts[1], - pathKind: parts[2], - status: parts[3], - pathSubstring: parts.slice(4).join(":"), - }; -} - -function parseProviderHandoffMoveExpectation(value) { - const parts = value.split(":"); - if (parts.length < 5 || parts.slice(0, 4).some(part => !part) || !parts.slice(4).join(":")) { - console.error(`Expected provider handoff move in form, got: ${value}`); - process.exit(2); - } - return { - providerId: parts[0], - moveSetId: parts[1], - pathKind: parts[2], - status: parts[3], - pathSubstring: parts.slice(4).join(":"), - }; -} - -function parsePluginExpectation(value, field) { - const separator = value.indexOf(":"); - if (separator <= 0 || separator === value.length - 1) { - console.error(`Expected plugin ${field} in form, got: ${value}`); - process.exit(2); - } - return { - pluginId: value.slice(0, separator), - [field]: value.slice(separator + 1), - }; -} - -function parsePluginRuntimeFieldExpectation(value) { - const parts = value.split(":"); - if (parts.length < 3 || !parts[0] || !parts[1] || !parts.slice(2).join(":")) { - console.error(`Expected plugin runtime field in form, got: ${value}`); - process.exit(2); - } - return { - pluginId: parts[0], - fieldPath: parts[1], - value: parts.slice(2).join(":"), - }; -} - -function parsePluginAbiFieldExpectation(value) { - const parts = value.split(":"); - if (parts.length < 4 || !parts[0] || !parts[1] || !parts[2] || !parts.slice(3).join(":")) { - console.error(`Expected plugin ABI field in form, got: ${value}`); - process.exit(2); - } - return { - pluginId: parts[0], - operation: parts[1], - fieldPath: parts[2], - value: parts.slice(3).join(":"), - }; -} - -function parsePluginAbiOperationCoverageExpectation(value) { - const parts = value.split(":"); - if (parts.length !== 4 || parts.some(part => !part)) { - console.error(`Expected plugin ABI operation coverage in form, got: ${value}`); - process.exit(2); - } - return { - pluginId: parts[0], - operation: parts[1], - status: parts[2], - ownerRepo: parts[3], - }; -} - -function parseProviderSurfaceFieldExpectation(value) { - const parts = value.split(":"); - if (parts.length < 4 || !parts[0] || !parts[1] || !parts[2] || !parts.slice(3).join(":")) { - console.error(`Expected provider surface field in form, got: ${value}`); - process.exit(2); - } - return { - providerId: parts[0], - surfaceId: parts[1], - fieldPath: parts[2], - value: parts.slice(3).join(":"), - }; -} - -function parseInteractiveWorldSurfaceExpectation(value) { - const parts = value.split(":"); - if (parts.length !== 4 || parts.some(part => !part)) { - console.error(`Expected interactive world surface in form, got: ${value}`); - process.exit(2); - } - return { - providerId: parts[0], - surfaceId: parts[1], - targetId: parts[2], - ownerRepo: parts[3], - }; -} - -function parseProviderPluginRequirementExpectation(value) { - const parts = value.split(":"); - if (![5, 6].includes(parts.length) || parts.some(part => !part)) { - console.error(`Expected provider plugin requirement in form, got: ${value}`); - process.exit(2); - } - return { - providerId: parts[0], - surfaceId: parts[1], - pluginId: parts[2], - status: parts[3], - pluginOwnerRepo: parts[4], - availability: parts[5] || "", - }; -} - -function parseProviderExpectation(value, field) { - const separator = value.indexOf(":"); - if (separator <= 0 || separator === value.length - 1) { - console.error(`Expected provider ${field} in form, got: ${value}`); - process.exit(2); - } - return { - providerId: value.slice(0, separator), - [field]: value.slice(separator + 1), - }; -} - -function parsePluginWitnessExpectation(value) { - const parts = value.split(":"); - if (parts.length !== 3 || parts.some(part => !part)) { - console.error(`Expected plugin witness in form, got: ${value}`); - process.exit(2); - } - return { pluginId: parts[0], transport: parts[1], status: parts[2] }; -} - -function parseProviderSurfaceKindExpectation(value) { - const parts = value.split(":"); - if (parts.length !== 3 || parts.some(part => !part)) { - console.error(`Expected provider surface kind in form, got: ${value}`); - process.exit(2); - } - return { - providerId: parts[0], - surfaceId: parts[1], - surfaceKind: parts[2], - }; -} - -function parseRuntimeExpectation(value, field) { - const separator = value.indexOf(":"); - if (separator <= 0 || separator === value.length - 1) { - console.error(`Expected runtime ${field} in form, got: ${value}`); - process.exit(2); - } - return { - runtimeId: value.slice(0, separator), - [field]: value.slice(separator + 1), - }; -} - -function parseRuntimeWitnessExpectation(value) { - const parts = value.split(":"); - if (parts.length !== 5 || parts.some(part => !part)) { - console.error(`Expected runtime witness in form, got: ${value}`); - process.exit(2); - } - return { - runtimeId: parts[0], - providerId: parts[1], - surfaceId: parts[2], - cacheState: parts[3], - status: parts[4], - }; -} - -function parseRuntimeWorldTargetExpectation(value) { - const separator = value.indexOf(":"); - if (separator <= 0 || separator === value.length - 1) { - console.error(`Expected runtime world target in form, got: ${value}`); - process.exit(2); - } - return { - runtimeId: value.slice(0, separator), - targetId: value.slice(separator + 1), - }; -} - -function parseRuntimeWorldFieldExpectation(value) { - const parts = value.split(":"); - if (parts.length < 4 || !parts[0] || !parts[1] || !parts[2] || !parts.slice(3).join(":")) { - console.error(`Expected runtime world field in form, got: ${value}`); - process.exit(2); - } - return { - runtimeId: parts[0], - targetId: parts[1], - fieldPath: parts[2], - value: parts.slice(3).join(":"), - }; -} - -function parseRuntimeLifecycleFieldExpectation(value) { - const parts = value.split(":"); - if (parts.length < 4 || !parts[0] || !parts[1] || !parts[2] || !parts.slice(3).join(":")) { - console.error(`Expected runtime lifecycle field in form, got: ${value}`); - process.exit(2); - } - return { - runtimeId: parts[0], - stage: parts[1], - fieldPath: parts[2], - value: parts.slice(3).join(":"), - }; -} - -function readNestedField(source, fieldPath) { - let value = source; - for (const segment of fieldPath.split(".")) { - if (!value || typeof value !== "object") return ""; - value = value[segment]; - } - return typeof value === "string" ? value : ""; -} - -function parseRuntimeLifecycleExpectation(value, field) { - const parts = value.split(":"); - if (parts.length < 3 || !parts[0] || !parts[1] || !parts.slice(2).join(":")) { - console.error(`Expected runtime lifecycle ${field} in form, got: ${value}`); - process.exit(2); - } - return { - runtimeId: parts[0], - stage: parts[1], - [field]: parts.slice(2).join(":"), - }; -} - -function parseSplitTargetExpectation(value, field) { - const separator = value.indexOf(":"); - if (separator <= 0 || separator === value.length - 1) { - console.error(`Expected split target ${field} in form, got: ${value}`); - process.exit(2); - } - return { - targetId: value.slice(0, separator), - [field]: value.slice(separator + 1), - }; -} - -function mergeRuntimeRecords(topLevelRuntimes, runtimeTargets) { - const byId = new Map(); - for (const runtime of runtimeTargets) { - if (runtime.runtimeId) byId.set(runtime.runtimeId, runtime); - } - for (const runtime of topLevelRuntimes) { - if (!runtime.runtimeId) continue; - byId.set(runtime.runtimeId, { - ...(byId.get(runtime.runtimeId) || {}), - ...runtime, - }); - } - return [...byId.values()]; -} diff --git a/tools/conformance/eveconformance-handoff.json b/tools/conformance/eveconformance-handoff.json deleted file mode 100644 index 4159a41..0000000 --- a/tools/conformance/eveconformance-handoff.json +++ /dev/null @@ -1,120 +0,0 @@ -{ - "schema": "gamecult.eve.conformance_handoff.v1", - "splitTarget": "EveConformance", - "ownerRepo": "EveConformance", - "currentHostRepo": "Eve", - "authorityBoundary": "EveConformance owns fixture corpus layout, conformance pack export, parity runner orchestration, consumer smokes, report artifacts, capability matrix evidence, and CI once the harness stabilizes. Eve keeps the kernel contracts, browser reference oracle, and minimal core fixtures.", - "moveSets": [ - { - "id": "conformance-runner", - "destinationOwner": "EveConformance", - "currentPaths": [ - "tools/parity/run-parity.mjs", - "tools/parity/parity-manifest.json", - "tools/conformance/consume-export.mjs" - ], - "replacementProof": "EveConformance runs the parity/conformance harness without depending on Eve source layout." - }, - { - "id": "conformance-schemas", - "destinationOwner": "EveConformance", - "currentPaths": [ - "schemas/gamecult.eve.conformance_export.v1.schema.json", - "schemas/gamecult.eve.conformance_handoff.v1.schema.json", - "schemas/gamecult.eve.capability_matrix.v1.schema.json", - "schemas/gamecult.eve.conformance_fixture.v1.schema.json", - "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.unity_scene_projection.v1.schema.json", - "schemas/gamecult.eve.unity_playable_world_asset_manifest.v1.schema.json", - "schemas/gamecult.eve.tui_grid.v1.schema.json", - "schemas/gamecult.eve.web_layout_probe.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", - "schemas/gamecult.eve.command_receipt.v1.schema.json", - "schemas/gamecult.eve.plugin.v1.schema.json", - "schemas/gamecult.eve.plugin_receipt.v1.schema.json", - "schemas/gamecult.eve.plugin_advertisement.v1.schema.json", - "schemas/gamecult.eve.plugin_handoff.v1.schema.json", - "schemas/gamecult.eve.plugin_witness.v1.schema.json", - "schemas/gamecult.eve.plugin_abi_fixture.v1.schema.json", - "schemas/gamecult.eve.plugin_abi.request.v1.schema.json", - "schemas/gamecult.eve.plugin_abi.response.v1.schema.json", - "schemas/gamecult.eve.runtime_release_request.v1.schema.json", - "schemas/gamecult.eve.runtime_capture_request.v1.schema.json", - "schemas/gamecult.eve.runtime_witness.v1.schema.json" - ], - "replacementProof": "EveConformance publishes the schema catalog and copied schema documents needed by conformance, plugin, provider, and runtime consumers." - }, - { - "id": "consumer-smokes", - "destinationOwner": "EveConformance", - "currentPaths": [ - "scripts/run-conformance-consumer-smoke.ps1", - "scripts/run-plugin-owner-conformance-consumer-smoke.ps1", - "scripts/run-runtime-owner-conformance-consumer-smoke.ps1", - "scripts/run-split-target-conformance-consumer-smoke.ps1", - "scripts/run-aetheria-conformance-consumer-smoke.ps1" - ], - "replacementProof": "Runtime, plugin, provider, and split-target consumers can verify exported packs without reading Eve's parity manifest." - }, - { - "id": "fixture-corpus", - "destinationOwner": "EveConformance", - "currentPaths": [ - "web/fixtures/cultui-slider-inspector.conformance.json", - "web/fixtures/sai-vn-surface.conformance.json", - "web/fixtures/fensalir-direct2d-surface.conformance.json", - "web/fixtures/cultui-embedded-surface.conformance.json" - ], - "replacementProof": "Core, plugin, provider, and runtime fixture metadata are exported as conformance packs with owner and exit criteria." - } - ], - "contractInputs": [ - "gamecult.eve.surface.v1", - "gamecult.eve.command.v1", - "gamecult.eve.command_invocation.v1", - "gamecult.eve.command_receipt.v1", - "gamecult.eve.conformance_export.v1", - "gamecult.eve.conformance_handoff.v1", - "gamecult.eve.capability_matrix.v1", - "gamecult.eve.conformance_fixture.v1", - "gamecult.eve.provider_scenario.v1", - "gamecult.eve.plugin.v1", - "gamecult.eve.plugin_receipt.v1", - "gamecult.eve.plugin_advertisement.v1", - "gamecult.eve.plugin_handoff.v1", - "gamecult.eve.plugin_witness.v1", - "gamecult.eve.plugin_abi_fixture.v1", - "gamecult.eve.plugin_abi.request.v1", - "gamecult.eve.plugin_abi.response.v1", - "gamecult.eve.runtime_release_request.v1", - "gamecult.eve.runtime_capture_request.v1", - "gamecult.eve.runtime_witness.v1", - "gamecult.eve.provider_advertisement.v1", - "gamecult.eve.provider_handoff.v1", - "gamecult.eve.electron_shell_projection.v1", - "gamecult.eve.unity_scene_projection.v1", - "gamecult.eve.unity_playable_world_asset_manifest.v1", - "gamecult.eve.tui_grid.v1", - "gamecult.eve.web_layout_probe.v1", - "gamecult.eve.runtime_capability.v1", - "gamecult.eve.runtime_lifecycle.v1", - "gamecult.eve.runtime_split_handoff.v1" - ], - "forbiddenImports": [ - "provider internals as conformance truth", - "runtime source layout as required consumer input", - "plugin semantic internals as Eve core authority", - "screenshot artifacts as provider state" - ], - "requiredExternalProofs": [ - "EveConformance publishes a consumable conformance export without depending on Eve source layout.", - "Runtime repos consume runtime targets, lifecycle evidence, and capture status from exported packs.", - "Plugin owner repos consume plugin manifests, ABI operations, capabilities, and handoff paths from exported packs.", - "Provider repos consume provider advertisements, scenarios, receipts, and handoff paths from exported packs." - ] -} diff --git a/tools/parity/export-fixture.mjs b/tools/parity/export-fixture.mjs deleted file mode 100644 index 9fd34cf..0000000 --- a/tools/parity/export-fixture.mjs +++ /dev/null @@ -1,36 +0,0 @@ -import { mkdir, readFile, writeFile } from "node:fs/promises"; -import path from "node:path"; -import { fileURLToPath } from "node:url"; -import { compileEveDsl } from "../../web/eve-dsl.js"; - -const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../.."); -const manifest = JSON.parse(await readFile(path.join(repoRoot, "tools/parity/parity-manifest.json"), "utf8")); - -const fixtureId = process.argv[2] || "cultui-inspector"; -const outputPath = process.argv[3] - ? path.resolve(process.argv[3]) - : path.join(repoRoot, "artifacts/parity/fixtures", `${fixtureId}.surface.json`); - -const fixture = manifest.fixtures.find(candidate => - candidate.id === fixtureId || candidate.expect?.providerId === fixtureId); - -if (!fixture) { - throw new Error(`Unknown parity fixture: ${fixtureId}`); -} - -const surface = await loadSurface(fixture.surface); -surface.fixtureId = fixture.id; -surface.values ||= surface.mesh?.snapshot?.() || {}; -delete surface.mesh; - -await mkdir(path.dirname(outputPath), { recursive: true }); -await writeFile(outputPath, `${JSON.stringify(surface, null, 2)}\n`); -console.log(outputPath); - -async function loadSurface(surface) { - const absolutePath = path.join(repoRoot, surface.path); - const source = await readFile(absolutePath, "utf8"); - if (surface.transport === "local-eve-dsl") return compileEveDsl(source); - if (surface.transport === "local-json") return JSON.parse(source); - throw new Error(`Unsupported parity surface transport: ${surface.transport}`); -} diff --git a/tools/parity/parity-manifest.json b/tools/parity/parity-manifest.json deleted file mode 100644 index 1e1f08c..0000000 --- a/tools/parity/parity-manifest.json +++ /dev/null @@ -1,1864 +0,0 @@ -{ - "schema": "gamecult.eve.parity_manifest.v1", - "repoStrategy": { - "boundaryRule": "Eve core owns contracts; plugins own semantics; runtime repos own native projection; provider repos own live truth.", - "incubationPolicy": "Eve may host runtimes, plugins, and conformance packs only while their contract is still forming.", - "graduationDocument": "docs/repo-strategy.md", - "conformanceHandoffPath": "tools/conformance/eveconformance-handoff.json" - }, - "schemas": { - "gamecult.eve.conformance_fixture.v1": "schemas/gamecult.eve.conformance_fixture.v1.schema.json", - "gamecult.eve.conformance_export.v1": "schemas/gamecult.eve.conformance_export.v1.schema.json", - "gamecult.eve.capability_matrix.v1": "schemas/gamecult.eve.capability_matrix.v1.schema.json", - "gamecult.eve.command.v1": "schemas/gamecult.eve.command.v1.schema.json", - "gamecult.eve.command_invocation.v1": "schemas/gamecult.eve.command_invocation.v1.schema.json", - "gamecult.eve.command_receipt.v1": "schemas/gamecult.eve.command_receipt.v1.schema.json", - "gamecult.eve.plugin.v1": "schemas/gamecult.eve.plugin.v1.schema.json", - "gamecult.eve.plugin_receipt.v1": "schemas/gamecult.eve.plugin_receipt.v1.schema.json", - "gamecult.eve.plugin_abi.request.v1": "schemas/gamecult.eve.plugin_abi.request.v1.schema.json", - "gamecult.eve.plugin_abi.response.v1": "schemas/gamecult.eve.plugin_abi.response.v1.schema.json", - "gamecult.eve.plugin_abi_fixture.v1": "schemas/gamecult.eve.plugin_abi_fixture.v1.schema.json", - "gamecult.eve.plugin_advertisement.v1": "schemas/gamecult.eve.plugin_advertisement.v1.schema.json", - "gamecult.eve.plugin_handoff.v1": "schemas/gamecult.eve.plugin_handoff.v1.schema.json", - "gamecult.eve.plugin_witness.v1": "schemas/gamecult.eve.plugin_witness.v1.schema.json", - "gamecult.eve.runtime_capability.v1": "schemas/gamecult.eve.runtime_capability.v1.schema.json", - "gamecult.eve.runtime_lifecycle.v1": "schemas/gamecult.eve.runtime_lifecycle.v1.schema.json", - "gamecult.eve.runtime_split_handoff.v1": "schemas/gamecult.eve.runtime_split_handoff.v1.schema.json", - "gamecult.eve.runtime_release_request.v1": "schemas/gamecult.eve.runtime_release_request.v1.schema.json", - "gamecult.eve.runtime_capture_request.v1": "schemas/gamecult.eve.runtime_capture_request.v1.schema.json", - "gamecult.eve.runtime_witness.v1": "schemas/gamecult.eve.runtime_witness.v1.schema.json", - "gamecult.eve.web_layout_probe.v1": "schemas/gamecult.eve.web_layout_probe.v1.schema.json", - "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.local_provider_catalog.v1": "schemas/gamecult.eve.local_provider_catalog.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.electron_shell_projection.v1": "schemas/gamecult.eve.electron_shell_projection.v1.schema.json", - "gamecult.eve.unity_uitoolkit_projection.v1": "schemas/gamecult.eve.unity_uitoolkit_projection.v1.schema.json", - "gamecult.eve.unity_scene_projection.v1": "schemas/gamecult.eve.unity_scene_projection.v1.schema.json", - "gamecult.eve.unity_playable_world_asset_manifest.v1": "schemas/gamecult.eve.unity_playable_world_asset_manifest.v1.schema.json", - "gamecult.eve.tui_grid.v1": "schemas/gamecult.eve.tui_grid.v1.schema.json" - }, - "conformancePacks": [ - { - "id": "core", - "ownerRepo": "Eve", - "exitCriteria": "Stays in Eve; proves surface contract, CultUI DSL, bindings, commands, and embedded documents." - }, - { - "id": "plugin", - "ownerRepo": "EvePlugins until owner repos stabilize", - "exitCriteria": "Moves when plugin semantics, fixtures, and conformance cases are stable." - }, - { - "id": "provider", - "ownerRepo": "Provider repo", - "exitCriteria": "Moves when fixture carries product-specific state, assets, flows, or receipts." - }, - { - "id": "runtime", - "ownerRepo": "Runtime repo", - "exitCriteria": "Moves when runtime has its own build/deploy lifecycle or platform SDK pressure." - } - ], - "responsiveCases": [ - { - "id": "phone", - "width": 390, - "height": 844, - "orientation": "portrait", - "deviceScale": 1 - }, - { - "id": "tablet", - "width": 768, - "height": 1024, - "orientation": "portrait", - "deviceScale": 1 - }, - { - "id": "desktop", - "width": 1280, - "height": 720, - "orientation": "landscape", - "deviceScale": 1 - } - ], - "androidDeviceCases": [ - { - "id": "native-portrait", - "orientation": "portrait" - }, - { - "id": "native-landscape", - "orientation": "landscape" - } - ], - "fixtures": [ - { - "id": "cultui-inspector", - "title": "CultUI Inspector", - "pack": "core", - "ownerRepo": "Eve", - "surface": { - "transport": "local-eve-dsl", - "path": "web/fixtures/cultui-slider-inspector.eve" - }, - "metadataPath": "web/fixtures/cultui-slider-inspector.conformance.json", - "expect": { - "providerId": "eve.cultui.inspector", - "componentKinds": ["surface", "partition", "pane", "label", "control.slider"], - "minimumCounts": { - "partition": 8, - "control.slider": 3, - "label": 3 - }, - "controlSkins": ["inspector.orangeSlider"], - "controlParts": ["track", "fill", "thumb"], - "bindings": ["tester.fov", "tester.throttleDecay", "tester.flowSpeed"] - } - }, - { - "id": "repixelizer", - "title": "Repixelizer", - "pack": "provider", - "ownerRepo": "Repixelizer", - "surface": { - "transport": "local-json", - "path": "web/fixtures/repixelizer.eve-surface.json" - }, - "metadataPath": "web/fixtures/repixelizer.conformance.json", - "expect": { - "providerId": "repixelizer", - "componentKinds": [ - "surface", - "pane", - "card", - "text", - "image.preview", - "canvas.preview", - "canvas.editor", - "input.file", - "dropzone", - "input.number", - "control.button", - "status.stage" - ], - "styleTokens": [ - "colorBackgroundTop", - "colorPanel", - "colorAccent", - "fontTitle", - "fontBody", - "imageRendering" - ] - } - }, - { - "id": "sai-vn", - "title": "Sai VN", - "pack": "plugin", - "ownerRepo": "Sai", - "requiredPlugins": [ - { - "pluginId": "sai.vn", - "capabilities": ["vn.stage", "story.choose", "story.continue", "story.jump"] - } - ], - "optionalNestedPlugins": [ - { - "pluginId": "norn.graph", - "capabilities": ["embed.norn"] - }, - { - "pluginId": "tex.math", - "capabilities": ["embed.tex"] - } - ], - "surface": { - "transport": "local-json", - "path": "web/fixtures/sai-vn-surface.json" - }, - "metadataPath": "web/fixtures/sai-vn-surface.conformance.json", - "expect": { - "providerId": "gamecult.home.vn", - "componentKinds": [ - "vn.stage", - "image.background", - "embed.norn", - "embed.tex", - "panel.dialogue", - "rail.actions", - "control.button" - ], - "styleTokens": [ - "colorBackground", - "colorPanel", - "colorPanelAlt", - "colorText", - "colorAccent", - "fontBody", - "fontTitle", - "pixelArt" - ], - "commandDescriptors": ["story.continue", "story.choose", "story.jump", "style.patch"], - "commandDescriptorSchema": "gamecult.eve.command.v1", - "commandReferencesDeclared": true - } - }, - { - "id": "aetheria-world", - "title": "Aetheria Interactive World", - "pack": "provider", - "ownerRepo": "Aetheria", - "sourceRoot": "../Aetheria", - "surface": { - "transport": "local-json", - "path": "conformance/eve/aetheria-world-surface.json" - }, - "metadataPath": "conformance/eve/aetheria-world-surface.conformance.json", - "expect": { - "providerId": "aetheria", - "componentKinds": ["surface", "card", "text", "metric", "partition", "field.surface2d", "control.button"], - "styleTokens": ["colorBackground", "colorPanel", "colorText", "colorMuted", "colorAccent"], - "bindings": ["aetheria.daemon.frameId", "aetheria.daemon.soaView.gravityField", "aetheria.daemon.commands"], - "commandDescriptors": ["aetheria.daemon.commands"], - "commandDescriptorSchema": "gamecult.eve.command.v1", - "commandReferencesDeclared": true - } - }, - { - "id": "eve-world-smoke", - "title": "Generic Interactive World Smoke", - "pack": "core", - "ownerRepo": "Eve", - "surface": { - "transport": "local-json", - "path": "web/fixtures/eve-world-smoke-surface.json" - }, - "metadataPath": "web/fixtures/eve-world-smoke-surface.conformance.json", - "expect": { - "providerId": "eve.world-smoke", - "componentKinds": ["surface", "card", "text", "metric", "field.surface2d", "control.button"], - "styleTokens": ["colorBackground", "colorPanel", "colorText", "colorMuted", "colorAccent", "fontBody", "fontTitle"], - "bindings": ["eve.world-smoke.frame", "eve.world-smoke.field"], - "commandDescriptors": ["eve.world-smoke.commands"], - "commandDescriptorSchema": "gamecult.eve.command.v1", - "commandReferencesDeclared": true - } - }, - { - "id": "fensalir-direct2d", - "title": "Fensalir Direct2D Adapter", - "pack": "runtime", - "ownerRepo": "Fensalir", - "surface": { - "transport": "local-json", - "path": "web/fixtures/fensalir-direct2d-surface.json" - }, - "metadataPath": "web/fixtures/fensalir-direct2d-surface.conformance.json", - "expect": { - "providerId": "fensalir.direct2d", - "componentKinds": ["surface", "card", "text", "metric", "partition", "surface.slot", "control.button"], - "styleTokens": ["colorBackground", "colorPanel", "colorText", "colorMuted", "colorAccent"], - "bindings": ["fensalir.overlay.phase"], - "embeddedDocuments": ["fensalir.adapter.detail"], - "commandDescriptors": ["fensalir.overlay.commands"], - "commandDescriptorSchema": "gamecult.eve.command.v1", - "commandReferencesDeclared": true, - "authorityStates": ["fresh"], - "authorityOwners": ["runtime"], - "witnessRefs": 1 - } - }, - { - "id": "authority-staleness", - "title": "Authority And Staleness Witness", - "pack": "core", - "ownerRepo": "Eve", - "surface": { - "transport": "local-json", - "path": "web/fixtures/authority-staleness-witness.json" - }, - "metadataPath": "web/fixtures/authority-staleness-witness.conformance.json", - "expect": { - "providerId": "gamecult.eve.authority-witness", - "componentKinds": ["surface", "card", "text", "metric", "partition"], - "styleTokens": ["colorBackground", "colorPanel", "colorText", "colorMuted", "colorAccent", "colorWarning", "colorDanger"], - "bindings": [ - "authority.temperature.accepted", - "authority.sensor.echo", - "authority.missing.field", - "authority.orbit.predicted", - "authority.orbit.accepted" - ], - "commandDescriptors": ["authority.fixture.patch"], - "commandDescriptorSchema": "gamecult.eve.command.v1", - "commandReferencesDeclared": true, - "authorityStates": ["accepted", "denied", "fresh", "missing", "pending", "predicted", "reconciled", "stale"], - "authorityOwners": ["provider", "renderer"], - "witnessRefs": 8, - "receiptRefs": 2 - } - }, - { - "id": "reactive-dsl", - "title": "Reactive DSL", - "pack": "core", - "ownerRepo": "Eve", - "surface": { - "transport": "local-eve-dsl", - "path": "web/fixtures/reactive-composition.eve" - }, - "metadataPath": "web/fixtures/reactive-composition.conformance.json", - "expect": { - "providerId": "gamecult.eve.reactive-demo", - "componentKinds": ["surface", "card", "text", "metric", "list", "control.button"], - "bindings": ["face.name", "face.body", "swarm.heat", "swarm.eventCount", "swarm.latestEvent"] - } - }, - { - "id": "huginn-cc", - "title": "Huginn .cc", - "pack": "provider", - "ownerRepo": "Huginn", - "surface": { - "transport": "local-eve-dsl", - "path": "web/fixtures/huginn-cc-surface.eve" - }, - "metadataPath": "web/fixtures/huginn-cc-surface.conformance.json", - "expect": { - "providerId": "cultcache.huginn.inspector", - "componentKinds": ["surface", "card", "text", "list"], - "bindings": ["huginn.catalog.entries", "huginn.records.entries"] - } - }, - { - "id": "embedded-surface", - "title": "Embedded Surface Slot", - "pack": "core", - "ownerRepo": "Eve", - "surface": { - "transport": "local-json", - "path": "web/fixtures/cultui-embedded-surface.json" - }, - "metadataPath": "web/fixtures/cultui-embedded-surface.conformance.json", - "expect": { - "providerId": "gamecult.eve.embedded-demo", - "componentKinds": ["surface", "card", "text", "surface.slot"], - "embeddedDocuments": ["inventory.dropdown"], - "styleTokens": ["colorBackground", "colorPanel", "colorText", "colorMuted", "colorAccent"] - } - } - ], - "providerAdvertisements": [ - { - "providerId": "repixelizer", - "title": "Repixelizer", - "kind": "service.product", - "ownerRepo": "Repixelizer", - "repoRole": "provider-external", - "graduationTrigger": "Provider-owned product state, assets, sessions, and receipts stay in Repixelizer; Eve keeps only the advertisement-shaped fixture.", - "schemaPath": "schemas/gamecult.eve.provider_advertisement.v1.schema.json", - "advertisementPath": "web/fixtures/repixelizer.provider-advertisement.json", - "requiredFixtures": ["repixelizer"], - "expectedSchemas": ["gamecult.eve.surface.v1"], - "expectedSurfaces": ["repixelizer.operator.surface"], - "expectedCommands": ["repixelizer.job.submit", "repixelizer.job.cancel_own"] - }, - { - "providerId": "gamecult.home.vn", - "title": "GameCult Compound VN", - "kind": "plugin.fixture.provider", - "ownerRepo": "Sai", - "repoRole": "provider-fixture", - "graduationTrigger": "Move this advertisement-shaped VN fixture to Sai when sai-vn-plugin owns VN/Ink semantics and conformance packs.", - "schemaPath": "schemas/gamecult.eve.provider_advertisement.v1.schema.json", - "advertisementPath": "web/fixtures/sai-vn.provider-advertisement.json", - "requiredFixtures": [], - "expectedSchemas": ["gamecult.eve.surface.v1", "gamecult.eve.command.v1", "gamecult.eve.command_invocation.v1", "sai.vn.story_session.v1"], - "expectedSurfaces": ["sai.visual_novel.surface"], - "expectedCommands": ["story.continue", "story.choose", "story.jump", "style.patch"] - }, - { - "providerId": "aetheria", - "title": "Aetheria", - "kind": "game.runtime", - "ownerRepo": "Aetheria", - "repoRole": "provider-external", - "sourceRoot": "../Aetheria", - "graduationTrigger": "Aetheria owns RTS/ARPG world surfaces, generated level state, assets, command receipts, and scenario conformance; Eve keeps contract fixtures only.", - "schemaPath": "schemas/gamecult.eve.provider_advertisement.v1.schema.json", - "advertisementPath": "conformance/eve/aetheria.provider-advertisement.json", - "scenarioPath": "conformance/eve/aetheria-world-scenario.json", - "requiredFixtures": ["aetheria-world"], - "expectedSchemas": ["gamecult.eve.surface.v1", "gamecult.eve.command.v1", "gamecult.eve.command_invocation.v1", "gamecult.eve.unity_playable_world_asset_manifest.v1", "aetheria.runtime.daemon.frame.v1"], - "expectedSurfaces": ["aetheria.daemon.game", "aetheria.daemon.editor"], - "expectedSurfaceKinds": { - "aetheria.daemon.game": "interactive-world", - "aetheria.daemon.editor": "interactive-world-editor" - }, - "expectedCommands": ["aetheria.daemon.commands"], - "expectedPaths": [ - "conformance/eve/provider-pack.json", - "conformance/eve/aetheria-world-scenario.json", - "scripts/verify-eve-conformance-pack.mjs" - ] - }, - { - "providerId": "eve.world-smoke", - "title": "Generic Interactive World Smoke", - "kind": "contract.fixture", - "ownerRepo": "Eve", - "repoRole": "core-contract-fixture", - "graduationTrigger": "Stays in Eve core as the minimal non-product interactive-world control fixture.", - "schemaPath": "schemas/gamecult.eve.provider_advertisement.v1.schema.json", - "advertisementPath": "web/fixtures/eve-world-smoke.provider-advertisement.json", - "scenarioPath": "web/fixtures/eve-world-smoke-scenario.json", - "requiredFixtures": ["eve-world-smoke"], - "expectedSchemas": ["gamecult.eve.surface.v1", "gamecult.eve.command.v1", "gamecult.eve.command_invocation.v1", "eve.world_smoke.frame.v1", "eve.world_smoke.command_receipt.v1"], - "expectedSurfaces": ["eve.world-smoke.surface"], - "expectedSurfaceKinds": { - "eve.world-smoke.surface": "interactive-world" - }, - "expectedCommands": ["eve.world-smoke.commands"], - "expectedPaths": [ - "web/fixtures/eve-world-smoke-surface.json", - "web/fixtures/eve-world-smoke-surface.conformance.json", - "web/fixtures/eve-world-smoke-scenario.json" - ] - } - ], - "pluginManifests": [ - { - "pluginId": "sai.vn", - "title": "Sai Visual Novel", - "kind": "external-owner", - "ownerRepo": "Sai", - "repoRole": "plugin-owner", - "splitTarget": "Sai", - "graduationTrigger": "Graduated: Sai owns retained Ink playback, fixtures, ABI sidecar, and release lifecycle.", - "capabilities": ["vn.stage", "story.choose", "story.continue", "story.jump"], - "optionalPlugins": ["norn.graph", "tex.math"], - "expectedRuntime": { - "invocationModel": "executable-sidecar", - "contract": "gamecult.eve.plugin_abi.v1", - "transports": ["stdio"], - "authority": ["renderer-independent", "no-provider-state-mutation", "provider-accepts-or-denies-commands"], - "sidecar": { - "processKind": "long-running-daemon", - "protocol": "stdio-ndjson", - "requestSchema": "gamecult.eve.plugin_abi.request.v1", - "responseSchema": "gamecult.eve.plugin_abi.response.v1", - "commandEnvelope": "gamecult.eve.command_invocation.v1", - "receiptSchema": "gamecult.eve.command_receipt.v1", - "stateAuthority": "sai-sidecar-owns-story-session-provider-owns-command-acceptance" - } - }, - "requiredFixtures": ["sai-vn"], - "schemaPath": "schemas/gamecult.eve.plugin.v1.schema.json", - "manifestPath": "../Sai/plugins/sai-vn.plugin.json", - "abiFixturePath": "../Sai/plugins/sai-vn.plugin-abi-fixture.json", - "advertisementSchemaPath": "schemas/gamecult.eve.plugin_advertisement.v1.schema.json", - "advertisementPath": "../Sai/plugins/sai-vn.plugin-advertisement.json", - "expectedPaths": [ - "../Sai/plugins/sai-vn.plugin.json", - "../Sai/plugins/sai-vn.plugin-abi-fixture.json", - "../Sai/plugins/sai-vn.plugin-advertisement.json", - "../Sai/src/eve-sidecar.cjs" - ] - }, - { - "pluginId": "norn.graph", - "title": "Norn Graph", - "kind": "external-owner", - "ownerRepo": "Norn", - "repoRole": "plugin-owner", - "splitTarget": "Norn", - "graduationTrigger": "Graduated: Norn owns graph semantics, fixtures, ABI sidecar, and release lifecycle.", - "capabilities": ["embed.norn", "graph.node.activate"], - "expectedRuntime": { - "invocationModel": "executable-sidecar", - "contract": "gamecult.eve.plugin_abi.v1", - "transports": ["stdio"], - "authority": ["renderer-independent", "no-provider-state-mutation", "provider-accepts-or-denies-commands"], - "sidecar": { - "processKind": "long-running-daemon", - "protocol": "stdio-ndjson", - "requestSchema": "gamecult.eve.plugin_abi.request.v1", - "responseSchema": "gamecult.eve.plugin_abi.response.v1", - "commandEnvelope": "gamecult.eve.command_invocation.v1", - "receiptSchema": "gamecult.eve.command_receipt.v1", - "stateAuthority": "proposes-plugin-state-only-provider-accepts" - } - }, - "requiredFixtures": [], - "schemaPath": "schemas/gamecult.eve.plugin.v1.schema.json", - "manifestPath": "../Norn/plugins/norn-graph.plugin.json", - "abiFixturePath": "../Norn/plugins/norn-graph.plugin-abi-fixture.json", - "advertisementSchemaPath": "schemas/gamecult.eve.plugin_advertisement.v1.schema.json", - "advertisementPath": "../Norn/plugins/norn-graph.plugin-advertisement.json", - "expectedPaths": [ - "../Norn/plugins/norn-graph.plugin.json", - "../Norn/plugins/norn-graph.plugin-abi-fixture.json", - "../Norn/plugins/norn-graph.plugin-advertisement.json", - "../Norn/crates/norn-eve-plugin/src/main.rs" - ] - }, - { - "pluginId": "tex.math", - "title": "TeX Math", - "kind": "external-owner", - "ownerRepo": "EvePlugins", - "repoRole": "plugin-owner", - "splitTarget": "EvePlugins/eve-plugin-tex", - "graduationTrigger": "Graduated: EvePlugins owns the executable TeX sidecar, fixtures, tests, and conformance witness.", - "capabilities": ["embed.tex", "tex.inline", "tex.block"], - "expectedRuntime": { - "invocationModel": "executable-sidecar", - "contract": "gamecult.eve.plugin_abi.v1", - "transports": ["stdio"], - "authority": ["renderer-independent", "no-provider-state-mutation", "provider-accepts-or-denies-commands"], - "sidecar": { - "processKind": "long-running-daemon", - "protocol": "stdio-ndjson", - "requestSchema": "gamecult.eve.plugin_abi.request.v1", - "responseSchema": "gamecult.eve.plugin_abi.response.v1", - "commandEnvelope": "gamecult.eve.command_invocation.v1", - "receiptSchema": "gamecult.eve.plugin_receipt.v1", - "stateAuthority": "emits-render-results-only-provider-owns-source-state" - } - }, - "requiredFixtures": [], - "schemaPath": "schemas/gamecult.eve.plugin.v1.schema.json", - "manifestPath": "../EvePlugins/plugins/eve-plugin-tex/plugin.json", - "abiFixturePath": "../EvePlugins/plugins/eve-plugin-tex/plugin-abi-fixture.json", - "advertisementSchemaPath": "schemas/gamecult.eve.plugin_advertisement.v1.schema.json", - "advertisementPath": "../EvePlugins/plugins/eve-plugin-tex/advertisement.json", - "expectedPaths": [ - "../EvePlugins/plugins/eve-plugin-tex/plugin.json", - "../EvePlugins/plugins/eve-plugin-tex/plugin-abi-fixture.json", - "../EvePlugins/plugins/eve-plugin-tex/advertisement.json", - "../EvePlugins/plugins/eve-plugin-tex/src/sidecar.mjs" - ] - } - ], - "splitTargets": [ - { - "id": "EveFlutter", - "ownerRepo": "EveFlutter", - "repoRole": "runtime-target", - "status": "incubating", - "runtimes": ["windows-flutter", "linux-flutter", "android-flutter"], - "requiredRuntimeStatuses": ["active"], - "requiredFeatures": ["embeddedDocuments"], - "requiredPlugins": [ - { - "pluginId": "sai.vn", - "capabilities": ["vn.stage", "story.choose", "story.continue", "story.jump"] - }, - { - "pluginId": "norn.graph", - "capabilities": ["embed.norn"] - }, - { - "pluginId": "tex.math", - "capabilities": ["embed.tex"] - } - ], - "proofs": [ - { - "description": "Provider picker consumes provider advertisements without Eve source-layout knowledge.", - "status": "passed", - "evidencePaths": [ - "../EveFlutter/lib/main.dart", - "../EveFlutter/test/parity_smoke_test.dart", - "tools/parity/run-parity.mjs" - ] - }, - { - "description": "Conformance pack can be consumed without depending on Eve's local harness layout.", - "status": "passed", - "evidencePaths": [ - "../EveFlutter/scripts/run-eveflutter-conformance-consumer-smoke.ps1", - "tools/conformance/consume-export.mjs", - "tools/parity/run-parity.mjs" - ] - }, - { - "description": "Flutter lifecycle evidence is declared and smoke-checked for EveFlutter incubation.", - "status": "passed", - "evidencePaths": [ - "../EveFlutter/eveflutter-lifecycle.json", - "../EveFlutter/scripts/run-eveflutter-lifecycle-smoke.ps1", - "../EveFlutter/scripts/capture-flutter-parity.ps1", - "../EveFlutter/scripts/capture-linux-flutter-parity.ps1", - "../EveFlutter/scripts/capture-android-flutter-parity.ps1" - ] - } - ], - "pendingProofs": [ - "Tagged EveFlutter release, test runner, and capture runners are owned by EveFlutter rather than Eve incubation." - ] - }, - { - "id": "EveUnity", - "ownerRepo": "EveUnity", - "repoRole": "runtime-target", - "status": "incubating", - "runtimes": ["unity-uitoolkit", "unity-scene"], - "requiredRuntimeStatuses": ["active"], - "requiredFeatures": ["embeddedDocuments"], - "requiredPlugins": [ - { - "pluginId": "sai.vn", - "capabilities": ["vn.stage", "story.choose", "story.continue", "story.jump"] - }, - { - "pluginId": "norn.graph", - "capabilities": ["embed.norn"] - } - ], - "proofs": [ - { - "description": "Unity UI Toolkit package lifecycle is owned and tested from EveUnity.", - "status": "passed", - "evidencePaths": ["../EveUnity/packages/org.gamecult.eve.unity-uitoolkit/eve-runtime-capability.json", "../EveUnity/scripts/pack-uitoolkit.ps1", "../EveUnity/scripts/run-uitoolkit-tests.ps1"] - }, - { - "description": "Aetheria consumes Unity surface and UI Toolkit packages from EveUnity.", - "status": "passed", - "evidencePaths": ["../EveUnity/packages/org.gamecult.eve.surface/package.json", "../EveUnity/packages/org.gamecult.eve.unity-uitoolkit/package.json", "../Aetheria/Packages/manifest.json"] - }, - { - "description": "Unity scene runtime is owned by EveUnity and its owner-repo sources are present.", - "status": "passed", - "evidencePaths": ["../EveUnity/packages/org.gamecult.eve.unity-scene/eve-runtime-capability.json", "../EveUnity/packages/org.gamecult.eve.unity-scene/package.json"] - }, - { - "description": "Unity scene lifecycle is owned and tested from EveUnity.", - "status": "passed", - "evidencePaths": ["../EveUnity/packages/org.gamecult.eve.unity-scene/eve-runtime-capability.json", "../EveUnity/scripts/pack.ps1", "../EveUnity/scripts/run-package-tests.ps1"] - }, - { - "description": "Aetheria daemon 3D ARPG world lowers through a generic EveUnity playable-world client without Aetheria runtime authority.", - "status": "passed", - "evidencePaths": ["../EveUnity/packages/org.gamecult.eve.unity-scene/eve-runtime-capability.json", "../Aetheria/Packages/org.gamecult.aetheria.eve-runtime/Runtime/AetheriaEveUnitySceneProviderBridge.cs", "../Aetheria/Assets/Scripts/Tests/DaemonRuntimeDocumentTests.cs"] - }, - { - "description": "Unity scene graph projection lowerer plus Sai, Norn, and TeX projection adapters exist without claiming provider simulation, plugin semantics, or capture ownership.", - "status": "passed", - "evidencePaths": ["../EveUnity/packages/org.gamecult.eve.unity-scene/package.json", "../EveUnity/packages/org.gamecult.eve.unity-scene/Runtime/EveUnitySceneSurfaceLowerer.cs", "../EveUnity/packages/org.gamecult.eve.unity-scene/Runtime/SaiVisualNovelUnitySceneProjectionAdapter.cs", "../EveUnity/packages/org.gamecult.eve.unity-scene/Runtime/NornGraphUnitySceneProjectionAdapter.cs", "../EveUnity/packages/org.gamecult.eve.unity-scene/Runtime/TeXMathUnitySceneProjectionAdapter.cs", "../EveUnity/packages/org.gamecult.eve.unity-scene/Tests/Editor/EveUnitySceneSurfaceLowererTests.cs"] - } - ], - "pendingProofs": ["Tagged UPM release is cut from EveUnity.", "Unity PlayMode frame capture is attached to conformance for Aetheria and the generic world-smoke provider.", "Generic live CultMesh/CultNet provider transport replaces Aetheria local state-file polling."] - }, - { - "id": "EveElectron", - "ownerRepo": "EveElectron", - "repoRole": "runtime-target", - "status": "graduated", - "runtimes": ["electron-shell"], - "requiredRuntimeStatuses": ["active"], - "requiredFeatures": ["providerAdvertisements", "surfaceTreeProjection", "embeddedDocuments"], - "requiredPlugins": [], - "proofs": [ - { - "description": "EveElectron owner repository publishes the generic runtime package.", - "status": "passed", - "evidencePaths": [ - "../EveElectron/package.json", - "../EveElectron/eve-runtime-capability.json", - "../EveElectron/src/index.mjs" - ] - }, - { - "description": "EveElectron owns secure window and preload lifecycle.", - "status": "passed", - "evidencePaths": [ - "../EveElectron/src/window-host.mjs", - "../EveElectron/src/eve-provider-preload.cjs", - "../EveElectron/test/window-host.test.mjs", - "../EveElectron/test/preload.test.cjs" - ] - }, - { - "description": "Aetheria consumes EveElectron for generic window and preload authority while retaining provider transport and daemon lifecycle.", - "status": "passed", - "evidencePaths": [ - "../Aetheria/Aetheria.Rts.Web/package.json", - "../Aetheria/Aetheria.Rts.Web/Electron/main.ts", - "../Aetheria/Aetheria.Rts.Web/Electron/preload.cjs", - "../Aetheria/Aetheria.Rts.Web/Client/app.ts" - ] - } - ], - "pendingProofs": [] - }, - { - "id": "EveTui", - "ownerRepo": "EveTui", - "repoRole": "runtime-target", - "status": "incubating", - "runtimes": ["tui"], - "requiredRuntimeStatuses": ["active"], - "requiredFeatures": ["providerAdvertisements", "embeddedDocuments", "commandTransport"], - "requiredPlugins": [], - "proofs": [ - { - "description": "EveTui split handoff is machine-readable.", - "status": "passed", - "evidencePaths": [ - "runtimes/incubating/eve-tui/evetui-split-handoff.json", - "scripts/run-evetui-split-handoff-smoke.ps1", - "docs/eve-migration-roadmap.md", - "docs/parity-testing-harness.md" - ] - }, - { - "description": "EveTui lifecycle contract is machine-readable and smoke-checked.", - "status": "passed", - "evidencePaths": [ - "runtimes/incubating/eve-tui/eve-runtime-capability.json", - "scripts/run-evetui-lifecycle-smoke.ps1", - "scripts/run-evetui-provider-shell-smoke.ps1" - ] - }, - { - "description": "EveTui provider-shell skeleton selects advertised surfaces, lowers Eve surface trees into terminal grids, and emits Eve command intents.", - "status": "passed", - "evidencePaths": [ - "runtimes/incubating/eve-tui/package.json", - "runtimes/incubating/eve-tui/src/eve-tui-shell.mjs", - "runtimes/incubating/eve-tui/test/eve-tui-shell.test.mjs", - "scripts/run-evetui-provider-shell-smoke.ps1" - ] - } - ], - "pendingProofs": [ - "TUI package release is cut from EveTui rather than Eve incubation.", - "TUI capture path is owned by EveTui rather than Eve incubation." - ] - } - ], - "runtimes": [ - { - "id": "electron-shell", - "title": "Electron shell", - "kind": "active", - "ownerRepo": "EveElectron", - "repoRole": "runtime-target", - "splitTarget": "EveElectron", - "currentHostRepo": "EveElectron", - "sourceRoot": "../EveElectron", - "graduationTrigger": "Graduated: the runtime body and package are owned here.", - "capabilityManifest": { - "schemaPath": "schemas/gamecult.eve.runtime_capability.v1.schema.json", - "manifestPath": "eve-runtime-capability.json" - }, - "requiredFixtures": ["embedded-surface", "aetheria-world", "eve-world-smoke"], - "pluginFixtures": ["sai-vn"], - "supportedFeatures": ["providerAdvertisements", "commandTransport", "surfaceTreeProjection", "embeddedDocuments", "pluginProjection", "securePreloadBridge", "windowLifecycle"], - "supportedPlugins": [ - { - "pluginId": "sai.vn", - "capabilities": ["vn.stage", "story.choose", "story.continue", "story.jump"] - }, - { - "pluginId": "norn.graph", - "capabilities": ["embed.norn"] - }, - { - "pluginId": "tex.math", - "capabilities": ["embed.tex", "tex.inline", "tex.block"] - } - ], - "unsupportedPlugins": [], - "worldSurfaceLowering": [ - { - "targetId": "electron-shell", - "surfaceKinds": ["interactive-world", "interactive-world-editor"], - "projectionKinds": ["provider-authored-world-surface", "provider-authored-world-editor-surface"], - "supportLevel": "provider-shell-surface-tree-command-surface", - "ownership": "runtime-lowers-provider-world-surface-without-owning-world-state", - "evidencePaths": [ - "src/eve-electron-shell.mjs", - "src/window-host.mjs", - "src/eve-provider-preload.cjs", - "test/shell.test.mjs", - "test/window-host.test.mjs", - "test/preload.test.cjs" - ] - } - ], - "expectedPaths": [ - "../EveElectron/package.json", - "../EveElectron/package-lock.json", - "../EveElectron/eve-runtime-capability.json", - "../EveElectron/src/eve-electron-shell.mjs", - "../EveElectron/src/window-host.mjs", - "../EveElectron/src/eve-provider-preload.cjs", - "../EveElectron/test/shell.test.mjs", - "../EveElectron/test/window-host.test.mjs", - "../EveElectron/test/preload.test.cjs" - ], - "expectedSourceSymbols": [ - { - "path": "../EveElectron/src/eve-electron-shell.mjs", - "contains": ["EveElectronShell", "selectSurface", "lowerSurface", "createCommandIntent", "normalizeSurfaceDocument", "buildShellNode", "buildPluginProjection", "normalizeEmbeddedDocuments", "embeddedDocuments", "shellElementKind", "sai.vn", "norn.graph", "tex.math", "sidecar-advertised-plugin-abi", "worldInteraction", "commandBoundary", "receiptSchema", "electron-shell"] - }, - { - "path": "../EveElectron/test/shell.test.mjs", - "contains": ["lowers a non-product interactive world", "fixture.world", "provider-authored-world-surface", "fixture.world.commands"] - }, - { - "path": "../EveElectron/src/window-host.mjs", - "contains": ["createEveElectronWindow", "registerEveWindowControls", "contextIsolation", "nodeIntegration", "setWindowOpenHandler"] - }, - { - "path": "../EveElectron/src/eve-provider-preload.cjs", - "contains": ["installEveProviderBridge", "providerAdvertisement", "surface", "document", "submitCommand", "windowControl"] - } - ], - "capture": { - "status": "owner-window-png", - "note": "EveElectron owns native window PNG production for the generic and Aetheria provider surfaces.", - "artifacts": [ - { - "kind": "png", - "schema": "image/png", - "path": "artifacts/capture/generic-world/window.png", - "runtimeId": "electron-shell", - "providerId": "eve.world-smoke", - "surfaceId": "eve.world-smoke.surface", - "sourceSurfacePath": "../Eve/web/fixtures/eve-world-smoke-surface.json", - "requestPath": "artifacts/capture/generic-world/runtime-witness.json" - }, - { - "kind": "png", - "schema": "image/png", - "path": "artifacts/capture/aetheria-world/window.png", - "runtimeId": "electron-shell", - "providerId": "aetheria", - "surfaceId": "aetheria.daemon.game", - "sourceSurfacePath": "../Aetheria/conformance/eve/aetheria-world-surface.json", - "requestPath": "artifacts/capture/aetheria-world/runtime-witness.json" - } - ] - } - }, - { - "id": "tui", - "title": "Compact TUI", - "kind": "active", - "ownerRepo": "EveTui", - "repoRole": "runtime-target", - "splitTarget": "EveTui", - "graduationTrigger": "Split when TUI has a provider-agnostic terminal/grid lowerer, provider advertisement consumption, command transport, terminal capability manifest, and transcript/cell-grid capture evidence.", - "capabilityManifest": { - "schemaPath": "schemas/gamecult.eve.runtime_capability.v1.schema.json", - "manifestPath": "runtimes/incubating/eve-tui/eve-runtime-capability.json" - }, - "splitHandoffPath": "runtimes/incubating/eve-tui/evetui-split-handoff.json", - "requiredFixtures": ["embedded-surface", "aetheria-world", "eve-world-smoke"], - "pluginFixtures": ["sai-vn"], - "supportedFeatures": ["providerAdvertisements", "commandTransport", "terminalGridSummary", "terminalGridLowering", "embeddedDocuments", "pluginProjection"], - "supportedPlugins": [ - { - "pluginId": "sai.vn", - "capabilities": ["vn.stage", "story.choose", "story.continue", "story.jump"] - }, - { - "pluginId": "norn.graph", - "capabilities": ["embed.norn"] - }, - { - "pluginId": "tex.math", - "capabilities": ["embed.tex", "tex.inline", "tex.block"] - } - ], - "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": [ - "runtimes/incubating/eve-tui/src/eve-tui-shell.mjs", - "runtimes/incubating/eve-tui/test/eve-tui-shell.test.mjs", - "web/fixtures/sai-vn-surface.json", - "../Aetheria/conformance/eve/aetheria.provider-advertisement.json", - "web/fixtures/eve-world-smoke.provider-advertisement.json" - ] - } - ], - "expectedPaths": [ - "runtimes/incubating/eve-tui/package.json", - "runtimes/incubating/eve-tui/src/eve-tui-shell.mjs", - "runtimes/incubating/eve-tui/test/eve-tui-shell.test.mjs", - "runtimes/incubating/eve-tui/evetui-split-handoff.json", - "runtimes/incubating/eve-tui/eve-runtime-capability.json", - "scripts/run-evetui-split-handoff-smoke.ps1", - "scripts/run-evetui-lifecycle-smoke.ps1", - "scripts/run-evetui-provider-shell-smoke.ps1", - "scripts/run-evetui-capture-contract-smoke.ps1", - "scripts/run-evetui-capture-smoke.ps1", - "scripts/run-evetui-release-contract-smoke.ps1", - "tools/evetui/evetui-capture-contract.mjs", - "tools/evetui/evetui-capture-contract.test.mjs", - "tools/evetui/evetui-capture-artifact.mjs", - "tools/evetui/evetui-capture-artifact.test.mjs", - "tools/evetui/evetui-release-contract.mjs", - "tools/evetui/evetui-release-contract.test.mjs" - ], - "expectedSourceSymbols": [ - { - "path": "runtimes/incubating/eve-tui/src/eve-tui-shell.mjs", - "contains": ["EveTuiShell", "selectSurface", "lowerSurface", "normalizeSurfaceDocument", "buildComponentLines", "buildPluginProjection", "collectPluginProjections", "collectEmbeddedDocumentSlots", "normalizeEmbeddedDocuments", "embeddedDocumentSlots", "pluginProjections", "terminalElementKind", "sai.vn", "norn.graph", "tex.math", "sidecar-advertised-plugin-abi", "createCommandIntent", "renderSummary", "gamecult.eve.tui_grid.v1", "commandBoundary", "receiptSchema"] - }, - { - "path": "runtimes/incubating/eve-tui/test/eve-tui-shell.test.mjs", - "contains": ["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", "embeddedDocumentSlots", "pluginProjections", "optional-nested", "norn.map", "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"] - }, - { - "path": "tools/evetui/evetui-capture-contract.mjs", - "contains": ["buildTuiCaptureRequest", "gamecult.eve.runtime_capture_request.v1", "artifactKind", "loweringTargets", "writeCaptureRequest"] - }, - { - "path": "tools/evetui/evetui-capture-contract.test.mjs", - "contains": ["builds TUI capture request from provider advertisement", "builds TUI capture request from a declared generic world provider", "rejects capture request when the surface does not advertise TUI", "artifacts/evetui-capture/smoke/tui-grid.json", "eve.world-smoke.commands"] - }, - { - "path": "tools/evetui/evetui-capture-artifact.mjs", - "contains": ["buildTuiCaptureArtifact", "EveTuiShell", "gamecult.eve.tui_grid.v1", "writeTuiCaptureArtifact", "request-output"] - }, - { - "path": "tools/evetui/evetui-capture-artifact.test.mjs", - "contains": ["builds TUI JSON grid capture artifact", "builds TUI JSON grid capture artifact from a generic world provider", "gamecult.eve.tui_grid.v1", "artifacts/evetui-capture/smoke/tui-grid.json", "eve.world-smoke.commands"] - }, - { - "path": "tools/evetui/evetui-release-contract.mjs", - "contains": ["buildTuiReleaseRequest", "gamecult.eve.runtime_release_request.v1", "evetui-release", "writeReleaseRequest"] - }, - { - "path": "tools/evetui/evetui-release-contract.test.mjs", - "contains": ["builds EveTui release request from package manifest", "evetui-v0.1.0", "artifacts/evetui-release/0.1.0/eve-tui-0.1.0.tgz"] - } - ], - "capture": { - "status": "json-grid", - "note": "TUI attaches a provider-advertisement-backed gamecult.eve.tui_grid.v1 JSON capture artifact; packaged runtime ownership remains an EveTui split blocker.", - "artifacts": [ - { - "kind": "json-grid", - "schema": "gamecult.eve.tui_grid.v1", - "path": "artifacts/evetui-capture/latest/tui-grid.json", - "providerId": "aetheria", - "surfaceId": "aetheria.daemon.game", - "sourceSurfacePath": "../Aetheria/conformance/eve/aetheria-world-surface.json", - "requestPath": "artifacts/evetui-capture/latest/capture-request.json" - }, - { - "kind": "json-grid", - "schema": "gamecult.eve.tui_grid.v1", - "path": "artifacts/evetui-capture/latest/eve-world-smoke-tui-grid.json", - "providerId": "eve.world-smoke", - "surfaceId": "eve.world-smoke.surface", - "sourceSurfacePath": "web/fixtures/eve-world-smoke-surface.json", - "requestPath": "artifacts/evetui-capture/latest/eve-world-smoke-capture-request.json" - } - ] - } - }, - { - "id": "unity-scene", - "title": "Unity / Scene", - "kind": "active", - "ownerRepo": "EveUnity", - "repoRole": "runtime-target", - "splitTarget": "EveUnity", - "graduationTrigger": "Graduated: EveUnity owns the provider-agnostic Unity scene package, release lifecycle, runtime tests, and generic PlayMode capture. The Aetheria daemon product witness remains active lifecycle work.", - "capabilityManifest": { - "schemaPath": "schemas/gamecult.eve.runtime_capability.v1.schema.json", - "manifestPath": "packages/org.gamecult.eve.unity-scene/eve-runtime-capability.json" - }, - "requiredFixtures": ["aetheria-world", "eve-world-smoke"], - "pluginFixtures": ["sai-vn"], - "supportedFeatures": ["providerAdvertisements", "commandTransport", "providerSurfaceSession", "providerSurfaceSource", "providerSurfaceDocumentSource", "liveProviderTransportBridge", "livePlayableWorldClient", "providerCommandReceipts", "providerAssetManifestDocumentSource", "playableWorldClientHost", "playableWorldClientBootstrap", "playableWorldInputDriver", "playableWorldCameraRig", "sceneGraphProjection", "playableWorldProjection", "playableWorldRuntimeHost", "playableWorldScenePresentation", "unityGameObjectSceneSink", "providerAssetManifestResolution", "providerAssetManifestSource", "embeddedDocuments"], - "supportedPlugins": [ - { - "pluginId": "sai.vn", - "capabilities": ["vn.stage", "story.choose", "story.continue", "story.jump"], - "projectionAdapter": "SaiVisualNovelUnitySceneProjectionAdapter" - }, - { - "pluginId": "norn.graph", - "capabilities": ["embed.norn"], - "projectionAdapter": "NornGraphUnitySceneProjectionAdapter" - }, - { - "pluginId": "tex.math", - "capabilities": ["embed.tex", "tex.inline", "tex.block", "tex.scene-placement"], - "projectionAdapter": "TeXMathUnitySceneProjectionAdapter" - } - ], - "unsupportedPlugins": [], - "worldSurfaceLowering": [ - { - "targetId": "unity-scene", - "surfaceKinds": ["interactive-world"], - "projectionKinds": ["provider-authored-world-surface"], - "supportLevel": "unity-playable-world-scene-command-surface", - "ownership": "runtime-lowers-provider-world-surface-without-owning-world-state", - "evidencePaths": ["packages/org.gamecult.eve.unity-scene/Runtime/EveUnitySceneSurfaceLowerer.cs", "packages/org.gamecult.eve.unity-scene/Runtime/EveUnitySceneClientSession.cs", "packages/org.gamecult.eve.unity-scene/Runtime/EveUnitySceneProviderConnection.cs", "packages/org.gamecult.eve.unity-scene/Runtime/EveUnitySceneLiveProviderBridge.cs", "packages/org.gamecult.eve.unity-scene/Runtime/EveUnityPlayableWorldLiveClient.cs", "packages/org.gamecult.eve.unity-scene/Runtime/EveUnityPlayableWorldRuntime.cs", "packages/org.gamecult.eve.unity-scene/Runtime/EveUnityPlayableWorldClientHost.cs", "packages/org.gamecult.eve.unity-scene/Runtime/EveUnityPlayableWorldClientBootstrap.cs", "packages/org.gamecult.eve.unity-scene/Runtime/EveUnityPlayableWorldInputDriver.cs", "packages/org.gamecult.eve.unity-scene/Runtime/EveUnityPlayableWorldCameraRig.cs", "packages/org.gamecult.eve.unity-scene/Runtime/EveUnityProviderRefreshSource.cs", "packages/org.gamecult.eve.unity-scene/Runtime/EveUnityPlayableWorldPresenter.cs", "packages/org.gamecult.eve.unity-scene/Runtime/EveUnityGameObjectPlayableWorldSceneSink.cs", "packages/org.gamecult.eve.unity-scene/Runtime/EveUnityPlayableWorldAssetManifest.cs", "packages/org.gamecult.eve.unity-scene/Runtime/SaiVisualNovelUnitySceneProjectionAdapter.cs", "packages/org.gamecult.eve.unity-scene/Runtime/NornGraphUnitySceneProjectionAdapter.cs", "packages/org.gamecult.eve.unity-scene/Runtime/TeXMathUnitySceneProjectionAdapter.cs", "packages/org.gamecult.eve.unity-scene/Tests/Editor/EveUnitySceneSurfaceLowererTests.cs"] - } - ], - "expectedPaths": ["packages/org.gamecult.eve.unity-scene/package.json", "packages/org.gamecult.eve.unity-scene/eve-runtime-capability.json", "packages/org.gamecult.eve.unity-scene/Runtime/EveUnitySceneSurfaceLowerer.cs", "packages/org.gamecult.eve.unity-scene/Runtime/EveUnitySceneClientSession.cs", "packages/org.gamecult.eve.unity-scene/Runtime/EveUnitySceneProviderConnection.cs", "packages/org.gamecult.eve.unity-scene/Runtime/EveUnitySceneLiveProviderBridge.cs", "packages/org.gamecult.eve.unity-scene/Runtime/EveUnityPlayableWorldLiveClient.cs", "packages/org.gamecult.eve.unity-scene/Runtime/EveUnityPlayableWorldRuntime.cs", "packages/org.gamecult.eve.unity-scene/Runtime/EveUnityPlayableWorldClientHost.cs", "packages/org.gamecult.eve.unity-scene/Runtime/EveUnityPlayableWorldClientBootstrap.cs", "packages/org.gamecult.eve.unity-scene/Runtime/EveUnityPlayableWorldInputDriver.cs", "packages/org.gamecult.eve.unity-scene/Runtime/EveUnityPlayableWorldCameraRig.cs", "packages/org.gamecult.eve.unity-scene/Runtime/EveUnityProviderRefreshSource.cs", "packages/org.gamecult.eve.unity-scene/Runtime/EveUnityPlayableWorldPresenter.cs", "packages/org.gamecult.eve.unity-scene/Runtime/EveUnityGameObjectPlayableWorldSceneSink.cs", "packages/org.gamecult.eve.unity-scene/Runtime/EveUnityPlayableWorldAssetManifest.cs", "packages/org.gamecult.eve.unity-scene/Runtime/SaiVisualNovelUnitySceneProjectionAdapter.cs", "packages/org.gamecult.eve.unity-scene/Runtime/NornGraphUnitySceneProjectionAdapter.cs", "packages/org.gamecult.eve.unity-scene/Runtime/TeXMathUnitySceneProjectionAdapter.cs", "packages/org.gamecult.eve.unity-scene/Tests/Editor/EveUnitySceneSurfaceLowererTests.cs", "packages/org.gamecult.eve.unity-scene/Runtime/GameCult.Eve.UnityScene.asmdef", "scripts/pack.ps1", "scripts/build-release-request.ps1", "packages/org.gamecult.eve.unity-scene/Tests/Editor/GameCult.Eve.UnityScene.Tests.asmdef", "scripts/run-package-tests.ps1", "scripts/build-capture-request.ps1"], - "expectedSourceSymbols": [ - { - "path": "packages/org.gamecult.eve.unity-scene/Runtime/EveUnitySceneSurfaceLowerer.cs", - "contains": ["EveUnitySceneSurfaceLowerer", "EveUnitySceneProviderSurfaceAdvertisement", "WorldInteraction", "CommandBoundary", "ReceiptSchema", "EveSurfaceCommandRequest", "BuildSceneGraph", "BuildPlayableWorld", "EveUnityPlayableWorldProjection", "EveUnityPlayableWorldEntity", "BuildEmbeddedDocumentSlots", "BuildPluginProjection", "SaiVisualNovelUnitySceneProjectionAdapter", "TeXMathUnitySceneProjectionAdapter", "EveUnitySceneEmbeddedDocumentSlot", "EveUnityScenePluginProjection", "EveUnitySceneNode", "SceneObjectKind", "playable-world-root", "unity-scene"] - }, - { - "path": "packages/org.gamecult.eve.unity-scene/Runtime/EveUnitySceneClientSession.cs", - "contains": ["EveUnitySceneClientSession", "EveUnitySceneProviderSurfaceSnapshot", "ActiveVersion", "Connect", "ApplySnapshot", "CreateMoveIntent", "CreateFocusIntent", "CreateTargetIntent", "CreateActionIntent", "CreatePlayableWorldIntent", "commandId", "targetPosition", "unity-scene"] - }, - { - "path": "packages/org.gamecult.eve.unity-scene/Runtime/EveUnitySceneProviderConnection.cs", - "contains": ["IEveUnitySceneProviderSurfaceSource", "IEveUnitySceneCommandSink", "IEveUnitySceneProviderSurfaceDocumentSource", "EveUnitySceneProviderSurfaceDocument", "EveUnitySceneProviderSurfaceDocumentSource", "ToSnapshot", "DocumentAvailable", "EveUnitySceneProviderConnection", "SnapshotAvailable", "CurrentSnapshot", "Connect", "Refresh", "SubmitMoveIntent", "SubmitFocusIntent", "SubmitTargetIntent", "SubmitActionIntent", "Disconnect"] - }, - { - "path": "packages/org.gamecult.eve.unity-scene/Runtime/EveUnitySceneLiveProviderBridge.cs", - "contains": ["IEveUnitySceneLiveProviderTransport", "EveUnitySceneLiveProviderTransportBehaviour", "EveUnitySceneLiveProviderBridgeComponent", "EveUnitySceneLiveProviderBridge", "IEveUnitySceneProviderSurfaceDocumentSource", "IEveUnityPlayableWorldAssetManifestDocumentSource", "IEveUnitySceneCommandSink", "IEveUnitySceneCommandReceiptSource", "IEveUnityProviderRefreshSource", "SubmitCommand", "CommandReceiptAvailable", "transportBehaviour"] - }, - { - "path": "packages/org.gamecult.eve.unity-scene/Runtime/EveUnityPlayableWorldLiveClient.cs", - "contains": ["EveUnityPlayableWorldLiveClient", "IEveUnitySceneCommandReceiptSource", "EveUnitySceneCommandReceipt", "EveUnitySceneProviderConnection", "EveUnityPlayableWorldPresenter", "ActiveWorld", "LastPresentation", "LastReceipt", "ReceiptAvailable", "Connect", "Refresh", "SubmitMoveIntent", "SubmitFocusIntent", "SubmitTargetIntent", "SubmitActionIntent", "ProjectionUpdated", "IsProviderOwned", "Disconnect"] - }, - { - "path": "packages/org.gamecult.eve.unity-scene/Runtime/EveUnityPlayableWorldRuntime.cs", - "contains": ["EveUnityPlayableWorldRuntime", "CreateForGameObjectScene", "EveUnityLivePlayableWorldAssetProvider", "IEveUnitySceneProviderSurfaceDocumentSource", "IEveUnitySceneCommandSink", "IEveUnityPlayableWorldAssetManifestDocumentSource", "IEveUnitySceneCommandReceiptSource", "EveUnityPlayableWorldAssetManifestCache", "AssetManifests", "ActiveWorld", "SubmitMoveIntent", "SubmitMoveVectorIntent", "SubmitFocusIntent", "SubmitTargetIntent", "SubmitActionIntent", "Connect", "Refresh", "Disconnect"] - }, - { - "path": "packages/org.gamecult.eve.unity-scene/Runtime/EveUnityPlayableWorldClientHost.cs", - "contains": ["EveUnityPlayableWorldClientHost", "MonoBehaviour", "providerSurfaceDocuments", "IEveUnitySceneProviderSurfaceDocumentSource", "IEveUnitySceneCommandSink", "IEveUnityPlayableWorldAssetManifestDocumentSource", "IEveUnitySceneCommandReceiptSource", "IEveUnityGameObjectAssetProvider", "IEveUnityProviderRefreshSource", "Configure", "ConnectOnEnable", "CreateForGameObjectScene", "Connect", "Refresh", "SceneRoot", "SubmitMoveIntent", "SubmitMoveVectorIntent", "SubmitFocusIntent", "SubmitTargetIntent", "SubmitActionIntent", "Disconnect"] - }, - { - "path": "packages/org.gamecult.eve.unity-scene/Runtime/EveUnityPlayableWorldClientBootstrap.cs", - "contains": ["EveUnityPlayableWorldClientBootstrap", "ConfigureProvider", "Mount", "ResolveHost", "ResolveSceneRoot", "ResolveCameraTransform", "EveUnityPlayableWorldClientHost", "EveUnityPlayableWorldInputDriver", "EveUnityPlayableWorldCameraRig", "IEveUnitySceneProviderSurfaceDocumentSource", "IEveUnitySceneCommandSink", "IEveUnityPlayableWorldAssetManifestDocumentSource", "IEveUnitySceneCommandReceiptSource", "IEveUnityGameObjectAssetProvider"] - }, - { - "path": "packages/org.gamecult.eve.unity-scene/Runtime/EveUnityPlayableWorldInputDriver.cs", - "contains": ["EveUnityPlayableWorldInputDriver", "EveUnityPlayableWorldMoveVector", "FromCameraRelativeInput", "SubmitMoveVectorInput", "SubmitPrimaryAction", "Input.GetAxisRaw", "Input.GetButtonDown", "directionX", "directionY", "scalarValue", "SubmitMoveVectorIntent"] - }, - { - "path": "packages/org.gamecult.eve.unity-scene/Runtime/EveUnityPlayableWorldCameraRig.cs", - "contains": ["EveUnityPlayableWorldCameraRig", "ApplyRig", "arpg.orbital-follow.v1", "EveUnityPlayableWorldEntityMarker", "SceneRoot", "LookAt"] - }, - { - "path": "packages/org.gamecult.eve.unity-scene/Runtime/EveUnityPlayableWorldPresenter.cs", - "contains": ["IEveUnityPlayableWorldSceneSink", "IEveUnityPlayableWorldAssetResolver", "EveUnityPlayableWorldPresenter", "EveUnityPlayableWorldAssetBinding", "EveUnityPlayableWorldPresentation", "EveUnityAssetRefResolver", "ConfigureWorld", "UpsertEntity", "RemoveEntity", "provider-asset-ref", "unity-generated-placeholder"] - }, - { - "path": "packages/org.gamecult.eve.unity-scene/Runtime/EveUnityGameObjectPlayableWorldSceneSink.cs", - "contains": ["EveUnityGameObjectPlayableWorldSceneSink", "IEveUnityGameObjectAssetProvider", "EveUnityResourcesAssetProvider", "EveUnityPlayableWorldEntityMarker", "GameObject", "Transform", "Resources.Load", "PrimitiveType.Capsule", "DestroyImmediate", "localPosition", "localRotation"] - }, - { - "path": "packages/org.gamecult.eve.unity-scene/Runtime/EveUnityPlayableWorldAssetManifest.cs", - "contains": ["EveUnityPlayableWorldAssetManifest", "IEveUnityPlayableWorldAssetManifestSource", "IEveUnityPlayableWorldAssetManifestDocumentSource", "EveUnityPlayableWorldAssetManifestDocumentSource", "EveUnityPlayableWorldAssetManifestDocument", "EveUnityPlayableWorldAssetManifestDocumentEntry", "FromDocument", "DocumentAvailable", "gamecult.eve.unity_playable_world_asset_manifest.v1", "EveUnityPlayableWorldAssetManifestCache", "EveUnityPlayableWorldAssetManifestEntry", "EveUnityManifestGameObjectAssetProvider", "ManifestAvailable", "GetForWorld", "CreateGameObjectAssetProvider", "ManifestRef", "ResourcesPath", "PrefabKey", "NormalizeResourcesPath", "ResolvePrefab", "Resources.Load"] - }, - { - "path": "packages/org.gamecult.eve.unity-scene/Runtime/SaiVisualNovelUnitySceneProjectionAdapter.cs", - "contains": ["SaiVisualNovelUnitySceneProjectionAdapter", "sai.vn", "vn.stage", "story.choose", "story.continue", "story.jump", "gamecult.eve.plugin_abi.v1", "sidecar-advertised-plugin-abi", "Sai"] - }, - { - "path": "packages/org.gamecult.eve.unity-scene/Runtime/NornGraphUnitySceneProjectionAdapter.cs", - "contains": ["NornGraphUnitySceneProjectionAdapter", "norn.graph", "embed.norn", "gamecult.eve.plugin_abi.v1", "sidecar-advertised-plugin-abi", "Norn"] - }, - { - "path": "packages/org.gamecult.eve.unity-scene/Runtime/TeXMathUnitySceneProjectionAdapter.cs", - "contains": ["TeXMathUnitySceneProjectionAdapter", "tex.math", "embed.tex", "tex.scene-placement", "gamecult.eve.plugin_abi.v1", "sidecar-advertised-plugin-abi", "EvePlugins"] - }, - { - "path": "packages/org.gamecult.eve.unity-scene/Tests/Editor/EveUnitySceneSurfaceLowererTests.cs", - "contains": ["LowerCarriesProviderAdvertisedWorldBoundary", "LowerBuildsProviderAgnosticSceneGraphFromSurfaceTree", "LowerExtractsPlayableArpgWorldFromGenericScene3dSurface", "GenericClientSessionConsumesAetheriaPlayableWorldSnapshotWithoutAetheriaTypes", "GenericProviderConnectionAppliesLiveSnapshotsAndSubmitsCommandsThroughSink", "ProviderSurfaceDocumentSourceFeedsPlayableClientWithoutAetheriaTypes", "PlayableWorldRuntimeComposesProviderDocumentsAssetsReceiptsAndSceneSink", "LiveProviderBridgeFeedsPlayableWorldRuntimeThroughTransportPorts", "PlayableWorldClientHostMountsInterfaceProviderWithoutProviderTypes", "PlayableWorldClientBootstrapWiresGenericUnityClientFromProviderInterfaces", "PlayableWorldClientHostSubmitsProviderOwnedMoveVector", "PlayableWorldInputDriverBuildsCameraRelativeMoveVectorWithoutProviderTypes", "PlayableWorldCameraRigFollowsAdvertisedPlayerEntityWithoutProviderTypes", "PlayableWorldPresenterInstantiatesUpdatesAndDespawnsProviderEntities", "LivePlayableWorldClientPresentsProviderSnapshotsAndKeepsCommandsProviderOwned", "LivePlayableWorldClientRefreshesFromProviderSnapshotAfterReceiptWithoutOwningMovement", "AssetManifestMapsProviderAssetRefsToUnityLoadKeysWithoutAetheriaTypes", "AssetManifestCacheTracksPlayableWorldManifestPointerAndLiveUpdates", "AssetManifestDocumentSourceFeedsCacheWithoutChangingSceneLowering", "FakeProviderSurfaceSource", "FakeProviderSurfaceDocumentSource", "FakeCommandReceiptSource", "FakeAssetManifestSource", "FakeAssetManifestDocumentSource", "FakeLiveProviderTransport", "FakeCommandSink", "FakePlayableWorldSceneSink", "FakePlayableWorldProviderComponent", "SaiVisualNovelLowersThroughRuntimeProjectionAdapterWithoutOwningStoryState", "CommandIntentCarriesAdvertisedBoundaryWithoutOwningReceipts", "pending", "reconciled", "world-projection-node", "playable-world-root", "playable-world-entity", "world-field-3d", "arpg-third-person", "cultmesh://aetheria/assets/manifest", "cultmesh://aetheria/eve/surfaces/aetheria.daemon.game", "aetheria.daemon.move_intent", "sai-vn-scene-stage", "norn-graph-scene-projection", "tex-math-scene-projection", "norn.map", "cultmesh://aetheria/norn/map", "sai.vn", "norn.graph", "tex.math", "sidecar-advertised-plugin-abi", "aetheria.daemon.commands", "aetheria.eve_command_acceptance_status.v1"] - } - ], - "capture": { - "status": "partial-world-smoke-playmode-capture", - "note": "The owner-controlled generic world-smoke PlayMode PNG passes; the separately running Aetheria daemon capture remains active lifecycle work.", - "artifacts": [ - { - "kind": "png", - "schema": "gamecult.eve.runtime_frame_capture.v1", - "path": "artifacts/playmode/world-smoke.png", - "providerId": "eve.world-smoke", - "surfaceId": "eve.world-smoke.surface" - } - ] - }, - "sourceRoot": "../EveUnity", - "currentHostRepo": "EveUnity", - "lifecycle": { - "release": { - "ownerRepo": "EveUnity", - "status": "owner-repo-upm-artifact", - "releaseContract": { - "ownerRepo": "EveUnity", - "packageName": "org.gamecult.eve.unity-scene", - "packageRoot": "packages/org.gamecult.eve.unity-scene", - "versionSource": "packages/org.gamecult.eve.unity-scene/package.json", - "tagPattern": "eveunity-scene-v{version}", - "artifactKind": "upm-package", - "requestSchema": "gamecult.eve.runtime_release_request.v1", - "requestBuilder": "scripts/build-release-request.ps1", - "artifactBuilder": "scripts/pack.ps1", - "artifactPattern": "artifacts/eveunity-scene-release/{version}/org.gamecult.eve.unity-scene-{version}.tgz", - "publishProof": "Tagged Unity scene package release is created from the EveUnity repository.", - "requiredPackageDependencies": [ - { - "packageName": "org.gamecult.eve.surface", - "version": "0.1.0", - "packageManager": "upm", - "ownerRepo": "EveUnity", - "purpose": "Shared Unity DTOs for gamecult.eve.surface.v1 documents." - } - ] - }, - "evidencePaths": ["packages/org.gamecult.eve.unity-scene/package.json", "packages/org.gamecult.eve.unity-scene/Runtime/GameCult.Eve.UnityScene.asmdef", "scripts/pack.ps1", "scripts/build-release-request.ps1"], - "pendingProofs": ["Cut and publish the first tagged EveUnity package release."] - }, - "test": { - "ownerRepo": "EveUnity", - "status": "unity-scene-editmode-batchmode", - "testContract": { - "ownerRepo": "EveUnity", - "runnerKind": "unity-editmode-batchmode", - "runnerScript": "scripts/run-package-tests.ps1", - "consumerProject": "E:\\Projects\\Aetheria", - "packageName": "org.gamecult.eve.unity-scene", - "testAssembly": "GameCult.Eve.UnityScene.Tests", - "testPlatform": "EditMode", - "resultsArtifact": "artifacts/aetheria-unity-scene-editmode/{stamp}/unity-editmode-results.xml", - "logArtifact": "artifacts/aetheria-unity-scene-editmode/{stamp}/unity-editmode.log", - "manifestMutation": "Temporarily adds org.gamecult.eve.unity-scene to Aetheria Unity testables, runs GameCult.Eve.UnityScene.Tests, and restores Packages/manifest.json." - }, - "evidencePaths": ["packages/org.gamecult.eve.unity-scene/Tests/Editor/GameCult.Eve.UnityScene.Tests.asmdef", "packages/org.gamecult.eve.unity-scene/Tests/Editor/EveUnitySceneSurfaceLowererTests.cs", "scripts/run-package-tests.ps1"], - "pendingProofs": ["Move the Aetheria consumer scenario into a provider-owned conformance pack that installs EveUnity from its owner repository."] - }, - "capture": { - "ownerRepo": "EveUnity", - "status": "partial-world-smoke-playmode-capture", - "captureContract": { - "ownerRepo": "EveUnity", - "runtimeId": "unity-scene", - "targetId": "unity-scene", - "requestSchema": "gamecult.eve.runtime_capture_request.v1", - "requestBuilder": "scripts/build-capture-request.ps1", - "advertisementPath": "Aetheria:conformance/eve/aetheria.provider-advertisement.json", - "captureKind": "unity-scene-frame-png", - "artifactKind": "png", - "artifactPattern": "artifacts/eveunity-scene-capture/{stamp}/unity-scene.png", - "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." - } - ], - "authority": "Capture observes Unity scene projection only; provider state, command acceptance, and receipts stay provider-owned.", - "publishProof": "Unity scene screenshot or frame-capture PNG artifact is attached to the runtime conformance report from EveUnity." - }, - "evidencePaths": ["scripts/build-capture-request.ps1", "scripts/run-playmode-capture.ps1", "TestProject/Assets/Tests/PlayMode/GenericWorldCaptureTests.cs", "artifacts/playmode/world-smoke.png"], - "pendingProofs": ["Attach the Aetheria daemon PlayMode PNG; the generic world-smoke control is captured by EveUnity."] - } - } - }, - { - "id": "web", - "title": "Web reference", - "kind": "active", - "ownerRepo": "Eve", - "repoRole": "core", - "splitTarget": "Eve", - "graduationTrigger": "Stay in Eve while it is the behavior oracle; split only if it becomes a deployable runtime product.", - "capabilityManifest": { - "schemaPath": "schemas/gamecult.eve.runtime_capability.v1.schema.json", - "manifestPath": "web/eve-runtime-capability.json" - }, - "semanticHarness": true, - "requiredFixtures": ["embedded-surface", "eve-world-smoke"], - "pluginFixtures": ["sai-vn"], - "supportedFeatures": ["embeddedDocuments", "providerAdvertisements", "stateBindings", "styleTokens", "authorityWitnesses"], - "localProviderCatalog": { - "path": "web/local-provider-catalog.json", - "requiredProviders": ["eve.world-smoke", "repixelizer", "gamecult.home.vn"], - "requiredAdvertisements": [ - "web/fixtures/eve-world-smoke.provider-advertisement.json", - "web/fixtures/repixelizer.provider-advertisement.json", - "web/fixtures/sai-vn.provider-advertisement.json" - ] - }, - "supportedPlugins": [ - { - "pluginId": "sai.vn", - "capabilities": ["vn.stage", "story.choose", "story.continue", "story.jump"] - }, - { - "pluginId": "norn.graph", - "capabilities": ["embed.norn"] - }, - { - "pluginId": "tex.math", - "capabilities": ["embed.tex"] - } - ], - "worldSurfaceLowering": [ - { - "targetId": "web-reference", - "surfaceKinds": ["interactive-world", "interactive-world-editor"], - "projectionKinds": ["provider-authored-world-surface", "provider-authored-world-editor-surface"], - "supportLevel": "reference-dom-canvas-command-boundary", - "ownership": "runtime-lowers-provider-world-surface-without-owning-world-state", - "evidencePaths": [ - "web/surface.js", - "packages/eve-browser-lowering/src/index.ts", - "web/fixtures/eve-world-smoke.provider-advertisement.json" - ] - } - ], - "expectedSourceSymbols": [ - { - "path": "web/surface.js", - "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 generic world fixture transport with advertised world interaction", "requiresPlugins", "worldInteraction", "optional-nested", "eve.world-smoke.commands"] - }, - { - "path": "web/local-provider-catalog.json", - "contains": ["eve-world-smoke.provider-advertisement.json", "repixelizer.provider-advertisement.json", "sai-vn.provider-advertisement.json"] - }, - { - "path": "packages/eve-browser-lowering/src/index.ts", - "contains": ["surface.slot", "embeddedDocuments", "cultui-embedded-slot", "createEveCommandIntent", "resolveComponentCommandId", "localAdvertisement", "activeSurfaceId"] - } - ], - "commandTransportSmoke": { - "schema": "gamecult.eve.command_invocation.v1", - "expectedPaths": ["packages/eve-browser-lowering/src/index.ts", "packages/eve-browser-lowering/test/browser-lowering.test.mjs", "web/surface.js"], - "expectedSourceSymbols": [ - { - "path": "packages/eve-browser-lowering/src/index.ts", - "contains": ["EveCommandIntent", "createEveCommandIntent", "schema: \"gamecult.eve.command_invocation.v1\"", "clientId", "commandBoundary", "receiptSchema", "activeSurfaceId"] - }, - { - "path": "packages/eve-browser-lowering/test/browser-lowering.test.mjs", - "contains": ["activeSurfaceId", "aetheria.daemon.editor", "aetheria.daemon.editor.commands", "component.fallback.boundary"] - }, - { - "path": "web/surface.js", - "contains": ["publishCommandIntent", "/hermodr/commands/eve", "createEveCommandIntent", "browser.reference", "activeSurfaceId"] - } - ] - }, - "lifecycle": { - "release": { - "ownerRepo": "Eve", - "status": "reference-oracle-in-core", - "evidencePaths": [ - "web/index.html", - "web/surface.js", - "web/provider-advertisements.mjs", - "packages/eve-browser-lowering/src/index.ts" - ], - "pendingProofs": [ - "Split to EveWeb only if the browser reference gains independent deployment and release lifecycle." - ] - }, - "test": { - "ownerRepo": "Eve", - "status": "node-tests-and-parity-harness", - "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" - ], - "pendingProofs": [] - }, - "capture": { - "ownerRepo": "Eve", - "status": "chrome-headless", - "evidencePaths": [ - "scripts/capture-web-reference.ps1", - "scripts/run-parity-smoke.ps1", - "tools/parity/render-contact-sheet.py" - ], - "pendingProofs": [] - } - }, - "capture": { - "status": "chrome-headless", - "note": "Chrome headless captures phone, tablet, and desktop PNGs through the provider query parameter.", - "layoutProbes": [ - { - "fixtureId": "embedded-surface", - "providerId": "gamecult.eve.embedded-demo", - "surfaceId": "gamecult.eve.embedded-demo", - "schema": "gamecult.eve.web_layout_probe.v1", - "path": "artifacts/web-reference-layout-probe/latest/embedded-surface.json", - "runner": "scripts/run-web-reference-layout-probe-smoke.ps1" - }, - { - "fixtureId": "sai-vn", - "providerId": "gamecult.home.vn", - "surfaceId": "sai.visual_novel.surface", - "schema": "gamecult.eve.web_layout_probe.v1", - "path": "artifacts/web-reference-layout-probe/latest/sai-vn.json", - "runner": "scripts/run-web-reference-layout-probe-smoke.ps1" - }, - { - "fixtureId": "eve-world-smoke", - "providerId": "eve.world-smoke", - "surfaceId": "eve.world-smoke.surface", - "schema": "gamecult.eve.web_layout_probe.v1", - "path": "artifacts/web-reference-layout-probe/latest/eve-world-smoke.json", - "runner": "scripts/run-web-reference-layout-probe-smoke.ps1" - } - ] - } - }, - { - "id": "windows-flutter", - "title": "Windows / Flutter", - "kind": "active", - "ownerRepo": "EveFlutter", - "repoRole": "runtime-target", - "splitTarget": "EveFlutter", - "currentHostRepo": "EveFlutter", - "sourceRoot": "../EveFlutter", - "graduationTrigger": "Graduated: EveFlutter owns Flutter projection, provider selection, command intents, platform builds, tests, and captures.", - "lifecycleManifest": { - "schemaPath": "schemas/gamecult.eve.runtime_lifecycle.v1.schema.json", - "manifestPath": "eveflutter-lifecycle.json" - }, - "requiredFixtures": ["embedded-surface"], - "pluginFixtures": ["sai-vn"], - "supportedFeatures": ["embeddedDocuments"], - "supportedPlugins": [ - { - "pluginId": "sai.vn", - "capabilities": ["vn.stage", "story.choose", "story.continue", "story.jump"] - }, - { - "pluginId": "norn.graph", - "capabilities": ["embed.norn"] - }, - { - "pluginId": "tex.math", - "capabilities": ["embed.tex"] - } - ], - "expectedPaths": ["lib/main.dart", "scripts/capture-flutter-parity.ps1"], - "expectedSourceSymbols": [ - { - "path": "lib/main.dart", - "contains": ["embeddedDocuments", "surface.slot", "EveEmbeddedDocumentSlot", "EveProviderCatalog", "EveProviderPicker"] - } - ], - "commandTransportSmoke": { - "schema": "gamecult.eve.command_invocation.v1", - "expectedPaths": ["lib/main.dart", "test/parity_smoke_test.dart"], - "expectedSourceSymbols": [ - { - "path": "lib/main.dart", - "contains": ["EveCommandIntent", "createEveCommandIntent", "resolveComponentCommandId", "commandSink", "gamecult.eve.command_invocation.v1", "flutter-parity"] - }, - { - "path": "test/parity_smoke_test.dart", - "contains": ["emits a provider-routed Eve command intent", "world.example.commands", "world.example.focus"] - } - ] - }, - "capture": { - "status": "golden", - "note": "Uses the Flutter parity golden smoke on the Windows host." - } - }, - { - "id": "linux-flutter", - "title": "Linux / Flutter", - "kind": "active", - "ownerRepo": "EveFlutter", - "repoRole": "runtime-target", - "splitTarget": "EveFlutter", - "currentHostRepo": "EveFlutter", - "sourceRoot": "../EveFlutter", - "graduationTrigger": "Graduated: EveFlutter owns Linux build and capture lifecycle.", - "lifecycleManifest": { - "schemaPath": "schemas/gamecult.eve.runtime_lifecycle.v1.schema.json", - "manifestPath": "eveflutter-lifecycle.json" - }, - "requiredFixtures": ["embedded-surface"], - "pluginFixtures": ["sai-vn"], - "supportedFeatures": ["embeddedDocuments"], - "supportedPlugins": [ - { - "pluginId": "sai.vn", - "capabilities": ["vn.stage", "story.choose", "story.continue", "story.jump"] - }, - { - "pluginId": "norn.graph", - "capabilities": ["embed.norn"] - }, - { - "pluginId": "tex.math", - "capabilities": ["embed.tex"] - } - ], - "expectedPaths": ["linux", "scripts/capture-linux-flutter-parity.ps1"], - "expectedSourceSymbols": [ - { - "path": "lib/main.dart", - "contains": ["embeddedDocuments", "surface.slot", "EveEmbeddedDocumentSlot", "EveProviderCatalog", "EveProviderPicker"] - } - ], - "commandTransportSmoke": { - "schema": "gamecult.eve.command_invocation.v1", - "expectedPaths": ["lib/main.dart", "test/parity_smoke_test.dart"], - "expectedSourceSymbols": [ - { - "path": "lib/main.dart", - "contains": ["EveCommandIntent", "createEveCommandIntent", "resolveComponentCommandId", "commandSink", "gamecult.eve.command_invocation.v1", "flutter-parity"] - }, - { - "path": "test/parity_smoke_test.dart", - "contains": ["emits a provider-routed Eve command intent", "world.example.commands", "world.example.focus"] - } - ] - }, - "capture": { - "status": "ssh-golden", - "note": "Runs the Flutter parity golden smoke on Nightwing over SSH and pulls back phone, tablet, and desktop PNGs." - } - }, - { - "id": "ios-uikit", - "title": "iOS / UIKit", - "kind": "active", - "ownerRepo": "Eve", - "repoRole": "runtime-incubating", - "splitTarget": "EveIOS", - "graduationTrigger": "Split when UIKit renderer has generic surface lowering, sensor edge release flow, and device CI independent of Eve core.", - "requiredFixtures": ["embedded-surface"], - "pluginFixtures": [], - "supportedFeatures": ["embeddedDocuments"], - "unsupportedPlugins": [], - "expectedPaths": ["Sources/EVEViewController.mm", "scripts/capture-eve-screenshot.ps1"], - "expectedSourceSymbols": [ - { - "path": "Sources/EVEViewController.mm", - "contains": ["surface.slot", "embeddedDocuments", "accessibilityIdentifier"] - } - ], - "capture": { - "status": "ssh-png", - "note": "SSH capture script foregrounds EveCanvas and requests latest-screenshot.png." - } - }, - { - "id": "android-flutter", - "title": "Android / Flutter", - "kind": "active", - "ownerRepo": "EveFlutter", - "repoRole": "runtime-target", - "splitTarget": "EveFlutter", - "currentHostRepo": "EveFlutter", - "sourceRoot": "../EveFlutter", - "graduationTrigger": "Graduated: EveFlutter owns Android APK, discovery, command, plugin projection, and capture lifecycle.", - "lifecycleManifest": { - "schemaPath": "schemas/gamecult.eve.runtime_lifecycle.v1.schema.json", - "manifestPath": "eveflutter-lifecycle.json" - }, - "requiredFixtures": ["embedded-surface"], - "pluginFixtures": ["sai-vn"], - "supportedFeatures": ["embeddedDocuments"], - "supportedPlugins": [ - { - "pluginId": "sai.vn", - "capabilities": ["vn.stage", "story.choose", "story.continue", "story.jump"] - }, - { - "pluginId": "norn.graph", - "capabilities": ["embed.norn"] - }, - { - "pluginId": "tex.math", - "capabilities": ["embed.tex"] - } - ], - "expectedPaths": ["lib/main.dart", "scripts/capture-android-flutter-parity.ps1"], - "expectedSourceSymbols": [ - { - "path": "lib/main.dart", - "contains": ["embeddedDocuments", "surface.slot", "EveEmbeddedDocumentSlot", "EveProviderCatalog", "EveProviderPicker"] - } - ], - "commandTransportSmoke": { - "schema": "gamecult.eve.command_invocation.v1", - "expectedPaths": ["lib/main.dart", "test/parity_smoke_test.dart"], - "expectedSourceSymbols": [ - { - "path": "lib/main.dart", - "contains": ["EveCommandIntent", "createEveCommandIntent", "resolveComponentCommandId", "commandSink", "gamecult.eve.command_invocation.v1", "flutter-parity"] - }, - { - "path": "test/parity_smoke_test.dart", - "contains": ["emits a provider-routed Eve command intent", "world.example.commands", "world.example.focus"] - } - ] - }, - "capture": { - "status": "adb-png", - "note": "Builds the shared Flutter parity APK, installs it on Periwinkle, and pulls responsive plus physical-panel screencaps through adb." - } - }, - { - "id": "android-kotlin", - "title": "Android / Kotlin device edge", - "kind": "active", - "ownerRepo": "Eve", - "repoRole": "runtime-incubating", - "splitTarget": "EveAndroid", - "graduationTrigger": "Split when Kotlin device edge has generic surface traversal, sensor release flow, and Android-specific CI.", - "requiredFixtures": ["embedded-surface"], - "pluginFixtures": [], - "supportedFeatures": ["cultmesh-dashboard-documents", "sensor-uplink", "embeddedDocuments", "styleTokens"], - "unsupportedPlugins": [], - "expectedPaths": ["android/app/src/main/java/org/gamecult/eve/MainActivity.kt", "scripts/build-android.ps1"], - "expectedSourceSymbols": [ - { - "path": "android/app/src/main/java/org/gamecult/eve/MainActivity.kt", - "contains": ["surface.slot", "embeddedDocuments", "contentDescription", "tokenLabel", "tokenTextSize", "typefaceForToken", "fontBody", "fontTitle", "fontMono"] - } - ], - "capture": { - "status": "manual", - "note": "Native Kotlin preserves CultUI embedded document slots in its lightweight dashboard renderer; full responsive GUI capture still belongs to Android Flutter." - } - }, - { - "id": "unity-uitoolkit", - "title": "Unity / UI Toolkit", - "kind": "active", - "ownerRepo": "EveUnity", - "repoRole": "runtime-target", - "splitTarget": "EveUnity", - "graduationTrigger": "Graduated: EveUnity owns the UI Toolkit package, plugin projection adapters, package build, and Unity tests.", - "sourceRoot": "../EveUnity", - "currentHostRepo": "EveUnity", - "capabilityManifest": { - "schemaPath": "schemas/gamecult.eve.runtime_capability.v1.schema.json", - "manifestPath": "packages/org.gamecult.eve.unity-uitoolkit/eve-runtime-capability.json" - }, - "requiredFixtures": ["embedded-surface", "eve-world-smoke"], - "pluginFixtures": ["sai-vn"], - "supportedFeatures": ["embeddedDocuments"], - "supportedPlugins": [ - { - "pluginId": "sai.vn", - "capabilities": ["vn.stage", "story.choose", "story.continue", "story.jump"] - }, - { - "pluginId": "norn.graph", - "capabilities": ["embed.norn"] - }, - { - "pluginId": "tex.math", - "capabilities": ["embed.tex", "tex.inline", "tex.block"] - } - ], - "unsupportedPlugins": [], - "worldSurfaceLowering": [ - { - "targetId": "unity-uitoolkit", - "surfaceKinds": ["interactive-world", "interactive-world-editor"], - "projectionKinds": ["provider-authored-world-surface", "provider-authored-world-editor-surface"], - "supportLevel": "ui-toolkit-semantic-command-surface", - "ownership": "runtime-lowers-provider-world-surface-without-owning-world-state", - "evidencePaths": [ - "packages/org.gamecult.eve.unity-uitoolkit/Runtime/EveUiToolkitSurfaceLowerer.cs", - "packages/org.gamecult.eve.unity-uitoolkit/Tests/Editor/EveUiToolkitSurfaceLowererTests.cs", - "../Aetheria/conformance/eve/aetheria.provider-advertisement.json", - "../Eve/web/fixtures/eve-world-smoke.provider-advertisement.json" - ] - } - ], - "expectedPaths": [ - "packages/org.gamecult.eve.unity-uitoolkit/Runtime/EveUiToolkitSurfaceLowerer.cs", - "packages/org.gamecult.eve.unity-uitoolkit/Runtime/IEveUiToolkitPluginProjectionAdapter.cs", - "packages/org.gamecult.eve.unity-uitoolkit/Runtime/SaiVisualNovelUiToolkitProjectionAdapter.cs", - "packages/org.gamecult.eve.unity-uitoolkit/Runtime/NornGraphUiToolkitProjectionAdapter.cs", - "packages/org.gamecult.eve.unity-uitoolkit/Runtime/TeXMathUiToolkitProjectionAdapter.cs", - "packages/org.gamecult.eve.unity-uitoolkit/Tests/Editor/GameCult.Eve.UnityUIToolkit.Tests.asmdef", - "packages/org.gamecult.eve.unity-uitoolkit/Tests/Editor/EveUiToolkitSurfaceLowererTests.cs", - "packages/org.gamecult.eve.surface/Runtime/EveSurfaceDocument.cs", - "scripts/pack-uitoolkit.ps1", - "scripts/run-uitoolkit-tests.ps1" - ], - "expectedSourceSymbols": [ - { - "path": "packages/org.gamecult.eve.unity-uitoolkit/Runtime/EveUiToolkitSurfaceLowerer.cs", - "contains": ["LowerEmbeddedDocument(", "EmbeddedDocuments", "eve-embedded-document", "FindPluginProjectionAdapter", "projectionAdapter.Lower"] - }, - { - "path": "packages/org.gamecult.eve.unity-uitoolkit/Runtime/IEveUiToolkitPluginProjectionAdapter.cs", - "contains": ["PluginId", "Capabilities", "CanLower", "Lower("] - }, - { - "path": "packages/org.gamecult.eve.unity-uitoolkit/Runtime/SaiVisualNovelUiToolkitProjectionAdapter.cs", - "contains": ["sai.vn", "vn.stage", "story.choose", "story.continue", "story.jump", "StoryButton", "EmitStoryCommand", "EveSurfaceCommandRequest", "sai-vn-stage", "sai-vn-dialogue", "sai-vn-actions"] - }, - { - "path": "packages/org.gamecult.eve.unity-uitoolkit/Runtime/NornGraphUiToolkitProjectionAdapter.cs", - "contains": ["norn.graph", "embed.norn", "NornGraphUiToolkitProjectionAdapter", "norn-graph", "EmitGraphCommand", "EveSurfaceCommandRequest"] - }, - { - "path": "packages/org.gamecult.eve.unity-uitoolkit/Runtime/TeXMathUiToolkitProjectionAdapter.cs", - "contains": ["tex.math", "embed.tex", "TeXMathUiToolkitProjectionAdapter", "tex-math", "tex-math-source-fallback"] - }, - { - "path": "packages/org.gamecult.eve.unity-uitoolkit/Tests/Editor/EveUiToolkitSurfaceLowererTests.cs", - "contains": ["FindPluginProjectionAdapter", "SaiStageLowersThroughRuntimeProjectionAdapter", "NornGraphLowersThroughRuntimeProjectionAdapter", "TeXMathLowersThroughRuntimeProjectionAdapterAsSourceFallback", "EmbeddedDocumentSlotMountsResolverBackedChildSurface", "eve-plugin-projection"] - } - ], - "commandTransportSmoke": { - "schema": "gamecult.eve.command_invocation.v1", - "expectedPaths": [ - "../EveUnity/packages/org.gamecult.eve.surface/Runtime/EveSurfaceDocument.cs", - "../EveUnity/packages/org.gamecult.eve.unity-uitoolkit/Runtime/EveUiToolkitSurfaceLowerer.cs" - ], - "expectedSourceSymbols": [ - { - "path": "../EveUnity/packages/org.gamecult.eve.surface/Runtime/EveSurfaceDocument.cs", - "contains": ["EveSurfaceCommandRequest", "SchemaId", "gamecult.eve.command_invocation.v1", "ClientId"] - }, - { - "path": "../EveUnity/packages/org.gamecult.eve.unity-uitoolkit/Runtime/EveUiToolkitSurfaceLowerer.cs", - "contains": ["commandSink", "EmitCommand", "ResolveOperation", "unity-uitoolkit"] - } - ] - }, - "lifecycle": { - "release": { - "ownerRepo": "EveUnity", - "status": "upm-package-artifact-smoke", - "releaseContract": { - "ownerRepo": "EveUnity", - "packageName": "org.gamecult.eve.unity-uitoolkit", - "packageRoot": "packages/org.gamecult.eve.unity-uitoolkit", - "versionSource": "packages/org.gamecult.eve.unity-uitoolkit/package.json", - "tagPattern": "eveunity-uitoolkit-v{version}", - "artifactKind": "upm-package", - "requestSchema": "gamecult.eve.runtime_release_request.v1", - "requestBuilder": "scripts/pack-uitoolkit.ps1", - "artifactBuilder": "scripts/pack-uitoolkit.ps1", - "artifactPattern": "artifacts/eveunity-uitoolkit-release/{version}/org.gamecult.eve.unity-uitoolkit-{version}.tgz", - "publishProof": "Tagged UPM release is created from the EveUnity repo and references this package root.", - "requiredPackageDependencies": [ - { - "packageName": "org.gamecult.eve.surface", - "version": "0.1.0", - "packageManager": "upm", - "ownerRepo": "EveUnity", - "purpose": "Shared Unity DTOs for gamecult.eve.surface.v1 documents." - } - ] - }, - "evidencePaths": [ - "packages/org.gamecult.eve.unity-uitoolkit/package.json", - "packages/org.gamecult.eve.unity-uitoolkit/README.md", - "scripts/pack-uitoolkit.ps1" - ], - "pendingProofs": [ - "Tagged UPM release cut from EveUnity rather than Eve incubation." - ] - }, - "test": { - "ownerRepo": "EveUnity", - "status": "batchmode-editmode-tests-and-consumer-build-smoke", - "command": "powershell -ExecutionPolicy Bypass -File .\\scripts\\run-uitoolkit-tests.ps1", - "testContract": { - "ownerRepo": "EveUnity", - "runnerKind": "unity-editmode-batchmode", - "runnerScript": "scripts/run-uitoolkit-tests.ps1", - "consumerProject": "E:\\Projects\\Aetheria", - "unityExeDefault": "C:\\Program Files\\Unity\\Hub\\Editor\\6000.4.2f1\\Editor\\Unity.exe", - "packageName": "org.gamecult.eve.unity-uitoolkit", - "testAssembly": "GameCult.Eve.UnityUIToolkit.Tests", - "testPlatform": "EditMode", - "resultsArtifact": "artifacts/aetheria-unity-editmode/{stamp}/unity-editmode-results.xml", - "logArtifact": "artifacts/aetheria-unity-editmode/{stamp}/unity-editmode.log", - "manifestMutation": "Temporarily adds org.gamecult.eve.unity-uitoolkit to Aetheria Packages/manifest.json testables and restores the manifest after the run.", - "managedAssemblyDependencies": [ - { - "assemblyName": "GameCult.Caching.dll", - "packageId": "GameCult.Caching", - "packageManager": "nuget", - "ownerRepo": "CultLib", - "resolutionMode": "Unity precompiled reference during EveUnity incubation.", - "dependencyContract": { - "sourceAuthority": "CultLib", - "packageSource": "nuget", - "packageId": "GameCult.Caching", - "assemblyName": "GameCult.Caching.dll", - "versionPolicy": "CultLib-owned package version stream.", - "unityResolution": "EveUnity consumes this as a Unity precompiled reference during incubation.", - "handoffRequirement": "EveUnity split must resolve this through CultLib's NuGet/package feed or a documented Unity import without vendoring CultLib ownership into Eve." - } - }, - { - "assemblyName": "GameCult.Mesh.dll", - "packageId": "GameCult.Mesh", - "packageManager": "nuget", - "ownerRepo": "CultLib", - "resolutionMode": "Unity precompiled reference during EveUnity incubation.", - "dependencyContract": { - "sourceAuthority": "CultLib", - "packageSource": "nuget", - "packageId": "GameCult.Mesh", - "assemblyName": "GameCult.Mesh.dll", - "versionPolicy": "CultLib-owned package version stream.", - "unityResolution": "EveUnity consumes this as a Unity precompiled reference during incubation.", - "handoffRequirement": "EveUnity split must resolve this through CultLib's NuGet/package feed or a documented Unity import without vendoring CultLib ownership into Eve." - } - }, - { - "assemblyName": "MessagePack.Annotations.dll", - "packageId": "MessagePack.Annotations", - "packageManager": "nuget", - "ownerRepo": "CultLib", - "resolutionMode": "Unity precompiled reference during EveUnity incubation.", - "dependencyContract": { - "sourceAuthority": "CultLib", - "packageSource": "nuget", - "packageId": "MessagePack.Annotations", - "assemblyName": "MessagePack.Annotations.dll", - "versionPolicy": "CultLib-owned transitive package version stream.", - "unityResolution": "EveUnity consumes this as a Unity precompiled reference during incubation.", - "handoffRequirement": "EveUnity split must resolve this through CultLib's NuGet/package feed or a documented Unity import without vendoring CultLib ownership into Eve." - } - } - ] - }, - "evidencePaths": [ - "packages/org.gamecult.eve.unity-uitoolkit/Tests/Editor/GameCult.Eve.UnityUIToolkit.Tests.asmdef", - "packages/org.gamecult.eve.unity-uitoolkit/Tests/Editor/EveUiToolkitSurfaceLowererTests.cs", - "scripts/run-uitoolkit-tests.ps1" - ], - "pendingProofs": [ - "Cut and publish the first tagged EveUnity UI Toolkit package release." - ] - }, - "capture": { - "ownerRepo": "EveUnity", - "status": "pending-editor-capture", - "captureContract": { - "ownerRepo": "EveUnity", - "runtimeId": "unity-uitoolkit", - "targetId": "unity-uitoolkit", - "requestSchema": "gamecult.eve.runtime_capture_request.v1", - "requestBuilder": "scripts/build-uitoolkit-capture-request.ps1", - "advertisementPath": "../Aetheria/conformance/eve/aetheria.provider-advertisement.json", - "captureKind": "unity-editor-or-batchmode-png", - "artifactKind": "png", - "artifactPattern": "artifacts/eveunity-uitoolkit-capture/{stamp}/unity-uitoolkit.png", - "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 runtime projection only; provider state, command acceptance, and receipts stay provider-owned.", - "publishProof": "Unity editor or batchmode capture artifact is attached to the runtime conformance report from EveUnity." - }, - "evidencePaths": [ - "packages/org.gamecult.eve.unity-uitoolkit/eve-runtime-capability.json", - "scripts/build-uitoolkit-capture-request.ps1" - ], - "pendingProofs": [ - "Unity batchmode or editor capture artifact attached to the runtime conformance report." - ] - } - }, - "capture": { - "status": "pending-native-capture", - "note": "Unity editor or batchmode PNG capture must be produced and attached by EveUnity; Eve does not manufacture a runtime proxy artifact.", - "artifacts": [] - } - }, - { - "id": "direct2d", - "title": "Fensalir Direct2D", - "kind": "pending", - "ownerRepo": "Fensalir", - "repoRole": "runtime-external-target", - "splitTarget": "Fensalir or EveDirect2D if it earns a separate body", - "graduationTrigger": "Do not graduate until the Eve-to-AquariumUiDocument adapter and Direct2D frame capture exist.", - "adapterSpike": "external", - "demotionReason": "Fensalir has an external Aquarium.EveSurface adapter spike and a recorded gamecult.eve.surface.v1 adapter fixture, but Eve still has no Direct2D frame capture or runtime-owned command smoke.", - "activationCriteria": [ - "Fensalir runs recorded gamecult.eve.surface.v1 fixtures without provider-specific renderer knowledge.", - "Fensalir emits gamecult.eve.command_invocation.v1 intents through the advertised provider command boundary.", - "Direct2D capture artifacts are produced and attached to the parity report." - ], - "requiredFixtures": ["fensalir-direct2d"], - "pluginFixtures": [], - "supportedFeatures": ["embeddedDocuments"], - "expectedPaths": ["web/fixtures/fensalir-direct2d-surface.json"], - "expectedExternalPaths": [ - "E:\\Projects\\Fensalir\\src\\Aquarium.EveSurface\\EveSurfaceRuntime.cs", - "E:\\Projects\\Fensalir\\docs\\eve-direct2d-client-surface.md" - ], - "expectedExternalSourceSymbols": [ - { - "path": "E:\\Projects\\Fensalir\\src\\Aquarium.EveSurface\\EveSurfaceRuntime.cs", - "contains": [ - "FENSALIR_EVE_BROKER", - "AquariumUiDocument Ui => ComposeUi()", - "ReadSurface(root)", - "EveCompositionElement", - "DirectWriteOverlay" - ] - } - ], - "capture": { - "status": "missing", - "note": "External Fensalir adapter spike and recorded surface fixture exist; Eve still needs runtime-owned command smoke and Direct2D frame capture before this runtime can become active." - } - } - ] -} diff --git a/tools/parity/run-parity.mjs b/tools/parity/run-parity.mjs deleted file mode 100644 index 852185d..0000000 --- a/tools/parity/run-parity.mjs +++ /dev/null @@ -1,3649 +0,0 @@ -import { mkdir, readFile, writeFile } from "node:fs/promises"; -import { existsSync, readFileSync } from "node:fs"; -import path from "node:path"; -import { fileURLToPath } from "node:url"; -import { compileEveDsl } from "../../web/eve-dsl.js"; - -const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../.."); -const manifestPath = path.join(repoRoot, "tools/parity/parity-manifest.json"); -const outputRoot = process.env.EVE_PARITY_OUTPUT - ? path.resolve(process.env.EVE_PARITY_OUTPUT) - : path.join(repoRoot, "artifacts/parity"); -const conformanceOutputRoot = process.env.EVE_CONFORMANCE_OUTPUT - ? path.resolve(process.env.EVE_CONFORMANCE_OUTPUT) - : path.join(repoRoot, "artifacts/conformance"); - -const manifest = JSON.parse(await readFile(manifestPath, "utf8")); -const stamp = new Date().toISOString().replace(/[:.]/g, "-"); -const runDirectory = path.join(outputRoot, stamp); - -await mkdir(runDirectory, { recursive: true }); - -const fixtureResults = []; -for (const fixture of manifest.fixtures) { - fixtureResults.push(await evaluateFixture(fixture)); -} - -const pluginResults = await Promise.all((manifest.pluginManifests || []).map(plugin => evaluatePlugin(plugin, fixtureResults))); -const providerResults = await Promise.all((manifest.providerAdvertisements || []).map(provider => evaluateProvider(provider, fixtureResults, pluginResults))); -const runtimeResults = await Promise.all(manifest.runtimes.map(runtime => evaluateRuntime(runtime, fixtureResults))); -const splitTargetResults = evaluateSplitTargets(manifest.splitTargets || [], runtimeResults); -const conformanceHandoffErrors = await validateConformanceHandoff(manifest.repoStrategy?.conformanceHandoffPath || ""); -const report = { - schema: "gamecult.eve.parity_report.v1", - generatedAt: new Date().toISOString(), - manifest: path.relative(repoRoot, manifestPath).replaceAll("\\", "/"), - repoStrategy: { - ...(manifest.repoStrategy || {}), - conformanceHandoffErrors, - }, - conformancePacks: manifest.conformancePacks || [], - responsiveCases: manifest.responsiveCases || [], - summary: summarize(fixtureResults, runtimeResults, pluginResults, providerResults, splitTargetResults), - fixtures: fixtureResults, - plugins: pluginResults, - providers: providerResults, - splitTargets: splitTargetResults, - runtimes: runtimeResults, -}; -const conformanceExport = buildConformanceExport(report); -const conformanceExportErrors = validateSchemaSubset( - JSON.parse(await readFile(path.join(repoRoot, manifest.schemas["gamecult.eve.conformance_export.v1"]), "utf8")), - conformanceExport, - "conformanceExport", -); - -await writeFile(path.join(runDirectory, "parity-report.json"), `${JSON.stringify(report, null, 2)}\n`); -await writeFile(path.join(runDirectory, "parity-report.md"), renderMarkdown(report)); -await writeFile(path.join(outputRoot, "latest.json"), `${JSON.stringify(report, null, 2)}\n`); -await writeFile(path.join(outputRoot, "latest.md"), renderMarkdown(report)); -await writeConformanceExport(conformanceExport, path.join(conformanceOutputRoot, stamp)); -await writeConformanceExport(conformanceExport, path.join(conformanceOutputRoot, "latest")); - -console.log(`Parity report: ${path.relative(repoRoot, path.join(runDirectory, "parity-report.md"))}`); -console.log(`Conformance export: ${path.relative(repoRoot, path.join(conformanceOutputRoot, "latest", "index.md"))}`); -for (const error of conformanceExportErrors) console.error(`Conformance export error: ${error}`); -if (report.summary.failedFixtures > 0) process.exitCode = 1; -if (report.summary.failedPlugins > 0) process.exitCode = 1; -if (report.summary.failedProviders > 0) process.exitCode = 1; -if (conformanceHandoffErrors.length > 0) process.exitCode = 1; -if (conformanceExportErrors.length > 0) process.exitCode = 1; - -async function evaluateFixture(fixture) { - const startedAt = Date.now(); - const state = await loadSurface(fixture, fixture.surface); - const root = state.surface?.root; - const nodes = root ? flattenSurface(root) : []; - const kindCounts = countBy(nodes, node => node.kind || "unknown"); - const bindings = [...new Set(nodes.flatMap(readBindings))].sort(); - const embeddedDocuments = [...new Set(nodes - .flatMap(node => node.embeddedDocuments || []) - .map(slot => slot.slotId || slot.documentId) - .filter(Boolean))].sort(); - const controlSkins = state.surface?.styles?.controlSkins || {}; - const skinNames = Object.keys(controlSkins); - const skinNodes = Object.values(controlSkins).flatMap(skin => skin.children || []); - const controlPartNames = [...new Set([...nodes, ...skinNodes] - .filter(node => node.kind === "control.part") - .map(node => node.props?.name) - .filter(Boolean))].sort(); - const tokenNames = Object.keys(state.surface?.styles?.tokens || {}).sort(); - const commandDescriptors = readCommandDescriptors(state.commands || []); - const commandIds = commandDescriptors.map(command => command.command).sort(); - const commandReferences = readCommandReferences(nodes); - const commandDescriptorErrors = await validateCommandDescriptors(fixture, commandDescriptors); - const authorityWitnesses = readAuthorityWitnesses(nodes); - const authorityStates = [...new Set(authorityWitnesses.map(witness => witness.state))].filter(Boolean).sort(); - const authorityOwners = [...new Set(authorityWitnesses.map(witness => witness.owner))].filter(Boolean).sort(); - const receiptRefs = [...new Set(authorityWitnesses.map(witness => witness.receiptRef))].filter(Boolean).sort(); - const witnessRefs = [...new Set(authorityWitnesses.map(witness => witness.witnessRef))].filter(Boolean).sort(); - const textNodeCount = nodes.filter(isTextLikeNode).length; - const nonEmptyTextNodeCount = nodes.filter(hasVisibleText).length; - const layoutBoxCount = nodes.filter(hasLayoutBox).length; - const metadataResult = await evaluateFixtureMetadata(fixture); - const checks = []; - - addCheck(checks, "providerId", state.providerId === fixture.expect.providerId, { - expected: fixture.expect.providerId, - actual: state.providerId, - }); - - for (const kind of fixture.expect.componentKinds || []) { - addCheck(checks, `component:${kind}`, Boolean(kindCounts[kind]), { - expected: "present", - actual: kindCounts[kind] || 0, - }); - } - - for (const [kind, minimum] of Object.entries(fixture.expect.minimumCounts || {})) { - addCheck(checks, `minimum:${kind}`, (kindCounts[kind] || 0) >= minimum, { - expected: minimum, - actual: kindCounts[kind] || 0, - }); - } - - for (const skin of fixture.expect.controlSkins || []) { - addCheck(checks, `skin:${skin}`, skinNames.includes(skin), { - expected: "present", - actual: skinNames, - }); - } - - for (const part of fixture.expect.controlParts || []) { - addCheck(checks, `controlPart:${part}`, controlPartNames.includes(part), { - expected: "present", - actual: controlPartNames, - }); - } - - for (const token of fixture.expect.styleTokens || []) { - addCheck(checks, `styleToken:${token}`, tokenNames.includes(token), { - expected: "present", - actual: tokenNames, - }); - } - - for (const binding of fixture.expect.bindings || []) { - addCheck(checks, `binding:${binding}`, bindings.includes(binding), { - expected: "present", - actual: bindings, - }); - } - - for (const slot of fixture.expect.embeddedDocuments || []) { - addCheck(checks, `embedded:${slot}`, embeddedDocuments.includes(slot), { - expected: "present", - actual: embeddedDocuments, - }); - } - - for (const command of fixture.expect.commandDescriptors || []) { - addCheck(checks, `command:${command}`, commandIds.includes(command), { - expected: "present", - actual: commandIds, - }); - } - - for (const error of commandDescriptorErrors) { - addCheck(checks, `commandDescriptor:${error}`, false, { - expected: "valid command descriptor", - actual: error, - }); - } - - if (fixture.expect.commandReferencesDeclared) { - for (const reference of commandReferences) { - addCheck(checks, `commandReference:${reference.nodeId}:${reference.command}`, commandIds.includes(reference.command), { - expected: "declared command descriptor", - actual: commandIds, - }); - } - } - - for (const state of fixture.expect.authorityStates || []) { - addCheck(checks, `authorityState:${state}`, authorityStates.includes(state), { - expected: "present", - actual: authorityStates, - }); - } - - for (const owner of fixture.expect.authorityOwners || []) { - addCheck(checks, `authorityOwner:${owner}`, authorityOwners.includes(owner), { - expected: "present", - actual: authorityOwners, - }); - } - - if (fixture.expect.receiptRefs) { - addCheck(checks, "receiptRefs", receiptRefs.length >= fixture.expect.receiptRefs, { - expected: fixture.expect.receiptRefs, - actual: receiptRefs.length, - }); - } - - if (fixture.expect.witnessRefs) { - addCheck(checks, "witnessRefs", witnessRefs.length >= fixture.expect.witnessRefs, { - expected: fixture.expect.witnessRefs, - actual: witnessRefs.length, - }); - } - - for (const error of metadataResult.errors) { - addCheck(checks, `metadata:${error}`, false, { - expected: "valid fixture metadata", - actual: error, - }); - } - - const knownPacks = new Set((manifest.conformancePacks || []).map(pack => pack.id)); - if (fixture.pack) { - addCheck(checks, "conformancePack", knownPacks.has(fixture.pack), { - expected: [...knownPacks].sort(), - actual: fixture.pack, - }); - } else { - addCheck(checks, "conformancePack", false, { - expected: "fixture declares pack", - actual: "", - }); - } - - return { - id: fixture.id, - title: fixture.title, - pack: fixture.pack || "", - ownerRepo: fixture.ownerRepo || "", - surface: fixture.surface || {}, - requiredPlugins: fixture.requiredPlugins || [], - optionalNestedPlugins: fixture.optionalNestedPlugins || [], - status: checks.every(check => check.pass) ? "pass" : "fail", - durationMs: Date.now() - startedAt, - providerId: state.providerId, - componentCount: nodes.length, - kindCounts, - styleTokens: tokenNames, - controlSkins: skinNames, - controlParts: controlPartNames, - bindings, - embeddedDocuments, - commands: commandIds, - commandReferences, - commandDescriptorErrors, - authorityStates, - authorityOwners, - receiptRefs, - witnessRefs, - authorityWitnesses, - textNodeCount, - nonEmptyTextNodeCount, - layoutBoxCount, - metadataPath: fixture.metadataPath || "", - metadata: metadataResult.metadata, - metadataErrors: metadataResult.errors, - checks, - }; -} - -async function loadSurface(owner, surface) { - const absolutePath = resolveOwnedPath(owner, surface.path); - const source = await readFile(absolutePath, "utf8"); - if (surface.transport === "local-eve-dsl") return compileEveDsl(source); - if (surface.transport === "local-json") return JSON.parse(source); - throw new Error(`Unsupported parity surface transport: ${surface.transport}`); -} - -async function evaluateRuntime(runtime, fixtureResults) { - const expectedPaths = runtime.expectedPaths || []; - const missingPaths = expectedPaths.filter(candidate => !existsSync(resolveRuntimeOwnedPath(runtime, candidate))); - const expectedExternalPaths = runtime.expectedExternalPaths || []; - const missingExternalPaths = expectedExternalPaths.filter(candidate => !existsSync(candidate)); - const expectedSourceSymbols = runtime.expectedSourceSymbols || []; - const missingSourceSymbols = []; - for (const expectation of expectedSourceSymbols) { - const sourcePath = expectation.path || ""; - const absolutePath = resolveRuntimeOwnedPath(runtime, sourcePath); - if (!existsSync(absolutePath)) { - missingSourceSymbols.push(`${sourcePath}:missing`); - continue; - } - - const source = await readFile(absolutePath, "utf8"); - for (const symbol of expectation.contains || []) { - if (!source.includes(symbol)) missingSourceSymbols.push(`${sourcePath}:${symbol}`); - } - } - const expectedExternalSourceSymbols = runtime.expectedExternalSourceSymbols || []; - const missingExternalSourceSymbols = []; - for (const expectation of expectedExternalSourceSymbols) { - const sourcePath = expectation.path || ""; - if (!existsSync(sourcePath)) { - missingExternalSourceSymbols.push(`${sourcePath}:missing`); - continue; - } - - const source = await readFile(sourcePath, "utf8"); - for (const symbol of expectation.contains || []) { - if (!source.includes(symbol)) missingExternalSourceSymbols.push(`${sourcePath}:${symbol}`); - } - } - const requiredFixtures = runtime.requiredFixtures || []; - const missingRequiredFixtures = requiredFixtures.filter(id => !fixtureResults.some(fixture => fixture.id === id && fixture.status === "pass")); - const supportedFeatures = runtime.supportedFeatures || []; - const supportedPlugins = runtime.supportedPlugins || []; - const missingRequiredFeatures = []; - if (requiredFixtures.includes("embedded-surface") && !supportedFeatures.includes("embeddedDocuments")) { - missingRequiredFeatures.push("embeddedDocuments"); - } - const commandTransportSmokeErrors = await validateRuntimeCommandTransportSmoke(runtime); - const capabilityManifestErrors = await validateRuntimeCapabilityManifest(runtime); - const capabilityManifestDocument = await readRuntimeCapabilityManifestDocument(runtime); - const lifecycleManifestDocument = await readRuntimeLifecycleManifestDocument(runtime); - const lifecycle = runtime.lifecycle || capabilityManifestDocument?.lifecycle || readLifecycleStages(lifecycleManifestDocument); - const capture = runtime.capture || capabilityManifestDocument?.capture || null; - const captureArtifactErrors = validateRuntimeCaptureArtifacts(runtime, capture); - const lifecycleErrors = await validateRuntimeLifecycle(runtime, lifecycle, lifecycleManifestDocument); - const splitHandoffPath = await readRuntimeSplitHandoffPath(runtime); - const splitHandoffErrors = await validateRuntimeSplitHandoff(runtime, splitHandoffPath); - const localProviderCatalogErrors = await validateRuntimeLocalProviderCatalog(runtime); - const missingIncubationFields = requiredIncubationFields(runtime).filter(field => !runtime[field]); - const pluginCapabilityGaps = collectPluginCapabilityGaps(runtime, fixtureResults); - const unsupportedPluginNotes = collectUnsupportedPluginNotes(runtime, fixtureResults); - let status = runtime.kind === "active" ? "active" : "pending"; - if (missingPaths.length) status = "missing-body"; - if (runtime.kind === "active" && missingSourceSymbols.length) status = "missing-source-symbol"; - if (runtime.kind === "active" && missingRequiredFixtures.length) status = "missing-required-fixture"; - if (runtime.kind === "active" && missingRequiredFeatures.length) status = "missing-required-feature"; - if (runtime.kind === "active" && commandTransportSmokeErrors.length) status = "missing-command-transport-smoke"; - if (runtime.kind === "active" && capabilityManifestErrors.length) status = "invalid-runtime-capability"; - if (runtime.kind === "active" && lifecycleErrors.length) status = "invalid-runtime-lifecycle"; - if (runtime.kind === "active" && captureArtifactErrors.length) status = "invalid-runtime-capture-artifact"; - if (runtime.kind === "active" && splitHandoffErrors.length) status = "invalid-runtime-split-handoff"; - if (runtime.kind === "active" && localProviderCatalogErrors.length) status = "invalid-local-provider-catalog"; - if (runtime.kind === "active" && missingIncubationFields.length) status = "missing-incubation-metadata"; - if (runtime.kind === "active" && pluginCapabilityGaps.length && status === "active") status = "active-with-capability-gaps"; - if (runtime.kind === "pending" && runtime.adapterSpike === "external" && !missingExternalPaths.length && !missingExternalSourceSymbols.length) { - status = "external-adapter-spike"; - } - if (["ssh-png", "adb-png", "golden"].includes(runtime.capture?.status) && status === "pending") { - status = "capture-ready"; - } - return { - id: runtime.id, - title: runtime.title, - kind: runtime.kind, - status, - semanticHarness: Boolean(runtime.semanticHarness), - expectedPaths, - missingPaths, - expectedExternalPaths, - missingExternalPaths, - expectedSourceSymbols, - missingSourceSymbols, - expectedExternalSourceSymbols, - missingExternalSourceSymbols, - requiredFixtures, - pluginFixtures: runtime.pluginFixtures || [], - missingRequiredFixtures, - supportedFeatures, - supportedPlugins, - unsupportedPlugins: runtime.unsupportedPlugins || [], - unsupportedPluginNotes, - worldSurfaceLowering: runtime.worldSurfaceLowering || [], - missingRequiredFeatures, - commandTransportSmoke: runtime.commandTransportSmoke || null, - commandTransport: runtime.commandTransport || capabilityManifestDocument?.commandTransport || null, - commandTransportSmokeErrors, - capabilityManifest: runtime.capabilityManifest || null, - capabilityManifestErrors, - lifecycleManifest: runtime.lifecycleManifest || null, - lifecycleErrors, - splitHandoffPath, - splitHandoffErrors, - localProviderCatalog: runtime.localProviderCatalog || null, - localProviderCatalogErrors, - lifecycle, - captureArtifactErrors, - ownerRepo: runtime.ownerRepo || "", - repoRole: runtime.repoRole || "", - graduationTrigger: runtime.graduationTrigger || "", - splitTarget: runtime.splitTarget || "", - adapterSpike: runtime.adapterSpike || "", - demotionReason: runtime.demotionReason || "", - activationCriteria: runtime.activationCriteria || [], - missingIncubationFields, - pluginCapabilityGaps, - capture, - }; -} - -function runtimeCommandTransportSchema(runtime) { - return runtime.commandTransportSmoke?.schema || runtime.commandTransport?.schema || ""; -} - -async function readRuntimeCapabilityManifestDocument(runtime) { - const manifestPath = runtime.capabilityManifest?.manifestPath; - if (!manifestPath) return null; - try { - return JSON.parse(await readFile(resolveRuntimeOwnedPath(runtime, manifestPath), "utf8")); - } catch { - return null; - } -} - -async function readRuntimeLifecycleManifestDocument(runtime) { - const lifecyclePath = runtime.lifecycleManifest?.manifestPath; - if (!lifecyclePath) return null; - try { - return JSON.parse(await readFile(resolveRuntimeOwnedPath(runtime, lifecyclePath), "utf8")); - } catch { - return null; - } -} - -function readLifecycleStages(lifecycleDocument) { - if (!lifecycleDocument) return null; - return { - release: lifecycleDocument.release, - test: lifecycleDocument.test, - capture: lifecycleDocument.capture, - }; -} - -async function validateRuntimeLifecycle(runtime, lifecycle, lifecycleDocument) { - if (!lifecycle && !runtime.lifecycleManifest) return []; - - const schemaPath = runtime.lifecycleManifest?.schemaPath || manifest.schemas?.["gamecult.eve.runtime_lifecycle.v1"]; - const errors = []; - if (runtime.lifecycleManifest?.manifestPath) { - if (!lifecycleDocument) { - errors.push(`${runtime.lifecycleManifest.manifestPath}:unreadable`); - } else { - errors.push(...await validateJsonDocumentAgainstSchemaPath( - schemaPath, - lifecycleDocument, - `runtimeLifecycleManifest:${runtime.id}`, - )); - if (lifecycleDocument.schema !== "gamecult.eve.runtime_lifecycle.v1") { - errors.push(`${runtime.lifecycleManifest.manifestPath}:schema:expected gamecult.eve.runtime_lifecycle.v1 got ${lifecycleDocument.schema || ""}`); - } - if (!Array.isArray(lifecycleDocument.runtimes) || !lifecycleDocument.runtimes.includes(runtime.id)) { - errors.push(`${runtime.lifecycleManifest.manifestPath}:runtimes:${runtime.id}:missing`); - } - } - } - - if (!lifecycle) { - errors.push("lifecycle:missing"); - return errors; - } - - const ownerRepo = lifecycleDocument?.ownerRepo - || lifecycle.release?.ownerRepo - || lifecycle.test?.ownerRepo - || lifecycle.capture?.ownerRepo - || runtime.splitTarget - || runtime.ownerRepo - || ""; - const currentHostRepo = lifecycleDocument?.currentHostRepo || runtime.ownerRepo || "Eve"; - const lifecycleEnvelope = { - schema: "gamecult.eve.runtime_lifecycle.v1", - splitTarget: lifecycleDocument?.splitTarget || runtime.splitTarget || runtime.id, - ownerRepo, - currentHostRepo, - status: lifecycleDocument?.status || runtime.status || "", - runtimes: lifecycleDocument?.runtimes || [runtime.id], - release: lifecycle.release, - test: lifecycle.test, - capture: lifecycle.capture, - remainingSplitBlockers: lifecycleDocument?.remainingSplitBlockers || [], - }; - - const schemaErrors = await validateJsonDocumentAgainstSchemaPath(schemaPath, lifecycleEnvelope, `runtimeLifecycle:${runtime.id}`); - errors.push(...schemaErrors); - - for (const stage of ["release", "test", "capture"]) { - const stageDocument = lifecycle[stage]; - for (const evidencePath of stageDocument?.evidencePaths || []) { - if (!existsSync(resolveRuntimeOwnedPath(runtime, evidencePath))) { - errors.push(`lifecycle.${stage}.evidence:${evidencePath}:missing`); - } - } - } - - return errors; -} - -async function validateRuntimeLocalProviderCatalog(runtime) { - const catalogConfig = runtime.localProviderCatalog; - if (!catalogConfig) return []; - - const errors = []; - const catalogPath = catalogConfig.path || ""; - const absolutePath = path.join(repoRoot, catalogPath); - if (!existsSync(absolutePath)) return [`${catalogPath}:missing`]; - - let catalog; - try { - catalog = JSON.parse(await readFile(absolutePath, "utf8")); - } catch (error) { - return [`${catalogPath}:invalid-json:${error instanceof Error ? error.message : String(error)}`]; - } - - errors.push(...await validateJsonDocument( - manifest.schemas?.["gamecult.eve.local_provider_catalog.v1"], - catalogPath, - { - schema: "gamecult.eve.local_provider_catalog.v1", - }, - )); - - const providers = Array.isArray(catalog.providers) ? catalog.providers : []; - const providerIds = new Set(providers.map(provider => provider.providerId).filter(Boolean)); - for (const providerId of catalogConfig.requiredProviders || []) { - if (!providerIds.has(providerId)) errors.push(`${catalogPath}:provider:${providerId}:missing`); - } - - const catalogDirectory = path.posix.dirname(normalizePath(catalogPath)); - const advertisedPaths = new Set(providers - .map(provider => resolveCatalogPath(catalogDirectory, provider.advertisement || "")) - .filter(Boolean)); - for (const advertisementPath of catalogConfig.requiredAdvertisements || []) { - const normalized = normalizePath(advertisementPath); - if (!advertisedPaths.has(normalized)) { - errors.push(`${catalogPath}:advertisement:${advertisementPath}:unreferenced`); - } - if (!existsSync(path.join(repoRoot, advertisementPath))) { - errors.push(`${advertisementPath}:missing`); - } - } - - return errors; -} - -async function readRuntimeSplitHandoffPath(runtime) { - if (runtime?.splitHandoffPath) return runtime.splitHandoffPath; - const capabilityManifest = runtime?.capabilityManifest; - if (!capabilityManifest?.manifestPath) return ""; - const documentPath = path.join(repoRoot, capabilityManifest.manifestPath); - if (!existsSync(documentPath)) return ""; - try { - const document = JSON.parse(await readFile(documentPath, "utf8")); - return document.incubation?.splitHandoff?.manifestPath || ""; - } catch { - return ""; - } -} - -async function validateRuntimeSplitHandoff(runtime, splitHandoffPath) { - if (!splitHandoffPath) return []; - return validateJsonDocument( - manifest.schemas?.["gamecult.eve.runtime_split_handoff.v1"], - splitHandoffPath, - { - schema: "gamecult.eve.runtime_split_handoff.v1", - runtimeId: runtime.id, - }, - ); -} - -async function validateConformanceHandoff(handoffPath) { - if (!handoffPath) return ["handoffPath:missing"]; - return validateJsonDocument( - manifest.schemas?.["gamecult.eve.conformance_handoff.v1"], - handoffPath, - { - schema: "gamecult.eve.conformance_handoff.v1", - splitTarget: "EveConformance", - ownerRepo: "EveConformance", - }, - ); -} - -function normalizePath(candidate) { - return candidate.replaceAll("\\", "/").replace(/^\.\//, ""); -} - -function resolveCatalogPath(catalogDirectory, candidate) { - const normalized = normalizePath(candidate); - if (!normalized) return ""; - if (/^(https?:|\/)/i.test(normalized)) return normalized; - return path.posix.normalize(path.posix.join(catalogDirectory, normalized)); -} - -async function evaluatePlugin(plugin, fixtureResults) { - const expectedPaths = plugin.expectedPaths || []; - const missingPaths = expectedPaths.filter(candidate => !existsSync(path.join(repoRoot, candidate))); - const requiredFixtures = plugin.requiredFixtures || []; - const missingRequiredFixtures = requiredFixtures.filter(id => !fixtureResults.some(fixture => fixture.id === id && fixture.status === "pass")); - const missingIncubationFields = requiredIncubationFields(plugin).filter(field => !plugin[field]); - const schemaErrors = await validateJsonDocument(plugin.schemaPath, plugin.manifestPath, { - schema: "gamecult.eve.plugin.v1", - pluginId: plugin.pluginId, - }); - const advertisementErrors = await validateJsonDocument(plugin.advertisementSchemaPath, plugin.advertisementPath, { - schema: "gamecult.eve.plugin_advertisement.v1", - pluginId: plugin.pluginId, - }); - const runtimeBoundary = await readPluginRuntimeBoundary(plugin); - const runtimeBoundaryErrors = validatePluginRuntimeBoundary(plugin, runtimeBoundary); - const abiOperations = await readPluginAbiOperations(plugin.abiFixturePath); - const abiOperationContracts = await readPluginAbiOperationContracts(plugin.abiFixturePath); - const abiErrors = await validatePluginAbiFixture(plugin); - const handoffErrors = await validatePluginHandoff(plugin); - const status = missingPaths.length - ? "missing-body" - : missingRequiredFixtures.length - ? "missing-required-fixture" - : missingIncubationFields.length - ? "missing-incubation-metadata" - : schemaErrors.length - ? "invalid-plugin-manifest" - : advertisementErrors.length - ? "invalid-plugin-advertisement" - : runtimeBoundaryErrors.length - ? "invalid-plugin-runtime-boundary" - : abiErrors.length - ? "invalid-plugin-abi-fixture" - : handoffErrors.length - ? "invalid-plugin-handoff" - : plugin.kind === "incubating" - ? "incubating" - : plugin.kind === "external-owner" - ? "active" - : "external-owner-planned"; - - return { - pluginId: plugin.pluginId, - title: plugin.title, - kind: plugin.kind, - ownerRepo: plugin.ownerRepo || "", - repoRole: plugin.repoRole || "", - graduationTrigger: plugin.graduationTrigger || "", - splitTarget: plugin.splitTarget || "", - capabilities: plugin.capabilities || [], - optionalPlugins: plugin.optionalPlugins || [], - runtimeBoundary, - runtimeBoundaryErrors, - requiredFixtures, - missingRequiredFixtures, - schemaPath: plugin.schemaPath || "", - manifestPath: plugin.manifestPath || "", - abiFixturePath: plugin.abiFixturePath || "", - handoffPath: plugin.handoffPath || "", - handoffErrors, - abiOperations, - abiOperationContracts, - advertisementSchemaPath: plugin.advertisementSchemaPath || "", - advertisementPath: plugin.advertisementPath || "", - schemaErrors, - advertisementErrors, - abiErrors, - expectedPaths, - missingPaths, - missingIncubationFields, - status, - }; -} - -async function validatePluginHandoff(plugin) { - if (!plugin.handoffPath) return []; - return validateJsonDocument( - manifest.schemas?.["gamecult.eve.plugin_handoff.v1"], - plugin.handoffPath, - { - schema: "gamecult.eve.plugin_handoff.v1", - pluginId: plugin.pluginId, - ownerRepo: plugin.ownerRepo, - }, - ); -} - -async function readPluginAbiOperations(abiFixturePath) { - if (!abiFixturePath) return []; - try { - const abiFixture = await readJsonDocument(abiFixturePath); - return (abiFixture.operations || []) - .map(operation => operation.operation) - .filter(Boolean) - .sort(); - } catch { - return []; - } -} - -async function readPluginAbiOperationContracts(abiFixturePath) { - if (!abiFixturePath) return []; - try { - const abiFixture = await readJsonDocument(abiFixturePath); - return (abiFixture.operations || []) - .filter(operation => operation.operation) - .map(operation => ({ - operation: operation.operation, - input: operation.input || {}, - expect: operation.expect || {}, - })) - .sort((left, right) => left.operation.localeCompare(right.operation)); - } catch { - return []; - } -} - -async function readPluginRuntimeBoundary(plugin) { - try { - const manifestDocument = await readJsonDocument(plugin.manifestPath); - const advertisementDocument = await readJsonDocument(plugin.advertisementPath); - return { - invocationModel: manifestDocument.runtime?.invocationModel || "", - contract: manifestDocument.runtime?.contract || "", - transports: manifestDocument.runtime?.transports || [], - authority: manifestDocument.runtime?.authority || [], - sidecar: manifestDocument.runtime?.sidecar || {}, - advertisementInvocationModel: advertisementDocument.runtime?.invocationModel || "", - advertisementContract: advertisementDocument.runtime?.contract || "", - advertisementTransports: advertisementDocument.runtime?.transports || [], - advertisementAuthority: advertisementDocument.runtime?.authority || [], - advertisementSidecar: advertisementDocument.runtime?.sidecar || {}, - }; - } catch { - return { - invocationModel: "", - contract: "", - transports: [], - authority: [], - sidecar: {}, - advertisementInvocationModel: "", - advertisementContract: "", - advertisementTransports: [], - advertisementAuthority: [], - advertisementSidecar: {}, - }; - } -} - -function validatePluginRuntimeBoundary(plugin, runtimeBoundary) { - const errors = []; - const expectedRuntime = plugin.expectedRuntime || {}; - if (!runtimeBoundary.invocationModel) errors.push("runtime.invocationModel:missing"); - if (!runtimeBoundary.contract) errors.push("runtime.contract:missing"); - if (!runtimeBoundary.transports?.length) errors.push("runtime.transports:missing"); - if (!runtimeBoundary.authority?.length) errors.push("runtime.authority:missing"); - if (runtimeBoundary.invocationModel !== runtimeBoundary.advertisementInvocationModel) { - errors.push(`runtime.invocationModel:advertisement:expected ${runtimeBoundary.invocationModel} got ${runtimeBoundary.advertisementInvocationModel}`); - } - if (runtimeBoundary.contract !== runtimeBoundary.advertisementContract) { - errors.push(`runtime.contract:advertisement:expected ${runtimeBoundary.contract} got ${runtimeBoundary.advertisementContract}`); - } - if (expectedRuntime.invocationModel && runtimeBoundary.invocationModel !== expectedRuntime.invocationModel) { - errors.push(`runtime.invocationModel:expected ${expectedRuntime.invocationModel} got ${runtimeBoundary.invocationModel}`); - } - if (expectedRuntime.contract && runtimeBoundary.contract !== expectedRuntime.contract) { - errors.push(`runtime.contract:expected ${expectedRuntime.contract} got ${runtimeBoundary.contract}`); - } - errors.push(...missingMembers(expectedRuntime.transports || [], runtimeBoundary.transports || [], "runtime.transports")); - errors.push(...missingMembers(expectedRuntime.transports || [], runtimeBoundary.advertisementTransports || [], "runtime.advertisementTransports")); - errors.push(...missingMembers(expectedRuntime.authority || [], runtimeBoundary.authority || [], "runtime.authority")); - errors.push(...missingMembers(expectedRuntime.authority || [], runtimeBoundary.advertisementAuthority || [], "runtime.advertisementAuthority")); - errors.push(...comparePluginSidecar(expectedRuntime.sidecar || {}, runtimeBoundary.sidecar || {}, "runtime.sidecar")); - errors.push(...comparePluginSidecar(runtimeBoundary.sidecar || {}, runtimeBoundary.advertisementSidecar || {}, "runtime.advertisementSidecar")); - for (const schemaId of [ - runtimeBoundary.sidecar?.requestSchema, - runtimeBoundary.sidecar?.responseSchema, - runtimeBoundary.sidecar?.commandEnvelope, - runtimeBoundary.sidecar?.receiptSchema, - ].filter(Boolean)) { - if (!manifest.schemas?.[schemaId]) { - errors.push(`runtime.sidecar.schemaCatalog:${schemaId}:missing`); - } - } - return errors; -} - -function comparePluginSidecar(expected, actual, label) { - const errors = []; - for (const key of ["processKind", "protocol", "requestSchema", "responseSchema", "commandEnvelope", "receiptSchema", "stateAuthority"]) { - if (expected[key] && expected[key] !== actual[key]) { - errors.push(`${label}.${key}:expected ${expected[key]} got ${actual[key] || ""}`); - } - } - if (Array.isArray(expected.operations) && expected.operations.length) { - errors.push(...missingMembers(expected.operations, actual.operations || [], `${label}.operations`)); - } - return errors; -} - -async function evaluateFixtureMetadata(fixture) { - const errors = []; - if (!fixture.metadataPath) return { metadata: null, errors: ["metadataPath:missing"] }; - - errors.push(...await validateOwnedJsonDocument( - fixture, - manifest.schemas?.["gamecult.eve.conformance_fixture.v1"], - fixture.metadataPath, - { - schema: "gamecult.eve.conformance_fixture.v1", - fixtureId: fixture.id, - }, - )); - if (errors.length) return { metadata: null, errors }; - - const metadata = await readOwnedJsonDocument(fixture, fixture.metadataPath); - if (metadata.pack !== fixture.pack) errors.push(`pack:expected ${fixture.pack} got ${metadata.pack}`); - if (metadata.ownerRepo !== fixture.ownerRepo) errors.push(`ownerRepo:expected ${fixture.ownerRepo} got ${metadata.ownerRepo}`); - if (metadata.surface?.transport !== fixture.surface?.transport) { - errors.push(`surface.transport:expected ${fixture.surface?.transport} got ${metadata.surface?.transport}`); - } - if (metadata.surface?.path !== fixture.surface?.path) { - errors.push(`surface.path:expected ${fixture.surface?.path} got ${metadata.surface?.path}`); - } - if (!Array.isArray(metadata.asserts) || metadata.asserts.length === 0) errors.push("asserts:empty"); - - return { metadata, errors }; -} - -async function evaluateProvider(provider, fixtureResults, pluginResults) { - const expectedPaths = provider.expectedPaths || []; - const missingPaths = expectedPaths.filter(candidate => !existsSync(resolveOwnedPath(provider, candidate))); - const requiredFixtures = provider.requiredFixtures || []; - const missingRequiredFixtures = requiredFixtures.filter(id => !fixtureResults.some(fixture => fixture.id === id && fixture.status === "pass")); - const missingIncubationFields = requiredIncubationFields(provider).filter(field => !provider[field]); - const advertisementErrors = await validateOwnedJsonDocument(provider, provider.schemaPath, provider.advertisementPath, { - schema: "gamecult.eve.provider_advertisement.v1", - providerId: provider.providerId, - }); - const advertisement = advertisementErrors.length ? null : await readOwnedJsonDocument(provider, provider.advertisementPath); - const schemaIds = advertisement ? extractProviderSchemaIds(advertisement.schemas || []) : []; - const advertisedSchemaIds = advertisement ? [...new Set([ - ...schemaIds, - ...(advertisement.surfaces || []).map(surface => surface.schema).filter(Boolean), - ...(advertisement.commands || []).map(command => command.schema).filter(Boolean), - ])].sort() : []; - const surfaceIds = advertisement ? (advertisement.surfaces || []).map(surface => surface.surfaceId).filter(Boolean).sort() : []; - const surfaceKinds = advertisement ? collectProviderSurfaceKinds(advertisement) : []; - const surfaceContracts = advertisement ? collectProviderSurfaceContracts(advertisement) : []; - const commandIds = advertisement ? (advertisement.commands || []).map(command => command.command).filter(Boolean).sort() : []; - const witnessKinds = advertisement ? (advertisement.witnesses || []).map(witness => witness.kind).filter(Boolean).sort() : []; - const pluginRequirements = advertisement ? collectProviderPluginRequirements(advertisement) : []; - const pluginRequirementErrors = validateProviderPluginRequirements(provider, pluginRequirements, pluginResults); - const missingSchemas = (provider.expectedSchemas || []).filter(schema => !advertisedSchemaIds.includes(schema)); - const missingSurfaces = (provider.expectedSurfaces || []).filter(surface => !surfaceIds.includes(surface)); - const missingSurfaceKinds = validateProviderSurfaceKinds(provider, surfaceKinds); - const missingCommands = (provider.expectedCommands || []).filter(command => !commandIds.includes(command)); - const scenarioErrors = await validateProviderScenario(provider, advertisement, fixtureResults, advertisedSchemaIds, surfaceIds, commandIds); - const scenario = scenarioErrors.length || !provider.scenarioPath ? null : await readOwnedJsonDocument(provider, provider.scenarioPath); - const handoffErrors = await validateProviderHandoff(provider); - const status = missingPaths.length - ? "missing-body" - : missingRequiredFixtures.length - ? "missing-required-fixture" - : missingIncubationFields.length - ? "missing-ownership-metadata" - : advertisementErrors.length - ? "invalid-provider-advertisement" - : missingSchemas.length || missingSurfaces.length || missingSurfaceKinds.length || missingCommands.length || pluginRequirementErrors.length - ? "capability-gap" - : scenarioErrors.length - ? "invalid-provider-scenario" - : handoffErrors.length - ? "invalid-provider-handoff" - : "advertised"; - - return { - providerId: provider.providerId, - title: provider.title, - kind: provider.kind, - ownerRepo: provider.ownerRepo || "", - repoRole: provider.repoRole || "", - graduationTrigger: provider.graduationTrigger || "", - requiredFixtures, - missingRequiredFixtures, - schemaPath: provider.schemaPath || "", - advertisementPath: provider.advertisementPath || "", - scenarioPath: provider.scenarioPath || "", - handoffPath: provider.handoffPath || "", - handoffErrors, - advertisementErrors, - scenarioErrors, - expectedPaths, - missingPaths, - missingIncubationFields, - expectedSchemas: provider.expectedSchemas || [], - expectedSurfaces: provider.expectedSurfaces || [], - expectedSurfaceKinds: provider.expectedSurfaceKinds || {}, - expectedCommands: provider.expectedCommands || [], - schemaIds, - advertisedSchemaIds, - surfaceIds, - surfaceKinds, - surfaceContracts, - commandIds, - witnessKinds, - pluginRequirements, - pluginRequirementErrors, - missingSchemas, - missingSurfaces, - missingSurfaceKinds, - missingCommands, - scenarioId: scenario?.scenarioId || "", - scenarioReceiptStates: scenario ? [...new Set((scenario.expectedReceipts || []).map(receipt => receipt.state).filter(Boolean))].sort() : [], - status, - }; -} - -async function validateProviderHandoff(provider) { - if (!provider.handoffPath) return []; - return validateJsonDocument( - manifest.schemas?.["gamecult.eve.provider_handoff.v1"], - provider.handoffPath, - { - schema: "gamecult.eve.provider_handoff.v1", - providerId: provider.providerId, - ownerRepo: provider.ownerRepo, - }, - ); -} - -function collectProviderPluginRequirements(advertisement) { - const requirements = []; - for (const surface of advertisement.surfaces || []) { - for (const requirement of surface.requiresPlugins || []) { - requirements.push({ - surfaceId: surface.surfaceId || "", - surfaceKey: surface.key || surface.url || "", - pluginId: requirement.pluginId || "", - versionRange: requirement.versionRange || "", - availability: requirement.availability || "required", - requiredCapabilities: requirement.requiredCapabilities || [], - optionalCapabilities: requirement.optionalCapabilities || [], - }); - } - } - return requirements; -} - -function collectProviderSurfaceKinds(advertisement) { - return (advertisement.surfaces || []) - .filter(surface => surface.surfaceId && surface.surfaceKind) - .map(surface => ({ - surfaceId: surface.surfaceId, - surfaceKind: surface.surfaceKind, - interactionModel: surface.interactionModel || "", - })) - .sort((left, right) => left.surfaceId.localeCompare(right.surfaceId)); -} - -function collectProviderSurfaceContracts(advertisement) { - return (advertisement.surfaces || []) - .filter(surface => surface.surfaceId && surface.worldInteraction) - .map(surface => ({ - surfaceId: surface.surfaceId, - surfaceKind: surface.surfaceKind || "", - interactionModel: surface.interactionModel || "", - worldInteraction: surface.worldInteraction || {}, - })) - .sort((left, right) => left.surfaceId.localeCompare(right.surfaceId)); -} - -function validateProviderSurfaceKinds(provider, surfaceKinds) { - const errors = []; - const kindsBySurface = new Map(surfaceKinds.map(surface => [surface.surfaceId, surface.surfaceKind])); - for (const [surfaceId, expectedKind] of Object.entries(provider.expectedSurfaceKinds || {})) { - const actualKind = kindsBySurface.get(surfaceId); - if (!actualKind) { - errors.push(`${surfaceId}:surfaceKind:missing`); - } else if (actualKind !== expectedKind) { - errors.push(`${surfaceId}:surfaceKind:expected ${expectedKind} got ${actualKind}`); - } - } - return errors; -} - -function validateProviderPluginRequirements(provider, requirements, pluginResults) { - const errors = []; - const pluginById = new Map((pluginResults || []).map(plugin => [plugin.pluginId, plugin])); - for (const requirement of requirements) { - errors.push(...validateProviderPluginRequirementShape(provider, requirement)); - const plugin = pluginById.get(requirement.pluginId); - const optional = requirement.availability && requirement.availability !== "required"; - if (!plugin) { - if (!optional) errors.push(`${provider.advertisementPath}:${requirement.surfaceId}:plugin:${requirement.pluginId}:missing`); - continue; - } - if (!["active", "incubating", "external-owner-planned"].includes(plugin.status)) { - errors.push(`${provider.advertisementPath}:${requirement.surfaceId}:plugin:${requirement.pluginId}:status:${plugin.status}`); - continue; - } - const capabilities = new Set(plugin.capabilities || []); - for (const capability of requirement.requiredCapabilities || []) { - if (!capabilities.has(capability)) { - errors.push(`${provider.advertisementPath}:${requirement.surfaceId}:plugin:${requirement.pluginId}:${capability}:missing`); - } - } - } - return errors; -} - -function validateProviderPluginRequirementShape(provider, requirement) { - const label = `${provider.advertisementPath}:${requirement.surfaceId}:plugin:${requirement.pluginId || "unknown"}`; - const errors = []; - const availability = requirement.availability || "required"; - if (!requirement.pluginId) errors.push(`${label}:pluginId:missing`); - - if (availability === "required" && !requirement.requiredCapabilities.length) { - errors.push(`${label}:requiredCapabilities:missing`); - } - - if (availability === "optional-nested") { - if (requirement.requiredCapabilities.length) { - errors.push(`${label}:optional-nested:requiredCapabilities:must-be-empty`); - } - if (!requirement.optionalCapabilities.length) { - errors.push(`${label}:optional-nested:optionalCapabilities:missing`); - } - } - - return errors; -} - -async function readJsonDocument(documentPath) { - return JSON.parse(await readFile(path.join(repoRoot, documentPath), "utf8")); -} - -function resolveOwnedPath(owner, candidate) { - return path.resolve(repoRoot, owner?.sourceRoot || ".", candidate); -} - -async function readOwnedJsonDocument(owner, documentPath) { - return JSON.parse(await readFile(resolveOwnedPath(owner, documentPath), "utf8")); -} - -async function validateOwnedJsonDocument(owner, schemaPath, documentPath, expected = {}) { - const errors = []; - if (!schemaPath) errors.push("schemaPath:missing"); - if (!documentPath) errors.push("documentPath:missing"); - if (errors.length) return errors; - - const absoluteSchemaPath = path.join(repoRoot, schemaPath); - const absoluteDocumentPath = resolveOwnedPath(owner, documentPath); - if (!existsSync(absoluteSchemaPath)) errors.push(`${schemaPath}:missing`); - if (!existsSync(absoluteDocumentPath)) errors.push(`${documentPath}:missing`); - if (errors.length) return errors; - - try { - const schema = JSON.parse(await readFile(absoluteSchemaPath, "utf8")); - const document = JSON.parse(await readFile(absoluteDocumentPath, "utf8")); - errors.push(...validateSchemaSubset(schema, document)); - for (const [key, value] of Object.entries(expected)) { - if (document[key] !== value) errors.push(`${documentPath}:${key}:expected ${value} got ${document[key]}`); - } - } catch (error) { - errors.push(`${documentPath}:invalid-json:${error instanceof Error ? error.message : String(error)}`); - } - return errors; -} - -async function validateProviderScenario(provider, advertisement, fixtureResults, advertisedSchemaIds, surfaceIds, commandIds) { - if (!provider.scenarioPath) return []; - - const errors = await validateOwnedJsonDocument( - provider, - manifest.schemas?.["gamecult.eve.provider_scenario.v1"], - provider.scenarioPath, - { - schema: "gamecult.eve.provider_scenario.v1", - providerId: provider.providerId, - }, - ); - if (errors.length) return errors; - - try { - const scenario = await readOwnedJsonDocument(provider, provider.scenarioPath); - if (scenario.ownerRepo !== provider.ownerRepo) { - errors.push(`${provider.scenarioPath}:ownerRepo:expected ${provider.ownerRepo} got ${scenario.ownerRepo}`); - } - if (scenario.advertisementPath !== provider.advertisementPath) { - errors.push(`${provider.scenarioPath}:advertisementPath:expected ${provider.advertisementPath} got ${scenario.advertisementPath}`); - } - - const advertisedScenario = (advertisement?.conformanceScenarios || []).find(candidate => - candidate.scenarioId === scenario.scenarioId && candidate.path === provider.scenarioPath); - if (!advertisedScenario) { - errors.push(`${provider.advertisementPath}:conformanceScenarios:${scenario.scenarioId}:missing`); - } - - for (const fixtureId of scenario.requires?.fixtures || []) { - if (!fixtureResults.some(fixture => fixture.id === fixtureId && fixture.status === "pass")) { - errors.push(`${provider.scenarioPath}:requires.fixtures:${fixtureId}:missing`); - } - } - errors.push(...missingMembers(scenario.requires?.surfaces || [], surfaceIds, `${provider.scenarioPath}:requires.surfaces`)); - errors.push(...missingMembers(scenario.requires?.commands || [], commandIds, `${provider.scenarioPath}:requires.commands`)); - errors.push(...missingMembers(scenario.requires?.schemas || [], advertisedSchemaIds, `${provider.scenarioPath}:requires.schemas`)); - const advertisedWorldSurfaces = new Map((advertisement.surfaces || []) - .filter(surface => surface.surfaceId && surface.worldInteraction) - .map(surface => [surface.surfaceId, surface])); - for (const requirement of scenario.requires?.worldSurfaces || []) { - const advertisedSurface = advertisedWorldSurfaces.get(requirement.surfaceId); - if (!advertisedSurface) { - errors.push(`${provider.scenarioPath}:requires.worldSurfaces:${requirement.surfaceId}:missing`); - continue; - } - if (advertisedSurface.surfaceKind !== requirement.surfaceKind) { - errors.push(`${provider.scenarioPath}:requires.worldSurfaces:${requirement.surfaceId}:surfaceKind:expected ${requirement.surfaceKind} got ${advertisedSurface.surfaceKind || ""}`); - } - for (const key of ["projectionKind", "commandBoundary", "receiptSchema"]) { - const actual = advertisedSurface.worldInteraction?.[key] || ""; - if (actual !== requirement[key]) { - errors.push(`${provider.scenarioPath}:requires.worldSurfaces:${requirement.surfaceId}:${key}:expected ${requirement[key]} got ${actual}`); - } - } - } - - const receipts = scenario.expectedReceipts || []; - const receiptKeys = new Set(receipts.map(receipt => `${receipt.command}:${receipt.commandId}`)); - for (const intent of scenario.commandIntents || []) { - if (!commandIds.includes(intent.command)) { - errors.push(`${provider.scenarioPath}:commandIntents:${intent.command}:unadvertised`); - } - if (!receiptKeys.has(`${intent.command}:${intent.commandId}`)) { - errors.push(`${provider.scenarioPath}:expectedReceipts:${intent.command}:${intent.commandId}:missing`); - } - } - - for (const receipt of receipts) { - if (!commandIds.includes(receipt.command)) { - errors.push(`${provider.scenarioPath}:expectedReceipts:${receipt.command}:unadvertised`); - } - if (receipt.ownerRepo !== provider.ownerRepo) { - errors.push(`${provider.scenarioPath}:expectedReceipts:${receipt.receiptId}:ownerRepo:expected ${provider.ownerRepo} got ${receipt.ownerRepo}`); - } - if (!["accepted", "denied", "pending", "reconciled"].includes(receipt.state)) { - errors.push(`${provider.scenarioPath}:expectedReceipts:${receipt.receiptId}:state:${receipt.state}:unknown`); - } - if (receipt.schema && !advertisedSchemaIds.includes(receipt.schema)) { - errors.push(`${provider.scenarioPath}:expectedReceipts:${receipt.receiptId}:schema:${receipt.schema}:unadvertised`); - } - } - } catch (error) { - errors.push(`${provider.scenarioPath}:invalid-json:${error instanceof Error ? error.message : String(error)}`); - } - - return errors; -} - -async function validatePluginAbiFixture(plugin) { - const errors = await validateJsonDocument( - manifest.schemas?.["gamecult.eve.plugin_abi_fixture.v1"], - plugin.abiFixturePath, - { - schema: "gamecult.eve.plugin_abi_fixture.v1", - pluginId: plugin.pluginId, - }, - ); - if (errors.length) return errors; - - try { - const abiFixture = await readJsonDocument(plugin.abiFixturePath); - const pluginManifest = await readJsonDocument(plugin.manifestPath); - const operations = new Map((abiFixture.operations || []).map(operation => [operation.operation, operation])); - - if (abiFixture.ownerRepo !== plugin.ownerRepo) { - errors.push(`${plugin.abiFixturePath}:ownerRepo:expected ${plugin.ownerRepo} got ${abiFixture.ownerRepo}`); - } - if (abiFixture.contract !== "gamecult.eve.plugin_abi.v1") { - errors.push(`${plugin.abiFixturePath}:contract:expected gamecult.eve.plugin_abi.v1 got ${abiFixture.contract}`); - } - if (abiFixture.requestSchema !== pluginManifest.runtime?.sidecar?.requestSchema) { - errors.push(`${plugin.abiFixturePath}:requestSchema:expected ${pluginManifest.runtime?.sidecar?.requestSchema || ""} got ${abiFixture.requestSchema || ""}`); - } - if (abiFixture.responseSchema !== pluginManifest.runtime?.sidecar?.responseSchema) { - errors.push(`${plugin.abiFixturePath}:responseSchema:expected ${pluginManifest.runtime?.sidecar?.responseSchema || ""} got ${abiFixture.responseSchema || ""}`); - } - const sidecarReceiptSchema = pluginManifest.runtime?.sidecar?.receiptSchema || ""; - for (const schemaId of [abiFixture.requestSchema, abiFixture.responseSchema, sidecarReceiptSchema].filter(Boolean)) { - if (!manifest.schemas?.[schemaId]) { - errors.push(`${plugin.abiFixturePath}:schemaCatalog:${schemaId}:missing`); - } - } - - const claimedOperations = pluginManifest.runtime?.sidecar?.operations || []; - for (const operation of claimedOperations) { - if (!operations.has(operation)) errors.push(`${plugin.abiFixturePath}:operation:${operation}:missing`); - } - errors.push(...missingMembers( - [...operations.keys()].filter(Boolean), - pluginManifest.runtime?.sidecar?.operations || [], - `${plugin.manifestPath}:runtime.sidecar.operations`, - )); - - const fixtureBasename = path.basename(plugin.abiFixturePath); - if (!(pluginManifest.abiFixtures || []).some(candidate => path.basename(candidate) === fixtureBasename)) { - errors.push(`${plugin.manifestPath}:abiFixtures:${plugin.abiFixturePath}:missing`); - } - - const manifestCommands = (pluginManifest.commands || []).map(command => command.command); - const manifestComponentKinds = pluginManifest.componentKinds || []; - const manifestCommandEffects = new Map((pluginManifest.commands || []).map(command => [command.command, command.effect])); - - const describe = operations.get("describe")?.expect || {}; - if (describe.pluginId !== plugin.pluginId) { - errors.push(`${plugin.abiFixturePath}:describe.pluginId:expected ${plugin.pluginId} got ${describe.pluginId}`); - } - errors.push(...missingMembers(manifestComponentKinds, describe.componentKinds || [], `${plugin.abiFixturePath}:describe.componentKinds`)); - errors.push(...missingMembers(manifestCommands, describe.commands || [], `${plugin.abiFixturePath}:describe.commands`)); - errors.push(...missingMembers(plugin.capabilities || [], describe.capabilities || [], `${plugin.abiFixturePath}:describe.capabilities`)); - - const validate = operations.get("validate")?.expect || {}; - errors.push(...missingMembers(manifestComponentKinds, validate.acceptedComponentKinds || [], `${plugin.abiFixturePath}:validate.acceptedComponentKinds`)); - - const project = operations.get("project")?.expect || {}; - errors.push(...missingMembers(manifestComponentKinds, project.ownedComponentKinds || [], `${plugin.abiFixturePath}:project.ownedComponentKinds`)); - if (!project.projectionKind) errors.push(`${plugin.abiFixturePath}:project.projectionKind:missing`); - - if (claimedOperations.includes("lower")) { - const lower = operations.get("lower")?.expect || {}; - errors.push(...missingMembers(manifestComponentKinds, lower.preservedComponentKinds || [], `${plugin.abiFixturePath}:lower.preservedComponentKinds`)); - if (!lower.loweringKind) errors.push(`${plugin.abiFixturePath}:lower.loweringKind:missing`); - } - - if (claimedOperations.includes("measure")) { - const measure = operations.get("measure")?.expect || {}; - if (!measure.measurementKind) errors.push(`${plugin.abiFixturePath}:measure.measurementKind:missing`); - if (!(measure.measurementOutputs || []).length) errors.push(`${plugin.abiFixturePath}:measure.measurementOutputs:missing`); - if (measure.preservesProviderAuthority !== true) errors.push(`${plugin.abiFixturePath}:measure.preservesProviderAuthority:expected true`); - } - - if (claimedOperations.includes("apply")) { - const apply = operations.get("apply")?.expect || {}; - if (apply.receiptSchema !== sidecarReceiptSchema) { - errors.push(`${plugin.abiFixturePath}:apply.receiptSchema:expected ${sidecarReceiptSchema} got ${apply.receiptSchema || ""}`); - } - if (manifestCommands.length) { - const commandEffects = apply.commandEffects || {}; - for (const command of manifestCommands) { - if (!commandEffects[command]) { - errors.push(`${plugin.abiFixturePath}:apply.commandEffects:${command}:missing`); - continue; - } - - const expectedEffect = manifestCommandEffects.get(command); - if (commandEffects[command] !== expectedEffect) { - errors.push(`${plugin.abiFixturePath}:apply.commandEffects:${command}:expected ${expectedEffect} got ${commandEffects[command]}`); - } - } - } else if (!(apply.stateEffects || []).length) { - errors.push(`${plugin.abiFixturePath}:apply.stateEffects:missing`); - } - } - } catch (error) { - errors.push(`${plugin.abiFixturePath}:invalid-json:${error instanceof Error ? error.message : String(error)}`); - } - - return errors; -} - -function missingMembers(expected, actual, label) { - const actualSet = new Set(actual); - return expected.filter(item => !actualSet.has(item)).map(item => `${label}:${item}:missing`); -} - -async function validateJsonDocument(schemaPath, documentPath, expected = {}) { - const errors = []; - if (!schemaPath) errors.push("schemaPath:missing"); - if (!documentPath) errors.push("documentPath:missing"); - if (!schemaPath || !documentPath) return errors; - - const absoluteSchemaPath = path.join(repoRoot, schemaPath); - const absoluteDocumentPath = path.join(repoRoot, documentPath); - if (!existsSync(absoluteSchemaPath)) errors.push(`${schemaPath}:missing`); - if (!existsSync(absoluteDocumentPath)) errors.push(`${documentPath}:missing`); - if (errors.length) return errors; - - try { - const schema = JSON.parse(await readFile(absoluteSchemaPath, "utf8")); - const document = JSON.parse(await readFile(absoluteDocumentPath, "utf8")); - errors.push(...validateSchemaSubset(schema, document)); - for (const [key, value] of Object.entries(expected)) { - if (document[key] !== value) errors.push(`${documentPath}:${key}:expected ${value} got ${document[key]}`); - } - } catch (error) { - errors.push(`${documentPath}:invalid-json:${error instanceof Error ? error.message : String(error)}`); - } - - return errors; -} - -async function validateJsonDocumentAgainstSchemaPath(schemaPath, document, label) { - const errors = []; - if (!schemaPath) return ["schemaPath:missing"]; - - const absoluteSchemaPath = path.join(repoRoot, schemaPath); - if (!existsSync(absoluteSchemaPath)) return [`${schemaPath}:missing`]; - - try { - const schema = JSON.parse(await readFile(absoluteSchemaPath, "utf8")); - errors.push(...validateSchemaSubset(schema, document, label)); - } catch (error) { - errors.push(`${label}:invalid-json:${error instanceof Error ? error.message : String(error)}`); - } - - return errors; -} - -function extractProviderSchemaIds(schemas) { - return schemas.map(schema => { - if (typeof schema === "string") return schema; - if (schema && typeof schema === "object") return schema.schema || schema.id || ""; - return ""; - }).filter(Boolean).sort(); -} - -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; - } -} - -function collectPluginCapabilityGaps(runtime, fixtureResults) { - const supported = new Map((runtime.supportedPlugins || []).map(plugin => [plugin.pluginId, new Set(plugin.capabilities || [])])); - const unsupported = new Map((runtime.unsupportedPlugins || []).map(plugin => [plugin.pluginId, plugin.reason || "unsupported"])); - const fixtureById = new Map((manifest.fixtures || []).map(fixture => [fixture.id, fixture])); - const gaps = []; - const fixtureIds = [...new Set([...(runtime.requiredFixtures || []), ...(runtime.pluginFixtures || [])])]; - for (const fixtureId of fixtureIds) { - const fixtureResult = fixtureResults.find(fixture => fixture.id === fixtureId); - if (!fixtureResult || fixtureResult.status !== "pass") continue; - const fixture = fixtureById.get(fixtureId); - for (const requirement of fixture?.requiredPlugins || []) { - if (unsupported.has(requirement.pluginId)) continue; - const capabilities = supported.get(requirement.pluginId); - if (!capabilities) { - gaps.push(`${fixtureId}:${requirement.pluginId}`); - continue; - } - for (const capability of requirement.capabilities || []) { - if (!capabilities.has(capability)) gaps.push(`${fixtureId}:${requirement.pluginId}:${capability}`); - } - } - } - return gaps; -} - -function collectUnsupportedPluginNotes(runtime, fixtureResults) { - const unsupported = new Map((runtime.unsupportedPlugins || []).map(plugin => [plugin.pluginId, plugin.reason || "unsupported"])); - const fixtureById = new Map((manifest.fixtures || []).map(fixture => [fixture.id, fixture])); - const notes = []; - const fixtureIds = [...new Set([...(runtime.requiredFixtures || []), ...(runtime.pluginFixtures || [])])]; - for (const fixtureId of fixtureIds) { - const fixtureResult = fixtureResults.find(fixture => fixture.id === fixtureId); - if (!fixtureResult || fixtureResult.status !== "pass") continue; - const fixture = fixtureById.get(fixtureId); - for (const requirement of fixture?.requiredPlugins || []) { - const reason = unsupported.get(requirement.pluginId); - if (reason) notes.push(`${fixtureId}:${requirement.pluginId}:${reason}`); - } - } - return notes; -} - -async function validateRuntimeCommandTransportSmoke(runtime) { - const smoke = runtime.commandTransportSmoke; - if (!smoke) return []; - - const errors = []; - if (smoke.schema !== "gamecult.eve.command_invocation.v1") { - errors.push(`schema:expected gamecult.eve.command_invocation.v1 got ${smoke.schema || ""}`); - } - - for (const candidate of smoke.expectedPaths || []) { - if (!existsSync(resolveRuntimeOwnedPath(runtime, candidate))) errors.push(`path:${candidate}:missing`); - } - - for (const expectation of smoke.expectedSourceSymbols || []) { - const sourcePath = expectation.path || ""; - const absolutePath = resolveRuntimeOwnedPath(runtime, sourcePath); - if (!existsSync(absolutePath)) { - errors.push(`source:${sourcePath}:missing`); - continue; - } - - const source = await readFile(absolutePath, "utf8"); - for (const symbol of expectation.contains || []) { - if (!source.includes(symbol)) errors.push(`source:${sourcePath}:${symbol}:missing`); - } - } - - return errors; -} - -async function validateRuntimeCapabilityManifest(runtime) { - const capabilityManifest = runtime.capabilityManifest; - if (!capabilityManifest) return []; - - const manifestDocumentPath = path.relative(repoRoot, resolveRuntimeOwnedPath(runtime, capabilityManifest.manifestPath)); - const errors = await validateJsonDocument(capabilityManifest.schemaPath, manifestDocumentPath, { - schema: "gamecult.eve.runtime_capability.v1", - runtimeId: runtime.id, - }); - if (errors.length) return errors; - - const documentPath = resolveRuntimeOwnedPath(runtime, capabilityManifest.manifestPath); - const document = JSON.parse(await readFile(documentPath, "utf8")); - const supportedFeatures = document.supportedFeatures || []; - const supportedPlugins = document.supportedPlugins || []; - const unsupportedPlugins = document.unsupportedPlugins || []; - const worldSurfaceLowering = document.worldSurfaceLowering || []; - const commandTransport = document.commandTransport || {}; - const lifecycle = document.lifecycle || {}; - - errors.push(...missingMembers(runtime.supportedFeatures || [], supportedFeatures, `${capabilityManifest.manifestPath}:supportedFeatures`)); - errors.push(...comparePluginCapabilityClaims( - runtime.supportedPlugins || [], - supportedPlugins, - `${capabilityManifest.manifestPath}:supportedPlugins`, - )); - errors.push(...compareUnsupportedPluginClaims( - runtime.unsupportedPlugins || [], - unsupportedPlugins, - `${capabilityManifest.manifestPath}:unsupportedPlugins`, - )); - errors.push(...compareWorldSurfaceLoweringClaims( - runtime.worldSurfaceLowering || [], - worldSurfaceLowering, - `${capabilityManifest.manifestPath}:worldSurfaceLowering`, - runtime, - )); - - const expectedCommandSchema = runtimeCommandTransportSchema(runtime); - if (expectedCommandSchema && commandTransport.schema !== expectedCommandSchema) { - errors.push(`${capabilityManifest.manifestPath}:commandTransport.schema:expected ${expectedCommandSchema} got ${commandTransport.schema || ""}`); - } - - const incubation = document.incubation || {}; - for (const [key, value] of Object.entries({ - ownerRepo: runtime.splitTarget || runtime.ownerRepo || "", - currentHostRepo: runtime.currentHostRepo || "Eve", - splitTarget: runtime.splitTarget || "", - graduationTrigger: runtime.graduationTrigger || "", - })) { - if (value && incubation[key] !== value) { - errors.push(`${capabilityManifest.manifestPath}:incubation.${key}:expected ${value} got ${incubation[key] || ""}`); - } - } - - errors.push(...compareRuntimeLifecycleClaims( - runtime.lifecycle || {}, - lifecycle, - capabilityManifest.manifestPath, - runtime, - )); - - return errors; -} - -function compareWorldSurfaceLoweringClaims(expectedClaims, actualClaims, label, runtime) { - const errors = []; - const actualByTarget = new Map((actualClaims || []).map(claim => [claim.targetId, claim])); - for (const expected of expectedClaims || []) { - const actual = actualByTarget.get(expected.targetId); - if (!actual) { - errors.push(`${label}:${expected.targetId}:missing`); - continue; - } - for (const key of ["supportLevel", "ownership"]) { - if ((expected[key] || "") !== (actual[key] || "")) { - errors.push(`${label}:${expected.targetId}.${key}:expected ${expected[key] || ""} got ${actual[key] || ""}`); - } - } - errors.push(...missingMembers(expected.surfaceKinds || [], actual.surfaceKinds || [], `${label}:${expected.targetId}.surfaceKinds`)); - errors.push(...missingMembers(expected.projectionKinds || [], actual.projectionKinds || [], `${label}:${expected.targetId}.projectionKinds`)); - errors.push(...missingMembers(expected.evidencePaths || [], actual.evidencePaths || [], `${label}:${expected.targetId}.evidencePaths`)); - for (const evidencePath of actual.evidencePaths || []) { - if (!existsSync(resolveRuntimeOwnedPath(runtime, evidencePath))) { - errors.push(`${label}:${expected.targetId}.evidencePath:${evidencePath}:missing`); - } - } - } - return errors; -} - -function compareRuntimeLifecycleClaims(expectedLifecycle, actualLifecycle, manifestPath, runtime) { - const errors = []; - for (const stage of ["release", "test", "capture"]) { - const expected = expectedLifecycle[stage]; - const actual = actualLifecycle[stage]; - const label = `${manifestPath}:lifecycle.${stage}`; - if (!expected) continue; - if (!actual) { - errors.push(`${label}:missing`); - continue; - } - - for (const key of ["ownerRepo", "status"]) { - if ((expected[key] || "") !== (actual[key] || "")) { - errors.push(`${label}.${key}:expected ${expected[key] || ""} got ${actual[key] || ""}`); - } - } - - errors.push(...missingMembers(expected.evidencePaths || [], actual.evidencePaths || [], `${label}.evidencePaths`)); - errors.push(...missingMembers(expected.pendingProofs || [], actual.pendingProofs || [], `${label}.pendingProofs`)); - errors.push(...compareReleaseContract(expected.releaseContract, actual.releaseContract, `${label}.releaseContract`, runtime)); - errors.push(...compareTestContract(expected.testContract, actual.testContract, `${label}.testContract`, runtime)); - errors.push(...compareCaptureContract(expected.captureContract, actual.captureContract, `${label}.captureContract`)); - - for (const evidencePath of actual.evidencePaths || []) { - if (!existsSync(resolveRuntimeOwnedPath(runtime, evidencePath))) { - errors.push(`${label}.evidencePaths:${evidencePath}:missing`); - } - } - } - return errors; -} - -function compareCaptureContract(expected, actual, label) { - const errors = []; - if (!expected) return errors; - if (!actual) return [`${label}:missing`]; - for (const key of [ - "ownerRepo", - "runtimeId", - "targetId", - "requestSchema", - "requestBuilder", - "advertisementPath", - "captureKind", - "artifactKind", - "artifactPattern", - "conformanceAttachment", - "requiredProvider", - "requiredSurface", - "authority", - "publishProof", - ]) { - if ((expected[key] || "") !== (actual[key] || "")) { - errors.push(`${label}.${key}:expected ${expected[key] || ""} got ${actual[key] || ""}`); - } - } - if (actual.requestSchema && !manifest.schemas?.[actual.requestSchema]) { - errors.push(`${label}.requestSchema:${actual.requestSchema}:missing-schema-catalog-entry`); - } - const expectedAdditionalSurfaces = captureSurfaceClaimKeys(expected.additionalProviderSurfaces || []); - const actualAdditionalSurfaces = captureSurfaceClaimKeys(actual.additionalProviderSurfaces || []); - errors.push(...missingMembers(expectedAdditionalSurfaces, actualAdditionalSurfaces, `${label}.additionalProviderSurfaces`)); - return errors; -} - -function captureSurfaceClaimKeys(claims) { - return (Array.isArray(claims) ? claims : []) - .filter(claim => claim && typeof claim === "object") - .map(claim => `${claim.providerId || ""}:${claim.surfaceId || ""}`) - .filter(claim => claim !== ":"); -} - -function compareTestContract(expected, actual, label, runtime) { - const errors = []; - if (!expected) return errors; - if (!actual) return [`${label}:missing`]; - for (const key of [ - "ownerRepo", - "runnerKind", - "runnerScript", - "consumerProject", - "unityExeDefault", - "packageName", - "testAssembly", - "testPlatform", - "resultsArtifact", - "logArtifact", - "manifestMutation", - ]) { - if ((expected[key] || "") !== (actual[key] || "")) { - errors.push(`${label}.${key}:expected ${expected[key] || ""} got ${actual[key] || ""}`); - } - } - if (actual.runnerScript && !existsSync(resolveRuntimeOwnedPath(runtime, actual.runnerScript))) { - errors.push(`${label}.runnerScript:${actual.runnerScript}:missing`); - } - if (actual.consumerProject && !existsSync(actual.consumerProject)) { - errors.push(`${label}.consumerProject:${actual.consumerProject}:missing`); - } - errors.push(...compareDependencyRecords( - expected.managedAssemblyDependencies || [], - actual.managedAssemblyDependencies || [], - `${label}.managedAssemblyDependencies`, - [ - "assemblyName", - "packageId", - "packageManager", - "ownerRepo", - "resolutionMode", - "dependencyContract.sourceAuthority", - "dependencyContract.packageSource", - "dependencyContract.packageId", - "dependencyContract.assemblyName", - "dependencyContract.versionPolicy", - "dependencyContract.unityResolution", - "dependencyContract.handoffRequirement", - ], - )); - return errors; -} - -function compareReleaseContract(expected, actual, label, runtime) { - const errors = []; - if (!expected) return errors; - if (!actual) return [`${label}:missing`]; - for (const key of ["ownerRepo", "packageName", "packageRoot", "versionSource", "tagPattern", "artifactKind", "requestSchema", "requestBuilder", "artifactBuilder", "artifactPattern", "publishProof"]) { - if ((expected[key] || "") !== (actual[key] || "")) { - errors.push(`${label}.${key}:expected ${expected[key] || ""} got ${actual[key] || ""}`); - } - } - if (actual.requestSchema && !manifest.schemas?.[actual.requestSchema]) { - errors.push(`${label}.requestSchema:${actual.requestSchema}:missing-schema-catalog-entry`); - } - for (const key of ["packageRoot", "versionSource", "requestBuilder", "artifactBuilder"]) { - if (actual[key] && !existsSync(resolveRuntimeOwnedPath(runtime, actual[key]))) { - errors.push(`${label}.${key}:${actual[key]}:missing`); - } - } - errors.push(...compareDependencyRecords( - expected.requiredPackageDependencies || [], - actual.requiredPackageDependencies || [], - `${label}.requiredPackageDependencies`, - ["packageName", "version", "packageManager", "ownerRepo", "purpose"], - )); - return errors; -} - -function resolveRuntimeOwnedPath(runtime, candidate) { - if (!candidate) return repoRoot; - if (path.isAbsolute(candidate)) return candidate; - const sourceRoot = runtime?.sourceRoot || "."; - return path.resolve(repoRoot, sourceRoot, candidate); -} - -function compareDependencyRecords(expectedRecords, actualRecords, label, keys) { - const errors = []; - for (const expected of expectedRecords) { - const identityKey = expected.packageName ? "packageName" : "assemblyName"; - const actual = actualRecords.find(candidate => candidate?.[identityKey] === expected[identityKey]); - if (!actual) { - errors.push(`${label}:${expected[identityKey]}:missing`); - continue; - } - for (const key of keys) { - const expectedValue = getByPath(expected, key) || ""; - const actualValue = getByPath(actual, key) || ""; - if (expectedValue !== actualValue) { - errors.push(`${label}:${expected[identityKey]}.${key}:expected ${expectedValue} got ${actualValue}`); - } - } - } - return errors; -} - -function getByPath(record, dottedPath) { - return dottedPath.split(".").reduce((current, segment) => current?.[segment], record); -} - -function comparePluginCapabilityClaims(expectedPlugins, actualPlugins, label) { - const errors = []; - const actual = new Map(actualPlugins.map(plugin => [plugin.pluginId, new Set(plugin.capabilities || [])])); - for (const plugin of expectedPlugins) { - const capabilities = actual.get(plugin.pluginId); - if (!capabilities) { - errors.push(`${label}:${plugin.pluginId}:missing`); - continue; - } - for (const capability of plugin.capabilities || []) { - if (!capabilities.has(capability)) errors.push(`${label}:${plugin.pluginId}:${capability}:missing`); - } - } - return errors; -} - -function compareUnsupportedPluginClaims(expectedPlugins, actualPlugins, label) { - const errors = []; - const actual = new Map(actualPlugins.map(plugin => [plugin.pluginId, plugin.reason || ""])); - for (const plugin of expectedPlugins) { - const reason = actual.get(plugin.pluginId); - if (!reason) { - errors.push(`${label}:${plugin.pluginId}:missing`); - continue; - } - if (plugin.reason && reason !== plugin.reason) { - errors.push(`${label}:${plugin.pluginId}:reason:expected ${plugin.reason} got ${reason}`); - } - } - return errors; -} - -function evaluateSplitTargets(splitTargets, runtimeResults) { - const runtimeById = new Map(runtimeResults.map(runtime => [runtime.id, runtime])); - return splitTargets.map(target => { - const requiredRuntimeStatuses = target.requiredRuntimeStatuses || ["active"]; - const requiredFeatures = target.requiredFeatures || []; - const requiredPlugins = target.requiredPlugins || []; - const proofs = target.proofs || []; - const runtimeIds = target.runtimes || []; - const blockers = []; - const blockerRecords = []; - const runtimeStatuses = {}; - const addBlocker = (text, record) => { - blockers.push(text); - blockerRecords.push({ - targetId: target.id, - ownerRepo: target.ownerRepo || "", - severity: "blocker", - subject: record.subject || "", - ...record, - text, - }); - }; - - for (const runtimeId of runtimeIds) { - const runtime = runtimeById.get(runtimeId); - if (!runtime) { - addBlocker(`runtime:${runtimeId}:missing`, { - kind: "runtime-missing", - subject: runtimeId, - runtimeId, - }); - continue; - } - - runtimeStatuses[runtimeId] = runtime.status; - if (!requiredRuntimeStatuses.includes(runtime.status)) { - addBlocker(`runtime:${runtimeId}:status:${runtime.status}`, { - kind: "runtime-status", - subject: runtimeId, - runtimeId, - status: runtime.status, - requiredStatuses: requiredRuntimeStatuses, - }); - } - - for (const feature of requiredFeatures) { - if (!runtime.supportedFeatures.includes(feature)) { - addBlocker(`runtime:${runtimeId}:feature:${feature}`, { - kind: "runtime-feature", - subject: `${runtimeId}:${feature}`, - runtimeId, - feature, - }); - } - } - - const supportedPlugins = new Map(runtime.supportedPlugins.map(plugin => [plugin.pluginId, new Set(plugin.capabilities || [])])); - const unsupportedPlugins = new Map((runtime.unsupportedPlugins || []).map(plugin => [plugin.pluginId, plugin.reason || "unsupported"])); - for (const requirement of requiredPlugins) { - if (unsupportedPlugins.has(requirement.pluginId)) { - addBlocker(`runtime:${runtimeId}:unsupported-plugin:${requirement.pluginId}:${unsupportedPlugins.get(requirement.pluginId)}`, { - kind: "runtime-plugin-projection", - subject: `${runtimeId}:${requirement.pluginId}`, - runtimeId, - pluginId: requirement.pluginId, - reason: unsupportedPlugins.get(requirement.pluginId), - }); - continue; - } - const capabilities = supportedPlugins.get(requirement.pluginId); - if (!capabilities) { - addBlocker(`runtime:${runtimeId}:plugin:${requirement.pluginId}`, { - kind: "runtime-plugin-missing", - subject: `${runtimeId}:${requirement.pluginId}`, - runtimeId, - pluginId: requirement.pluginId, - }); - continue; - } - - for (const capability of requirement.capabilities || []) { - if (!capabilities.has(capability)) { - addBlocker(`runtime:${runtimeId}:plugin:${requirement.pluginId}:${capability}`, { - kind: "runtime-plugin-capability", - subject: `${runtimeId}:${requirement.pluginId}:${capability}`, - runtimeId, - pluginId: requirement.pluginId, - capability, - }); - } - } - } - } - - for (const proof of proofs) { - const missingEvidence = (proof.evidencePaths || []).filter(candidate => !existsSync(path.join(repoRoot, candidate))); - if (proof.status !== "passed") { - const description = proof.description || "unnamed"; - addBlocker(`proof:${description}:status:${proof.status || "missing"}`, { - kind: "proof-status", - subject: description, - proof: description, - status: proof.status || "missing", - }); - } - for (const evidencePath of missingEvidence) { - const description = proof.description || "unnamed"; - addBlocker(`proof:${description}:evidence:${evidencePath}:missing`, { - kind: "proof-evidence", - subject: `${description}:${evidencePath}`, - proof: description, - evidencePath, - status: "missing", - }); - } - } - - for (const proof of target.pendingProofs || []) { - addBlocker(`proof:${proof}`, { - kind: "pending-proof", - subject: proof, - proof, - }); - } - - return { - id: target.id, - ownerRepo: target.ownerRepo || "", - repoRole: target.repoRole || "", - status: blockers.length ? "incubating" : target.status === "graduated" ? "graduated" : "ready-to-split", - declaredStatus: target.status || "", - runtimes: runtimeIds, - runtimeStatuses, - requiredRuntimeStatuses, - requiredFeatures, - requiredPlugins, - proofs, - pendingProofs: target.pendingProofs || [], - blockers, - blockerRecords, - }; - }); -} - -function requiredIncubationFields(entry) { - if (entry.kind === "external-owner") return ["ownerRepo", "repoRole"]; - if (entry.repoRole === "core") return ["ownerRepo", "repoRole"]; - return ["ownerRepo", "repoRole", "graduationTrigger"]; -} - -function flattenSurface(root) { - const nodes = []; - const visit = (node) => { - nodes.push(node); - for (const child of node.children || []) visit(child); - }; - visit(root); - return nodes; -} - -function readBindings(node) { - const props = node.props || {}; - const bindings = []; - if (props.bind) bindings.push(props.bind); - if (props.action?.target) bindings.push(props.action.target); - return bindings; -} - -function readCommandDescriptors(commands) { - return commands - .filter(command => command && typeof command === "object") - .map(command => ({ - ...command, - command: typeof command.command === "string" ? command.command : "", - schema: typeof command.schema === "string" ? command.schema : "", - })) - .filter(command => command.command); -} - -async function validateCommandDescriptors(fixture, commandDescriptors) { - if (!fixture.expect.commandDescriptorSchema) return []; - const schemaPath = manifest.schemas?.[fixture.expect.commandDescriptorSchema]; - if (!schemaPath) return [`${fixture.expect.commandDescriptorSchema}:schemaPath:missing`]; - const absoluteSchemaPath = path.join(repoRoot, schemaPath); - if (!existsSync(absoluteSchemaPath)) return [`${schemaPath}:missing`]; - const schema = JSON.parse(await readFile(absoluteSchemaPath, "utf8")); - return commandDescriptors.flatMap(command => - validateSchemaSubset(schema, command, `command:${command.command}`)); -} - -function readCommandReferences(nodes) { - return nodes.flatMap(node => { - const props = node.props || {}; - const action = objectProps(props.action); - const command = firstNonEmptyString(props.command, props.commandId, action.command, action.target, action.type, node.commandId); - if (!command) return []; - return [{ - nodeId: node.id || "", - kind: node.kind || "", - command, - }]; - }).sort((a, b) => `${a.nodeId}:${a.command}`.localeCompare(`${b.nodeId}:${b.command}`)); -} - -function readAuthorityWitnesses(nodes) { - return nodes.flatMap(node => { - const props = node.props || {}; - const freshness = objectProps(props.freshness); - const state = firstNonEmptyString(props.authorityState, props.truthState, props.commandState, freshness.state); - const owner = firstNonEmptyString(props.authorityOwner, props.owner, props.authority); - const witnessRef = firstNonEmptyString(props.witnessRef, props.sourceId); - const receiptRef = firstNonEmptyString(props.receiptRef, props.receiptId); - if (!state && !owner && !witnessRef && !receiptRef) return []; - return [{ - nodeId: node.id || "", - kind: node.kind || "", - state, - owner, - witnessRef, - receiptRef, - }]; - }).sort((a, b) => `${a.nodeId}:${a.state}`.localeCompare(`${b.nodeId}:${b.state}`)); -} - -function firstNonEmptyString(...values) { - for (const value of values) { - if (typeof value === "string" && value.trim()) return value; - } - return ""; -} - -function hasVisibleText(node) { - const props = objectProps(node.props); - return isTextLikeNode(node) && Boolean(firstNonEmptyString( - node.text, - node.value, - props.text, - props.value, - props.label, - props.title, - props.body, - )); -} - -function isTextLikeNode(node) { - const kind = typeof node.kind === "string" ? node.kind : ""; - return kind === "text" || kind.startsWith("text."); -} - -function hasLayoutBox(node) { - const layout = objectProps(node.layout); - return ["x", "y", "width", "height"].some(key => layout[key] !== undefined); -} - -function objectProps(value) { - return value && typeof value === "object" && !Array.isArray(value) ? value : {}; -} - -function countBy(items, selector) { - const counts = {}; - for (const item of items) { - const key = selector(item); - counts[key] = (counts[key] || 0) + 1; - } - return counts; -} - -function addCheck(checks, id, pass, detail) { - checks.push({ id, pass, ...detail }); -} - -function summarize(fixtures, runtimes, plugins, providers, splitTargets) { - return { - totalFixtures: fixtures.length, - passedFixtures: fixtures.filter(fixture => fixture.status === "pass").length, - failedFixtures: fixtures.filter(fixture => fixture.status !== "pass").length, - totalPlugins: plugins.length, - healthyPlugins: plugins.filter(plugin => ["active", "incubating", "external-owner-planned"].includes(plugin.status)).length, - failedPlugins: plugins.filter(plugin => !["active", "incubating", "external-owner-planned"].includes(plugin.status)).length, - totalProviders: providers.length, - advertisedProviders: providers.filter(provider => provider.status === "advertised").length, - failedProviders: providers.filter(provider => provider.status !== "advertised").length, - totalRuntimes: runtimes.length, - activeRuntimes: runtimes.filter(runtime => runtime.status === "active").length, - pendingRuntimes: runtimes.filter(runtime => runtime.status !== "active").length, - totalSplitTargets: splitTargets.length, - readySplitTargets: splitTargets.filter(target => target.status === "ready-to-split").length, - graduatedSplitTargets: splitTargets.filter(target => target.status === "graduated").length, - }; -} - -function buildConformanceExport(report) { - const packs = (report.conformancePacks || []).map(pack => { - const fixtures = report.fixtures - .filter(fixture => fixture.pack === pack.id) - .map(fixture => ({ - fixtureId: fixture.id, - title: fixture.title, - ownerRepo: fixture.ownerRepo, - status: fixture.status, - surface: fixture.surface, - metadataPath: fixture.metadataPath, - purpose: fixture.metadata?.purpose || "", - exitCriteria: fixture.metadata?.exitCriteria || "", - asserts: fixture.metadata?.asserts || [], - requiredPlugins: fixture.requiredPlugins || [], - optionalNestedPlugins: fixture.optionalNestedPlugins || fixture.metadata?.optionalNestedPlugins || [], - })); - const runtimeTargets = pack.id === "runtime" - ? (report.runtimes || []).map(runtime => ({ - runtimeId: runtime.id, - title: runtime.title, - status: runtime.status, - ownerRepo: runtime.ownerRepo, - splitTarget: runtime.splitTarget, - repoRole: runtime.repoRole, - supportedFeatures: runtime.supportedFeatures, - supportedPlugins: runtime.supportedPlugins, - unsupportedPlugins: runtime.unsupportedPlugins, - worldSurfaceLowering: runtime.worldSurfaceLowering, - requiredFixtures: runtime.requiredFixtures, - pluginFixtures: runtime.pluginFixtures, - capabilityManifestPath: runtime.capabilityManifest?.manifestPath || "", - lifecycleManifestPath: runtime.lifecycleManifest?.manifestPath || "", - commandTransportSchema: runtimeCommandTransportSchema(runtime), - captureStatus: runtime.capture?.status || "", - captureArtifacts: runtime.capture?.artifacts || [], - captureArtifactErrors: runtime.captureArtifactErrors || [], - lifecycle: runtime.lifecycle, - lifecycleErrors: runtime.lifecycleErrors || [], - missingEvidence: [ - ...runtime.missingPaths, - ...runtime.missingRequiredFixtures.map(id => `fixture:${id}`), - ...runtime.missingRequiredFeatures.map(id => `feature:${id}`), - ...runtime.commandTransportSmokeErrors.map(id => `command-smoke:${id}`), - ...runtime.capabilityManifestErrors.map(id => `runtime-capability:${id}`), - ...runtime.lifecycleErrors.map(id => `runtime-lifecycle:${id}`), - ...runtime.captureArtifactErrors.map(id => `runtime-capture:${id}`), - ...runtime.localProviderCatalogErrors.map(id => `local-provider-catalog:${id}`), - ], - })) - : []; - - return { - id: pack.id, - ownerRepo: pack.ownerRepo || "", - exitCriteria: pack.exitCriteria || "", - fixtures, - ...(runtimeTargets.length ? { runtimeTargets } : {}), - }; - }); - - return { - schema: "gamecult.eve.conformance_export.v1", - generatedAt: report.generatedAt, - sourceManifest: report.manifest, - boundaryRule: report.repoStrategy.boundaryRule || "", - incubationPolicy: report.repoStrategy.incubationPolicy || "", - schemaCatalog: buildSchemaCatalog(), - conformanceHandoffPath: report.repoStrategy.conformanceHandoffPath || "", - conformanceHandoffExportPath: makeHandoffExportPath("conformance", "EveConformance", report.repoStrategy.conformanceHandoffPath || ""), - packs, - capabilityMatrix: buildCapabilityMatrix(report), - runtimePluginProjectionCoverage: collectRuntimePluginProjectionCoverage(report), - runtimePluginProjectionGaps: collectRuntimePluginProjectionGaps(report), - providerRuntimePluginProjectionCoverage: collectProviderRuntimePluginProjectionCoverage(report), - localProviderCatalogs: collectLocalProviderCatalogs(report), - interactiveWorldSurfaces: collectInteractiveWorldSurfaces(report), - worldSurfaceLoweringCoverage: collectWorldSurfaceLoweringCoverage(report), - commandBoundaryCoverage: collectCommandBoundaryCoverage(report), - screenshotComparisonMetrics: collectScreenshotComparisonMetrics(report), - runtimeCaptureProbeCoverage: collectRuntimeCaptureProbeCoverage(report), - worldSurfaceLoweringGaps: collectWorldSurfaceLoweringGaps(report), - splitTargetBlockers: collectSplitTargetBlockers(report), - splitHandoffMoveCoverage: collectSplitHandoffMoveCoverage(report), - pluginHandoffMoveCoverage: collectPluginHandoffMoveCoverage(report), - providerHandoffMoveCoverage: collectProviderHandoffMoveCoverage(report), - pluginAbiOperationCoverage: collectPluginAbiOperationCoverage(report), - providerPluginRequirementCoverage: collectProviderPluginRequirementCoverage(report), - capabilityGaps: collectCapabilityGaps(report), - plugins: (report.plugins || []).map(plugin => ({ - pluginId: plugin.pluginId, - status: plugin.status, - ownerRepo: plugin.ownerRepo, - splitTarget: plugin.splitTarget, - graduationTrigger: plugin.graduationTrigger, - manifestPath: plugin.manifestPath, - advertisementPath: plugin.advertisementPath, - handoffPath: plugin.handoffPath, - handoffErrors: plugin.handoffErrors || [], - handoffExportPath: makeHandoffExportPath("plugin", plugin.pluginId, plugin.handoffPath), - abiFixturePath: plugin.abiFixturePath, - abiOperations: plugin.abiOperations || [], - abiOperationContracts: plugin.abiOperationContracts || [], - runtimeBoundary: plugin.runtimeBoundary, - optionalPlugins: plugin.optionalPlugins || [], - capabilities: plugin.capabilities, - })), - providers: (report.providers || []).map(provider => ({ - providerId: provider.providerId, - status: provider.status, - ownerRepo: provider.ownerRepo, - advertisementPath: provider.advertisementPath, - scenarioPath: provider.scenarioPath, - handoffPath: provider.handoffPath, - handoffErrors: provider.handoffErrors || [], - handoffExportPath: makeHandoffExportPath("provider", provider.providerId, provider.handoffPath), - scenarioId: provider.scenarioId, - receiptStates: provider.scenarioReceiptStates || [], - surfaces: provider.surfaceIds, - surfaceKinds: provider.surfaceKinds, - surfaceContracts: provider.surfaceContracts, - commands: provider.commandIds, - pluginRequirements: provider.pluginRequirements, - })), - runtimes: (report.runtimes || []).map(runtime => ({ - runtimeId: runtime.id, - status: runtime.status, - ownerRepo: runtime.ownerRepo, - splitTarget: runtime.splitTarget, - supportedFeatures: runtime.supportedFeatures, - supportedPlugins: runtime.supportedPlugins, - unsupportedPlugins: runtime.unsupportedPlugins, - capabilityManifestPath: runtime.capabilityManifest?.manifestPath || "", - capabilityManifestErrors: runtime.capabilityManifestErrors || [], - lifecycleManifestPath: runtime.lifecycleManifest?.manifestPath || "", - lifecycleErrors: runtime.lifecycleErrors || [], - splitHandoffPath: runtime.splitHandoffPath || "", - splitHandoffErrors: runtime.splitHandoffErrors || [], - splitHandoffExportPath: makeHandoffExportPath("runtime", runtime.id, runtime.splitHandoffPath || ""), - commandTransportSchema: runtimeCommandTransportSchema(runtime), - captureStatus: runtime.capture?.status || "", - captureArtifacts: runtime.capture?.artifacts || [], - captureArtifactErrors: runtime.captureArtifactErrors || [], - ...(runtime.lifecycle ? { lifecycle: runtime.lifecycle } : {}), - worldSurfaceLowering: runtime.worldSurfaceLowering || [], - })), - splitTargets: report.splitTargets || [], - }; -} - -function buildSchemaCatalog() { - return Object.entries(manifest.schemas || {}) - .map(([schemaId, schemaPath]) => ({ - schemaId, - path: schemaPath, - exportPath: `schemas/${path.basename(schemaPath)}`, - })) - .sort((left, right) => left.schemaId.localeCompare(right.schemaId)); -} - -function collectLocalProviderCatalogs(report) { - return (report.runtimes || []) - .filter(runtime => runtime.localProviderCatalog?.path) - .map(runtime => { - const catalogPath = runtime.localProviderCatalog.path; - let catalog = {}; - try { - catalog = JSON.parse(readFileSync(path.join(repoRoot, catalogPath), "utf8")); - } catch { - catalog = {}; - } - const providers = Array.isArray(catalog.providers) ? catalog.providers : []; - const catalogDirectory = path.posix.dirname(normalizePath(catalogPath)); - return { - runtimeId: runtime.id, - ownerRepo: runtime.ownerRepo || "", - catalogPath, - catalogExportPath: makeLocalProviderCatalogExportPath(runtime.id, catalogPath), - schema: catalog.schema || "", - purpose: catalog.purpose || "", - status: (runtime.localProviderCatalogErrors || []).length ? "invalid" : "valid", - providerIds: providers.map(provider => provider.providerId).filter(Boolean).sort(), - advertisementPaths: providers - .map(provider => resolveCatalogPath(catalogDirectory, provider.advertisement || "")) - .filter(Boolean) - .sort(), - surfaceCount: providers.reduce((count, provider) => count + (Array.isArray(provider.surfaces) ? provider.surfaces.length : 0), 0), - errors: runtime.localProviderCatalogErrors || [], - }; - }) - .sort((left, right) => left.runtimeId.localeCompare(right.runtimeId)); -} - -function makeLocalProviderCatalogExportPath(runtimeId, catalogPath) { - if (!runtimeId || !catalogPath) return ""; - return `catalogs/${runtimeId}/${path.basename(catalogPath)}`; -} - -function buildCapabilityMatrix(report) { - const packs = (report.conformancePacks || []).map(pack => { - const fixtures = (report.fixtures || []).filter(fixture => fixture.pack === pack.id); - return { - packId: pack.id, - ownerRepo: pack.ownerRepo || "", - fixtureCount: fixtures.length, - passedFixtures: fixtures.filter(fixture => fixture.status === "pass").length, - failedFixtures: fixtures.filter(fixture => fixture.status !== "pass").length, - exitCriteria: pack.exitCriteria || "", - }; - }); - - const plugins = (report.plugins || []).map(plugin => ({ - pluginId: plugin.pluginId, - ownerRepo: plugin.ownerRepo, - status: plugin.status, - capabilities: plugin.capabilities || [], - abiOperations: plugin.abiOperations || [], - handoffExportPath: makeHandoffExportPath("plugin", plugin.pluginId, plugin.handoffPath), - })); - - const providers = (report.providers || []).map(provider => ({ - providerId: provider.providerId, - ownerRepo: provider.ownerRepo, - status: provider.status, - surfaces: provider.surfaceIds || [], - surfaceKinds: provider.surfaceKinds || [], - surfaceContracts: provider.surfaceContracts || [], - commands: provider.commandIds || [], - receiptStates: provider.scenarioReceiptStates || [], - handoffExportPath: makeHandoffExportPath("provider", provider.providerId, provider.handoffPath), - })); - - const runtimes = (report.runtimes || []).map(runtime => ({ - runtimeId: runtime.id, - ownerRepo: runtime.ownerRepo, - splitTarget: runtime.splitTarget, - status: runtime.status, - supportedFeatures: runtime.supportedFeatures || [], - supportedPlugins: runtime.supportedPlugins || [], - unsupportedPlugins: runtime.unsupportedPlugins || [], - worldSurfaceLowering: runtime.worldSurfaceLowering || [], - commandTransportSchema: runtimeCommandTransportSchema(runtime), - captureStatus: runtime.capture?.status || "", - captureArtifacts: runtime.capture?.artifacts || [], - splitHandoffExportPath: makeHandoffExportPath("runtime", runtime.id, runtime.splitHandoffPath || ""), - })); - - const splitTargets = (report.splitTargets || []).map(target => ({ - id: target.id, - ownerRepo: target.ownerRepo, - status: target.status, - blockerCount: (target.blockers || []).length, - passedProofs: (target.proofs || []).filter(proof => proof.status === "passed").length, - })); - - return { - schema: "gamecult.eve.capability_matrix.v1", - generatedAt: report.generatedAt, - summary: report.summary || {}, - capabilityGapCount: collectCapabilityGaps(report).length, - handoffs: { - conformance: report.repoStrategy.conformanceHandoffPath ? 1 : 0, - plugins: plugins.filter(plugin => plugin.handoffExportPath).length, - providers: providers.filter(provider => provider.handoffExportPath).length, - runtimes: runtimes.filter(runtime => runtime.splitHandoffExportPath).length, - }, - packs, - plugins, - providers, - runtimes, - splitTargets, - }; -} - -function collectCapabilityGaps(report) { - const gaps = []; - const addGap = ({ kind, ownerRepo, subjectId, gap, severity = "gap", detail = "" }) => { - if (!kind || !subjectId || !gap) return; - gaps.push({ - kind, - ownerRepo: ownerRepo || "", - subjectId, - gap, - severity, - detail, - }); - }; - - for (const plugin of report.plugins || []) { - for (const error of [ - ...(plugin.missingPaths || []).map(id => `path:${id}`), - ...(plugin.missingRequiredFixtures || []).map(id => `fixture:${id}`), - ...(plugin.missingIncubationFields || []).map(id => `metadata:${id}`), - ...(plugin.schemaErrors || []).map(id => `schema:${id}`), - ...(plugin.advertisementErrors || []).map(id => `advertisement:${id}`), - ...(plugin.runtimeBoundaryErrors || []).map(id => `runtime-boundary:${id}`), - ...(plugin.abiErrors || []).map(id => `abi:${id}`), - ...(plugin.handoffErrors || []).map(id => `handoff:${id}`), - ]) { - addGap({ - kind: "plugin", - ownerRepo: plugin.ownerRepo, - subjectId: plugin.pluginId, - gap: error, - severity: "blocker", - }); - } - } - - for (const provider of report.providers || []) { - for (const error of [ - ...(provider.missingPaths || []).map(id => `path:${id}`), - ...(provider.missingRequiredFixtures || []).map(id => `fixture:${id}`), - ...(provider.missingIncubationFields || []).map(id => `metadata:${id}`), - ...(provider.advertisementErrors || []).map(id => `advertisement:${id}`), - ...(provider.scenarioErrors || []).map(id => `scenario:${id}`), - ...(provider.missingSchemas || []).map(id => `schema:${id}`), - ...(provider.missingSurfaces || []).map(id => `surface:${id}`), - ...(provider.missingSurfaceKinds || []).map(id => `surface-kind:${id}`), - ...(provider.missingCommands || []).map(id => `command:${id}`), - ...(provider.pluginRequirementErrors || []).map(id => `plugin:${id}`), - ...(provider.handoffErrors || []).map(id => `handoff:${id}`), - ]) { - addGap({ - kind: "provider", - ownerRepo: provider.ownerRepo, - subjectId: provider.providerId, - gap: error, - severity: "blocker", - }); - } - } - - for (const runtime of report.runtimes || []) { - if (runtime.capture?.status === "missing") { - addGap({ - kind: "runtime", - ownerRepo: runtime.ownerRepo, - subjectId: runtime.id, - gap: "capture:missing", - severity: runtime.kind === "active" ? "blocker" : "activation-blocker", - }); - } - for (const error of [ - ...(runtime.missingPaths || []).map(id => `path:${id}`), - ...(runtime.missingExternalPaths || []).map(id => `external:${id}`), - ...(runtime.missingSourceSymbols || []).map(id => `source:${id}`), - ...(runtime.missingExternalSourceSymbols || []).map(id => `external-source:${id}`), - ...(runtime.missingRequiredFixtures || []).map(id => `fixture:${id}`), - ...(runtime.missingRequiredFeatures || []).map(id => `feature:${id}`), - ...(runtime.commandTransportSmokeErrors || []).map(id => `command-smoke:${id}`), - ...(runtime.capabilityManifestErrors || []).map(id => `runtime-capability:${id}`), - ...(runtime.lifecycleErrors || []).map(id => `runtime-lifecycle:${id}`), - ...(runtime.captureArtifactErrors || []).map(id => `runtime-capture:${id}`), - ...(runtime.splitHandoffErrors || []).map(id => `runtime-split-handoff:${id}`), - ...(runtime.localProviderCatalogErrors || []).map(id => `local-provider-catalog:${id}`), - ...(runtime.pluginCapabilityGaps || []).map(id => `plugin-capability:${id}`), - ]) { - addGap({ - kind: "runtime", - ownerRepo: runtime.ownerRepo, - subjectId: runtime.id, - gap: error, - severity: "blocker", - }); - } - for (const note of runtime.unsupportedPluginNotes || []) { - addGap({ - kind: "runtime", - ownerRepo: resolveRuntimeProjectionOwnerRepo(runtime), - subjectId: runtime.id, - gap: `unsupported-plugin:${note}`, - severity: "declared-gap", - }); - } - } - - for (const gap of collectWorldSurfaceLoweringGaps(report)) { - addGap({ - kind: "runtime", - ownerRepo: gap.ownerRepo, - subjectId: gap.runtimeId, - gap: `provider:${gap.providerId}:surface:${gap.surfaceId}:world-lowering-target:${gap.targetId}:missing-runtime`, - severity: gap.severity, - detail: gap.detail, - }); - } - - for (const target of report.splitTargets || []) { - for (const blocker of target.blockers || []) { - addGap({ - kind: "split-target", - ownerRepo: target.ownerRepo, - subjectId: target.id, - gap: blocker, - severity: "blocker", - }); - } - } - - for (const error of report.repoStrategy?.conformanceHandoffErrors || []) { - addGap({ - kind: "conformance", - ownerRepo: "EveConformance", - subjectId: "EveConformance", - gap: `handoff:${error}`, - severity: "blocker", - }); - } - - return gaps; -} - -function collectRuntimePluginProjectionGaps(report) { - const gaps = []; - for (const runtime of report.runtimes || []) { - for (const plugin of runtime.unsupportedPlugins || []) { - if (!plugin.pluginId) continue; - gaps.push({ - runtimeId: runtime.id, - runtimeStatus: runtime.status || "", - ownerRepo: resolveRuntimeProjectionOwnerRepo(runtime), - splitTarget: runtime.splitTarget || "", - pluginId: plugin.pluginId, - severity: "declared-gap", - reason: plugin.reason || "unsupported", - requiredFixtures: runtime.requiredFixtures || [], - pluginFixtures: runtime.pluginFixtures || [], - }); - } - } - return gaps; -} - -function collectRuntimePluginProjectionCoverage(report) { - const pluginById = new Map((report.plugins || []).map(plugin => [plugin.pluginId, plugin])); - const coverage = []; - for (const runtime of report.runtimes || []) { - for (const plugin of runtime.supportedPlugins || []) { - if (!plugin.pluginId) continue; - const pluginRecord = pluginById.get(plugin.pluginId); - coverage.push({ - runtimeId: runtime.id, - runtimeStatus: runtime.status || "", - runtimeOwnerRepo: resolveRuntimeProjectionOwnerRepo(runtime), - splitTarget: runtime.splitTarget || "", - pluginId: plugin.pluginId, - pluginOwnerRepo: pluginRecord?.ownerRepo || "", - pluginStatus: pluginRecord?.status || "", - status: "supported", - severity: "ok", - capabilities: plugin.capabilities || [], - reason: "", - requiredFixtures: runtime.requiredFixtures || [], - pluginFixtures: runtime.pluginFixtures || [], - }); - } - for (const plugin of runtime.unsupportedPlugins || []) { - if (!plugin.pluginId) continue; - const pluginRecord = pluginById.get(plugin.pluginId); - coverage.push({ - runtimeId: runtime.id, - runtimeStatus: runtime.status || "", - runtimeOwnerRepo: resolveRuntimeProjectionOwnerRepo(runtime), - splitTarget: runtime.splitTarget || "", - pluginId: plugin.pluginId, - pluginOwnerRepo: pluginRecord?.ownerRepo || "", - pluginStatus: pluginRecord?.status || "", - status: "unsupported", - severity: "declared-gap", - capabilities: [], - reason: plugin.reason || "unsupported", - requiredFixtures: runtime.requiredFixtures || [], - pluginFixtures: runtime.pluginFixtures || [], - }); - } - } - return coverage.sort((left, right) => - `${left.runtimeId}:${left.pluginId}:${left.status}`.localeCompare(`${right.runtimeId}:${right.pluginId}:${right.status}`)); -} - -function collectProviderRuntimePluginProjectionCoverage(report) { - const runtimeProjectionByKey = new Map(); - for (const record of collectRuntimePluginProjectionCoverage(report)) { - runtimeProjectionByKey.set(`${record.runtimeId}:${record.pluginId}`, record); - } - const fixtureIdByPath = new Map((report.fixtures || []) - .map(fixture => [normalizePath(fixture.surface?.path || ""), fixture.id])); - const coverage = []; - for (const provider of report.providers || []) { - for (const requirement of provider.pluginRequirements || []) { - const fixtureId = fixtureIdByPath.get(normalizePath(requirement.surfaceKey || "")) || ""; - if (!fixtureId) continue; - const runtimes = (report.runtimes || []) - .filter(runtime => (runtime.pluginFixtures || []).includes(fixtureId)); - for (const runtime of runtimes) { - const projection = runtimeProjectionByKey.get(`${runtime.id}:${requirement.pluginId}`); - coverage.push(buildProviderRuntimePluginProjectionRecord(provider, requirement, runtime, projection, fixtureId)); - } - } - } - return coverage.sort((left, right) => - `${left.providerId}:${left.surfaceId}:${left.runtimeId}:${left.pluginId}` - .localeCompare(`${right.providerId}:${right.surfaceId}:${right.runtimeId}:${right.pluginId}`)); -} - -function buildProviderRuntimePluginProjectionRecord(provider, requirement, runtime, projection, fixtureId) { - const availability = requirement.availability || "required"; - const optional = availability !== "required"; - const runtimeCapabilities = new Set(projection?.capabilities || []); - const missingRuntimeRequiredCapabilities = (requirement.requiredCapabilities || []) - .filter(capability => !runtimeCapabilities.has(capability)); - const missingRuntimeOptionalCapabilities = (requirement.optionalCapabilities || []) - .filter(capability => projection?.status === "supported" && !runtimeCapabilities.has(capability)); - let status = optional ? "optional-supported" : "supported"; - if (!projection) { - status = optional ? "optional-missing-runtime-projection" : "missing-runtime-projection"; - } else if (projection.status !== "supported") { - status = optional ? "optional-unsupported-runtime-projection" : "unsupported-runtime-projection"; - } else if (missingRuntimeRequiredCapabilities.length) { - status = optional ? "optional-missing-runtime-capability" : "missing-runtime-capability"; - } else if (missingRuntimeOptionalCapabilities.length) { - status = "optional-missing-runtime-capability"; - } - - return { - providerId: provider.providerId, - providerOwnerRepo: provider.ownerRepo || "", - surfaceId: requirement.surfaceId || "", - surfaceKey: requirement.surfaceKey || "", - fixtureId, - runtimeId: runtime.id, - runtimeStatus: runtime.status || "", - runtimeOwnerRepo: resolveRuntimeProjectionOwnerRepo(runtime), - splitTarget: runtime.splitTarget || "", - pluginId: requirement.pluginId || "", - pluginOwnerRepo: projection?.pluginOwnerRepo || "", - availability, - status, - severity: status === "supported" || status === "optional-supported" - ? "ok" - : optional ? "degraded" : "blocker", - requiredCapabilities: requirement.requiredCapabilities || [], - optionalCapabilities: requirement.optionalCapabilities || [], - runtimeCapabilities: projection?.capabilities || [], - missingRuntimeRequiredCapabilities, - missingRuntimeOptionalCapabilities, - reason: projection?.reason || "", - }; -} - -function collectPluginAbiOperationCoverage(report) { - const requiredOperations = ["describe", "validate", "project", "lower", "measure", "apply"]; - const coverage = []; - for (const plugin of report.plugins || []) { - const operations = new Map((plugin.abiOperationContracts || []).map(contract => [contract.operation, contract])); - for (const operation of requiredOperations) { - const contract = operations.get(operation); - const input = contract?.input || {}; - const expect = contract?.expect || {}; - coverage.push({ - pluginId: plugin.pluginId, - ownerRepo: plugin.ownerRepo || "", - splitTarget: plugin.splitTarget || "", - status: contract ? "contracted" : "missing-contract", - operation, - abiFixturePath: plugin.abiFixturePath || "", - inputKeys: Object.keys(input).sort(), - expectKeys: Object.keys(expect).sort(), - projectionKind: expect.projectionKind || "", - loweringKind: expect.loweringKind || "", - measurementKind: expect.measurementKind || "", - commandEnvelope: expect.commandEnvelope || "", - receiptSchema: expect.receiptSchema || "", - preservesProviderAuthority: expect.preservesProviderAuthority === true, - }); - } - } - return coverage; -} - -function collectProviderPluginRequirementCoverage(report) { - const pluginById = new Map((report.plugins || []).map(plugin => [plugin.pluginId, plugin])); - const coverage = []; - for (const provider of report.providers || []) { - for (const requirement of provider.pluginRequirements || []) { - const plugin = pluginById.get(requirement.pluginId); - const pluginCapabilities = new Set(plugin?.capabilities || []); - const missingRequiredCapabilities = (requirement.requiredCapabilities || []) - .filter(capability => !pluginCapabilities.has(capability)); - const missingOptionalCapabilities = (requirement.optionalCapabilities || []) - .filter(capability => plugin && !pluginCapabilities.has(capability)); - const optional = requirement.availability && requirement.availability !== "required"; - coverage.push({ - providerId: provider.providerId, - providerOwnerRepo: provider.ownerRepo || "", - surfaceId: requirement.surfaceId || "", - pluginId: requirement.pluginId || "", - pluginOwnerRepo: plugin?.ownerRepo || "", - pluginStatus: plugin?.status || "missing", - versionRange: requirement.versionRange || "", - availability: requirement.availability || "required", - status: !plugin - ? optional ? "optional-missing-plugin" : "missing-plugin" - : missingRequiredCapabilities.length - ? optional ? "optional-missing-capability" : "missing-capability" - : optional ? "optional-satisfied" : "satisfied", - requiredCapabilities: requirement.requiredCapabilities || [], - optionalCapabilities: requirement.optionalCapabilities || [], - missingRequiredCapabilities, - missingOptionalCapabilities, - }); - } - } - return coverage; -} - -function collectScreenshotComparisonMetrics(report) { - const fixtureById = new Map((report.fixtures || []).map(fixture => [fixture.id, fixture])); - const metrics = []; - for (const runtime of report.runtimes || []) { - const fixtureIds = [...new Set([ - ...(runtime.requiredFixtures || []), - ...(runtime.pluginFixtures || []), - ])].sort(); - for (const fixtureId of fixtureIds) { - const fixture = fixtureById.get(fixtureId); - if (!fixture) continue; - metrics.push(buildStructureMetric(runtime, fixture)); - metrics.push(buildColorTokenMetric(runtime, fixture)); - metrics.push(buildBoundingBoxMetric(runtime, fixture)); - metrics.push(buildTextPresenceMetric(runtime, fixture)); - } - } - return metrics.sort((left, right) => - `${left.runtimeId}:${left.fixtureId}:${left.metricKind}`.localeCompare(`${right.runtimeId}:${right.fixtureId}:${right.metricKind}`)); -} - -function collectRuntimeCaptureProbeCoverage(report) { - return (report.runtimes || []) - .filter(runtime => (runtime.capture?.artifacts || []).length || runtime.lifecycle?.capture?.captureContract) - .map(runtime => buildRuntimeCaptureProbeRecord(runtime)) - .sort((left, right) => left.runtimeId.localeCompare(right.runtimeId)); -} - -function buildRuntimeCaptureProbeRecord(runtime) { - const capture = runtime.capture || {}; - const lifecycleCapture = runtime.lifecycle?.capture || {}; - const contract = lifecycleCapture.captureContract || {}; - const artifacts = Array.isArray(capture.artifacts) ? capture.artifacts : []; - const artifact = selectCaptureArtifactForContract(artifacts, contract) || artifacts[0] || {}; - const errors = runtime.captureArtifactErrors || []; - const contractArtifactKind = contract.artifactKind || ""; - const currentArtifactKind = artifact.kind || ""; - const hasContract = Boolean(contractArtifactKind || contract.captureKind || contract.requestSchema); - const artifactPresent = Boolean(currentArtifactKind && artifact.path); - const providerMatches = !contract.requiredProvider || artifact.providerId === contract.requiredProvider; - const surfaceMatches = !contract.requiredSurface || artifact.surfaceId === contract.requiredSurface; - const contractTargetMatches = providerMatches && surfaceMatches; - const contractArtifactPresent = hasContract && artifactPresent && contractTargetMatches && currentArtifactKind === contractArtifactKind && !errors.length; - const controlArtifactPresent = hasContract && artifactPresent && !contractTargetMatches && currentArtifactKind === contractArtifactKind && !errors.length; - const semanticArtifactOnly = hasContract && artifactPresent && currentArtifactKind !== contractArtifactKind && !errors.length; - - let status = "missing-capture-contract"; - let severity = "blocker"; - let detail = "Runtime has capture evidence but no lifecycle capture contract."; - if (contractArtifactPresent) { - status = "contract-artifact-present"; - severity = "info"; - detail = "Runtime capture artifact satisfies the lifecycle capture contract."; - } else if (controlArtifactPresent) { - status = "control-artifact-present-product-capture-pending"; - severity = "blocker"; - detail = `Runtime publishes a ${currentArtifactKind} control artifact for ${artifact.providerId || "another provider"}; ${contract.requiredProvider || "the required provider"} product capture remains owner-repo work.`; - } else if (semanticArtifactOnly) { - status = "semantic-artifact-present-capture-pending"; - severity = contractArtifactKind === "png" ? "blocker" : "gap"; - detail = `Runtime publishes ${currentArtifactKind} semantic evidence; ${contractArtifactKind || "contract"} capture remains owner-repo work.`; - } else if (hasContract && errors.length) { - status = "invalid-current-artifact"; - severity = "blocker"; - detail = "Runtime capture artifact is declared but invalid or missing."; - } else if (hasContract && !artifactPresent) { - status = "contract-artifact-missing"; - severity = "blocker"; - detail = "Runtime lifecycle capture contract exists, but no capture artifact is attached."; - } - - return { - runtimeId: runtime.id, - runtimeStatus: runtime.status, - runtimeOwnerRepo: runtime.ownerRepo || "", - splitTarget: runtime.splitTarget || "", - captureStatus: capture.status || "", - lifecycleStatus: lifecycleCapture.status || "", - captureOwnerRepo: contract.ownerRepo || lifecycleCapture.ownerRepo || runtime.splitTarget || runtime.ownerRepo || "", - captureKind: contract.captureKind || "", - contractArtifactKind, - contractRequestSchema: contract.requestSchema || "", - requestBuilder: contract.requestBuilder || "", - advertisementPath: contract.advertisementPath || "", - artifactPattern: contract.artifactPattern || "", - conformanceAttachment: contract.conformanceAttachment || "", - requiredProvider: contract.requiredProvider || "", - requiredSurface: contract.requiredSurface || "", - additionalProviderSurfaces: contract.additionalProviderSurfaces || [], - authority: contract.authority || "", - currentArtifactKind, - currentArtifactSchema: artifact.schema || "", - currentArtifactPath: artifact.path || "", - requestPath: artifact.requestPath || "", - sourceSurfacePath: artifact.sourceSurfacePath || "", - pendingProofs: lifecycleCapture.pendingProofs || [], - errors, - status, - severity, - detail, - }; -} - -function selectCaptureArtifactForContract(artifacts, contract) { - if (!artifacts.length) return null; - const requiredProvider = contract.requiredProvider || ""; - const requiredSurface = contract.requiredSurface || ""; - const contractArtifactKind = contract.artifactKind || ""; - return artifacts.find(artifact => - (!requiredProvider || artifact.providerId === requiredProvider) - && (!requiredSurface || artifact.surfaceId === requiredSurface) - && (!contractArtifactKind || artifact.kind === contractArtifactKind)) - || artifacts.find(artifact => - (!requiredProvider || artifact.providerId === requiredProvider) - && (!requiredSurface || artifact.surfaceId === requiredSurface)) - || null; -} - -function buildStructureMetric(runtime, fixture) { - const checks = (fixture.checks || []).filter(check => - check.id.startsWith("component:") - || check.id.startsWith("minimum:") - || check.id.startsWith("controlPart:") - || check.id.startsWith("skin:") - || check.id.startsWith("embedded:")); - return buildScreenshotMetric(runtime, fixture, { - metricKind: "structure", - status: checks.length ? checks.every(check => check.pass) ? "pass" : "fail" : "not-required", - score: ratio(checks.filter(check => check.pass).length, checks.length), - expected: checks.map(check => check.id).sort(), - actual: { - kindCounts: fixture.kindCounts || {}, - embeddedDocuments: fixture.embeddedDocuments || [], - controlParts: fixture.controlParts || [], - controlSkins: fixture.controlSkins || [], - }, - detail: "Compares authored surface structure and slot/control anatomy, not byte-identical pixels.", - }); -} - -function buildColorTokenMetric(runtime, fixture) { - const checks = (fixture.checks || []).filter(check => check.id.startsWith("styleToken:")); - return buildScreenshotMetric(runtime, fixture, { - metricKind: "color-tokens", - status: checks.length ? checks.every(check => check.pass) ? "pass" : "fail" : "not-required", - score: ratio(checks.filter(check => check.pass).length, checks.length), - expected: checks.map(check => check.id.replace("styleToken:", "")).sort(), - actual: fixture.styleTokens || [], - detail: "Compares declared CultUI style token presence before runtime-specific color sampling exists.", - }); -} - -function buildBoundingBoxMetric(runtime, fixture) { - const layoutProbe = readRuntimeLayoutProbe(runtime, fixture.id); - if (layoutProbe.config) { - if (layoutProbe.document && !layoutProbe.errors.length) { - const measured = Number(layoutProbe.document.summary?.measuredNodeCount || 0); - const total = Number(layoutProbe.document.summary?.nodeCount || 0); - return buildScreenshotMetric(runtime, fixture, { - metricKind: "bounding-boxes", - status: measured > 0 ? "pass" : "fail", - score: ratio(measured, total), - expected: { - schema: layoutProbe.config.schema || "", - path: layoutProbe.config.path || "", - providerId: layoutProbe.config.providerId || "", - surfaceId: layoutProbe.config.surfaceId || "", - }, - actual: { - nodeCount: total, - measuredNodeCount: measured, - zeroAreaCount: Number(layoutProbe.document.summary?.zeroAreaCount || 0), - path: layoutProbe.config.path || "", - }, - evidenceLayer: "runtime-layout-probe", - detail: "Compares runtime-owned DOM bounding boxes from the web reference layout probe.", - }); - } - return buildScreenshotMetric(runtime, fixture, { - metricKind: "bounding-boxes", - status: "fail", - score: 0, - expected: { - schema: layoutProbe.config.schema || "", - path: layoutProbe.config.path || "", - }, - actual: { - errors: layoutProbe.errors, - }, - evidenceLayer: "runtime-layout-probe", - detail: "Declared runtime layout probe exists, but the artifact is missing or invalid.", - }); - } - - const captureStatus = runtime.capture?.status || ""; - if (captureStatus === "json-grid") { - const artifact = (runtime.capture?.artifacts || []).find(candidate => candidate.kind === "json-grid"); - const errors = runtime.captureArtifactErrors || []; - return buildScreenshotMetric(runtime, fixture, { - metricKind: "bounding-boxes", - status: errors.length ? "fail" : "terminal-grid-capture", - score: errors.length ? 0 : 1, - expected: { - source: "runtime terminal grid capture", - schema: artifact?.schema || "gamecult.eve.tui_grid.v1", - path: artifact?.path || "", - }, - actual: { - captureStatus, - artifactKind: artifact?.kind || "", - errors, - }, - evidenceLayer: "runtime-terminal-grid-capture", - detail: errors.length - ? "Declared terminal grid capture artifact is missing or invalid." - : "Terminal grid capture is present; graphical bounding boxes are not a TUI invariant.", - }); - } - const runtimeHasCaptureBody = ["chrome-headless", "ssh-png", "adb-png", "golden", "ssh-golden"].includes(captureStatus); - let status = "pass"; - let score = 1; - let detail = "Authored layout boxes are present in the surface evidence."; - if (!fixture.layoutBoxCount) { - status = runtimeHasCaptureBody ? "pending-runtime-probe" : "pending-capture"; - score = 0; - detail = runtimeHasCaptureBody - ? "Runtime capture exists, but no exported layout/bounding-box probe is attached yet." - : "Bounding-box comparison is blocked until this runtime publishes capture or layout probe artifacts."; - } - return buildScreenshotMetric(runtime, fixture, { - metricKind: "bounding-boxes", - status, - score, - expected: { - source: runtimeHasCaptureBody ? "runtime layout/capture probe" : "runtime capture artifact", - }, - actual: { - layoutBoxCount: fixture.layoutBoxCount || 0, - captureStatus, - }, - detail, - }); -} - -function buildTextPresenceMetric(runtime, fixture) { - const expectedText = (fixture.textNodeCount || 0) > 0; - const status = expectedText - ? fixture.nonEmptyTextNodeCount > 0 ? "pass" : "fail" - : "not-required"; - return buildScreenshotMetric(runtime, fixture, { - metricKind: "text-presence", - status, - score: expectedText ? ratio(fixture.nonEmptyTextNodeCount || 0, fixture.textNodeCount || 1) : 1, - expected: { - textNodes: fixture.textNodeCount || 0, - }, - actual: { - nonEmptyTextNodes: fixture.nonEmptyTextNodeCount || 0, - }, - detail: "Compares authored text presence before runtime OCR/text-layer extraction exists.", - }); -} - -function buildScreenshotMetric(runtime, fixture, metric) { - return { - runtimeId: runtime.id, - runtimeStatus: runtime.status || "", - runtimeOwnerRepo: resolveRuntimeProjectionOwnerRepo(runtime), - splitTarget: runtime.splitTarget || "", - fixtureId: fixture.id, - fixturePack: fixture.pack || "", - fixtureOwnerRepo: fixture.ownerRepo || "", - captureStatus: runtime.capture?.status || "", - metricKind: metric.metricKind, - status: metric.status, - score: Number(metric.score.toFixed(3)), - expected: metric.expected, - actual: metric.actual, - evidenceLayer: metric.evidenceLayer || "surface-structure", - detail: metric.detail, - }; -} - -function ratio(numerator, denominator) { - return denominator ? Math.max(0, Math.min(1, numerator / denominator)) : 1; -} - -function readRuntimeLayoutProbe(runtime, fixtureId) { - const config = (runtime.capture?.layoutProbes || []).find(candidate => candidate.fixtureId === fixtureId); - if (!config) return { config: null, document: null, errors: [] }; - const errors = []; - const probePath = config.path || ""; - const absoluteProbePath = path.join(repoRoot, probePath); - if (!probePath || !existsSync(absoluteProbePath)) { - return { config, document: null, errors: [`${probePath || "layoutProbe.path"}:missing`] }; - } - let document; - try { - document = JSON.parse(readFileSync(absoluteProbePath, "utf8")); - } catch (error) { - return { config, document: null, errors: [`${probePath}:invalid-json:${error instanceof Error ? error.message : String(error)}`] }; - } - if (config.schema && document.schema !== config.schema) errors.push(`${probePath}:schema:expected ${config.schema} got ${document.schema || ""}`); - if (config.providerId && document.providerId !== config.providerId) errors.push(`${probePath}:providerId:expected ${config.providerId} got ${document.providerId || ""}`); - if (config.surfaceId && document.surfaceId !== config.surfaceId) errors.push(`${probePath}:surfaceId:expected ${config.surfaceId} got ${document.surfaceId || ""}`); - if (document.fixtureId !== fixtureId) errors.push(`${probePath}:fixtureId:expected ${fixtureId} got ${document.fixtureId || ""}`); - const schemaPath = config.schema ? manifest.schemas?.[config.schema] : ""; - if (schemaPath) { - try { - const schema = JSON.parse(readFileSync(path.join(repoRoot, schemaPath), "utf8")); - errors.push(...validateSchemaSubset(schema, document, `layoutProbe:${fixtureId}`)); - } catch (error) { - errors.push(`${schemaPath}:unreadable:${error instanceof Error ? error.message : String(error)}`); - } - } - return { config, document, errors }; -} - -function validateRuntimeCaptureArtifacts(runtime, capture) { - const artifacts = Array.isArray(capture?.artifacts) ? capture.artifacts : []; - const errors = []; - for (const artifact of artifacts) { - const artifactPath = artifact.path || ""; - if (!artifactPath) { - errors.push("artifact.path:missing"); - continue; - } - const absoluteArtifactPath = resolveRuntimeOwnedPath(runtime, artifactPath); - if (!existsSync(absoluteArtifactPath)) { - errors.push(`${artifactPath}:missing`); - continue; - } - if (artifact.kind === "png") { - continue; - } - let document; - try { - document = JSON.parse(readFileSync(absoluteArtifactPath, "utf8")); - } catch (error) { - errors.push(`${artifactPath}:invalid-json:${error instanceof Error ? error.message : String(error)}`); - continue; - } - for (const [field, expected] of [ - ["schema", artifact.schema], - ["runtimeId", artifact.runtimeId], - ["providerId", artifact.providerId], - ["surfaceId", artifact.surfaceId], - ]) { - if (expected && document[field] !== expected) { - errors.push(`${artifactPath}:${field}:expected ${expected} got ${document[field] || ""}`); - } - } - if (artifact.schema && manifest.schemas?.[artifact.schema]) { - try { - const schema = JSON.parse(readFileSync(path.join(repoRoot, manifest.schemas[artifact.schema]), "utf8")); - errors.push(...validateSchemaSubset(schema, document, `captureArtifact:${artifactPath}`)); - } catch (error) { - errors.push(`${manifest.schemas[artifact.schema]}:unreadable:${error instanceof Error ? error.message : String(error)}`); - } - } - for (const linkedPath of [artifact.sourceSurfacePath, artifact.requestPath].filter(Boolean)) { - if (!existsSync(resolveRuntimeOwnedPath(runtime, linkedPath))) { - errors.push(`${artifactPath}:linked-path:${linkedPath}:missing`); - } - } - } - return errors; -} - -function collectSplitTargetBlockers(report) { - return (report.splitTargets || []).flatMap(target => target.blockerRecords || []); -} - -function collectSplitHandoffMoveCoverage(report) { - const records = []; - for (const runtime of report.runtimes || []) { - if (!runtime.splitHandoffPath) continue; - const absoluteHandoffPath = path.join(repoRoot, runtime.splitHandoffPath); - if (!existsSync(absoluteHandoffPath)) continue; - - let handoff; - try { - handoff = JSON.parse(readFileSync(absoluteHandoffPath, "utf8")); - } catch { - continue; - } - - for (const moveSet of handoff.moveSets || []) { - const currentPaths = Array.isArray(moveSet.currentPaths) ? moveSet.currentPaths : []; - const observedProviderPaths = Array.isArray(moveSet.observedProviderPaths) ? moveSet.observedProviderPaths : []; - for (const sourcePath of currentPaths) { - const absoluteSourcePath = path.join(repoRoot, sourcePath); - records.push(buildSplitHandoffMoveRecord(runtime, handoff, moveSet, { - pathKind: "current", - sourcePath, - pathExists: existsSync(absoluteSourcePath), - })); - } - for (const sourcePath of observedProviderPaths) { - records.push(buildSplitHandoffMoveRecord(runtime, handoff, moveSet, { - pathKind: "observed-provider", - sourcePath, - pathExists: existsSync(sourcePath), - })); - } - if (!currentPaths.length && !observedProviderPaths.length) { - records.push(buildSplitHandoffMoveRecord(runtime, handoff, moveSet, { - pathKind: "replacement-required", - sourcePath: "(none)", - pathExists: false, - })); - } - } - } - return records.sort((left, right) => - `${left.splitTarget}:${left.runtimeId}:${left.moveSetId}:${left.pathKind}:${left.sourcePath}` - .localeCompare(`${right.splitTarget}:${right.runtimeId}:${right.moveSetId}:${right.pathKind}:${right.sourcePath}`)); -} - -function buildSplitHandoffMoveRecord(runtime, handoff, moveSet, source) { - const status = source.pathKind === "replacement-required" - ? "no-source-paths" - : source.pathExists ? "exists" : "missing"; - return { - splitTarget: handoff.splitTarget || runtime.splitTarget || "", - runtimeId: handoff.runtimeId || runtime.id || "", - runtimeOwnerRepo: handoff.ownerRepo || resolveRuntimeProjectionOwnerRepo(runtime), - handoffPath: runtime.splitHandoffPath || "", - handoffExportPath: makeHandoffExportPath("runtime", runtime.id, runtime.splitHandoffPath || ""), - moveSetId: moveSet.id || "", - stage: moveSet.stage || "", - destinationOwner: moveSet.destinationOwner || "", - replacementProof: moveSet.replacementProof || "", - pathKind: source.pathKind, - sourcePath: normalizePath(source.sourcePath), - pathExists: source.pathExists, - status, - severity: "blocker", - }; -} - -function collectPluginHandoffMoveCoverage(report) { - const records = []; - for (const plugin of report.plugins || []) { - if (!plugin.handoffPath) continue; - const absoluteHandoffPath = path.join(repoRoot, plugin.handoffPath); - if (!existsSync(absoluteHandoffPath)) continue; - - let handoff; - try { - handoff = JSON.parse(readFileSync(absoluteHandoffPath, "utf8")); - } catch { - continue; - } - - for (const moveSet of handoff.moveSets || []) { - const currentPaths = Array.isArray(moveSet.currentPaths) ? moveSet.currentPaths : []; - for (const sourcePath of currentPaths) { - const absoluteSourcePath = path.join(repoRoot, sourcePath); - records.push(buildPluginHandoffMoveRecord(plugin, handoff, moveSet, { - pathKind: "current", - sourcePath, - pathExists: existsSync(absoluteSourcePath), - })); - } - if (!currentPaths.length) { - records.push(buildPluginHandoffMoveRecord(plugin, handoff, moveSet, { - pathKind: "replacement-required", - sourcePath: "(none)", - pathExists: false, - })); - } - } - } - return records.sort((left, right) => - `${left.pluginId}:${left.moveSetId}:${left.pathKind}:${left.sourcePath}` - .localeCompare(`${right.pluginId}:${right.moveSetId}:${right.pathKind}:${right.sourcePath}`)); -} - -function buildPluginHandoffMoveRecord(plugin, handoff, moveSet, source) { - const status = source.pathKind === "replacement-required" - ? "no-source-paths" - : source.pathExists ? "exists" : "missing"; - return { - pluginId: handoff.pluginId || plugin.pluginId || "", - pluginOwnerRepo: handoff.ownerRepo || plugin.ownerRepo || "", - splitTarget: handoff.splitTarget || plugin.splitTarget || "", - handoffPath: plugin.handoffPath || "", - handoffExportPath: makeHandoffExportPath("plugin", plugin.pluginId, plugin.handoffPath || ""), - moveSetId: moveSet.id || "", - destinationOwner: moveSet.destinationOwner || "", - replacementProof: moveSet.replacementProof || "", - pathKind: source.pathKind, - sourcePath: normalizePath(source.sourcePath), - pathExists: source.pathExists, - status, - severity: "blocker", - }; -} - -function collectProviderHandoffMoveCoverage(report) { - const records = []; - for (const provider of report.providers || []) { - if (!provider.handoffPath) continue; - const absoluteHandoffPath = path.join(repoRoot, provider.handoffPath); - if (!existsSync(absoluteHandoffPath)) continue; - - let handoff; - try { - handoff = JSON.parse(readFileSync(absoluteHandoffPath, "utf8")); - } catch { - continue; - } - - for (const moveSet of handoff.moveSets || []) { - const currentPaths = Array.isArray(moveSet.currentPaths) ? moveSet.currentPaths : []; - for (const sourcePath of currentPaths) { - const absoluteSourcePath = path.join(repoRoot, sourcePath); - records.push(buildProviderHandoffMoveRecord(provider, handoff, moveSet, { - pathKind: "current", - sourcePath, - pathExists: existsSync(absoluteSourcePath), - })); - } - if (!currentPaths.length) { - records.push(buildProviderHandoffMoveRecord(provider, handoff, moveSet, { - pathKind: "replacement-required", - sourcePath: "(none)", - pathExists: false, - })); - } - } - } - return records.sort((left, right) => - `${left.providerId}:${left.moveSetId}:${left.pathKind}:${left.sourcePath}` - .localeCompare(`${right.providerId}:${right.moveSetId}:${right.pathKind}:${right.sourcePath}`)); -} - -function buildProviderHandoffMoveRecord(provider, handoff, moveSet, source) { - const status = source.pathKind === "replacement-required" - ? "no-source-paths" - : source.pathExists ? "exists" : "missing"; - return { - providerId: handoff.providerId || provider.providerId || "", - providerOwnerRepo: handoff.ownerRepo || provider.ownerRepo || "", - handoffPath: provider.handoffPath || "", - handoffExportPath: makeHandoffExportPath("provider", provider.providerId, provider.handoffPath || ""), - moveSetId: moveSet.id || "", - destinationOwner: moveSet.destinationOwner || "", - replacementProof: moveSet.replacementProof || "", - pathKind: source.pathKind, - sourcePath: normalizePath(source.sourcePath), - pathExists: source.pathExists, - status, - severity: "blocker", - }; -} - -function collectInteractiveWorldSurfaces(report) { - const surfaces = []; - for (const provider of report.providers || []) { - for (const surface of provider.surfaceContracts || []) { - const worldInteraction = surface.worldInteraction || {}; - surfaces.push({ - providerId: provider.providerId, - ownerRepo: provider.ownerRepo || "", - surfaceId: surface.surfaceId, - surfaceKind: surface.surfaceKind || "", - interactionModel: surface.interactionModel || "", - projectionKind: worldInteraction.projectionKind || "", - stateSchemas: worldInteraction.stateSchemas || [], - commandBoundary: worldInteraction.commandBoundary || "", - receiptSchema: worldInteraction.receiptSchema || "", - loweringTargets: worldInteraction.loweringTargets || [], - ownership: worldInteraction.ownership || "", - }); - } - } - return surfaces.sort((left, right) => - `${left.providerId}:${left.surfaceId}`.localeCompare(`${right.providerId}:${right.surfaceId}`)); -} - -function collectWorldSurfaceLoweringCoverage(report) { - const { claimByTarget, runtimeByTarget } = buildWorldSurfaceLoweringTargetIndex(report); - const coverage = []; - for (const surface of collectInteractiveWorldSurfaces(report)) { - for (const targetId of surface.loweringTargets || []) { - const claim = claimByTarget.get(targetId); - const runtime = runtimeByTarget.get(targetId); - const status = claim ? "claimed" : runtime ? "missing-claim" : "missing-runtime"; - coverage.push({ - providerId: surface.providerId, - providerOwnerRepo: surface.ownerRepo || "", - surfaceId: surface.surfaceId, - surfaceKind: surface.surfaceKind || "", - projectionKind: surface.projectionKind || "", - targetId, - status, - severity: status === "claimed" ? "ok" : "blocker", - runtimeId: runtime?.id || "world-surface-lowering", - runtimeOwnerRepo: runtime ? resolveRuntimeProjectionOwnerRepo(runtime) : "Eve", - splitTarget: runtime?.splitTarget || "", - runtimeStatus: runtime?.status || "", - supportLevel: claim?.supportLevel || "", - loweringOwnership: claim?.ownership || "", - }); - } - } - return coverage; -} - -function collectCommandBoundaryCoverage(report) { - const { claimByTarget, runtimeByTarget } = buildWorldSurfaceLoweringTargetIndex(report); - const coverage = []; - for (const surface of collectInteractiveWorldSurfaces(report)) { - for (const targetId of surface.loweringTargets || []) { - const claim = claimByTarget.get(targetId); - const runtime = runtimeByTarget.get(targetId); - const runtimeCommandSchema = runtime ? runtimeCommandTransportSchema(runtime) : ""; - const missingProviderBoundary = !surface.commandBoundary || !surface.receiptSchema; - let status = "covered"; - if (!runtime) { - status = "missing-runtime"; - } else if (!claim) { - status = "missing-runtime-claim"; - } else if (missingProviderBoundary) { - status = "missing-provider-boundary"; - } else if (runtimeCommandSchema !== "gamecult.eve.command_invocation.v1") { - status = "missing-command-transport"; - } - coverage.push({ - providerId: surface.providerId, - providerOwnerRepo: surface.ownerRepo || "", - surfaceId: surface.surfaceId, - surfaceKind: surface.surfaceKind || "", - targetId, - runtimeId: runtime?.id || "world-surface-lowering", - runtimeOwnerRepo: runtime ? resolveRuntimeProjectionOwnerRepo(runtime) : "Eve", - splitTarget: runtime?.splitTarget || "", - runtimeStatus: runtime?.status || "", - status, - severity: status === "covered" ? "ok" : "blocker", - commandBoundary: surface.commandBoundary || "", - receiptSchema: surface.receiptSchema || "", - runtimeCommandSchema, - }); - } - } - return coverage; -} - -function resolveRuntimeProjectionOwnerRepo(runtime) { - return runtime.lifecycle?.release?.ownerRepo - || runtime.lifecycle?.test?.ownerRepo - || runtime.lifecycle?.capture?.ownerRepo - || runtime.splitTarget - || runtime.ownerRepo - || ""; -} - -function collectWorldSurfaceLoweringGaps(report) { - const { claimByTarget, runtimeByTarget } = buildWorldSurfaceLoweringTargetIndex(report); - const gaps = []; - for (const provider of report.providers || []) { - for (const surface of provider.surfaceContracts || []) { - for (const targetId of surface.worldInteraction?.loweringTargets || []) { - if (claimByTarget.has(targetId)) continue; - const targetRuntime = runtimeByTarget.get(targetId); - gaps.push({ - providerId: provider.providerId, - providerOwnerRepo: provider.ownerRepo || "", - surfaceId: surface.surfaceId, - surfaceKind: surface.surfaceKind || "", - projectionKind: surface.worldInteraction?.projectionKind || "", - targetId, - ownerRepo: targetRuntime ? resolveRuntimeProjectionOwnerRepo(targetRuntime) : "Eve", - runtimeId: targetRuntime?.id || "world-surface-lowering", - splitTarget: targetRuntime?.splitTarget || "", - runtimeStatus: targetRuntime?.status || "", - severity: "blocker", - detail: targetRuntime - ? `splitTarget:${targetRuntime.splitTarget || ""}:status:${targetRuntime.status || ""}` - : "no-runtime-target-declared", - }); - } - } - } - return gaps; -} - -function buildWorldSurfaceLoweringTargetIndex(report) { - const claimByTarget = new Map(); - const runtimeByTarget = new Map(); - for (const runtime of report.runtimes || []) { - if (runtime.id) runtimeByTarget.set(runtime.id, runtime); - for (const claim of runtime.worldSurfaceLowering || []) { - if (!claim.targetId) continue; - claimByTarget.set(claim.targetId, claim); - runtimeByTarget.set(claim.targetId, runtime); - } - } - return { claimByTarget, runtimeByTarget }; -} - -async function writeConformanceExport(conformanceExport, directory) { - await mkdir(path.join(directory, "packs"), { recursive: true }); - await mkdir(path.join(directory, "handoffs"), { recursive: true }); - await mkdir(path.join(directory, "schemas"), { recursive: true }); - await writeFile(path.join(directory, "index.json"), `${JSON.stringify(conformanceExport, null, 2)}\n`); - await writeFile(path.join(directory, "index.md"), renderConformanceExportMarkdown(conformanceExport)); - for (const pack of conformanceExport.packs) { - await writeFile(path.join(directory, "packs", `${pack.id}.json`), `${JSON.stringify(pack, null, 2)}\n`); - } - for (const schema of conformanceExport.schemaCatalog || []) { - if (!schema.path || !schema.exportPath) continue; - const sourcePath = path.join(repoRoot, schema.path); - if (!existsSync(sourcePath)) continue; - const destinationPath = path.join(directory, schema.exportPath); - await mkdir(path.dirname(destinationPath), { recursive: true }); - await writeFile(destinationPath, await readFile(sourcePath, "utf8")); - } - for (const handoff of collectConformanceHandoffs(conformanceExport)) { - if (!handoff.sourcePath || !handoff.exportPath) continue; - const sourcePath = path.join(repoRoot, handoff.sourcePath); - if (!existsSync(sourcePath)) continue; - const destinationPath = path.join(directory, handoff.exportPath); - await mkdir(path.dirname(destinationPath), { recursive: true }); - await writeFile(destinationPath, await readFile(sourcePath, "utf8")); - } - for (const catalog of conformanceExport.localProviderCatalogs || []) { - if (!catalog.catalogPath || !catalog.catalogExportPath) continue; - const sourcePath = path.join(repoRoot, catalog.catalogPath); - if (!existsSync(sourcePath)) continue; - const destinationPath = path.join(directory, catalog.catalogExportPath); - await mkdir(path.dirname(destinationPath), { recursive: true }); - await writeFile(destinationPath, await readFile(sourcePath, "utf8")); - } -} - -function collectConformanceHandoffs(conformanceExport) { - return [ - { - sourcePath: conformanceExport.conformanceHandoffPath || "", - exportPath: conformanceExport.conformanceHandoffExportPath || "", - }, - ...(conformanceExport.plugins || []).map(plugin => ({ - sourcePath: plugin.handoffPath || "", - exportPath: plugin.handoffExportPath || "", - })), - ...(conformanceExport.providers || []).map(provider => ({ - sourcePath: provider.handoffPath || "", - exportPath: provider.handoffExportPath || "", - })), - ...(conformanceExport.runtimes || []).map(runtime => ({ - sourcePath: runtime.splitHandoffPath || "", - exportPath: runtime.splitHandoffExportPath || "", - })), - ]; -} - -function makeHandoffExportPath(kind, id, sourcePath) { - if (!sourcePath) return ""; - return `handoffs/${kind}-${slugify(id)}.json`; -} - -function slugify(value) { - return String(value || "unknown") - .replace(/[^a-zA-Z0-9._-]+/g, "-") - .replace(/^-+|-+$/g, "") - || "unknown"; -} - -function renderConformanceExportMarkdown(conformanceExport) { - const lines = [ - "# Eve Conformance Export", - "", - `Generated: ${conformanceExport.generatedAt}`, - "", - `Boundary rule: ${conformanceExport.boundaryRule || "not declared"}`, - `Conformance handoff: ${conformanceExport.conformanceHandoffExportPath || conformanceExport.conformanceHandoffPath || "not declared"}`, - `Capability matrix: ${conformanceExport.capabilityMatrix?.schema || "not declared"}`, - `Capability gaps: ${(conformanceExport.capabilityGaps || []).length}`, - "", - "## Packs", - "", - "| Pack | Owner | Fixtures | Exit |", - "| --- | --- | ---: | --- |", - ]; - - for (const pack of conformanceExport.packs) { - const runtimeTargets = (pack.runtimeTargets || []).length; - const evidenceCount = runtimeTargets ? `${pack.fixtures.length} fixtures, ${runtimeTargets} runtimes` : `${pack.fixtures.length}`; - lines.push(`| ${pack.id} | ${pack.ownerRepo} | ${evidenceCount} | ${pack.exitCriteria} |`); - } - - lines.push("", "## Fixtures", "", "| Pack | Fixture | Status | Owner | Surface | Metadata |", "| --- | --- | --- | --- | --- | --- |"); - for (const pack of conformanceExport.packs) { - for (const fixture of pack.fixtures) { - lines.push(`| ${pack.id} | ${fixture.title} | ${fixture.status} | ${fixture.ownerRepo} | ${fixture.surface?.path || ""} | ${fixture.metadataPath || ""} |`); - } - } - - lines.push("", "## Runtime Targets", "", "| Runtime | Status | Split Target | Command Schema | Unsupported Plugins |", "| --- | --- | --- | --- | --- |"); - const runtimePack = conformanceExport.packs.find(pack => pack.id === "runtime"); - const runtimeTargets = runtimePack?.runtimeTargets?.length ? runtimePack.runtimeTargets : conformanceExport.runtimes; - for (const runtime of runtimeTargets) { - const unsupported = (runtime.unsupportedPlugins || []).map(plugin => plugin.pluginId).join(", "); - const commandSchema = runtime.commandTransportSchema || ""; - lines.push(`| ${runtime.runtimeId} | ${runtime.status} | ${runtime.splitTarget || ""} | ${commandSchema} | ${unsupported} |`); - } - - return `${lines.join("\n")}\n`; -} - -function renderMarkdown(report) { - const lines = [ - "# Eve Parity Report", - "", - `Generated: ${report.generatedAt}`, - "", - "## Summary", - "", - `- Fixtures: ${report.summary.passedFixtures}/${report.summary.totalFixtures} passed`, - `- Plugins: ${report.summary.healthyPlugins}/${report.summary.totalPlugins} declared`, - `- Providers: ${report.summary.advertisedProviders}/${report.summary.totalProviders} advertised`, - `- Runtimes: ${report.summary.activeRuntimes}/${report.summary.totalRuntimes} active`, - `- Split targets: ${report.summary.readySplitTargets}/${report.summary.totalSplitTargets} ready`, - "", - "## Repo Strategy", - "", - `- Boundary rule: ${report.repoStrategy.boundaryRule || "not declared"}`, - `- Incubation policy: ${report.repoStrategy.incubationPolicy || "not declared"}`, - "", - "## Conformance Packs", - "", - "| Pack | Owner | Exit |", - "| --- | --- | --- |", - ]; - - for (const pack of report.conformancePacks || []) { - lines.push(`| ${pack.id} | ${pack.ownerRepo || ""} | ${pack.exitCriteria || ""} |`); - } - - lines.push( - "", - "## Responsive Cases", - "", - "| Case | Size |", - "| --- | --- |", - ); - - for (const viewport of report.responsiveCases || []) { - lines.push(`| ${viewport.id} | ${viewport.width} x ${viewport.height} |`); - } - - lines.push( - "", - "## Fixtures", - "", - "| Fixture | Pack | Status | Provider | Components | Failed Checks |", - "| --- | --- | --- | --- | ---: | --- |", - ); - - for (const fixture of report.fixtures) { - const failed = fixture.checks.filter(check => !check.pass).map(check => check.id).join(", ") || ""; - lines.push(`| ${fixture.title} | ${fixture.pack} | ${fixture.status} | ${fixture.providerId} | ${fixture.componentCount} | ${failed} |`); - } - - lines.push("", "## Fixture Commands", "", "| Fixture | Descriptors | References | Descriptor Errors |", "| --- | --- | --- | --- |"); - for (const fixture of report.fixtures) { - const refs = fixture.commandReferences.map(reference => `${reference.nodeId}:${reference.command}`).join(", "); - lines.push(`| ${fixture.title} | ${fixture.commands.join(", ")} | ${refs} | ${fixture.commandDescriptorErrors.join(", ")} |`); - } - - lines.push("", "## Authority Witnesses", "", "| Fixture | States | Owners | Witness Refs | Receipt Refs |", "| --- | --- | --- | ---: | ---: |"); - for (const fixture of report.fixtures) { - lines.push(`| ${fixture.title} | ${fixture.authorityStates.join(", ")} | ${fixture.authorityOwners.join(", ")} | ${fixture.witnessRefs.length} | ${fixture.receiptRefs.length} |`); - } - - lines.push("", "## Fixture Metadata", "", "| Fixture | Metadata | Purpose | Errors |", "| --- | --- | --- | --- |"); - for (const fixture of report.fixtures) { - lines.push(`| ${fixture.title} | ${fixture.metadataPath} | ${fixture.metadata?.purpose || ""} | ${fixture.metadataErrors.join(", ")} |`); - } - - lines.push("", "## Plugins", "", "| Plugin | Status | Owner | ABI Operations | ABI Fixture | Capabilities | Missing |", "| --- | --- | --- | --- | --- | --- | --- |"); - for (const plugin of report.plugins || []) { - const missing = [ - ...plugin.missingPaths, - ...plugin.missingRequiredFixtures.map(id => `fixture:${id}`), - ...plugin.missingIncubationFields.map(id => `metadata:${id}`), - ...plugin.schemaErrors.map(id => `schema:${id}`), - ...plugin.advertisementErrors.map(id => `advertisement:${id}`), - ...plugin.runtimeBoundaryErrors.map(id => `runtime-boundary:${id}`), - ...plugin.abiErrors.map(id => `abi:${id}`), - ].join(", "); - lines.push(`| ${plugin.title || plugin.pluginId} | ${plugin.status} | ${plugin.ownerRepo} | ${(plugin.abiOperations || []).join(", ")} | ${plugin.abiFixturePath || ""} | ${plugin.capabilities.join(", ")} | ${missing} |`); - } - - lines.push("", "## Providers", "", "| Provider | Status | Owner | Scenario | Receipt States | Surfaces | Commands | Plugin Requirements | Witnesses | Missing |", "| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- |"); - for (const provider of report.providers || []) { - const missing = [ - ...provider.missingPaths, - ...provider.missingRequiredFixtures.map(id => `fixture:${id}`), - ...provider.missingIncubationFields.map(id => `metadata:${id}`), - ...provider.advertisementErrors.map(id => `advertisement:${id}`), - ...provider.scenarioErrors.map(id => `scenario:${id}`), - ...provider.missingSchemas.map(id => `schema:${id}`), - ...provider.missingSurfaces.map(id => `surface:${id}`), - ...provider.missingSurfaceKinds.map(id => `surface-kind:${id}`), - ...provider.missingCommands.map(id => `command:${id}`), - ...provider.pluginRequirementErrors.map(id => `plugin:${id}`), - ].join(", "); - lines.push(`| ${provider.title || provider.providerId} | ${provider.status} | ${provider.ownerRepo} | ${provider.scenarioId || ""} | ${provider.scenarioReceiptStates.join(", ")} | ${provider.surfaceIds.join(", ")} | ${provider.commandIds.join(", ")} | ${summarizeProviderPluginRequirements(provider.pluginRequirements)} | ${provider.witnessKinds.join(", ")} | ${missing} |`); - } - - lines.push("", "## Runtimes", "", "| Runtime | Status | Owner | Lifecycle | Capture | Command Smoke | Required Fixtures | Plugin Fixtures | Features | Plugin Gaps | Unsupported Plugins | Missing |", "| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- |"); - for (const runtime of report.runtimes) { - const missing = [ - ...runtime.missingPaths, - ...runtime.missingExternalPaths.map(id => `external:${id}`), - ...runtime.missingSourceSymbols.map(id => `source:${id}`), - ...runtime.missingExternalSourceSymbols.map(id => `external-source:${id}`), - ...runtime.missingRequiredFixtures.map(id => `fixture:${id}`), - ...runtime.missingRequiredFeatures.map(id => `feature:${id}`), - ...runtime.commandTransportSmokeErrors.map(id => `command-smoke:${id}`), - ...runtime.capabilityManifestErrors.map(id => `runtime-capability:${id}`), - ...runtime.localProviderCatalogErrors.map(id => `local-provider-catalog:${id}`), - ...runtime.missingIncubationFields.map(id => `metadata:${id}`), - ].join(", "); - lines.push(`| ${runtime.title} | ${runtime.status} | ${runtime.ownerRepo} | ${summarizeLifecycle(runtime.lifecycle)} | ${runtime.capture?.status || "unknown"} | ${runtimeCommandTransportSchema(runtime)} | ${runtime.requiredFixtures.join(", ")} | ${runtime.pluginFixtures.join(", ")} | ${runtime.supportedFeatures.join(", ")} | ${runtime.pluginCapabilityGaps.join(", ")} | ${runtime.unsupportedPluginNotes.join(", ")} | ${missing} |`); - } - - lines.push("", "## Split Readiness", "", "| Target | Status | Owner | Runtimes | Blockers |", "| --- | --- | --- | --- | --- |"); - for (const target of report.splitTargets) { - const runtimes = target.runtimes.map(runtimeId => `${runtimeId}:${target.runtimeStatuses[runtimeId] || "missing"}`).join(", "); - lines.push(`| ${target.id} | ${target.status} | ${target.ownerRepo} | ${runtimes} | ${target.blockers.join("
")} |`); - } - - lines.push("", "## Split Proofs", "", "| Target | Proof | Status | Evidence |", "| --- | --- | --- | --- |"); - for (const target of report.splitTargets) { - for (const proof of target.proofs || []) { - lines.push(`| ${target.id} | ${proof.description || ""} | ${proof.status || ""} | ${(proof.evidencePaths || []).join("
")} |`); - } - } - - lines.push("", "## Runtime Notes", ""); - for (const runtime of report.runtimes) { - if (runtime.capture?.note) lines.push(`- ${runtime.title}: ${runtime.capture.note}`); - if (runtime.adapterSpike) lines.push(`- ${runtime.title} adapter: ${runtime.adapterSpike}`); - if (runtime.demotionReason) lines.push(`- ${runtime.title} demotion: ${runtime.demotionReason}`); - for (const criterion of runtime.activationCriteria || []) { - lines.push(`- ${runtime.title} activation: ${criterion}`); - } - } - - return `${lines.join("\n")}\n`; -} - -function summarizeProviderPluginRequirements(requirements = []) { - return requirements - .map(requirement => `${requirement.surfaceId}:${requirement.pluginId}(${(requirement.requiredCapabilities || []).join(", ")})`) - .join("
"); -} - -function summarizeLifecycle(lifecycle) { - if (!lifecycle) return ""; - return ["release", "test", "capture"] - .map(stage => lifecycle[stage] ? `${stage}:${lifecycle[stage].status || "unknown"}` : `${stage}:missing`) - .join("
"); -} diff --git a/web/eve-runtime-capability.json b/web/eve-runtime-capability.json index 335ca25..7c14d34 100644 --- a/web/eve-runtime-capability.json +++ b/web/eve-runtime-capability.json @@ -84,7 +84,7 @@ "web/eve-dsl.test.mjs", "web/provider-advertisements.test.mjs", "packages/eve-browser-lowering/test/browser-lowering.test.mjs", - "tools/parity/parity-manifest.json", + "../EveConformance/tools/parity/parity-manifest.json", "scripts/run-parity-harness.ps1" ], "pendingProofs": []