From 9dc4e0ef81cc9a40268171119c951e6bbb48917d Mon Sep 17 00:00:00 2001 From: Meta Vens Date: Thu, 9 Jul 2026 16:03:45 +0100 Subject: [PATCH] Type Unity dependency ownership --- docs/eve-migration-roadmap.md | 25 ++++--- docs/parity-testing-harness.md | 8 ++- .../README.md | 10 +-- .../eve-runtime-capability.json | 36 +++++++++- .../eve-runtime-capability.json | 11 ++- ...cult.eve.runtime_capability.v1.schema.json | 70 +++++++++++++++++++ ...eve.runtime_release_request.v1.schema.json | 34 +++++++++ scripts/run-eveunity-lifecycle-smoke.ps1 | 29 ++++++++ .../run-eveunity-release-contract-smoke.ps1 | 10 +++ .../run-eveunity-scene-lifecycle-smoke.ps1 | 11 +++ ...-eveunity-scene-release-contract-smoke.ps1 | 10 +++ ...ntime-owner-conformance-consumer-smoke.ps1 | 9 +++ tools/eveunity/eveunity-release-contract.mjs | 18 ++++- .../eveunity-release-contract.test.mjs | 19 +++++ tools/parity/parity-manifest.json | 36 +++++++++- tools/parity/run-parity.mjs | 30 ++++++++ 16 files changed, 341 insertions(+), 25 deletions(-) diff --git a/docs/eve-migration-roadmap.md b/docs/eve-migration-roadmap.md index 86163bc..d40ab1e 100644 --- a/docs/eve-migration-roadmap.md +++ b/docs/eve-migration-roadmap.md @@ -557,8 +557,9 @@ Recently cut: `testables` manifest mutation. The lifecycle smoke validates the runner script and Aetheria project, parity compares the contract against the ledger, and the runtime-owner consumer smoke asserts exported test fields. This is - Unity package consumption proof for CultLib-produced assemblies; CultLib - remains the owner of the .NET/NuGet dependency story. + Unity package consumption proof for CultLib-produced assemblies; the test + contract names those CultLib-owned NuGet/precompiled assembly inputs + explicitly. CultLib remains the owner of the .NET/NuGet dependency story. - 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 @@ -566,10 +567,11 @@ Recently cut: `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, and UPM artifact path. 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. + tag, package dependency set, required package dependency owners, and UPM + artifact path. 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 @@ -852,11 +854,12 @@ Recently cut: `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 Unity package assembly plumbing: CultLib already - owns the .NET/NuGet dependency story. 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. + 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 repeatable Unity package consumer-build evidence for the Eve UI Toolkit runtime. `scripts/run-aetheria-unity-package-smoke.ps1` verifies Aetheria's Unity `Packages/manifest.json` consumes Eve's surface and diff --git a/docs/parity-testing-harness.md b/docs/parity-testing-harness.md index e2c0320..d2fc668 100644 --- a/docs/parity-testing-harness.md +++ b/docs/parity-testing-harness.md @@ -404,10 +404,12 @@ exist. The current lifecycle proof is deliberately split: - release: `tools/eveunity/eveunity-release-contract.mjs` builds a typed `gamecult.eve.runtime_release_request.v1` request from the runtime capability - manifest and UPM package manifest; the tagged EveUnity release remains a - split blocker; + manifest and UPM package manifest, including required package dependency + owners; the tagged EveUnity release remains a split blocker; - test: package-owned EditMode tests run through Aetheria in Unity batchmode, - and Aetheria can build the package through Unity's generated project; + and Aetheria can build the package through Unity's generated project. The + test contract names CultLib-owned NuGet/precompiled assembly inputs without + making EveUnity own CultLib package distribution; - capture: `tools/eveunity/eveunity-capture-contract.mjs` builds a typed `gamecult.eve.runtime_capture_request.v1` request from the runtime capability manifest and Aetheria provider advertisement; Unity editor or batchmode PNG diff --git a/packages/org.gamecult.eve.unity-uitoolkit/README.md b/packages/org.gamecult.eve.unity-uitoolkit/README.md index 6a3556d..ddc35fe 100644 --- a/packages/org.gamecult.eve.unity-uitoolkit/README.md +++ b/packages/org.gamecult.eve.unity-uitoolkit/README.md @@ -50,16 +50,18 @@ its version from `package.json`, and uses tag pattern `tools/eveunity/eveunity-release-contract.mjs` builds a `gamecult.eve.runtime_release_request.v1` request from the runtime capability manifest and UPM package manifest. The request names the package version, tag, -artifact path, dependency set, and target `GameCult/EveUnity` repository without -pretending Eve has published the tagged release. +artifact path, dependency set, required package dependency owners, and target +`GameCult/EveUnity` repository without pretending Eve has published the tagged +release. The test stage declares the Unity EditMode runner contract: the current runner is `scripts/run-aetheria-unity-editmode-tests.ps1`, runs `GameCult.Eve.UnityUIToolkit.Tests` on `EditMode`, writes XML and log artifacts under `artifacts/aetheria-unity-editmode/{stamp}`, and temporarily adds `org.gamecult.eve.unity-uitoolkit` to Aetheria's Unity `testables`. This is a -Unity package consumption proof. CultLib still owns the .NET/NuGet dependency -story for its assemblies. +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. 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 e83b2e1..c509b4e 100644 --- a/packages/org.gamecult.eve.unity-uitoolkit/eve-runtime-capability.json +++ b/packages/org.gamecult.eve.unity-uitoolkit/eve-runtime-capability.json @@ -81,7 +81,16 @@ "requestSchema": "gamecult.eve.runtime_release_request.v1", "requestBuilder": "tools/eveunity/eveunity-release-contract.mjs", "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." + "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": "Eve", + "purpose": "Shared Unity DTOs for gamecult.eve.surface.v1 documents." + } + ] }, "evidencePaths": [ "packages/org.gamecult.eve.unity-uitoolkit/package.json", @@ -110,7 +119,30 @@ "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." + "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." + }, + { + "assemblyName": "GameCult.Mesh.dll", + "packageId": "GameCult.Mesh", + "packageManager": "nuget", + "ownerRepo": "CultLib", + "resolutionMode": "Unity precompiled reference during EveUnity incubation." + }, + { + "assemblyName": "MessagePack.Annotations.dll", + "packageId": "MessagePack.Annotations", + "packageManager": "nuget", + "ownerRepo": "CultLib", + "resolutionMode": "Unity precompiled reference during EveUnity incubation." + } + ] }, "evidencePaths": [ "packages/org.gamecult.eve.unity-uitoolkit/Tests/Editor/GameCult.Eve.UnityUIToolkit.Tests.asmdef", diff --git a/runtimes/incubating/eve-unity-scene/eve-runtime-capability.json b/runtimes/incubating/eve-unity-scene/eve-runtime-capability.json index e62c0b1..1802b90 100644 --- a/runtimes/incubating/eve-unity-scene/eve-runtime-capability.json +++ b/runtimes/incubating/eve-unity-scene/eve-runtime-capability.json @@ -87,7 +87,16 @@ "requestSchema": "gamecult.eve.runtime_release_request.v1", "requestBuilder": "tools/eveunity/eveunity-release-contract.mjs", "artifactPattern": "artifacts/eveunity-scene-release/{version}/org.gamecult.eve.unity-scene-{version}.tgz", - "publishProof": "Tagged Unity scene package release is created from the EveUnity repo and references the provider-agnostic scene package root." + "publishProof": "Tagged Unity scene package release is created from the EveUnity repo and references the provider-agnostic scene package root.", + "requiredPackageDependencies": [ + { + "packageName": "org.gamecult.eve.surface", + "version": "0.1.0", + "packageManager": "upm", + "ownerRepo": "Eve", + "purpose": "Shared Unity DTOs for gamecult.eve.surface.v1 documents." + } + ] }, "evidencePaths": [ "runtimes/incubating/eve-unity-scene/package.json", diff --git a/schemas/gamecult.eve.runtime_capability.v1.schema.json b/schemas/gamecult.eve.runtime_capability.v1.schema.json index ec689e9..eeceab1 100644 --- a/schemas/gamecult.eve.runtime_capability.v1.schema.json +++ b/schemas/gamecult.eve.runtime_capability.v1.schema.json @@ -254,6 +254,40 @@ "publishProof": { "type": "string", "minLength": 1 + }, + "requiredPackageDependencies": { + "type": "array", + "items": { + "type": "object", + "required": [ + "packageName", + "version", + "packageManager", + "ownerRepo" + ], + "properties": { + "packageName": { + "type": "string", + "minLength": 1 + }, + "version": { + "type": "string", + "minLength": 1 + }, + "packageManager": { + "type": "string", + "minLength": 1 + }, + "ownerRepo": { + "type": "string", + "minLength": 1 + }, + "purpose": { + "type": "string" + } + }, + "additionalProperties": true + } } }, "additionalProperties": true @@ -352,6 +386,42 @@ "manifestMutation": { "type": "string", "minLength": 1 + }, + "managedAssemblyDependencies": { + "type": "array", + "items": { + "type": "object", + "required": [ + "assemblyName", + "packageId", + "packageManager", + "ownerRepo", + "resolutionMode" + ], + "properties": { + "assemblyName": { + "type": "string", + "minLength": 1 + }, + "packageId": { + "type": "string", + "minLength": 1 + }, + "packageManager": { + "type": "string", + "minLength": 1 + }, + "ownerRepo": { + "type": "string", + "minLength": 1 + }, + "resolutionMode": { + "type": "string", + "minLength": 1 + } + }, + "additionalProperties": true + } } }, "additionalProperties": true diff --git a/schemas/gamecult.eve.runtime_release_request.v1.schema.json b/schemas/gamecult.eve.runtime_release_request.v1.schema.json index 6740b97..6e8489b 100644 --- a/schemas/gamecult.eve.runtime_release_request.v1.schema.json +++ b/schemas/gamecult.eve.runtime_release_request.v1.schema.json @@ -81,6 +81,40 @@ "dependencies": { "type": "object" }, + "requiredPackageDependencies": { + "type": "array", + "items": { + "type": "object", + "required": [ + "packageName", + "version", + "packageManager", + "ownerRepo" + ], + "properties": { + "packageName": { + "type": "string", + "minLength": 1 + }, + "version": { + "type": "string", + "minLength": 1 + }, + "packageManager": { + "type": "string", + "minLength": 1 + }, + "ownerRepo": { + "type": "string", + "minLength": 1 + }, + "purpose": { + "type": "string" + } + }, + "additionalProperties": true + } + }, "devDependencies": { "type": "object" } diff --git a/scripts/run-eveunity-lifecycle-smoke.ps1 b/scripts/run-eveunity-lifecycle-smoke.ps1 index 5e4b894..1c41c57 100644 --- a/scripts/run-eveunity-lifecycle-smoke.ps1 +++ b/scripts/run-eveunity-lifecycle-smoke.ps1 @@ -130,6 +130,16 @@ if (-not $packageManifest.version) { if ($packageManifest.dependencies."org.gamecult.eve.surface" -ne "0.1.0") { throw "EveUnity package manifest missing org.gamecult.eve.surface dependency" } +$surfaceDependency = @($releaseContract.requiredPackageDependencies) | Where-Object { $_.packageName -eq "org.gamecult.eve.surface" } | Select-Object -First 1 +if (-not $surfaceDependency) { + throw "EveUnity release contract missing required org.gamecult.eve.surface dependency contract" +} +if ($surfaceDependency.ownerRepo -ne "Eve" -or $surfaceDependency.packageManager -ne "upm") { + throw "EveUnity release contract has unexpected surface dependency owner/package manager: $($surfaceDependency.ownerRepo)/$($surfaceDependency.packageManager)" +} +if ($surfaceDependency.version -ne $packageManifest.dependencies."org.gamecult.eve.surface") { + throw "EveUnity release contract surface dependency version mismatch: $($surfaceDependency.version)" +} & (Join-Path $projectRoot "scripts\run-eveunity-release-contract-smoke.ps1") if ($LASTEXITCODE -ne 0) { @@ -155,6 +165,25 @@ if ($testContract.testAssembly -ne "GameCult.Eve.UnityUIToolkit.Tests") { if ($testContract.testPlatform -ne "EditMode") { throw "EveUnity test contract has unexpected test platform: $($testContract.testPlatform)" } +$managedDependencies = @($testContract.managedAssemblyDependencies) +$testAssemblyDefinitionPath = Join-Path $projectRoot "packages\org.gamecult.eve.unity-uitoolkit\Tests\Editor\GameCult.Eve.UnityUIToolkit.Tests.asmdef" +$testAssemblyDefinition = Get-Content -LiteralPath $testAssemblyDefinitionPath -Raw | ConvertFrom-Json +foreach ($dependency in @( + @{ assemblyName = "GameCult.Caching.dll"; packageId = "GameCult.Caching" }, + @{ assemblyName = "GameCult.Mesh.dll"; packageId = "GameCult.Mesh" }, + @{ assemblyName = "MessagePack.Annotations.dll"; packageId = "MessagePack.Annotations" } +)) { + $record = $managedDependencies | Where-Object { $_.assemblyName -eq $dependency.assemblyName } | Select-Object -First 1 + if (-not $record) { + throw "EveUnity test contract missing managed assembly dependency: $($dependency.assemblyName)" + } + 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 (-not (@($testAssemblyDefinition.precompiledReferences) -contains $dependency.assemblyName)) { + throw "EveUnity test asmdef missing managed dependency precompiled reference: $($dependency.assemblyName)" + } +} foreach ($pathProperty in @("runnerScript", "consumerProject")) { $candidate = $testContract.$pathProperty if (-not $candidate) { diff --git a/scripts/run-eveunity-release-contract-smoke.ps1 b/scripts/run-eveunity-release-contract-smoke.ps1 index d926042..9d7fd43 100644 --- a/scripts/run-eveunity-release-contract-smoke.ps1 +++ b/scripts/run-eveunity-release-contract-smoke.ps1 @@ -91,5 +91,15 @@ if ($request.artifactPath -ne $expectedArtifactPath) { if ($request.dependencies."org.gamecult.eve.surface" -ne $packageManifest.dependencies."org.gamecult.eve.surface") { throw "EveUnity release request lost org.gamecult.eve.surface dependency" } +$surfaceDependency = @($request.requiredPackageDependencies) | Where-Object { $_.packageName -eq "org.gamecult.eve.surface" } | Select-Object -First 1 +if (-not $surfaceDependency) { + throw "EveUnity release request missing required org.gamecult.eve.surface dependency contract" +} +if ($surfaceDependency.ownerRepo -ne "Eve" -or $surfaceDependency.packageManager -ne "upm") { + throw "EveUnity release request has unexpected surface dependency owner/package manager: $($surfaceDependency.ownerRepo)/$($surfaceDependency.packageManager)" +} +if ($surfaceDependency.version -ne $packageManifest.dependencies."org.gamecult.eve.surface") { + throw "EveUnity release request surface dependency version mismatch: $($surfaceDependency.version)" +} Write-Host "EveUnity release contract smoke passed: $absoluteOutputPath" diff --git a/scripts/run-eveunity-scene-lifecycle-smoke.ps1 b/scripts/run-eveunity-scene-lifecycle-smoke.ps1 index 670c8bc..edf38c6 100644 --- a/scripts/run-eveunity-scene-lifecycle-smoke.ps1 +++ b/scripts/run-eveunity-scene-lifecycle-smoke.ps1 @@ -164,6 +164,17 @@ foreach ($pathProperty in @("requestBuilder")) { throw "EveUnity scene release contract $pathProperty does not exist: $relativePath" } } +$packageManifest = Get-Content -LiteralPath (Join-Path $projectRoot $releaseContract.versionSource) -Raw | ConvertFrom-Json +$surfaceDependency = @($releaseContract.requiredPackageDependencies) | Where-Object { $_.packageName -eq "org.gamecult.eve.surface" } | Select-Object -First 1 +if (-not $surfaceDependency) { + throw "EveUnity scene release contract missing required org.gamecult.eve.surface dependency contract" +} +if ($surfaceDependency.ownerRepo -ne "Eve" -or $surfaceDependency.packageManager -ne "upm") { + throw "EveUnity scene release contract has unexpected surface dependency owner/package manager: $($surfaceDependency.ownerRepo)/$($surfaceDependency.packageManager)" +} +if ($surfaceDependency.version -ne $packageManifest.dependencies."org.gamecult.eve.surface") { + throw "EveUnity scene release contract surface dependency version mismatch: $($surfaceDependency.version)" +} $testContract = $manifest.lifecycle.test.testContract foreach ($field in @("ownerRepo", "runnerKind", "runnerScript", "consumerProject", "packageName", "testAssembly", "testPlatform", "resultsArtifact", "logArtifact", "manifestMutation")) { diff --git a/scripts/run-eveunity-scene-release-contract-smoke.ps1 b/scripts/run-eveunity-scene-release-contract-smoke.ps1 index 50f6f89..a6151c7 100644 --- a/scripts/run-eveunity-scene-release-contract-smoke.ps1 +++ b/scripts/run-eveunity-scene-release-contract-smoke.ps1 @@ -94,5 +94,15 @@ if ($request.artifactPath -ne $expectedArtifactPath) { if ($request.dependencies."org.gamecult.eve.surface" -ne $packageManifest.dependencies."org.gamecult.eve.surface") { throw "EveUnity scene release request lost org.gamecult.eve.surface dependency" } +$surfaceDependency = @($request.requiredPackageDependencies) | Where-Object { $_.packageName -eq "org.gamecult.eve.surface" } | Select-Object -First 1 +if (-not $surfaceDependency) { + throw "EveUnity scene release request missing required org.gamecult.eve.surface dependency contract" +} +if ($surfaceDependency.ownerRepo -ne "Eve" -or $surfaceDependency.packageManager -ne "upm") { + throw "EveUnity scene release request has unexpected surface dependency owner/package manager: $($surfaceDependency.ownerRepo)/$($surfaceDependency.packageManager)" +} +if ($surfaceDependency.version -ne $packageManifest.dependencies."org.gamecult.eve.surface") { + throw "EveUnity scene release request surface dependency version mismatch: $($surfaceDependency.version)" +} Write-Host "EveUnity scene release contract smoke passed: $absoluteOutputPath" diff --git a/scripts/run-runtime-owner-conformance-consumer-smoke.ps1 b/scripts/run-runtime-owner-conformance-consumer-smoke.ps1 index f02af67..08358b6 100644 --- a/scripts/run-runtime-owner-conformance-consumer-smoke.ps1 +++ b/scripts/run-runtime-owner-conformance-consumer-smoke.ps1 @@ -165,11 +165,17 @@ node $consumerScript $consumerExport ` --expect-runtime-lifecycle-field "unity-uitoolkit:release:releaseContract.artifactPattern:artifacts/eveunity-uitoolkit-release/{version}/org.gamecult.eve.unity-uitoolkit-{version}.tgz" ` --expect-runtime-lifecycle-field "unity-uitoolkit:release:releaseContract.tagPattern:eveunity-uitoolkit-v{version}" ` --expect-runtime-lifecycle-field "unity-uitoolkit:release:releaseContract.packageRoot:packages/org.gamecult.eve.unity-uitoolkit" ` + --expect-runtime-lifecycle-field "unity-uitoolkit:release:releaseContract.requiredPackageDependencies.0.packageName:org.gamecult.eve.surface" ` + --expect-runtime-lifecycle-field "unity-uitoolkit:release:releaseContract.requiredPackageDependencies.0.ownerRepo:Eve" ` + --expect-runtime-lifecycle-field "unity-uitoolkit:release:releaseContract.requiredPackageDependencies.0.packageManager:upm" ` --expect-runtime-lifecycle-field "unity-uitoolkit:test:testContract.runnerKind:unity-editmode-batchmode" ` --expect-runtime-lifecycle-field "unity-uitoolkit:test:testContract.runnerScript:scripts/run-aetheria-unity-editmode-tests.ps1" ` --expect-runtime-lifecycle-field "unity-uitoolkit:test:testContract.packageName:org.gamecult.eve.unity-uitoolkit" ` --expect-runtime-lifecycle-field "unity-uitoolkit:test:testContract.testAssembly:GameCult.Eve.UnityUIToolkit.Tests" ` --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.1.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" ` @@ -192,6 +198,9 @@ node $consumerScript $consumerExport ` --expect-runtime-lifecycle-field "unity-scene:release:releaseContract.requestSchema:gamecult.eve.runtime_release_request.v1" ` --expect-runtime-lifecycle-field "unity-scene:release:releaseContract.requestBuilder:tools/eveunity/eveunity-release-contract.mjs" ` --expect-runtime-lifecycle-field "unity-scene:release:releaseContract.artifactPattern:artifacts/eveunity-scene-release/{version}/org.gamecult.eve.unity-scene-{version}.tgz" ` + --expect-runtime-lifecycle-field "unity-scene:release:releaseContract.requiredPackageDependencies.0.packageName:org.gamecult.eve.surface" ` + --expect-runtime-lifecycle-field "unity-scene:release:releaseContract.requiredPackageDependencies.0.ownerRepo:Eve" ` + --expect-runtime-lifecycle-field "unity-scene:release:releaseContract.requiredPackageDependencies.0.packageManager:upm" ` --expect-runtime-lifecycle-field "unity-scene:test:testContract.runnerKind:unity-scene-provider-shell-smoke" ` --expect-runtime-lifecycle-field "unity-scene:test:testContract.runnerScript:scripts/run-eveunity-scene-provider-shell-smoke.ps1" ` --expect-runtime-lifecycle-field "unity-scene:capture:captureContract.captureKind:unity-scene-frame-png" ` diff --git a/tools/eveunity/eveunity-release-contract.mjs b/tools/eveunity/eveunity-release-contract.mjs index 3e171a9..44a60eb 100644 --- a/tools/eveunity/eveunity-release-contract.mjs +++ b/tools/eveunity/eveunity-release-contract.mjs @@ -27,8 +27,21 @@ export function buildUnityReleaseRequest({ if (!packageManifest.version || !/^\d+\.\d+\.\d+(?:[-+][0-9A-Za-z.-]+)?$/.test(packageManifest.version)) { throw new Error(`package version is not a SemVer-compatible release version: ${packageManifest.version || "missing"}`); } - if (packageManifest.dependencies && !packageManifest.dependencies["org.gamecult.eve.surface"]) { - throw new Error("Unity UI Toolkit package must depend on org.gamecult.eve.surface"); + const requiredPackageDependencies = Array.isArray(releaseContract.requiredPackageDependencies) + ? releaseContract.requiredPackageDependencies + : [{ packageName: "org.gamecult.eve.surface", version: "0.1.0", packageManager: "upm", ownerRepo: "Eve" }]; + const packageDependencies = packageManifest.dependencies || {}; + for (const dependency of requiredPackageDependencies) { + if (!dependency?.packageName) { + throw new Error("release contract requiredPackageDependencies entries must name packageName"); + } + const actualVersion = packageDependencies[dependency.packageName]; + if (!actualVersion) { + throw new Error(`${releaseContract.packageName} must depend on ${dependency.packageName}`); + } + if (dependency.version && actualVersion !== dependency.version) { + throw new Error(`${releaseContract.packageName} dependency ${dependency.packageName} expected ${dependency.version} got ${actualVersion}`); + } } const tagName = replaceVersion(releaseContract.tagPattern, packageManifest.version); @@ -54,6 +67,7 @@ export function buildUnityReleaseRequest({ sourceCapabilityManifestPath: capabilityManifestPath, sourcePackageManifestPath: packageManifestPath, dependencies: packageManifest.dependencies || {}, + requiredPackageDependencies, }; } diff --git a/tools/eveunity/eveunity-release-contract.test.mjs b/tools/eveunity/eveunity-release-contract.test.mjs index 5e91492..151ee0b 100644 --- a/tools/eveunity/eveunity-release-contract.test.mjs +++ b/tools/eveunity/eveunity-release-contract.test.mjs @@ -27,6 +27,15 @@ test("builds EveUnity UPM release request from package manifest", () => { assert.equal(request.artifactKind, "upm-package"); assert.equal(request.artifactPath, "artifacts/eveunity-uitoolkit-release/0.1.0/org.gamecult.eve.unity-uitoolkit-0.1.0.tgz"); assert.equal(request.dependencies["org.gamecult.eve.surface"], "0.1.0"); + assert.deepEqual(request.requiredPackageDependencies, [ + { + packageName: "org.gamecult.eve.surface", + version: "0.1.0", + packageManager: "upm", + ownerRepo: "Eve", + purpose: "Shared Unity DTOs for gamecult.eve.surface.v1 documents.", + }, + ]); }); test("rejects package manifests that do not match the release contract package", () => { @@ -38,6 +47,15 @@ test("rejects package manifests that do not match the release contract package", ); }); +test("rejects package manifests missing required UPM dependencies", () => { + const missingDependencyPackage = { ...packageManifest, dependencies: {} }; + + assert.throws( + () => buildUnityReleaseRequest({ capabilityManifest, packageManifest: missingDependencyPackage }), + /must depend on org\.gamecult\.eve\.surface/, + ); +}); + test("builds EveUnity scene UPM release request from package manifest", () => { const request = buildUnityReleaseRequest({ capabilityManifest: sceneCapabilityManifest, @@ -57,4 +75,5 @@ test("builds EveUnity scene UPM release request from package manifest", () => { assert.equal(request.artifactKind, "upm-package"); assert.equal(request.artifactPath, "artifacts/eveunity-scene-release/0.1.0/org.gamecult.eve.unity-scene-0.1.0.tgz"); assert.equal(request.dependencies["org.gamecult.eve.surface"], "0.1.0"); + assert.equal(request.requiredPackageDependencies[0].ownerRepo, "Eve"); }); diff --git a/tools/parity/parity-manifest.json b/tools/parity/parity-manifest.json index c29d1bd..2eadf6a 100644 --- a/tools/parity/parity-manifest.json +++ b/tools/parity/parity-manifest.json @@ -1569,7 +1569,16 @@ "requestSchema": "gamecult.eve.runtime_release_request.v1", "requestBuilder": "tools/eveunity/eveunity-release-contract.mjs", "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." + "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": "Eve", + "purpose": "Shared Unity DTOs for gamecult.eve.surface.v1 documents." + } + ] }, "evidencePaths": [ "packages/org.gamecult.eve.unity-uitoolkit/package.json", @@ -1598,7 +1607,30 @@ "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." + "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." + }, + { + "assemblyName": "GameCult.Mesh.dll", + "packageId": "GameCult.Mesh", + "packageManager": "nuget", + "ownerRepo": "CultLib", + "resolutionMode": "Unity precompiled reference during EveUnity incubation." + }, + { + "assemblyName": "MessagePack.Annotations.dll", + "packageId": "MessagePack.Annotations", + "packageManager": "nuget", + "ownerRepo": "CultLib", + "resolutionMode": "Unity precompiled reference during EveUnity incubation." + } + ] }, "evidencePaths": [ "packages/org.gamecult.eve.unity-uitoolkit/Tests/Editor/GameCult.Eve.UnityUIToolkit.Tests.asmdef", diff --git a/tools/parity/run-parity.mjs b/tools/parity/run-parity.mjs index af31eaa..bf99eeb 100644 --- a/tools/parity/run-parity.mjs +++ b/tools/parity/run-parity.mjs @@ -1541,6 +1541,12 @@ function compareTestContract(expected, actual, label) { 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"], + )); return errors; } @@ -1561,6 +1567,30 @@ function compareReleaseContract(expected, actual, label) { errors.push(`${label}.${key}:${actual[key]}:missing`); } } + errors.push(...compareDependencyRecords( + expected.requiredPackageDependencies || [], + actual.requiredPackageDependencies || [], + `${label}.requiredPackageDependencies`, + ["packageName", "version", "packageManager", "ownerRepo", "purpose"], + )); + return errors; +} + +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) { + if ((expected[key] || "") !== (actual[key] || "")) { + errors.push(`${label}:${expected[identityKey]}.${key}:expected ${expected[key] || ""} got ${actual[key] || ""}`); + } + } + } return errors; }