diff --git a/docs/eve-migration-roadmap.md b/docs/eve-migration-roadmap.md index fafc404..529a9f0 100644 --- a/docs/eve-migration-roadmap.md +++ b/docs/eve-migration-roadmap.md @@ -986,6 +986,14 @@ Recently cut: 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. - 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 diff --git a/packages/org.gamecult.eve.unity-uitoolkit/README.md b/packages/org.gamecult.eve.unity-uitoolkit/README.md index ddc35fe..bf89f2d 100644 --- a/packages/org.gamecult.eve.unity-uitoolkit/README.md +++ b/packages/org.gamecult.eve.unity-uitoolkit/README.md @@ -10,12 +10,13 @@ component kinds degrade to inert containers instead of gaining local semantics. Plugin semantics enter through plugin ABI sidecars and advertisements, not through Unity. This package includes first-party projection adapters for the `sai.vn` projection surface (`vn.stage`, dialogue panels, action rails, and -story command requests) and the `norn.graph` projection surface (`embed.norn`). -Sai still owns story state and command semantics. Norn still owns graph layout -and graph semantics. Unity only owns native projection of already-declared -plugin capabilities and emits `gamecult.eve.command.v1` requests. `tex.math` -still passes through as generic Eve component structure until it gains an -explicit Unity projection adapter. +story command requests), the `norn.graph` projection surface (`embed.norn`), +and the `tex.math` source-fallback projection surface (`embed.tex`, +`tex.inline`, and `tex.block`). Sai still owns story state and command +semantics. Norn still owns graph layout and graph semantics. TeX still owns +parsing, typesetting, baseline metrics, and cached render semantics. Unity only +owns native projection of already-declared plugin capabilities and emits +`gamecult.eve.command.v1` requests. This package lives in the Eve repository as the shared Unity lowering target. Aetheria and other Unity consumers should import it from Eve instead of @@ -62,6 +63,12 @@ under `artifacts/aetheria-unity-editmode/{stamp}`, and temporarily adds Unity package consumption proof. The test contract names the CultLib-owned NuGet/precompiled assembly inputs Unity needs during incubation; CultLib still owns the .NET/NuGet dependency story for its assemblies. +Each managed dependency record carries a nested `dependencyContract` naming +CultLib as `sourceAuthority`, `nuget` as `packageSource`, the package id, +assembly name, version policy, Unity incubation resolution mode, and the +handoff requirement for EveUnity. This is not Eve inventing a second dependency +system. It is EveUnity declaring the assemblies it consumes while CultLib keeps +the package authority. The capture stage declares `gamecult.eve.runtime_capture_request.v1` request construction through `tools/eveunity/eveunity-capture-contract.mjs`. That diff --git a/packages/org.gamecult.eve.unity-uitoolkit/eve-runtime-capability.json b/packages/org.gamecult.eve.unity-uitoolkit/eve-runtime-capability.json index c509b4e..efec482 100644 --- a/packages/org.gamecult.eve.unity-uitoolkit/eve-runtime-capability.json +++ b/packages/org.gamecult.eve.unity-uitoolkit/eve-runtime-capability.json @@ -126,21 +126,48 @@ "packageId": "GameCult.Caching", "packageManager": "nuget", "ownerRepo": "CultLib", - "resolutionMode": "Unity precompiled reference during EveUnity incubation." + "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." + "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." + "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." + } } ] }, diff --git a/schemas/gamecult.eve.runtime_capability.v1.schema.json b/schemas/gamecult.eve.runtime_capability.v1.schema.json index eeceab1..dbeacd0 100644 --- a/schemas/gamecult.eve.runtime_capability.v1.schema.json +++ b/schemas/gamecult.eve.runtime_capability.v1.schema.json @@ -396,7 +396,8 @@ "packageId", "packageManager", "ownerRepo", - "resolutionMode" + "resolutionMode", + "dependencyContract" ], "properties": { "assemblyName": { @@ -418,6 +419,49 @@ "resolutionMode": { "type": "string", "minLength": 1 + }, + "dependencyContract": { + "type": "object", + "required": [ + "sourceAuthority", + "packageSource", + "packageId", + "assemblyName", + "versionPolicy", + "unityResolution", + "handoffRequirement" + ], + "properties": { + "sourceAuthority": { + "type": "string", + "minLength": 1 + }, + "packageSource": { + "type": "string", + "minLength": 1 + }, + "packageId": { + "type": "string", + "minLength": 1 + }, + "assemblyName": { + "type": "string", + "minLength": 1 + }, + "versionPolicy": { + "type": "string", + "minLength": 1 + }, + "unityResolution": { + "type": "string", + "minLength": 1 + }, + "handoffRequirement": { + "type": "string", + "minLength": 1 + } + }, + "additionalProperties": true } }, "additionalProperties": true diff --git a/scripts/run-eveunity-lifecycle-smoke.ps1 b/scripts/run-eveunity-lifecycle-smoke.ps1 index 1c41c57..32b2da7 100644 --- a/scripts/run-eveunity-lifecycle-smoke.ps1 +++ b/scripts/run-eveunity-lifecycle-smoke.ps1 @@ -180,6 +180,20 @@ foreach ($dependency in @( if ($record.packageId -ne $dependency.packageId -or $record.packageManager -ne "nuget" -or $record.ownerRepo -ne "CultLib") { throw "EveUnity test contract has unexpected managed dependency record for $($dependency.assemblyName)" } + if ($null -eq $record.dependencyContract) { + throw "EveUnity test contract missing dependency contract for $($dependency.assemblyName)" + } + if ($record.dependencyContract.sourceAuthority -ne "CultLib" -or $record.dependencyContract.packageSource -ne "nuget") { + throw "EveUnity test contract has unexpected dependency source for $($dependency.assemblyName): $($record.dependencyContract.sourceAuthority)/$($record.dependencyContract.packageSource)" + } + if ($record.dependencyContract.packageId -ne $dependency.packageId -or $record.dependencyContract.assemblyName -ne $dependency.assemblyName) { + throw "EveUnity test contract dependency contract does not match managed dependency identity for $($dependency.assemblyName)" + } + foreach ($field in @("versionPolicy", "unityResolution", "handoffRequirement")) { + if (-not $record.dependencyContract.$field) { + throw "EveUnity test contract dependency contract missing $field for $($dependency.assemblyName)" + } + } if (-not (@($testAssemblyDefinition.precompiledReferences) -contains $dependency.assemblyName)) { throw "EveUnity test asmdef missing managed dependency precompiled reference: $($dependency.assemblyName)" } diff --git a/scripts/run-runtime-owner-conformance-consumer-smoke.ps1 b/scripts/run-runtime-owner-conformance-consumer-smoke.ps1 index a190748..595b0ff 100644 --- a/scripts/run-runtime-owner-conformance-consumer-smoke.ps1 +++ b/scripts/run-runtime-owner-conformance-consumer-smoke.ps1 @@ -179,7 +179,13 @@ node $consumerScript $consumerExport ` --expect-runtime-lifecycle-field "unity-uitoolkit:test:testContract.testPlatform:EditMode" ` --expect-runtime-lifecycle-field "unity-uitoolkit:test:testContract.managedAssemblyDependencies.0.ownerRepo:CultLib" ` --expect-runtime-lifecycle-field "unity-uitoolkit:test:testContract.managedAssemblyDependencies.0.packageManager:nuget" ` + --expect-runtime-lifecycle-field "unity-uitoolkit:test:testContract.managedAssemblyDependencies.0.dependencyContract.sourceAuthority:CultLib" ` + --expect-runtime-lifecycle-field "unity-uitoolkit:test:testContract.managedAssemblyDependencies.0.dependencyContract.packageSource:nuget" ` + --expect-runtime-lifecycle-field "unity-uitoolkit:test:testContract.managedAssemblyDependencies.0.dependencyContract.packageId:GameCult.Caching" ` --expect-runtime-lifecycle-field "unity-uitoolkit:test:testContract.managedAssemblyDependencies.1.packageId:GameCult.Mesh" ` + --expect-runtime-lifecycle-field "unity-uitoolkit:test:testContract.managedAssemblyDependencies.1.dependencyContract.sourceAuthority:CultLib" ` + --expect-runtime-lifecycle-field "unity-uitoolkit:test:testContract.managedAssemblyDependencies.1.dependencyContract.packageSource:nuget" ` + --expect-runtime-lifecycle-field "unity-uitoolkit:test:testContract.managedAssemblyDependencies.1.dependencyContract.packageId:GameCult.Mesh" ` --expect-runtime-lifecycle-field "unity-uitoolkit:capture:captureContract.captureKind:unity-editor-or-batchmode-png" ` --expect-runtime-lifecycle-field "unity-uitoolkit:capture:captureContract.artifactKind:png" ` --expect-runtime-lifecycle-field "unity-uitoolkit:capture:captureContract.targetId:unity-uitoolkit" ` diff --git a/tools/parity/parity-manifest.json b/tools/parity/parity-manifest.json index 79b0523..613aea1 100644 --- a/tools/parity/parity-manifest.json +++ b/tools/parity/parity-manifest.json @@ -1625,21 +1625,48 @@ "packageId": "GameCult.Caching", "packageManager": "nuget", "ownerRepo": "CultLib", - "resolutionMode": "Unity precompiled reference during EveUnity incubation." + "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." + "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." + "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." + } } ] }, diff --git a/tools/parity/run-parity.mjs b/tools/parity/run-parity.mjs index 47d10c8..e87e0d0 100644 --- a/tools/parity/run-parity.mjs +++ b/tools/parity/run-parity.mjs @@ -1574,7 +1574,20 @@ function compareTestContract(expected, actual, label) { expected.managedAssemblyDependencies || [], actual.managedAssemblyDependencies || [], `${label}.managedAssemblyDependencies`, - ["assemblyName", "packageId", "packageManager", "ownerRepo", "resolutionMode"], + [ + "assemblyName", + "packageId", + "packageManager", + "ownerRepo", + "resolutionMode", + "dependencyContract.sourceAuthority", + "dependencyContract.packageSource", + "dependencyContract.packageId", + "dependencyContract.assemblyName", + "dependencyContract.versionPolicy", + "dependencyContract.unityResolution", + "dependencyContract.handoffRequirement", + ], )); return errors; } @@ -1615,14 +1628,20 @@ function compareDependencyRecords(expectedRecords, actualRecords, label, keys) { continue; } for (const key of keys) { - if ((expected[key] || "") !== (actual[key] || "")) { - errors.push(`${label}:${expected[identityKey]}.${key}:expected ${expected[key] || ""} got ${actual[key] || ""}`); + 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 || [])]));