Define EveUnity release contract
This commit is contained in:
parent
06fea925ff
commit
5b60023832
10 changed files with 170 additions and 2 deletions
|
|
@ -280,6 +280,14 @@ Exit criteria:
|
|||
|
||||
Recently cut:
|
||||
|
||||
- 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`. 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.
|
||||
- Runtime-owner conformance consumption now asserts EveUnity lifecycle stage
|
||||
evidence directly. The generic consumer supports
|
||||
`--expect-runtime-lifecycle-status` and
|
||||
|
|
|
|||
|
|
@ -117,7 +117,9 @@ consume status, supported features, plugin support, command schema, lifecycle,
|
|||
capture status, and missing evidence without reading Eve's local parity
|
||||
manifest. The runtime-owner consumer smoke asserts EveUnity lifecycle stage
|
||||
status and pending release/test/capture proofs from the exported runtime record,
|
||||
so split blockers stay machine-readable instead of hiding in handoff prose.
|
||||
plus release contract fields such as package name, artifact kind, tag pattern,
|
||||
and package root, so split blockers stay machine-readable instead of hiding in
|
||||
handoff prose.
|
||||
|
||||
After generating the parity report, `run-parity-harness.ps1` copies
|
||||
`artifacts/conformance/latest` into
|
||||
|
|
|
|||
|
|
@ -42,6 +42,11 @@ evidence:
|
|||
plus Aetheria consumer-build smoke through Unity's generated project;
|
||||
- capture: pending Unity editor or batchmode artifact.
|
||||
|
||||
The release stage also declares the UPM release contract:
|
||||
`org.gamecult.eve.unity-uitoolkit` is released from this package root, reads
|
||||
its version from `package.json`, and uses tag pattern
|
||||
`eveunity-uitoolkit-v{version}` once the tag is cut from EveUnity.
|
||||
|
||||
Those lifecycle claims are validated by the parity harness. A missing evidence
|
||||
path is a runtime capability error, not a README footnote.
|
||||
|
||||
|
|
|
|||
|
|
@ -52,6 +52,15 @@
|
|||
"status": "incubating-upm-package",
|
||||
"packageName": "org.gamecult.eve.unity-uitoolkit",
|
||||
"packageManifest": "packages/org.gamecult.eve.unity-uitoolkit/package.json",
|
||||
"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",
|
||||
"publishProof": "Tagged UPM release is created from the EveUnity repo and references this package root."
|
||||
},
|
||||
"evidencePaths": [
|
||||
"packages/org.gamecult.eve.unity-uitoolkit/package.json",
|
||||
"packages/org.gamecult.eve.unity-uitoolkit/README.md",
|
||||
|
|
|
|||
|
|
@ -151,6 +151,49 @@
|
|||
"command": {
|
||||
"type": "string"
|
||||
},
|
||||
"releaseContract": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"ownerRepo",
|
||||
"packageName",
|
||||
"packageRoot",
|
||||
"versionSource",
|
||||
"tagPattern",
|
||||
"artifactKind",
|
||||
"publishProof"
|
||||
],
|
||||
"properties": {
|
||||
"ownerRepo": {
|
||||
"type": "string",
|
||||
"minLength": 1
|
||||
},
|
||||
"packageName": {
|
||||
"type": "string",
|
||||
"minLength": 1
|
||||
},
|
||||
"packageRoot": {
|
||||
"type": "string",
|
||||
"minLength": 1
|
||||
},
|
||||
"versionSource": {
|
||||
"type": "string",
|
||||
"minLength": 1
|
||||
},
|
||||
"tagPattern": {
|
||||
"type": "string",
|
||||
"minLength": 1
|
||||
},
|
||||
"artifactKind": {
|
||||
"type": "string",
|
||||
"minLength": 1
|
||||
},
|
||||
"publishProof": {
|
||||
"type": "string",
|
||||
"minLength": 1
|
||||
}
|
||||
},
|
||||
"additionalProperties": true
|
||||
},
|
||||
"evidencePaths": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
|
|
|
|||
|
|
@ -87,6 +87,37 @@ foreach ($stage in @("release", "test", "capture")) {
|
|||
}
|
||||
}
|
||||
|
||||
$releaseContract = $manifest.lifecycle.release.releaseContract
|
||||
if ($null -eq $releaseContract) {
|
||||
throw "EveUnity release lifecycle missing releaseContract"
|
||||
}
|
||||
if ($releaseContract.ownerRepo -ne "EveUnity") {
|
||||
throw "EveUnity release contract has unexpected owner: $($releaseContract.ownerRepo)"
|
||||
}
|
||||
if ($releaseContract.packageName -ne "org.gamecult.eve.unity-uitoolkit") {
|
||||
throw "EveUnity release contract has unexpected package name: $($releaseContract.packageName)"
|
||||
}
|
||||
if ($releaseContract.artifactKind -ne "upm-package") {
|
||||
throw "EveUnity release contract has unexpected artifact kind: $($releaseContract.artifactKind)"
|
||||
}
|
||||
if (-not ($releaseContract.tagPattern -match "\{version\}")) {
|
||||
throw "EveUnity release contract tag pattern must include {version}: $($releaseContract.tagPattern)"
|
||||
}
|
||||
foreach ($pathProperty in @("packageRoot", "versionSource")) {
|
||||
$relativePath = $releaseContract.$pathProperty
|
||||
if (-not $relativePath) {
|
||||
throw "EveUnity release contract missing $pathProperty"
|
||||
}
|
||||
$absolutePath = Join-Path $projectRoot $relativePath
|
||||
if (-not (Test-Path -LiteralPath $absolutePath)) {
|
||||
throw "EveUnity release contract $pathProperty does not exist: $relativePath"
|
||||
}
|
||||
}
|
||||
$packageManifest = Get-Content -LiteralPath (Join-Path $projectRoot $releaseContract.versionSource) -Raw | ConvertFrom-Json
|
||||
if ($packageManifest.name -ne $releaseContract.packageName) {
|
||||
throw "EveUnity release contract package name does not match package manifest: $($releaseContract.packageName) vs $($packageManifest.name)"
|
||||
}
|
||||
|
||||
& (Join-Path $projectRoot "scripts\run-aetheria-unity-package-smoke.ps1")
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
throw "Aetheria Unity package smoke failed with exit code $LASTEXITCODE"
|
||||
|
|
|
|||
|
|
@ -63,7 +63,11 @@ node $consumerScript $consumerExport `
|
|||
--expect-runtime-lifecycle-status unity-uitoolkit:capture:pending-editor-capture `
|
||||
--expect-runtime-lifecycle-pending "unity-uitoolkit:release:Tagged UPM release" `
|
||||
--expect-runtime-lifecycle-pending "unity-uitoolkit:test:Unity batchmode EditMode runner" `
|
||||
--expect-runtime-lifecycle-pending "unity-uitoolkit:capture:Unity batchmode or editor capture artifact"
|
||||
--expect-runtime-lifecycle-pending "unity-uitoolkit:capture:Unity batchmode or editor capture artifact" `
|
||||
--expect-runtime-lifecycle-field "unity-uitoolkit:release:releaseContract.packageName:org.gamecult.eve.unity-uitoolkit" `
|
||||
--expect-runtime-lifecycle-field "unity-uitoolkit:release:releaseContract.artifactKind:upm-package" `
|
||||
--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"
|
||||
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
throw "Runtime owner conformance consumer smoke failed with exit code $LASTEXITCODE"
|
||||
|
|
|
|||
|
|
@ -37,6 +37,7 @@ if (!exportDirectory) {
|
|||
" --expect-runtime-capture-status <runtimeId:status>",
|
||||
" --expect-runtime-lifecycle-status <runtimeId:stage:status>",
|
||||
" --expect-runtime-lifecycle-pending <runtimeId:stage:pending-proof-substring>",
|
||||
" --expect-runtime-lifecycle-field <runtimeId:stage:field.path:value>",
|
||||
" --expect-split-target-status <targetId:status>",
|
||||
" --expect-split-target-blocker <targetId:blocker-substring>",
|
||||
" --expect-split-target-proof <targetId:proof-substring>",
|
||||
|
|
@ -325,6 +326,17 @@ function validateIndex(index, directory, expectations, errors) {
|
|||
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) {
|
||||
|
|
@ -500,6 +512,7 @@ function parseArguments(args) {
|
|||
runtimeCaptureStatuses: [],
|
||||
runtimeLifecycleStatuses: [],
|
||||
runtimeLifecyclePendingProofs: [],
|
||||
runtimeLifecycleFields: [],
|
||||
scenarios: [],
|
||||
splitTargets: [],
|
||||
splitTargetStatuses: [],
|
||||
|
|
@ -535,6 +548,7 @@ function parseArguments(args) {
|
|||
["--expect-runtime-capture-status", expectations.runtimeCaptureStatuses],
|
||||
["--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-proof", expectations.splitTargetProofs],
|
||||
|
|
@ -597,6 +611,8 @@ function parseArguments(args) {
|
|||
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") {
|
||||
|
|
@ -661,6 +677,29 @@ function parseRuntimeExpectation(value, field) {
|
|||
};
|
||||
}
|
||||
|
||||
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 <runtimeId:stage:field.path:value> 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(":")) {
|
||||
|
|
|
|||
|
|
@ -974,6 +974,15 @@
|
|||
"release": {
|
||||
"ownerRepo": "EveUnity",
|
||||
"status": "incubating-upm-package",
|
||||
"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",
|
||||
"publishProof": "Tagged UPM release is created from the EveUnity repo and references this package root."
|
||||
},
|
||||
"evidencePaths": [
|
||||
"packages/org.gamecult.eve.unity-uitoolkit/package.json",
|
||||
"packages/org.gamecult.eve.unity-uitoolkit/README.md",
|
||||
|
|
|
|||
|
|
@ -1138,6 +1138,7 @@ function compareRuntimeLifecycleClaims(expectedLifecycle, actualLifecycle, manif
|
|||
|
||||
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`));
|
||||
|
||||
for (const evidencePath of actual.evidencePaths || []) {
|
||||
if (!existsSync(path.join(repoRoot, evidencePath))) {
|
||||
|
|
@ -1148,6 +1149,23 @@ function compareRuntimeLifecycleClaims(expectedLifecycle, actualLifecycle, manif
|
|||
return errors;
|
||||
}
|
||||
|
||||
function compareReleaseContract(expected, actual, label) {
|
||||
const errors = [];
|
||||
if (!expected) return errors;
|
||||
if (!actual) return [`${label}:missing`];
|
||||
for (const key of ["ownerRepo", "packageName", "packageRoot", "versionSource", "tagPattern", "artifactKind", "publishProof"]) {
|
||||
if ((expected[key] || "") !== (actual[key] || "")) {
|
||||
errors.push(`${label}.${key}:expected ${expected[key] || ""} got ${actual[key] || ""}`);
|
||||
}
|
||||
}
|
||||
for (const key of ["packageRoot", "versionSource"]) {
|
||||
if (actual[key] && !existsSync(path.join(repoRoot, actual[key]))) {
|
||||
errors.push(`${label}.${key}:${actual[key]}:missing`);
|
||||
}
|
||||
}
|
||||
return errors;
|
||||
}
|
||||
|
||||
function comparePluginCapabilityClaims(expectedPlugins, actualPlugins, label) {
|
||||
const errors = [];
|
||||
const actual = new Map(actualPlugins.map(plugin => [plugin.pluginId, new Set(plugin.capabilities || [])]));
|
||||
|
|
|
|||
Loading…
Reference in a new issue