From 58255251001b444b63b8ef9bc651df0eb43e0a9e Mon Sep 17 00:00:00 2001 From: Sid Jain Date: Thu, 1 Oct 2026 20:46:22 +0000 Subject: [PATCH 1/3] fix(ci): consolidate qualification reuse and publication planning --- .github/actions/setup-moon/action.yml | 8 +- .github/scripts/moon-producer-receipt.mts | 107 --------- .github/scripts/moon-task-capabilities.mts | 8 +- .../scripts/moon-task-capabilities.test.mts | 18 ++ .github/scripts/release-candidate-lib.mts | 59 ----- .github/scripts/require-workflow-success.sh | 2 + .github/scripts/write-release-candidate.mts | 1 - .github/workflows/broker-runtime.yml | 2 + .github/workflows/ci.yml | 34 ++- .../workflows/extension-artifacts-native.yml | 2 + .../workflows/liboliphaunt-native-desktop.yml | 2 + .../workflows/mobile-extension-packages.yml | 2 + .github/workflows/release.yml | 222 +++++++----------- .github/workflows/wasix-host.yml | 2 + src/docs/maintainers/release.md | 8 +- src/wasix/postmaster/moon.yml | 6 +- .../release/bootstrap-publication-capsule.mts | 36 --- tools/release/moon-producer-receipt.test.mts | 62 ----- tools/release/publication-controller.mts | 2 +- tools/release/publication-controller.test.sh | 5 + tools/release/publication-lock.mts | 18 +- tools/release/release-candidate-lib.test.mts | 46 ---- .../release/require-workflow-success.test.sh | 14 ++ .../require-workflow-success-github.mts | 7 + 24 files changed, 184 insertions(+), 489 deletions(-) delete mode 100644 .github/scripts/moon-producer-receipt.mts delete mode 100644 tools/release/moon-producer-receipt.test.mts diff --git a/.github/actions/setup-moon/action.yml b/.github/actions/setup-moon/action.yml index d1c14b269..5ed616202 100644 --- a/.github/actions/setup-moon/action.yml +++ b/.github/actions/setup-moon/action.yml @@ -2,6 +2,10 @@ name: Set up Moon description: Install verified Node.js, Moon, and Bun binaries and optionally hydrate JavaScript workspace dependencies. inputs: + cache-partition: + description: Stable task group or target; matrix jobs must keep independent cache writers. + required: false + default: "default" task-cache: description: Restore/save Moon task outputs; disable for planning and uncached finalizers. required: false @@ -99,9 +103,9 @@ runs: path: | .moon/cache/blobs .moon/cache/manifests - key: moon-task-cache-v2-${{ runner.os }}-${{ runner.arch }}-${{ github.job }}-${{ strategy.job-index }}-${{ hashFiles('.prototools', '.moon/toolchains.yml') }}-${{ github.sha }} + key: moon-task-cache-v3-${{ runner.os }}-${{ runner.arch }}-${{ github.job }}-${{ inputs.cache-partition }}-${{ hashFiles('.prototools', '.moon/toolchains.yml') }}-${{ github.sha }} restore-keys: | - moon-task-cache-v2-${{ runner.os }}-${{ runner.arch }}-${{ github.job }}-${{ strategy.job-index }}-${{ hashFiles('.prototools', '.moon/toolchains.yml') }}- + moon-task-cache-v3-${{ runner.os }}-${{ runner.arch }}-${{ github.job }}-${{ inputs.cache-partition }}-${{ hashFiles('.prototools', '.moon/toolchains.yml') }}- - name: Hydrate Moon plugins shell: bash diff --git a/.github/scripts/moon-producer-receipt.mts b/.github/scripts/moon-producer-receipt.mts deleted file mode 100644 index 85fabfce5..000000000 --- a/.github/scripts/moon-producer-receipt.mts +++ /dev/null @@ -1,107 +0,0 @@ -import { readFileSync, appendFileSync } from 'node:fs'; -import path from 'node:path'; -import assert from 'node:assert/strict'; -import { createRequire } from 'node:module'; -import { requestGithubRepositoryJson } from '../../tools/release/github-read.mts'; - -const HASH = /^[0-9a-f]{64}$/u; - -export function producerTypescriptVersion(packageDirectory) { - const require = createRequire(path.resolve(packageDirectory, 'package.json')); - return require('typescript/package.json').version; -} - -/** Moon owns input hashing. A receipt only records a complete, observed chain. */ -export function producerHashes(report, cacheRoot, target) { - const actions = report.actions.filter((action) => action.node?.action === 'run-task'); - const action = actions.find((entry) => entry.node.params.target === target); - if (!action || !['passed', 'cached'].includes(action.status)) - return { eligible: false, reason: 'producer did not complete in this invocation' }; - const hash = action.operations.find((operation) => operation.meta?.type === 'hash-generation') - ?.meta.hash; - if (!HASH.test(hash ?? '')) return { eligible: false, reason: 'producer hashing is disabled' }; - const hashes = new Map(); - const visit = (expectedTarget, currentHash) => { - if (!HASH.test(currentHash ?? '')) - throw new Error(`incomplete producer hash: ${expectedTarget}=${currentHash}`); - if (hashes.has(expectedTarget)) { - assert.equal(hashes.get(expectedTarget).hash, currentHash, 'inconsistent producer ancestry'); - return; - } - const manifest = JSON.parse( - readFileSync(path.join(cacheRoot, 'hashes', `${currentHash}.json`), 'utf8'), - ); - const task = manifest.find((part) => part.target === expectedTarget); - assert( - task && task.deps && Array.isArray(task.toolchains), - `missing Moon task manifest: ${expectedTarget}`, - ); - hashes.set(expectedTarget, { - target: expectedTarget, - hash: currentHash, - dependencies: task.deps, - }); - for (const [dependency, dependencyHash] of Object.entries(task.deps)) - visit(dependency, dependencyHash); - }; - try { - visit(target, hash); - } catch (error) { - return { eligible: false, reason: error.message }; - } - return { - eligible: true, - taskHash: hash, - cacheHit: action.status === 'cached', - hashes: [...hashes.values()].sort((left, right) => left.target.localeCompare(right.target)), - }; -} - -if (import.meta.main) { - const [target, artifactName, packageDirectory] = process.argv.slice(2); - assert( - target && artifactName && packageDirectory, - 'usage: moon-producer-receipt.mts TARGET ARTIFACT_NAME PACKAGE_DIRECTORY', - ); - const artifactId = Number(process.env.PRODUCER_ARTIFACT_ID); - assert(Number.isSafeInteger(artifactId) && artifactId > 0, 'immutable artifact ID is required'); - const artifact = await requestGithubRepositoryJson( - `repos/${process.env.GITHUB_REPOSITORY}/actions/artifacts/${artifactId}`, - ); - assert.equal(artifact.id, artifactId); - assert.equal(artifact.name, artifactName); - assert.equal(artifact.expired, false); - const uploadDigest = (process.env.PRODUCER_ARTIFACT_DIGEST ?? '').replace(/^sha256:/u, ''); - assert(HASH.test(uploadDigest), 'upload did not return a SHA-256 digest'); - assert.equal(artifact.digest, `sha256:${uploadDigest}`); - assert.equal(String(artifact.workflow_run?.id), process.env.GITHUB_RUN_ID); - assert.equal(artifact.workflow_run?.head_sha, process.env.CI_HEAD_SHA); - const report = JSON.parse(readFileSync('.moon/cache/runReport.json', 'utf8')); - const scope = producerHashes(report, '.moon/cache', target); - const receipt = { - target, - ...scope, - producer: { - sha: process.env.CI_HEAD_SHA, - runId: process.env.GITHUB_RUN_ID, - runAttempt: Number(process.env.GITHUB_RUN_ATTEMPT), - }, - toolchain: { - moon: process.env.PRODUCER_MOON_VERSION, - bun: Bun.version, - typescript: producerTypescriptVersion(packageDirectory), - target: 'portable-typescript', - }, - artifact: { - id: artifact.id, - name: artifact.name, - digest: artifact.digest, - size: artifact.size_in_bytes, - }, - }; - assert( - receipt.toolchain.moon && receipt.toolchain.typescript, - 'actual producer toolchain is required', - ); - appendFileSync(process.env.GITHUB_OUTPUT, `receipt=${JSON.stringify(receipt)}\n`); -} diff --git a/.github/scripts/moon-task-capabilities.mts b/.github/scripts/moon-task-capabilities.mts index e488bda18..69cb5bb66 100644 --- a/.github/scripts/moon-task-capabilities.mts +++ b/.github/scripts/moon-task-capabilities.mts @@ -189,12 +189,14 @@ export function groupTargets(targets, { maxTargets = MAX_TARGETS_PER_JOB } = {}) byCapabilities.set(key, [...(byCapabilities.get(key) ?? []), target]); } const groups = []; - for (const targetsWithSameSetup of [...byCapabilities.values()]) { + const rows = []; + for (const [key, targetsWithSameSetup] of byCapabilities) { for (let index = 0; index < targetsWithSameSetup.length; index += maxTargets) { - groups.push(targetsWithSameSetup.slice(index, index + maxTargets)); + const batch = targetsWithSameSetup.slice(index, index + maxTargets); + groups.push(batch); + rows.push({ ...groupRow(batch), cache_partition: `${key}-${index / maxTargets}` }); } } - const rows = groups.map(groupRow); return rows.map((row, index) => { if (rows.filter(({ label }) => label === row.label).length === 1) return row; // A component may have separate setup profiles or span multiple batches. diff --git a/.github/scripts/moon-task-capabilities.test.mts b/.github/scripts/moon-task-capabilities.test.mts index f7f4f6778..4cd4f4cd6 100644 --- a/.github/scripts/moon-task-capabilities.test.mts +++ b/.github/scripts/moon-task-capabilities.test.mts @@ -103,6 +103,24 @@ describe('Moon task capabilities', () => { assert.equal(new Set(labels(targets)).size, labels(targets).length); }); + test('keeps cache partitions stable across unrelated groups and separates batch writers', () => { + const taskMap = tasks( + { target: 'a:check' }, + { target: 'rust:first', tags: ['requires-rust'] }, + { target: 'rust:second', tags: ['requires-rust'] }, + ); + const targets = [...taskMap.values()].map((task) => matrixTarget(task, 'deep', taskMap)); + const rustOnly = groupTargets(targets.slice(1), { maxTargets: 1 }); + const withUnrelated = groupTargets(targets, { maxTargets: 1 }).filter( + ({ requires_rust }) => requires_rust, + ); + assert.deepEqual( + withUnrelated.map(({ cache_partition }) => cache_partition), + rustOnly.map(({ cache_partition }) => cache_partition), + ); + assert.equal(new Set(withUnrelated.map(({ cache_partition }) => cache_partition)).size, 2); + }); + test('rejects duplicate targets and invalid shard limits', () => { const row = { target: 'repo:check', diff --git a/.github/scripts/release-candidate-lib.mts b/.github/scripts/release-candidate-lib.mts index 172fb54c6..5f161a0cb 100644 --- a/.github/scripts/release-candidate-lib.mts +++ b/.github/scripts/release-candidate-lib.mts @@ -352,65 +352,6 @@ export function assertCandidateBindingShape(candidate) { candidate?.schemaVersion === 2, `release candidate schemaVersion must be 2, got ${candidate?.schemaVersion}`, ); - if (candidate.producers !== undefined) { - assert(Array.isArray(candidate.producers), 'candidate producers must be a list'); - const targets = new Set(); - for (const receipt of candidate.producers) { - assert( - typeof receipt.target === 'string' && !targets.has(receipt.target), - 'duplicate or invalid producer', - ); - targets.add(receipt.target); - positiveInteger(candidate.runAttempt, 'candidate runAttempt'); - positiveInteger(receipt.producer?.runAttempt, 'producer receipt runAttempt'); - // Failed-job reruns retain successful producers from earlier attempts. - assert( - receipt.producer?.sha === candidate.sha && - receipt.producer?.runId === candidate.runId && - receipt.producer.runAttempt <= candidate.runAttempt, - 'producer receipt must match the qualification SHA/run and not exceed its attempt', - ); - assert( - Number.isSafeInteger(receipt.artifact?.id) && - receipt.artifact.id > 0 && - Number.isSafeInteger(receipt.artifact.size) && - receipt.artifact.size > 0 && - /^sha256:[0-9a-f]{64}$/.test(receipt.artifact.digest), - 'producer artifact identity is invalid', - ); - assert( - receipt.toolchain?.moon && - receipt.toolchain?.bun && - receipt.toolchain?.typescript && - receipt.toolchain.target === 'portable-typescript', - 'producer toolchain identity is incomplete', - ); - assert(typeof receipt.eligible === 'boolean', 'producer eligibility is missing'); - if (!receipt.eligible) { - assert( - typeof receipt.reason === 'string' && receipt.reason.length > 0, - 'ineligible producer requires a reason', - ); - continue; - } - assert( - typeof receipt.cacheHit === 'boolean' && Array.isArray(receipt.hashes), - 'producer execution evidence is missing', - ); - const hashes = new Map(receipt.hashes.map((entry) => [entry.target, entry.hash])); - assert( - /^[0-9a-f]{64}$/.test(receipt.taskHash ?? '') && - hashes.size === receipt.hashes.length && - hashes.get(receipt.target) === receipt.taskHash, - 'producer hash chain is inconsistent', - ); - for (const entry of receipt.hashes) { - assert(/^[0-9a-f]{64}$/.test(entry.hash), 'producer hash is invalid'); - for (const [dependency, hash] of Object.entries(entry.dependencies)) - assert(hashes.get(dependency) === hash, 'producer dependency hash is incomplete'); - } - } - } assert( candidate.affectedPlan !== null && typeof candidate.affectedPlan === 'object', 'release candidate affectedPlan is missing', diff --git a/.github/scripts/require-workflow-success.sh b/.github/scripts/require-workflow-success.sh index ead6efbff..313e7809c 100644 --- a/.github/scripts/require-workflow-success.sh +++ b/.github/scripts/require-workflow-success.sh @@ -427,6 +427,8 @@ while true; do fi if [[ "$status" -eq 75 ]]; then echo "transient GitHub read budget exhausted while inspecting $workflow run $run_id; the waiter remains active" + # An unread candidate is unknown, so absence cannot authorize a dispatch. + inventory_ready=false continue fi echo "$workflow run $run_id does not satisfy the required job/artifact gate" diff --git a/.github/scripts/write-release-candidate.mts b/.github/scripts/write-release-candidate.mts index 0a606e618..80876c767 100644 --- a/.github/scripts/write-release-candidate.mts +++ b/.github/scripts/write-release-candidate.mts @@ -110,7 +110,6 @@ const candidate = { ref: requiredEnv('GITHUB_REF'), sha: checkedOutSha, tree, - producers: JSON.parse(process.env.PRODUCER_RECEIPTS_JSON || '[]'), affectedPlan, evidenceRequirements: { wasixReleaseRegression: wasixRequired, diff --git a/.github/workflows/broker-runtime.yml b/.github/workflows/broker-runtime.yml index 4883f0527..545610791 100644 --- a/.github/workflows/broker-runtime.yml +++ b/.github/workflows/broker-runtime.yml @@ -39,6 +39,8 @@ jobs: - name: Set up Moon uses: ./.github/actions/setup-moon + with: + cache-partition: broker-${{ matrix.target }} - name: Set up Rust uses: ./.github/actions/setup-rust diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8a29720d3..38cb36452 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -283,6 +283,7 @@ jobs: - name: Set up Moon uses: ./.github/actions/setup-moon with: + cache-partition: ${{ matrix.cache_partition }} install-workspace: ${{ matrix.requires_workspace && 'true' || 'false' }} - name: Set up Rust @@ -435,6 +436,7 @@ jobs: - name: Set up Moon uses: ./.github/actions/setup-moon with: + cache-partition: ${{ matrix.cache_partition }} install-workspace: ${{ matrix.requires_workspace && 'true' || 'false' }} - name: Set up Rust @@ -595,6 +597,8 @@ jobs: - name: Set up Moon uses: ./.github/actions/setup-moon + with: + cache-partition: ${{ matrix.target }} - name: Set up Rust uses: ./.github/actions/setup-rust @@ -782,6 +786,8 @@ jobs: - name: Set up Moon uses: ./.github/actions/setup-moon + with: + cache-partition: ${{ matrix.target }} - name: Set up Rust uses: ./.github/actions/setup-rust @@ -873,6 +879,8 @@ jobs: - name: Set up Moon uses: ./.github/actions/setup-moon + with: + cache-partition: ${{ matrix.target }} - name: Set up Apple uses: ./.github/actions/setup-apple @@ -1295,6 +1303,8 @@ jobs: - name: Set up Moon uses: ./.github/actions/setup-moon + with: + cache-partition: ${{ matrix.target }} - name: Set up Rust uses: ./.github/actions/setup-rust @@ -1679,11 +1689,6 @@ jobs: js-sdk-package: name: Packages / JavaScript SDK + ICU - permissions: - contents: read - actions: read - outputs: - producer_receipt: ${{ steps.query_producer_receipt.outputs.receipt }} needs: - affected - checks @@ -1743,7 +1748,6 @@ jobs: if-no-files-found: error - name: Upload shared TypeScript query package - id: query_package_artifact if: ${{ contains(fromJson(needs.affected.outputs.job_targets)['js-sdk-package'], 'oliphaunt-query-ts:package') }} uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a with: @@ -1751,19 +1755,6 @@ jobs: path: target/sdk-artifacts/oliphaunt-query-ts if-no-files-found: error - - name: Record TypeScript query producer evidence - id: query_producer_receipt - if: ${{ steps.query_package_artifact.outcome == 'success' }} - env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - CI_HEAD_SHA: ${{ github.event.pull_request.head.sha || github.sha }} - PRODUCER_ARTIFACT_ID: ${{ steps.query_package_artifact.outputs.artifact-id }} - PRODUCER_ARTIFACT_DIGEST: ${{ steps.query_package_artifact.outputs.artifact-digest }} - run: | - PRODUCER_MOON_VERSION="$(moon --version)" - export PRODUCER_MOON_VERSION - bun .github/scripts/moon-producer-receipt.mts oliphaunt-query-ts:package oliphaunt-query-ts-sdk-package-artifacts src/query/ts - native-consumers: name: Tests / Native Consumers needs: [affected, js-sdk-package, rust-sdk-package, liboliphaunt-native-desktop-linux, broker-runtime-linux, node-direct] @@ -2445,6 +2436,8 @@ jobs: - name: Set up Moon uses: ./.github/actions/setup-moon + with: + cache-partition: ${{ matrix.target_id }} - name: Set up Rust uses: ./.github/actions/setup-rust @@ -2582,6 +2575,7 @@ jobs: - name: Set up Moon uses: ./.github/actions/setup-moon with: + cache-partition: shard-${{ matrix.shard }} install-workspace: "false" - name: Set up Deno for packed native tools smoke @@ -2879,6 +2873,7 @@ jobs: - name: Set up Moon uses: ./.github/actions/setup-moon with: + cache-partition: ${{ matrix.target }} install-workspace: "true" - name: Set up Android @@ -3496,7 +3491,6 @@ jobs: WASIX_RELEASE_REGRESSION_REQUIRED: ${{ needs.affected.outputs.wasix_release_regression_required }} WASIX_EVIDENCE_ROOT: target/qualification/wasix-release-regression-evidence NATIVE_EVIDENCE_ROOT: target/qualification/native-extension-lifecycle-evidence - PRODUCER_RECEIPTS_JSON: ${{ format('[{0}]', needs.js-sdk-package.outputs.producer_receipt || '') }} run: bash .github/scripts/release-candidate.sh write - name: Upload exact-SHA qualification record diff --git a/.github/workflows/extension-artifacts-native.yml b/.github/workflows/extension-artifacts-native.yml index 7e908dd67..79ffdc81c 100644 --- a/.github/workflows/extension-artifacts-native.yml +++ b/.github/workflows/extension-artifacts-native.yml @@ -50,6 +50,8 @@ jobs: - name: Set up Moon uses: ./.github/actions/setup-moon + with: + cache-partition: extensions-${{ matrix.target }} - name: Set up Apple if: ${{ runner.os == 'macOS' }} diff --git a/.github/workflows/liboliphaunt-native-desktop.yml b/.github/workflows/liboliphaunt-native-desktop.yml index a84c5ddd1..fac53764b 100644 --- a/.github/workflows/liboliphaunt-native-desktop.yml +++ b/.github/workflows/liboliphaunt-native-desktop.yml @@ -45,6 +45,8 @@ jobs: - name: Set up Moon uses: ./.github/actions/setup-moon + with: + cache-partition: native-${{ matrix.target }} - name: Set up Rust uses: ./.github/actions/setup-rust diff --git a/.github/workflows/mobile-extension-packages.yml b/.github/workflows/mobile-extension-packages.yml index 849260043..9923684c1 100644 --- a/.github/workflows/mobile-extension-packages.yml +++ b/.github/workflows/mobile-extension-packages.yml @@ -40,6 +40,8 @@ jobs: - name: Set up Moon uses: ./.github/actions/setup-moon + with: + cache-partition: ${{ inputs.family }} - name: Set up Rust uses: ./.github/actions/setup-rust diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 5996a5663..8bea1bf61 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -876,6 +876,7 @@ jobs: publish-bootstrap: name: Bootstrap registry identities needs: + - plan-candidate - prepare-candidate runs-on: ubuntu-24.04 timeout-minutes: 360 @@ -920,42 +921,20 @@ jobs: GH_TOKEN: ${{ github.token }} GH_REPO: ${{ github.repository }} run: bash .github/scripts/require-workflow-success.sh CI "$GITHUB_SHA" 0 --job Required - - name: Plan bootstrap releases - id: release_plan - run: | - bash tools/release/release-plan.sh \ - --from-product-tags \ - --include-current-tags \ - --head-ref "$RELEASE_HEAD_SHA" \ - --format github-output \ - >> "$GITHUB_OUTPUT" - - name: No package release planned - if: ${{ steps.release_plan.outputs.has_release_changes != 'true' }} - run: echo "No release-affecting product changes were found since the last product tag." - name: Prove Release Please PR can complete after bootstrap - if: ${{ steps.release_plan.outputs.has_release_changes == 'true' }} + if: ${{ fromJSON(needs.plan-candidate.outputs.release_plan).has_release_changes == 'true' }} env: GH_TOKEN: ${{ github.token }} - PRODUCTS_JSON: ${{ steps.release_plan.outputs.products_json }} + RELEASE_SHA: ${{ fromJSON(needs.plan-candidate.outputs.verify_publication_candidate).release_sha }} run: | - bash tools/release/release-please-state.sh "$PWD" HEAD bash tools/release/with-release-history.sh "$PWD" "$RELEASE_HEAD_SHA" tools/dev/bun.sh tools/release/verify-publication-candidate.mts \ - --products-json "$PRODUCTS_JSON" --head-ref "$RELEASE_HEAD_SHA" \ - --github-output "$RUNNER_TEMP/bootstrap-release-identity" - release_sha="$(sed -n 's/^release_sha=//p' "$RUNNER_TEMP/bootstrap-release-identity")" tools/dev/bun.sh tools/release/release-please-pr-lifecycle.mts \ - assert-markable --release-sha "$release_sha" --base main - - name: Resolve selected bootstrap authentication needs - id: registry_needs - if: ${{ steps.release_plan.outputs.has_release_changes == 'true' }} - env: - PRODUCTS_JSON: ${{ steps.release_plan.outputs.products_json }} - run: bun .github/scripts/selected-registry-needs.mts + assert-markable --release-sha "$RELEASE_SHA" --base main - name: Resolve registry identity bootstrap scope id: bootstrap_scope - if: ${{ steps.release_plan.outputs.has_release_changes == 'true' }} + if: ${{ fromJSON(needs.plan-candidate.outputs.release_plan).has_release_changes == 'true' }} env: - NEEDS_CARGO: ${{ steps.registry_needs.outputs.needs_cargo }} - NEEDS_NPM: ${{ steps.registry_needs.outputs.needs_npm }} + NEEDS_CARGO: ${{ fromJSON(needs.plan-candidate.outputs.registry_needs).needs_cargo }} + NEEDS_NPM: ${{ fromJSON(needs.plan-candidate.outputs.registry_needs).needs_npm }} run: | required=false if [[ "$NEEDS_CARGO" == true || "$NEEDS_NPM" == true ]]; then @@ -963,11 +942,11 @@ jobs: fi echo "required=$required" >> "$GITHUB_OUTPUT" - name: No registry identities require bootstrap - if: ${{ steps.release_plan.outputs.has_release_changes == 'true' && steps.bootstrap_scope.outputs.required != 'true' }} + if: ${{ fromJSON(needs.plan-candidate.outputs.release_plan).has_release_changes == 'true' && steps.bootstrap_scope.outputs.required != 'true' }} run: echo 'The selected release has no Cargo or npm identities; bootstrap is a no-op.' - name: Set up pinned npm publisher id: setup_bootstrap_npm - if: ${{ steps.bootstrap_scope.outputs.required == 'true' && steps.registry_needs.outputs.needs_npm == 'true' }} + if: ${{ steps.bootstrap_scope.outputs.required == 'true' && fromJSON(needs.plan-candidate.outputs.registry_needs).needs_npm == 'true' }} timeout-minutes: 3 uses: ./.github/actions/setup-npm-publisher with: @@ -976,7 +955,7 @@ jobs: if: ${{ steps.bootstrap_scope.outputs.required == 'true' }} env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - PRODUCTS_JSON: ${{ steps.release_plan.outputs.products_json }} + PRODUCTS_JSON: ${{ fromJSON(needs.plan-candidate.outputs.release_plan).products_json }} run: | bash tools/release/verify-product-tags.sh \ --products-json "$PRODUCTS_JSON" \ @@ -986,7 +965,7 @@ jobs: --products-json "$PRODUCTS_JSON" \ --head-ref "$RELEASE_HEAD_SHA" - name: Download the frozen candidate from preparation - if: ${{ steps.release_plan.outputs.has_release_changes == 'true' }} + if: ${{ fromJSON(needs.plan-candidate.outputs.release_plan).has_release_changes == 'true' }} uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c with: artifact-ids: ${{ format('{0},{1}', needs.prepare-candidate.outputs.lock_artifact_id, needs.prepare-candidate.outputs.candidate_artifact_id) }} @@ -996,7 +975,7 @@ jobs: id: verify_bootstrap_candidate if: ${{ steps.bootstrap_scope.outputs.required == 'true' }} env: - PRODUCTS_JSON: ${{ steps.release_plan.outputs.products_json }} + PRODUCTS_JSON: ${{ fromJSON(needs.plan-candidate.outputs.release_plan).products_json }} APPROVAL_RUN_ID: ${{ inputs.approval_run_id || github.run_id }} run: | if [[ -e "$GITHUB_WORKSPACE/target" ]]; then @@ -1032,7 +1011,7 @@ jobs: id: bootstrap_credential_needs if: ${{ steps.bootstrap_scope.outputs.required == 'true' }} env: - PRODUCTS_JSON: ${{ steps.release_plan.outputs.products_json }} + PRODUCTS_JSON: ${{ fromJSON(needs.plan-candidate.outputs.release_plan).products_json }} REGISTRY_MUTATION_DEADLINE_EPOCH: ${{ env.REGISTRY_JOB_HARD_DEADLINE_EPOCH }} run: bun .github/scripts/bootstrap-registry-identities.mts --credential-needs - name: Require bootstrap credentials before mutation @@ -1107,7 +1086,7 @@ jobs: env: CARGO_REGISTRY_TOKEN: ${{ steps.bootstrap_credential_needs.outputs.needs_cargo_token == 'true' && secrets.CRATES_IO_BOOTSTRAP_TOKEN || '' }} NPM_CONFIG_USERCONFIG: ${{ runner.temp }}/oliphaunt-bootstrap.npmrc - PRODUCTS_JSON: ${{ steps.release_plan.outputs.products_json }} + PRODUCTS_JSON: ${{ fromJSON(needs.plan-candidate.outputs.release_plan).products_json }} REGISTRY_BOOTSTRAP_CARGO_SECONDS_PER_CARRIER: ${{ vars.REGISTRY_BOOTSTRAP_CARGO_SECONDS_PER_CARRIER || '30' }} REGISTRY_BOOTSTRAP_NPM_SECONDS_PER_CARRIER: ${{ vars.REGISTRY_BOOTSTRAP_NPM_SECONDS_PER_CARRIER || '30' }} REGISTRY_BOOTSTRAP_RECONCILIATION_SECONDS_PER_CARRIER: ${{ vars.REGISTRY_BOOTSTRAP_RECONCILIATION_SECONDS_PER_CARRIER || '6' }} @@ -1222,6 +1201,7 @@ jobs: publish: name: Publish release needs: + - plan-candidate - prepare-candidate - publish-bootstrap runs-on: macos-26 @@ -1276,58 +1256,28 @@ jobs: run: bash .github/scripts/require-workflow-success.sh CI "$GITHUB_SHA" 0 --job Required - name: Set up Rust uses: ./.github/actions/setup-rust - - name: Plan product releases - id: release_plan - run: | - release_plan_args=( - --from-product-tags - --include-current-tags - --head-ref "$RELEASE_HEAD_SHA" - --format github-output - ) - bash tools/release/release-plan.sh "${release_plan_args[@]}" >> "$GITHUB_OUTPUT" - - name: No package release planned - if: ${{ steps.release_plan.outputs.has_release_changes != 'true' }} - run: echo "No release-affecting product changes were found since the last product tag." - - name: Resolve selected registry authentication needs - id: registry_needs - if: ${{ steps.release_plan.outputs.has_release_changes == 'true' }} - env: - PRODUCTS_JSON: ${{ steps.release_plan.outputs.products_json }} - run: bun .github/scripts/selected-registry-needs.mts - name: Verify direct-workflow OIDC identity id: verify_oidc_identity - if: ${{ steps.release_plan.outputs.has_release_changes == 'true' }} + if: ${{ fromJSON(needs.plan-candidate.outputs.release_plan).has_release_changes == 'true' }} env: RELEASE_OPERATION: publish run: bun .github/scripts/verify-github-oidc-identity.mts - - name: Prove pending release identity - id: verify_publication_candidate - if: ${{ steps.release_plan.outputs.has_release_changes == 'true' }} - timeout-minutes: 2 - env: - PRODUCTS_JSON: ${{ steps.release_plan.outputs.products_json }} - run: | - bash tools/release/release-please-state.sh "$PWD" HEAD bash tools/release/with-release-history.sh "$PWD" "$RELEASE_HEAD_SHA" tools/dev/bun.sh tools/release/verify-publication-candidate.mts \ - --products-json "$PRODUCTS_JSON" \ - --head-ref "$RELEASE_HEAD_SHA" \ - --github-output "$GITHUB_OUTPUT" - name: Prove Release Please PR can complete after publication id: assert_release_please_markable - if: ${{ steps.release_plan.outputs.has_release_changes == 'true' }} + if: ${{ fromJSON(needs.plan-candidate.outputs.release_plan).has_release_changes == 'true' }} timeout-minutes: 1 env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | tools/dev/bun.sh tools/release/release-please-pr-lifecycle.mts \ assert-markable \ - --release-sha "${{ steps.verify_publication_candidate.outputs.release_sha }}" \ + --release-sha "${{ fromJSON(needs.plan-candidate.outputs.verify_publication_candidate).release_sha }}" \ --base main - name: Preflight selected product tag and release collisions - if: ${{ steps.release_plan.outputs.has_release_changes == 'true' }} + if: ${{ fromJSON(needs.plan-candidate.outputs.release_plan).has_release_changes == 'true' }} env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - PRODUCTS_JSON: ${{ steps.release_plan.outputs.products_json }} + PRODUCTS_JSON: ${{ fromJSON(needs.plan-candidate.outputs.release_plan).products_json }} run: | gh auth setup-git bash tools/release/verify-product-tags.sh \ @@ -1339,15 +1289,15 @@ jobs: --head-ref "$RELEASE_HEAD_SHA" - name: Check release tag App permissions id: check_release_tag_app - if: ${{ steps.release_plan.outputs.has_release_changes == 'true' }} + if: ${{ fromJSON(needs.plan-candidate.outputs.release_plan).has_release_changes == 'true' }} uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 with: *release_tag_app - name: Check publish environment - if: ${{ steps.release_plan.outputs.has_release_changes == 'true' }} + if: ${{ fromJSON(needs.plan-candidate.outputs.release_plan).has_release_changes == 'true' }} env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - PRODUCTS_JSON: ${{ steps.release_plan.outputs.products_json }} + PRODUCTS_JSON: ${{ fromJSON(needs.plan-candidate.outputs.release_plan).products_json }} ORG_GRADLE_PROJECT_mavenCentralUsername: ${{ secrets.MAVEN_CENTRAL_USERNAME }} ORG_GRADLE_PROJECT_mavenCentralPassword: ${{ secrets.MAVEN_CENTRAL_PASSWORD }} ORG_GRADLE_PROJECT_signingInMemoryKey: ${{ secrets.MAVEN_GPG_PRIVATE_KEY }} @@ -1355,15 +1305,15 @@ jobs: ORG_GRADLE_PROJECT_signingInMemoryKeyPassword: ${{ secrets.MAVEN_GPG_PASSPHRASE }} run: tools/release/check_publish_environment.mts --products-json "${PRODUCTS_JSON}" - name: Verify external registry ownership and trust links - if: ${{ steps.release_plan.outputs.has_release_changes == 'true' }} + if: ${{ fromJSON(needs.plan-candidate.outputs.release_plan).has_release_changes == 'true' }} env: - PRODUCTS_JSON: ${{ steps.release_plan.outputs.products_json }} + PRODUCTS_JSON: ${{ fromJSON(needs.plan-candidate.outputs.release_plan).products_json }} ORG_GRADLE_PROJECT_mavenCentralUsername: ${{ secrets.MAVEN_CENTRAL_USERNAME }} ORG_GRADLE_PROJECT_mavenCentralPassword: ${{ secrets.MAVEN_CENTRAL_PASSWORD }} run: bun .github/scripts/verify-external-publish-readiness.mts - name: Import, sign, and verify Maven credentials before mutation id: verify_maven_signing - if: ${{ steps.release_plan.outputs.has_release_changes == 'true' && steps.registry_needs.outputs.needs_maven == 'true' }} + if: ${{ fromJSON(needs.plan-candidate.outputs.release_plan).has_release_changes == 'true' && fromJSON(needs.plan-candidate.outputs.registry_needs).needs_maven == 'true' }} timeout-minutes: 2 env: ORG_GRADLE_PROJECT_signingInMemoryKey: ${{ secrets.MAVEN_GPG_PRIVATE_KEY }} @@ -1371,16 +1321,16 @@ jobs: ORG_GRADLE_PROJECT_signingInMemoryKeyPassword: ${{ secrets.MAVEN_GPG_PASSPHRASE }} run: bash tools/release/verify-maven-signing-readiness.sh - name: Download the frozen candidate from preparation - if: ${{ steps.release_plan.outputs.has_release_changes == 'true' }} + if: ${{ fromJSON(needs.plan-candidate.outputs.release_plan).has_release_changes == 'true' }} uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c with: artifact-ids: ${{ format('{0},{1}', needs.prepare-candidate.outputs.lock_artifact_id, needs.prepare-candidate.outputs.candidate_artifact_id) }} path: ${{ runner.temp }}/approved-publication merge-multiple: true - name: Verify and install the complete approved candidate - if: ${{ steps.release_plan.outputs.has_release_changes == 'true' }} + if: ${{ fromJSON(needs.plan-candidate.outputs.release_plan).has_release_changes == 'true' }} env: - PRODUCTS_JSON: ${{ steps.release_plan.outputs.products_json }} + PRODUCTS_JSON: ${{ fromJSON(needs.plan-candidate.outputs.release_plan).products_json }} APPROVAL_RUN_ID: ${{ inputs.approval_run_id || github.run_id }} run: | bash tools/release/with-source.sh "$RELEASE_HEAD_SHA" bash tools/dev/bun.sh tools/release/bootstrap-publication-capsule.mts verify-extract \ @@ -1392,28 +1342,28 @@ jobs: --workspace-root "$GITHUB_WORKSPACE" - name: Validate product versions and registry state id: validate_release_registry_state - if: ${{ steps.release_plan.outputs.has_release_changes == 'true' }} + if: ${{ fromJSON(needs.plan-candidate.outputs.release_plan).has_release_changes == 'true' }} env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - PRODUCTS_JSON: ${{ steps.release_plan.outputs.products_json }} + PRODUCTS_JSON: ${{ fromJSON(needs.plan-candidate.outputs.release_plan).products_json }} run: | bash tools/release/release-check-registries.sh \ --products-json "$PRODUCTS_JSON" \ --head-ref "$RELEASE_HEAD_SHA" - name: Set up pinned npm publisher id: setup_github_stage_npm - if: ${{ steps.release_plan.outputs.has_release_changes == 'true' && steps.registry_needs.outputs.needs_npm == 'true' }} + if: ${{ fromJSON(needs.plan-candidate.outputs.release_plan).has_release_changes == 'true' && fromJSON(needs.plan-candidate.outputs.registry_needs).needs_npm == 'true' }} timeout-minutes: 3 uses: ./.github/actions/setup-npm-publisher with: npm-version: ${{ env.NPM_VERSION }} - name: Assemble and sign the exact Maven Central bundle before release mutation id: preflight_maven_bundle - if: ${{ steps.release_plan.outputs.has_release_changes == 'true' && steps.registry_needs.outputs.needs_maven == 'true' }} + if: ${{ fromJSON(needs.plan-candidate.outputs.release_plan).has_release_changes == 'true' && fromJSON(needs.plan-candidate.outputs.registry_needs).needs_maven == 'true' }} timeout-minutes: 15 env: - PRODUCTS_JSON: ${{ steps.release_plan.outputs.products_json }} + PRODUCTS_JSON: ${{ fromJSON(needs.plan-candidate.outputs.release_plan).products_json }} ORG_GRADLE_PROJECT_signingInMemoryKey: ${{ secrets.MAVEN_GPG_PRIVATE_KEY }} ORG_GRADLE_PROJECT_signingInMemoryKeyId: ${{ secrets.MAVEN_GPG_KEY_ID }} ORG_GRADLE_PROJECT_signingInMemoryKeyPassword: ${{ secrets.MAVEN_GPG_PASSPHRASE }} @@ -1424,7 +1374,7 @@ jobs: --release-commit "$RELEASE_HEAD_SHA" - name: Prove the exact SwiftPM source tag is remotely collision-free id: preflight_swift_source_tag - if: ${{ steps.release_plan.outputs.has_release_changes == 'true' && contains(fromJson(steps.release_plan.outputs.products_json), 'oliphaunt-swift') }} + if: ${{ fromJSON(needs.plan-candidate.outputs.release_plan).has_release_changes == 'true' && contains(fromJson(fromJSON(needs.plan-candidate.outputs.release_plan).products_json), 'oliphaunt-swift') }} timeout-minutes: 2 run: | bash tools/release/publish-swiftpm-source-tag.sh --preflight \ @@ -1432,9 +1382,9 @@ jobs: --release-commit "$RELEASE_HEAD_SHA" - name: Classify pre-tag registry publication state id: bootstrap_ledger_state - if: ${{ steps.release_plan.outputs.has_release_changes == 'true' && (steps.registry_needs.outputs.needs_cargo == 'true' || steps.registry_needs.outputs.needs_npm == 'true') }} + if: ${{ fromJSON(needs.plan-candidate.outputs.release_plan).has_release_changes == 'true' && (fromJSON(needs.plan-candidate.outputs.registry_needs).needs_cargo == 'true' || fromJSON(needs.plan-candidate.outputs.registry_needs).needs_npm == 'true') }} env: - PRODUCTS_JSON: ${{ steps.release_plan.outputs.products_json }} + PRODUCTS_JSON: ${{ fromJSON(needs.plan-candidate.outputs.release_plan).products_json }} RELEASE_HEAD_SHA: ${{ steps.release_head.outputs.sha }} run: bash .github/scripts/registry-bootstrap-ledger-state.sh - name: Download the bootstrap ledger from this run @@ -1452,7 +1402,7 @@ jobs: - name: Verify immutable bootstrap ledger and registry existence if: ${{ steps.bootstrap_ledger_state.outputs.needs_ledger == 'true' }} env: - PRODUCTS_JSON: ${{ steps.release_plan.outputs.products_json }} + PRODUCTS_JSON: ${{ fromJSON(needs.plan-candidate.outputs.release_plan).products_json }} run: | tools/dev/bun.sh tools/release/bootstrap-ledger.mts verify \ --lock "$PUBLICATION_LOCK_PATH" \ @@ -1461,7 +1411,7 @@ jobs: --require-complete \ --verify-registries - name: Upload publication lock audit evidence - if: ${{ steps.release_plan.outputs.has_release_changes == 'true' }} + if: ${{ fromJSON(needs.plan-candidate.outputs.release_plan).has_release_changes == 'true' }} uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a with: name: oliphaunt-publication-lock-${{ inputs.operation }} @@ -1471,18 +1421,18 @@ jobs: retention-days: 90 - name: Create release tag token id: release_tag_token - if: ${{ steps.release_plan.outputs.has_release_changes == 'true' }} + if: ${{ fromJSON(needs.plan-candidate.outputs.release_plan).has_release_changes == 'true' }} uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 with: *release_tag_app - name: Reserve release transport content write - if: ${{ steps.release_plan.outputs.has_release_changes == 'true' }} + if: ${{ fromJSON(needs.plan-candidate.outputs.release_plan).has_release_changes == 'true' }} timeout-minutes: 3 run: | tools/dev/bun.sh tools/release/github-content-write-pacer.mts reserve \ --label "release transport tag" - name: Ensure exact immutable release transport ref id: ensure_release_transport_ref - if: ${{ steps.release_plan.outputs.has_release_changes == 'true' }} + if: ${{ fromJSON(needs.plan-candidate.outputs.release_plan).has_release_changes == 'true' }} timeout-minutes: 3 env: GH_TOKEN: ${{ steps.release_tag_token.outputs.token }} @@ -1492,11 +1442,11 @@ jobs: run: bash tools/release/publication-controller.sh "$RELEASE_HEAD_SHA" "$GITHUB_SHA" bun .github/scripts/release-transport-ref.mts ensure "$RELEASE_HEAD_SHA" - name: Stage exact-SHA product tags and draft releases id: stage_github_releases - if: ${{ steps.release_plan.outputs.has_release_changes == 'true' }} + if: ${{ fromJSON(needs.plan-candidate.outputs.release_plan).has_release_changes == 'true' }} timeout-minutes: 31 env: GH_TOKEN: ${{ steps.release_tag_token.outputs.token }} - PRODUCTS_JSON: ${{ steps.release_plan.outputs.products_json }} + PRODUCTS_JSON: ${{ fromJSON(needs.plan-candidate.outputs.release_plan).products_json }} run: | bun .github/scripts/manage-release-drafts.mts stage \ --products-json "$PRODUCTS_JSON" \ @@ -1504,71 +1454,71 @@ jobs: --state staged - name: Verify exact product tags id: verify_product_tags - if: ${{ steps.release_plan.outputs.has_release_changes == 'true' }} + if: ${{ fromJSON(needs.plan-candidate.outputs.release_plan).has_release_changes == 'true' }} timeout-minutes: 5 env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - PRODUCTS_JSON: ${{ steps.release_plan.outputs.products_json }} + PRODUCTS_JSON: ${{ fromJSON(needs.plan-candidate.outputs.release_plan).products_json }} run: bash tools/release/verify-product-tags.sh --products-json "${PRODUCTS_JSON}" --target "$RELEASE_HEAD_SHA" - name: Verify exact-SHA GitHub release staging id: verify_github_staging - if: ${{ steps.release_plan.outputs.has_release_changes == 'true' }} + if: ${{ fromJSON(needs.plan-candidate.outputs.release_plan).has_release_changes == 'true' }} timeout-minutes: 5 env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - PRODUCTS_JSON: ${{ steps.release_plan.outputs.products_json }} + PRODUCTS_JSON: ${{ fromJSON(needs.plan-candidate.outputs.release_plan).products_json }} run: bun .github/scripts/manage-release-drafts.mts verify --products-json "$PRODUCTS_JSON" --head-ref "$RELEASE_HEAD_SHA" --state staged - name: Publish all selected GitHub release asset sets concurrently id: publish_github_assets - if: ${{ steps.release_plan.outputs.has_release_changes == 'true' }} + if: ${{ fromJSON(needs.plan-candidate.outputs.release_plan).has_release_changes == 'true' }} timeout-minutes: 95 env: GITHUB_RELEASE_ASSET_UPLOAD_REPORT_PATH: ${{ runner.temp }}/oliphaunt-github-release-asset-upload-report.json GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - PRODUCTS_JSON: ${{ steps.release_plan.outputs.products_json }} + PRODUCTS_JSON: ${{ fromJSON(needs.plan-candidate.outputs.release_plan).products_json }} run: bash tools/release/with-source.sh "$RELEASE_HEAD_SHA" bash tools/dev/bun.sh tools/release/release-publish.mts publish --step github-release-assets --products-json "${PRODUCTS_JSON}" --head-ref "$RELEASE_HEAD_SHA" --publication-lock "$PUBLICATION_LOCK_PATH" - name: Resolve exact selected extension attestation subjects id: extension_attestation_subjects - if: ${{ steps.release_plan.outputs.has_release_changes == 'true' && steps.release_plan.outputs.has_extension_products == 'true' }} + if: ${{ fromJSON(needs.plan-candidate.outputs.release_plan).has_release_changes == 'true' && fromJSON(needs.plan-candidate.outputs.release_plan).has_extension_products == 'true' }} env: - EXTENSION_PRODUCTS_JSON: ${{ steps.release_plan.outputs.extension_products_json }} + EXTENSION_PRODUCTS_JSON: ${{ fromJSON(needs.plan-candidate.outputs.release_plan).extension_products_json }} run: | tools/dev/bun.sh tools/release/locked-attestation-subjects.mts \ --publication-lock "$PUBLICATION_LOCK_PATH" \ --products-json "$EXTENSION_PRODUCTS_JSON" \ --github-output "$GITHUB_OUTPUT" - name: Reserve extension provenance write (batch 1) - if: ${{ steps.release_plan.outputs.has_release_changes == 'true' && steps.release_plan.outputs.has_extension_products == 'true' && steps.extension_attestation_subjects.outputs.nonempty_1 == 'true' }} + if: ${{ fromJSON(needs.plan-candidate.outputs.release_plan).has_release_changes == 'true' && fromJSON(needs.plan-candidate.outputs.release_plan).has_extension_products == 'true' && steps.extension_attestation_subjects.outputs.nonempty_1 == 'true' }} run: | tools/dev/bun.sh tools/release/github-content-write-pacer.mts reserve --label "extension provenance batch 1" tools/dev/bun.sh tools/release/github-core-request-journal.mts reserve --label "extension provenance batch 1 API attempt" - name: Attest extension release assets (batch 1) id: attest_extensions_1 - if: ${{ steps.release_plan.outputs.has_release_changes == 'true' && steps.release_plan.outputs.has_extension_products == 'true' && steps.extension_attestation_subjects.outputs.nonempty_1 == 'true' }} + if: ${{ fromJSON(needs.plan-candidate.outputs.release_plan).has_release_changes == 'true' && fromJSON(needs.plan-candidate.outputs.release_plan).has_extension_products == 'true' && steps.extension_attestation_subjects.outputs.nonempty_1 == 'true' }} timeout-minutes: 5 uses: actions/attest-build-provenance@43d14bc2b83dec42d39ecae14e916627a18bb661 with: subject-path: ${{ steps.extension_attestation_subjects.outputs.paths_1 }} - name: Reserve extension provenance write (batch 2) - if: ${{ steps.release_plan.outputs.has_release_changes == 'true' && steps.release_plan.outputs.has_extension_products == 'true' && steps.extension_attestation_subjects.outputs.nonempty_2 == 'true' }} + if: ${{ fromJSON(needs.plan-candidate.outputs.release_plan).has_release_changes == 'true' && fromJSON(needs.plan-candidate.outputs.release_plan).has_extension_products == 'true' && steps.extension_attestation_subjects.outputs.nonempty_2 == 'true' }} run: | tools/dev/bun.sh tools/release/github-content-write-pacer.mts reserve --label "extension provenance batch 2" tools/dev/bun.sh tools/release/github-core-request-journal.mts reserve --label "extension provenance batch 2 API attempt" - name: Attest extension release assets (batch 2) id: attest_extensions_2 - if: ${{ steps.release_plan.outputs.has_release_changes == 'true' && steps.release_plan.outputs.has_extension_products == 'true' && steps.extension_attestation_subjects.outputs.nonempty_2 == 'true' }} + if: ${{ fromJSON(needs.plan-candidate.outputs.release_plan).has_release_changes == 'true' && fromJSON(needs.plan-candidate.outputs.release_plan).has_extension_products == 'true' && steps.extension_attestation_subjects.outputs.nonempty_2 == 'true' }} timeout-minutes: 5 uses: actions/attest-build-provenance@43d14bc2b83dec42d39ecae14e916627a18bb661 with: subject-path: ${{ steps.extension_attestation_subjects.outputs.paths_2 }} - name: Reserve liboliphaunt attestation content write - if: ${{ steps.release_plan.outputs.has_release_changes == 'true' && contains(fromJson(steps.release_plan.outputs.products_json), 'liboliphaunt-native') }} + if: ${{ fromJSON(needs.plan-candidate.outputs.release_plan).has_release_changes == 'true' && contains(fromJson(fromJSON(needs.plan-candidate.outputs.release_plan).products_json), 'liboliphaunt-native') }} run: | tools/dev/bun.sh tools/release/github-content-write-pacer.mts reserve --label "liboliphaunt native attestation" tools/dev/bun.sh tools/release/github-core-request-journal.mts reserve --label "liboliphaunt native attestation API attempt" - name: Attest liboliphaunt release assets id: attest_liboliphaunt_native - if: ${{ steps.release_plan.outputs.has_release_changes == 'true' && contains(fromJson(steps.release_plan.outputs.products_json), 'liboliphaunt-native') }} + if: ${{ fromJSON(needs.plan-candidate.outputs.release_plan).has_release_changes == 'true' && contains(fromJson(fromJSON(needs.plan-candidate.outputs.release_plan).products_json), 'liboliphaunt-native') }} timeout-minutes: 5 uses: actions/attest-build-provenance@43d14bc2b83dec42d39ecae14e916627a18bb661 with: @@ -1581,24 +1531,24 @@ jobs: target/extension-artifacts/liboliphaunt-native/oliphaunt-extension-contrib-pg18/release-assets/* - name: Create fresh SwiftPM tag token id: swift_tag_token - if: ${{ steps.release_plan.outputs.has_release_changes == 'true' && contains(fromJson(steps.release_plan.outputs.products_json), 'oliphaunt-swift') }} + if: ${{ fromJSON(needs.plan-candidate.outputs.release_plan).has_release_changes == 'true' && contains(fromJson(fromJSON(needs.plan-candidate.outputs.release_plan).products_json), 'oliphaunt-swift') }} uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 with: *release_tag_app - name: Publish Swift SDK GitHub release and SwiftPM tags id: publish_swift_source_tag - if: ${{ steps.release_plan.outputs.has_release_changes == 'true' && contains(fromJson(steps.release_plan.outputs.products_json), 'oliphaunt-swift') }} + if: ${{ fromJSON(needs.plan-candidate.outputs.release_plan).has_release_changes == 'true' && contains(fromJson(fromJSON(needs.plan-candidate.outputs.release_plan).products_json), 'oliphaunt-swift') }} timeout-minutes: 6 env: GH_TOKEN: ${{ steps.swift_tag_token.outputs.token }} run: bash tools/release/publish-swiftpm-source-tag.sh --push --target "$RELEASE_HEAD_SHA" --publication-lock "$PUBLICATION_LOCK_PATH" - name: Reserve broker attestation content write - if: ${{ steps.release_plan.outputs.has_release_changes == 'true' && contains(fromJson(steps.release_plan.outputs.products_json), 'oliphaunt-broker') }} + if: ${{ fromJSON(needs.plan-candidate.outputs.release_plan).has_release_changes == 'true' && contains(fromJson(fromJSON(needs.plan-candidate.outputs.release_plan).products_json), 'oliphaunt-broker') }} run: | tools/dev/bun.sh tools/release/github-content-write-pacer.mts reserve --label "broker attestation" tools/dev/bun.sh tools/release/github-core-request-journal.mts reserve --label "broker attestation API attempt" - name: Attest broker release assets id: attest_broker - if: ${{ steps.release_plan.outputs.has_release_changes == 'true' && contains(fromJson(steps.release_plan.outputs.products_json), 'oliphaunt-broker') }} + if: ${{ fromJSON(needs.plan-candidate.outputs.release_plan).has_release_changes == 'true' && contains(fromJson(fromJSON(needs.plan-candidate.outputs.release_plan).products_json), 'oliphaunt-broker') }} timeout-minutes: 5 uses: actions/attest-build-provenance@43d14bc2b83dec42d39ecae14e916627a18bb661 with: @@ -1607,13 +1557,13 @@ jobs: target/oliphaunt-broker/release-assets/*.zip target/oliphaunt-broker/release-assets/*.sha256 - name: Reserve Node direct attestation content write - if: ${{ steps.release_plan.outputs.has_release_changes == 'true' && contains(fromJson(steps.release_plan.outputs.products_json), 'oliphaunt-node-direct') }} + if: ${{ fromJSON(needs.plan-candidate.outputs.release_plan).has_release_changes == 'true' && contains(fromJson(fromJSON(needs.plan-candidate.outputs.release_plan).products_json), 'oliphaunt-node-direct') }} run: | tools/dev/bun.sh tools/release/github-content-write-pacer.mts reserve --label "Node direct attestation" tools/dev/bun.sh tools/release/github-core-request-journal.mts reserve --label "Node direct attestation API attempt" - name: Attest Node direct release assets id: attest_node_direct - if: ${{ steps.release_plan.outputs.has_release_changes == 'true' && contains(fromJson(steps.release_plan.outputs.products_json), 'oliphaunt-node-direct') }} + if: ${{ fromJSON(needs.plan-candidate.outputs.release_plan).has_release_changes == 'true' && contains(fromJson(fromJSON(needs.plan-candidate.outputs.release_plan).products_json), 'oliphaunt-node-direct') }} timeout-minutes: 5 uses: actions/attest-build-provenance@43d14bc2b83dec42d39ecae14e916627a18bb661 with: @@ -1622,13 +1572,13 @@ jobs: target/oliphaunt-node-direct/release-assets/*.zip target/oliphaunt-node-direct/release-assets/*.sha256 - name: Reserve WASIX Node-API attestation content write - if: ${{ steps.release_plan.outputs.has_release_changes == 'true' && contains(fromJson(steps.release_plan.outputs.products_json), 'oliphaunt-wasix-napi') }} + if: ${{ fromJSON(needs.plan-candidate.outputs.release_plan).has_release_changes == 'true' && contains(fromJson(fromJSON(needs.plan-candidate.outputs.release_plan).products_json), 'oliphaunt-wasix-napi') }} run: | tools/dev/bun.sh tools/release/github-content-write-pacer.mts reserve --label "WASIX Node-API attestation" tools/dev/bun.sh tools/release/github-core-request-journal.mts reserve --label "WASIX Node-API attestation API attempt" - name: Attest WASIX Node-API release assets id: attest_wasix_napi - if: ${{ steps.release_plan.outputs.has_release_changes == 'true' && contains(fromJson(steps.release_plan.outputs.products_json), 'oliphaunt-wasix-napi') }} + if: ${{ fromJSON(needs.plan-candidate.outputs.release_plan).has_release_changes == 'true' && contains(fromJson(fromJSON(needs.plan-candidate.outputs.release_plan).products_json), 'oliphaunt-wasix-napi') }} timeout-minutes: 5 uses: actions/attest-build-provenance@43d14bc2b83dec42d39ecae14e916627a18bb661 with: @@ -1637,13 +1587,13 @@ jobs: target/oliphaunt-wasix-napi/release-assets/*.zip target/oliphaunt-wasix-napi/release-assets/*.sha256 - name: Reserve WASIX attestation content write - if: ${{ steps.release_plan.outputs.has_release_changes == 'true' && contains(fromJson(steps.release_plan.outputs.products_json), 'liboliphaunt-wasix') }} + if: ${{ fromJSON(needs.plan-candidate.outputs.release_plan).has_release_changes == 'true' && contains(fromJson(fromJSON(needs.plan-candidate.outputs.release_plan).products_json), 'liboliphaunt-wasix') }} run: | tools/dev/bun.sh tools/release/github-content-write-pacer.mts reserve --label "WASIX attestation" tools/dev/bun.sh tools/release/github-core-request-journal.mts reserve --label "WASIX attestation API attempt" - name: Attest WASIX release assets id: attest_wasix - if: ${{ steps.release_plan.outputs.has_release_changes == 'true' && contains(fromJson(steps.release_plan.outputs.products_json), 'liboliphaunt-wasix') }} + if: ${{ fromJSON(needs.plan-candidate.outputs.release_plan).has_release_changes == 'true' && contains(fromJson(fromJSON(needs.plan-candidate.outputs.release_plan).products_json), 'liboliphaunt-wasix') }} timeout-minutes: 5 uses: actions/attest-build-provenance@43d14bc2b83dec42d39ecae14e916627a18bb661 with: @@ -1652,13 +1602,13 @@ jobs: target/oliphaunt-wasix/release-assets/*.sha256 target/extension-artifacts/liboliphaunt-wasix/oliphaunt-extension-contrib-pg18/release-assets/* - name: Reserve WASIX postmaster attestation content write - if: ${{ steps.release_plan.outputs.has_release_changes == 'true' && contains(fromJson(steps.release_plan.outputs.products_json), 'liboliphaunt-wasix-postmaster') }} + if: ${{ fromJSON(needs.plan-candidate.outputs.release_plan).has_release_changes == 'true' && contains(fromJson(fromJSON(needs.plan-candidate.outputs.release_plan).products_json), 'liboliphaunt-wasix-postmaster') }} run: | tools/dev/bun.sh tools/release/github-content-write-pacer.mts reserve --label "WASIX postmaster attestation" tools/dev/bun.sh tools/release/github-core-request-journal.mts reserve --label "WASIX postmaster attestation API attempt" - name: Attest WASIX postmaster release assets id: attest_wasix_postmaster - if: ${{ steps.release_plan.outputs.has_release_changes == 'true' && contains(fromJson(steps.release_plan.outputs.products_json), 'liboliphaunt-wasix-postmaster') }} + if: ${{ fromJSON(needs.plan-candidate.outputs.release_plan).has_release_changes == 'true' && contains(fromJson(fromJSON(needs.plan-candidate.outputs.release_plan).products_json), 'liboliphaunt-wasix-postmaster') }} timeout-minutes: 5 uses: actions/attest-build-provenance@43d14bc2b83dec42d39ecae14e916627a18bb661 with: @@ -1667,12 +1617,12 @@ jobs: target/oliphaunt-wasix-postmaster/release-assets/*.sha256 - name: Freeze exact GitHub release asset and attestation evidence id: freeze_github_evidence - if: ${{ steps.release_plan.outputs.has_release_changes == 'true' }} + if: ${{ fromJSON(needs.plan-candidate.outputs.release_plan).has_release_changes == 'true' }} timeout-minutes: 10 env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - PRODUCTS_JSON: ${{ steps.release_plan.outputs.products_json }} + PRODUCTS_JSON: ${{ fromJSON(needs.plan-candidate.outputs.release_plan).products_json }} EXTENSIONS_ATTESTATION_BUNDLE_1: ${{ steps.attest_extensions_1.outputs.bundle-path }} EXTENSIONS_ATTESTATION_BUNDLE_2: ${{ steps.attest_extensions_2.outputs.bundle-path }} LIBOLIPHAUNT_NATIVE_ATTESTATION_BUNDLE: ${{ steps.attest_liboliphaunt_native.outputs.bundle-path }} @@ -1705,7 +1655,7 @@ jobs: "${bundle_args[@]}" - name: Open registry publication window id: registry_publication_window - if: ${{ steps.release_plan.outputs.has_release_changes == 'true' }} + if: ${{ fromJSON(needs.plan-candidate.outputs.release_plan).has_release_changes == 'true' }} run: | for name in RELEASE_JOB_HARD_DEADLINE_EPOCH POST_REGISTRY_RESERVE_SECONDS; do if [[ ! "${!name:-}" =~ ^[1-9][0-9]*$ ]]; then @@ -1721,11 +1671,11 @@ jobs: echo "REGISTRY_MUTATION_DEADLINE_EPOCH=$mutation_deadline" >> "$GITHUB_ENV" - name: Publish and reconcile every exact-lock registry carrier id: publish_registries - if: ${{ steps.release_plan.outputs.has_release_changes == 'true' }} + if: ${{ fromJSON(needs.plan-candidate.outputs.release_plan).has_release_changes == 'true' }} timeout-minutes: 240 env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - PRODUCTS_JSON: ${{ steps.release_plan.outputs.products_json }} + PRODUCTS_JSON: ${{ fromJSON(needs.plan-candidate.outputs.release_plan).products_json }} ORG_GRADLE_PROJECT_mavenCentralUsername: ${{ secrets.MAVEN_CENTRAL_USERNAME }} ORG_GRADLE_PROJECT_mavenCentralPassword: ${{ secrets.MAVEN_CENTRAL_PASSWORD }} run: | @@ -1735,12 +1685,12 @@ jobs: --publication-lock "$PUBLICATION_LOCK_PATH" - name: Verify published release id: verify_published_release - if: ${{ steps.release_plan.outputs.has_release_changes == 'true' }} + if: ${{ fromJSON(needs.plan-candidate.outputs.release_plan).has_release_changes == 'true' }} timeout-minutes: 8 env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - PRODUCTS_JSON: ${{ steps.release_plan.outputs.products_json }} + PRODUCTS_JSON: ${{ fromJSON(needs.plan-candidate.outputs.release_plan).products_json }} run: | gh auth setup-git git fetch --force --tags origin @@ -1752,10 +1702,10 @@ jobs: --github-release-receipt target/release/github-release-attestation-receipt.json - name: Resolve and install exact public consumer surfaces id: public_consumer_smoke - if: ${{ steps.release_plan.outputs.has_release_changes == 'true' }} + if: ${{ fromJSON(needs.plan-candidate.outputs.release_plan).has_release_changes == 'true' }} timeout-minutes: 15 env: - PRODUCTS_JSON: ${{ steps.release_plan.outputs.products_json }} + PRODUCTS_JSON: ${{ fromJSON(needs.plan-candidate.outputs.release_plan).products_json }} run: | command -v gtimeout >/dev/null || brew install coreutils bash tools/release/public-consumer-smoke.sh \ @@ -1766,7 +1716,7 @@ jobs: --output target/release/public-consumer-smoke.json - name: Preserve public consumer evidence id: preserve_consumer_evidence - if: ${{ steps.release_plan.outputs.has_release_changes == 'true' }} + if: ${{ fromJSON(needs.plan-candidate.outputs.release_plan).has_release_changes == 'true' }} timeout-minutes: 2 uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a with: @@ -1777,7 +1727,7 @@ jobs: retention-days: 90 - name: Reverify exact publication lock before promotion id: reverify_publication_lock - if: ${{ steps.release_plan.outputs.has_release_changes == 'true' }} + if: ${{ fromJSON(needs.plan-candidate.outputs.release_plan).has_release_changes == 'true' }} timeout-minutes: 2 run: | bash tools/release/with-source.sh "$RELEASE_HEAD_SHA" bash tools/dev/bun.sh tools/release/publication-lock.mts \ @@ -1786,7 +1736,7 @@ jobs: --head-ref "$RELEASE_HEAD_SHA" - name: Preserve release evidence id: preserve_release_evidence - if: ${{ always() && steps.release_plan.outputs.has_release_changes == 'true' }} + if: ${{ always() && fromJSON(needs.plan-candidate.outputs.release_plan).has_release_changes == 'true' }} continue-on-error: true timeout-minutes: 3 uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a @@ -1804,22 +1754,22 @@ jobs: retention-days: 90 - name: Promote verified GitHub release drafts id: promote_github_releases - if: ${{ steps.release_plan.outputs.has_release_changes == 'true' }} + if: ${{ fromJSON(needs.plan-candidate.outputs.release_plan).has_release_changes == 'true' }} timeout-minutes: 16 env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - PRODUCTS_JSON: ${{ steps.release_plan.outputs.products_json }} + PRODUCTS_JSON: ${{ fromJSON(needs.plan-candidate.outputs.release_plan).products_json }} run: | tools/dev/bun.sh tools/release/release-please-pr-lifecycle.mts \ assert-markable \ - --release-sha "${{ steps.verify_publication_candidate.outputs.release_sha }}" \ + --release-sha "${{ fromJSON(needs.plan-candidate.outputs.verify_publication_candidate).release_sha }}" \ --base main bun .github/scripts/manage-release-drafts.mts promote \ --products-json "$PRODUCTS_JSON" \ --head-ref "$RELEASE_HEAD_SHA" tools/dev/bun.sh tools/release/release-please-pr-lifecycle.mts \ mark-tagged \ - --release-sha "${{ steps.verify_publication_candidate.outputs.release_sha }}" \ + --release-sha "${{ fromJSON(needs.plan-candidate.outputs.verify_publication_candidate).release_sha }}" \ --base main request-docs-refresh: name: Refresh published docs diff --git a/.github/workflows/wasix-host.yml b/.github/workflows/wasix-host.yml index 05fe30b76..060b1f665 100644 --- a/.github/workflows/wasix-host.yml +++ b/.github/workflows/wasix-host.yml @@ -45,6 +45,8 @@ jobs: - name: Set up Moon uses: ./.github/actions/setup-moon + with: + cache-partition: wasix-host-${{ matrix.target }} - name: Set up Rust uses: ./.github/actions/setup-rust diff --git a/src/docs/maintainers/release.md b/src/docs/maintainers/release.md index c5ce660f3..c2dcc2f3e 100644 --- a/src/docs/maintainers/release.md +++ b/src/docs/maintainers/release.md @@ -558,12 +558,8 @@ bundle, and promotion reserves the same 10-second content-write pacer before transport, allowing at most 361 writes in any rolling hour and seven in any rolling minute. Asset-backed products execute in bounded waves of at most five uploader processes; products with an exact empty asset set are proven by the -pre-mutation and final receipts without consuming an uploader lane. A new -runner starts a conservative -rolling-hour cooldown at the beginning of the normal publish job; read-only -qualification and artifact preparation overlap that window. The first actual -write still waits for the window to mature, and every reservation is persisted -before its request. Every core REST attempt, including retries and the API call +pre-mutation and final receipts without consuming an uploader lane. Every core +REST attempt, including retries and the API call inside each attestation action, is also reserved in a durable run-identity-bound journal before transport; attempt 901 inside one rolling hour is refused. Both journals remain bound to the repository, root run, source, and manifest diff --git a/src/wasix/postmaster/moon.yml b/src/wasix/postmaster/moon.yml index 72e8bab3f..28e64dfcb 100644 --- a/src/wasix/postmaster/moon.yml +++ b/src/wasix/postmaster/moon.yml @@ -150,7 +150,7 @@ tasks: deps: - liboliphaunt-wasix-postmaster:prepare-runtime inputs: - - "@group(cargo-workspace)" + - /rust-toolchain.toml - /tools/dev/acquisition.sh - /src/wasix/runtime/assets/build/docker/**/* - "!/src/wasix/runtime/assets/build/docker/**/*.test.*" @@ -180,7 +180,7 @@ tasks: deps: - liboliphaunt-wasix-postmaster:prepare-runtime inputs: - - "@group(cargo-workspace)" + - /rust-toolchain.toml - /tools/dev/acquisition.sh - /src/wasix/runtime/assets/build/docker/**/* - "!/src/wasix/runtime/assets/build/docker/**/*.test.*" @@ -360,7 +360,7 @@ tasks: - liboliphaunt-wasix-postmaster:runtime-capabilities inputs: - "@group(legal-files)" - - "@group(cargo-workspace)" + - /rust-toolchain.toml - "@group(production)" - /src/wasix/postmaster/sources/*.toml - "/tools/packaging/*.{mjs,mts}" diff --git a/tools/release/bootstrap-publication-capsule.mts b/tools/release/bootstrap-publication-capsule.mts index fe6264c94..442765b9b 100644 --- a/tools/release/bootstrap-publication-capsule.mts +++ b/tools/release/bootstrap-publication-capsule.mts @@ -157,31 +157,6 @@ function readMetadataFile(file, context) { return bytes; } -function hashRegularFile(file, context, expectedSize = undefined) { - const { descriptor, stat } = openRegularNoFollow(file, context); - try { - if (expectedSize !== undefined && stat.size !== expectedSize) { - throw error(`${context} size ${stat.size} does not match the frozen size ${expectedSize}`); - } - const hash = createHash('sha256'); - const buffer = Buffer.allocUnsafe(COPY_BUFFER_SIZE); - let position = 0; - for (;;) { - const count = readSync(descriptor, buffer, 0, buffer.length, position); - if (count === 0) break; - hash.update(buffer.subarray(0, count)); - position += count; - } - const finalStat = fstatSync(descriptor); - if (position !== stat.size || finalStat.size !== stat.size) { - throw error(`${context} changed while it was hashed`); - } - return { size: stat.size, sha256: hash.digest('hex') }; - } finally { - closeSync(descriptor); - } -} - function sameStrings(left, right) { return stableJson(left.slice().sort(compareText)) === stableJson(right.slice().sort(compareText)); } @@ -236,16 +211,6 @@ function expectedManifest( }; } -function verifyCandidateFiles(manifest, workspaceRoot) { - for (const artifact of manifest.files) { - const file = workspaceFile(workspaceRoot, artifact.path, 'frozen candidate file path'); - const observed = hashRegularFile(file, artifact.path, artifact.size); - if (observed.sha256 !== artifact.sha256) { - throw error(`${artifact.path} bytes do not match the approved publication lock`); - } - } -} - function tarHeader(relative, size) { if (!Number.isSafeInteger(size) || size < 0) throw error(`invalid archive member size: ${size}`); return createTarHeader({ name: safeArchivePath(relative, 'archive member path') }, size, 0o644); @@ -338,7 +303,6 @@ function capsuleEntries( approvalRunId, qualificationRunId, ); - verifyCandidateFiles(manifest, workspaceRoot); const manifestBytes = Buffer.from(canonicalJson(manifest)); const entries = [ { diff --git a/tools/release/moon-producer-receipt.test.mts b/tools/release/moon-producer-receipt.test.mts deleted file mode 100644 index 43d53db59..000000000 --- a/tools/release/moon-producer-receipt.test.mts +++ /dev/null @@ -1,62 +0,0 @@ -import { test } from 'bun:test'; -import assert from 'node:assert/strict'; -import { mkdtempSync, mkdirSync, writeFileSync, rmSync } from 'node:fs'; -import { tmpdir } from 'node:os'; -import path from 'node:path'; -import { - producerHashes, - producerTypescriptVersion, -} from '../../.github/scripts/moon-producer-receipt.mts'; - -test('producer compiler identity resolves from its isolated workspace dependencies', () => { - const root = mkdtempSync(path.join(tmpdir(), 'moon-producer-toolchain-')); - try { - const owner = path.join(root, 'sdks', 'query'); - const compiler = path.join(owner, 'node_modules', 'typescript'); - mkdirSync(compiler, { recursive: true }); - writeFileSync(path.join(owner, 'package.json'), '{"name":"query"}'); - writeFileSync(path.join(compiler, 'package.json'), '{"name":"typescript","version":"6.0.3"}'); - assert.equal(producerTypescriptVersion(owner), '6.0.3'); - } finally { - rmSync(root, { recursive: true, force: true }); - } -}); - -test('producer receipts require complete Moon ancestry and retain actual cache behavior', () => { - const root = mkdtempSync(path.join(tmpdir(), 'moon-producer-receipt-')); - try { - mkdirSync(path.join(root, 'hashes')); - const producer = 'a'.repeat(64), - dependency = 'b'.repeat(64); - const report = { - actions: [ - { - node: { action: 'run-task', params: { target: 'sdk:package' } }, - status: 'passed', - operations: [{ meta: { type: 'hash-generation', hash: producer } }], - }, - ], - }; - const manifest = (hash, target, deps) => - writeFileSync( - path.join(root, 'hashes', `${hash}.json`), - JSON.stringify([{ target, deps, toolchains: ['bun'] }]), - ); - manifest(producer, 'sdk:package', { 'sdk:build': dependency }); - manifest(dependency, 'sdk:build', {}); - const fresh = producerHashes(report, root, 'sdk:package'); - assert.equal(fresh.eligible, true); - assert.equal(fresh.cacheHit, false); - assert.equal(fresh.hashes.length, 2); - report.actions[0].status = 'cached'; - assert.equal(producerHashes(report, root, 'sdk:package').cacheHit, true); - manifest(producer, 'sdk:package', { 'sdk:build': 'passthrough' }); - assert.match(producerHashes(report, root, 'sdk:package').reason, /incomplete producer hash/); - report.actions[0].operations = []; - assert.match(producerHashes(report, root, 'sdk:package').reason, /hashing is disabled/); - report.actions[0].status = 'failed'; - assert.match(producerHashes(report, root, 'sdk:package').reason, /did not complete/); - } finally { - rmSync(root, { recursive: true, force: true }); - } -}); diff --git a/tools/release/publication-controller.mts b/tools/release/publication-controller.mts index b16d55551..839a53a24 100644 --- a/tools/release/publication-controller.mts +++ b/tools/release/publication-controller.mts @@ -62,7 +62,7 @@ function validateChanges(source, controller, mode, diff) { (file) => !CONTROL_FILES.has(file) && !/^tools\/release\/[^/]+[.]test[.](?:mts|sh)$/u.test(file) && - !/^docs\/maintainers\/release(?:-setup)?[.]md$/u.test(file), + !/^src\/docs\/maintainers\/release(?:-setup)?[.]md$/u.test(file), ); if (rejected.length) throw new Error( diff --git a/tools/release/publication-controller.test.sh b/tools/release/publication-controller.test.sh index 78fb0bea5..10629d943 100644 --- a/tools/release/publication-controller.test.sh +++ b/tools/release/publication-controller.test.sh @@ -27,6 +27,11 @@ mkdir -p .github/scripts printf 'newer publisher' > .github/scripts/download-completed-bootstrap.mts newer="$(commit)" bash "$owner/publication-controller.sh" "$source" "$newer" +mkdir -p src/docs/maintainers +printf 'release guidance' > src/docs/maintainers/release.md +printf 'setup guidance' > src/docs/maintainers/release-setup.md +documentation="$(commit)" +bash "$owner/publication-controller.sh" "$source" "$documentation" bash "$owner/publication-controller.sh" --changes-only "$source" "$controller" reject 'checkout|HEAD' "$source" "$controller" for file in product src/extensions/artifacts/packages/tools/package-extension-release-carriers.mts Cargo.lock .github/workflows/ci.yml tools/release/moon.yml; do diff --git a/tools/release/publication-lock.mts b/tools/release/publication-lock.mts index 89af5ed74..60679040a 100644 --- a/tools/release/publication-lock.mts +++ b/tools/release/publication-lock.mts @@ -370,7 +370,7 @@ function mavenManifestArtifacts(file) { })); } -function directoryEnvelope(directory) { +function directoryEnvelope(directory, fileEnvelopes = undefined) { const files = walkFiles(directory, { ignoreBuildDirectories: true }); const hash = createHash('sha256'); let size = 0; @@ -380,6 +380,11 @@ function directoryEnvelope(directory) { hash.update(`${relative}\0${bytes.length}\0`); hash.update(bytes); size += bytes.length; + fileEnvelopes?.push({ + path: file, + size: bytes.length, + sha256: createHash('sha256').update(bytes).digest('hex'), + }); } return { path: rel(directory), sha256: hash.digest('hex'), size }; } @@ -2817,20 +2822,19 @@ export function lockedPublicationFiles(lock, { products, workspaceRoot = ROOT } `${context} frozen artifact must be a regular file or directory: ${artifact.path}`, ); } + const concrete = []; const observed = metadata.isFile() ? { sha256: sha256File(value), size: metadata.size } - : directoryEnvelope(value); + : directoryEnvelope(value, concrete); + if (metadata.isFile()) concrete.push({ path: value, ...observed }); if (observed.sha256 !== artifact.sha256 || observed.size !== artifact.size) { throw error( `${context} frozen artifact bytes do not match the publication lock: ${artifact.path}`, ); } - const concrete = metadata.isFile() - ? [value] - : walkFiles(value, { ignoreBuildDirectories: true }); - for (const file of concrete) { + for (const { path: file, ...digest } of concrete) { const filePath = path.relative(workspaceRoot, file).split(path.sep).join('/'); - const envelope = { path: filePath, size: statSync(file).size, sha256: sha256File(file) }; + const envelope = { path: filePath, size: digest.size, sha256: digest.sha256 }; const prior = files.get(filePath); if (prior !== undefined && stableJson(prior) !== stableJson(envelope)) { throw error(`overlapping frozen artifacts disagree for ${filePath}`); diff --git a/tools/release/release-candidate-lib.test.mts b/tools/release/release-candidate-lib.test.mts index 3a25d744d..b154a3a96 100644 --- a/tools/release/release-candidate-lib.test.mts +++ b/tools/release/release-candidate-lib.test.mts @@ -52,52 +52,6 @@ test('selected-product evidence binds scope and candidate SHA and rejects uncove expect(() => assertCandidateBindingShape({ ...candidate, sha: 'b'.repeat(40) })).toThrow( /candidate SHA/, ); - const receipt = { - target: 'oliphaunt-query-ts:package', - eligible: true, - cacheHit: true, - taskHash: 'c'.repeat(64), - hashes: [{ target: 'oliphaunt-query-ts:package', hash: 'c'.repeat(64), dependencies: {} }], - producer: { sha, runId: '77', runAttempt: 2 }, - artifact: { id: 901, name: 'query', size: 42, digest: `sha256:${'d'.repeat(64)}` }, - toolchain: { - moon: 'moon 2.5.4', - bun: '1.4.2', - typescript: '6.0.3', - target: 'portable-typescript', - }, - }; - const recorded = { ...candidate, runId: '77', runAttempt: 2, producers: [receipt] }; - expect(() => assertCandidateBindingShape(recorded)).not.toThrow(); - expect(() => - assertCandidateBindingShape({ - ...recorded, - producers: [{ ...receipt, taskHash: undefined, hashes: [] }], - }), - ).toThrow(/producer hash chain is inconsistent/); - expect(() => assertCandidateBindingShape({ ...recorded, runAttempt: 3 })).not.toThrow(); - for (const producer of [ - { ...receipt.producer, sha: 'b'.repeat(40) }, - { ...receipt.producer, runId: '78' }, - { ...receipt.producer, runAttempt: 3 }, - ]) { - expect(() => - assertCandidateBindingShape({ ...recorded, producers: [{ ...receipt, producer }] }), - ).toThrow(/qualification SHA\/run/); - } - for (const runAttempt of [undefined, null, '1', 0, -1, 1.5, Number.MAX_SAFE_INTEGER + 1]) { - expect(() => assertCandidateBindingShape({ ...recorded, runAttempt })).toThrow( - /candidate runAttempt/, - ); - expect(() => - assertCandidateBindingShape({ - ...recorded, - producers: [{ ...receipt, producer: { ...receipt.producer, runAttempt } }], - }), - ).toThrow(/producer receipt runAttempt/); - } - receipt.hashes[0].dependencies = { 'query:build': 'passthrough' }; - expect(() => assertCandidateBindingShape(recorded)).toThrow(/dependency hash is incomplete/); } finally { cleanup(); } diff --git a/tools/release/require-workflow-success.test.sh b/tools/release/require-workflow-success.test.sh index e12aeb5b2..5afb1e7de 100644 --- a/tools/release/require-workflow-success.test.sh +++ b/tools/release/require-workflow-success.test.sh @@ -63,6 +63,20 @@ for mode in reuse active absent failed advanced ambiguous race uncovered; do *) [[ ! -e "$case_root/dispatch.json" ]] ;; esac done +for endpoint in jobs artifacts; do + prepare "qualification-transient-$endpoint" + bun tools/release/require-workflow-success.test.mts prepare "$case_root" reuse + invoke "qualification-transient-$endpoint" CI "$sha" 10 --job Qualified --artifact oliphaunt-release-candidate --plan-qualification '["oliphaunt-js"]' + expect_status 0 + rg -q 'qualification_request_required=false' "$case_root/output" + rg -q 'run_id=77' "$case_root/output" + [[ "$(cat "$case_root/state.inspection")" -ge 2 && ! -e "$case_root/dispatch.json" ]] + prepare "qualification-unavailable-$endpoint" + bun tools/release/require-workflow-success.test.mts prepare "$case_root" reuse + invoke "qualification-unavailable-$endpoint" CI "$sha" 0 --job Qualified --artifact oliphaunt-release-candidate --plan-qualification '["oliphaunt-js"]' + expect_status 1 + [[ ! -s "$case_root/output" && ! -e "$case_root/dispatch.json" ]] +done prepare transient invoke transient "${standard[@]}" expect_status 0 diff --git a/tools/release/testdata/require-workflow-success-github.mts b/tools/release/testdata/require-workflow-success-github.mts index c061c31ea..5ee5ec7e7 100644 --- a/tools/release/testdata/require-workflow-success-github.mts +++ b/tools/release/testdata/require-workflow-success-github.mts @@ -55,6 +55,13 @@ globalThis.fetch = async (input, options) => { const match = endpoint.match(/actions\/runs\/(77|88)(?:\/(jobs|artifacts))?(?:\?.*)?$/); if (match) { const id = Number(match[1]); + if (mode.endsWith(`-${match[2]}`) && /qualification-(?:transient|unavailable)-/.test(mode)) { + const state = `${process.env.FAKE_STATE}.inspection`; + const count = fs.existsSync(state) ? Number(fs.readFileSync(state, 'utf8')) : 0; + fs.writeFileSync(state, String(count + 1)); + if (count === 0 || mode.startsWith('qualification-unavailable-')) + return new Response('unavailable', { status: 504 }); + } if (match[2] === 'jobs') return Response.json({ jobs: [ From 798e0e9b14d315d9fc2b292a2159bb4c44c6ad77 Mon Sep 17 00:00:00 2001 From: Sid Jain Date: Thu, 1 Oct 2026 21:29:26 +0000 Subject: [PATCH 2/3] fix(ci): unify product selection and avoid redundant release work --- .github/workflows/release.yml | 171 ++++++++------------- .moon/tasks/inputs.yml | 4 + src/database-resources/moon.yml | 2 +- src/extensions/artifacts/native/moon.yml | 2 +- src/extensions/artifacts/packages/moon.yml | 4 +- src/extensions/artifacts/wasix/moon.yml | 2 +- src/native/postgres-tools/moon.yml | 2 +- src/native/runtime/moon.yml | 4 +- src/native/sdks/rust/moon.yml | 2 +- src/wasix/node-addon/moon.yml | 4 +- src/wasix/pgwire-server/moon.yml | 4 +- src/wasix/postgres-tools/moon.yml | 4 +- src/wasix/postmaster/moon.yml | 6 +- src/wasix/runtime/moon.yml | 7 +- src/wasix/sdks/rust/moon.yml | 4 +- tools/ci/ci-plan-node-products.test.mts | 60 ++++++++ tools/ci/ci-plan-test-inputs.mts | 5 + tools/ci/ci_plan.mts | 8 +- 18 files changed, 156 insertions(+), 139 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 8bea1bf61..d10822d06 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -109,6 +109,7 @@ jobs: - name: Set up Moon uses: ./.github/actions/setup-moon with: + task-cache: "false" install-workspace: "true" - name: Generate the Release Please candidate locally id: prepare_candidate @@ -155,6 +156,7 @@ jobs: - name: Set up Moon uses: ./.github/actions/setup-moon with: + task-cache: "false" install-workspace: "true" - name: Plan product releases id: release_plan @@ -282,14 +284,17 @@ jobs: - name: Restore exact publication source env: INPUT_RELEASE_COMMIT: ${{ fromJSON(needs.plan-candidate.outputs.release_head).sha }} - run: .github/scripts/resolve-release-head.sh + run: | + .github/scripts/resolve-release-head.sh + echo "OLIPHAUNT_GITHUB_RUN_SNAPSHOT_DIR=$RUNNER_TEMP/oliphaunt-github-run-snapshots" >> "$GITHUB_ENV" - name: Set up Moon uses: ./.github/actions/setup-moon with: + task-cache: "false" install-workspace: "true" - name: Require qualified release-commit CI run id: ci_qualification - if: ${{ fromJSON(needs.plan-candidate.outputs.release_plan).has_release_changes == 'true' && inputs.approval_run_id == '' }} + if: ${{ inputs.approval_run_id == '' }} env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} GH_REPO: ${{ github.repository }} @@ -321,7 +326,7 @@ jobs: fi bash .github/scripts/require-workflow-success.sh "${qualification_args[@]}" - name: Download exact-SHA qualification record - if: ${{ fromJSON(needs.plan-candidate.outputs.release_plan).has_release_changes == 'true' && inputs.approval_run_id == '' }} + if: ${{ inputs.approval_run_id == '' }} env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} GH_REPO: ${{ github.repository }} @@ -335,7 +340,7 @@ jobs: --job Qualified \ --artifact oliphaunt-release-candidate - name: Download exact-SHA affected plan - if: ${{ fromJSON(needs.plan-candidate.outputs.release_plan).has_release_changes == 'true' && inputs.approval_run_id == '' }} + if: ${{ inputs.approval_run_id == '' }} env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} GH_REPO: ${{ github.repository }} @@ -349,7 +354,7 @@ jobs: --job "Planning / Affected Work" \ --artifact artifact-build-plan - name: Download required exact-SHA WASIX evidence - if: ${{ fromJSON(needs.plan-candidate.outputs.release_plan).has_release_changes == 'true' && inputs.approval_run_id == '' && fromJSON(needs.plan-candidate.outputs.release_plan).requires_wasix_release_regression_evidence == 'true' }} + if: ${{ inputs.approval_run_id == '' && fromJSON(needs.plan-candidate.outputs.release_plan).requires_wasix_release_regression_evidence == 'true' }} env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} GH_REPO: ${{ github.repository }} @@ -363,7 +368,7 @@ jobs: --job "E2E / WASIX Extension Lifecycle" \ --artifact wasix-release-regression-evidence - name: Download required exact-SHA native evidence - if: ${{ fromJSON(needs.plan-candidate.outputs.release_plan).has_release_changes == 'true' && inputs.approval_run_id == '' && fromJSON(needs.plan-candidate.outputs.release_plan).requires_native_extension_lifecycle_evidence == 'true' }} + if: ${{ inputs.approval_run_id == '' && fromJSON(needs.plan-candidate.outputs.release_plan).requires_native_extension_lifecycle_evidence == 'true' }} env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} GH_REPO: ${{ github.repository }} @@ -378,7 +383,7 @@ jobs: --artifact native-extension-lifecycle-evidence - name: Verify exact-SHA qualification record id: verify_qualification - if: ${{ fromJSON(needs.plan-candidate.outputs.release_plan).has_release_changes == 'true' && inputs.approval_run_id == '' }} + if: ${{ inputs.approval_run_id == '' }} env: PRODUCTS_JSON: ${{ fromJSON(needs.plan-candidate.outputs.release_plan).products_json }} CI_RUN_ID: ${{ steps.ci_qualification.outputs.run_id }} @@ -396,7 +401,7 @@ jobs: --wasix-evidence-root target/release-candidate/wasix-evidence - name: Require the explicitly selected prepared candidate id: approved_publication_lock - if: ${{ fromJSON(needs.plan-candidate.outputs.release_plan).has_release_changes == 'true' && inputs.approval_run_id != '' }} + if: ${{ inputs.approval_run_id != '' }} env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} GH_REPO: ${{ github.repository }} @@ -418,7 +423,7 @@ jobs: "${approved_artifacts[@]}" cat "$gate_output" >> "$GITHUB_OUTPUT" - name: Download the approved lock and frozen candidate - if: ${{ fromJSON(needs.plan-candidate.outputs.release_plan).has_release_changes == 'true' && inputs.approval_run_id != '' }} + if: ${{ inputs.approval_run_id != '' }} uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c with: github-token: ${{ secrets.GITHUB_TOKEN }} @@ -427,7 +432,7 @@ jobs: merge-multiple: true path: ${{ runner.temp }}/approved-publication - name: Verify and install the complete approved candidate - if: ${{ fromJSON(needs.plan-candidate.outputs.release_plan).has_release_changes == 'true' && inputs.approval_run_id != '' }} + if: ${{ inputs.approval_run_id != '' }} env: PRODUCTS_JSON: ${{ fromJSON(needs.plan-candidate.outputs.release_plan).products_json }} APPROVAL_RUN_ID: ${{ inputs.approval_run_id }} @@ -441,7 +446,6 @@ jobs: --workspace-root "$GITHUB_WORKSPACE" - name: Validate product versions and registry state id: validate_release_registry_state - if: ${{ fromJSON(needs.plan-candidate.outputs.release_plan).has_release_changes == 'true' }} env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} @@ -451,14 +455,14 @@ jobs: --products-json "$PRODUCTS_JSON" \ --head-ref "$RELEASE_HEAD_SHA" - name: Download WASIX runtime build artifacts - if: ${{ fromJSON(needs.plan-candidate.outputs.release_plan).has_release_changes == 'true' && inputs.approval_run_id == '' && contains(fromJson(fromJSON(needs.plan-candidate.outputs.release_plan).products_json), 'liboliphaunt-wasix') }} + if: ${{ inputs.approval_run_id == '' && contains(fromJson(fromJSON(needs.plan-candidate.outputs.release_plan).products_json), 'liboliphaunt-wasix') }} env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} CI_RUN_ID: ${{ steps.ci_qualification.outputs.run_id }} RELEASE_ARTIFACT_SHA: ${{ fromJSON(needs.plan-candidate.outputs.release_head).sha }} run: bash .github/scripts/download-wasix-runtime-build-artifacts.sh - name: Download WASIX release assets - if: ${{ fromJSON(needs.plan-candidate.outputs.release_plan).has_release_changes == 'true' && inputs.approval_run_id == '' && contains(fromJson(fromJSON(needs.plan-candidate.outputs.release_plan).products_json), 'liboliphaunt-wasix') }} + if: ${{ inputs.approval_run_id == '' && contains(fromJson(fromJSON(needs.plan-candidate.outputs.release_plan).products_json), 'liboliphaunt-wasix') }} env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} GH_REPO: ${{ github.repository }} @@ -473,7 +477,7 @@ jobs: --job Builds \ --artifact liboliphaunt-wasix-release-assets - name: Download WASIX postmaster release assets - if: ${{ fromJSON(needs.plan-candidate.outputs.release_plan).has_release_changes == 'true' && inputs.approval_run_id == '' && contains(fromJson(fromJSON(needs.plan-candidate.outputs.release_plan).products_json), 'liboliphaunt-wasix-postmaster') }} + if: ${{ inputs.approval_run_id == '' && contains(fromJson(fromJSON(needs.plan-candidate.outputs.release_plan).products_json), 'liboliphaunt-wasix-postmaster') }} env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} GH_REPO: ${{ github.repository }} @@ -488,7 +492,7 @@ jobs: --job Builds \ --artifact liboliphaunt-wasix-postmaster-release-assets - name: Download exact-extension package artifacts - if: ${{ fromJSON(needs.plan-candidate.outputs.release_plan).has_release_changes == 'true' && inputs.approval_run_id == '' && fromJSON(needs.plan-candidate.outputs.release_plan).has_extension_artifacts == 'true' }} + if: ${{ inputs.approval_run_id == '' && fromJSON(needs.plan-candidate.outputs.release_plan).has_extension_artifacts == 'true' }} env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} GH_REPO: ${{ github.repository }} @@ -503,7 +507,7 @@ jobs: --job Builds \ --artifact oliphaunt-extension-package-artifacts - name: Download SDK package artifacts - if: ${{ fromJSON(needs.plan-candidate.outputs.release_plan).has_release_changes == 'true' && inputs.approval_run_id == '' }} + if: ${{ inputs.approval_run_id == '' }} env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} GH_REPO: ${{ github.repository }} @@ -529,7 +533,7 @@ jobs: download_sdk_artifact "$product" done < <(tools/dev/bun.sh tools/release/query.mts ci-products --family sdk-package --products-json "$PRODUCTS_JSON" --format lines) - name: Download liboliphaunt release assets - if: ${{ fromJSON(needs.plan-candidate.outputs.release_plan).has_release_changes == 'true' && inputs.approval_run_id == '' && contains(fromJson(fromJSON(needs.plan-candidate.outputs.release_plan).products_json), 'liboliphaunt-native') }} + if: ${{ inputs.approval_run_id == '' && contains(fromJson(fromJSON(needs.plan-candidate.outputs.release_plan).products_json), 'liboliphaunt-native') }} env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} GH_REPO: ${{ github.repository }} @@ -544,7 +548,7 @@ jobs: --job Builds \ --artifact liboliphaunt-native-release-assets - name: Download canonical database resources - if: ${{ fromJSON(needs.plan-candidate.outputs.release_plan).has_release_changes == 'true' && inputs.approval_run_id == '' && contains(fromJson(fromJSON(needs.plan-candidate.outputs.release_plan).products_json), 'database-resources') }} + if: ${{ inputs.approval_run_id == '' && contains(fromJson(fromJSON(needs.plan-candidate.outputs.release_plan).products_json), 'database-resources') }} env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} GH_REPO: ${{ github.repository }} @@ -565,7 +569,7 @@ jobs: --job Builds \ "${artifacts[@]}" - name: Download native helper release assets - if: ${{ fromJSON(needs.plan-candidate.outputs.release_plan).has_release_changes == 'true' && inputs.approval_run_id == '' && (contains(fromJson(fromJSON(needs.plan-candidate.outputs.release_plan).products_json), 'oliphaunt-broker') || contains(fromJson(fromJSON(needs.plan-candidate.outputs.release_plan).products_json), 'oliphaunt-node-direct') || contains(fromJson(fromJSON(needs.plan-candidate.outputs.release_plan).products_json), 'oliphaunt-wasix-napi') || contains(fromJson(fromJSON(needs.plan-candidate.outputs.release_plan).products_json), 'postgres-tools-native') || contains(fromJson(fromJSON(needs.plan-candidate.outputs.release_plan).products_json), 'postgres-tools-wasix')) }} + if: ${{ inputs.approval_run_id == '' && (contains(fromJson(fromJSON(needs.plan-candidate.outputs.release_plan).products_json), 'oliphaunt-broker') || contains(fromJson(fromJSON(needs.plan-candidate.outputs.release_plan).products_json), 'oliphaunt-node-direct') || contains(fromJson(fromJSON(needs.plan-candidate.outputs.release_plan).products_json), 'oliphaunt-wasix-napi') || contains(fromJson(fromJSON(needs.plan-candidate.outputs.release_plan).products_json), 'postgres-tools-native') || contains(fromJson(fromJSON(needs.plan-candidate.outputs.release_plan).products_json), 'postgres-tools-wasix')) }} env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} GH_REPO: ${{ github.repository }} @@ -619,7 +623,7 @@ jobs: target/oliphaunt-wasix-napi/release-assets fi - name: Download Node direct optional npm packages - if: ${{ fromJSON(needs.plan-candidate.outputs.release_plan).has_release_changes == 'true' && inputs.approval_run_id == '' && contains(fromJson(fromJSON(needs.plan-candidate.outputs.release_plan).products_json), 'oliphaunt-node-direct') }} + if: ${{ inputs.approval_run_id == '' && contains(fromJson(fromJSON(needs.plan-candidate.outputs.release_plan).products_json), 'oliphaunt-node-direct') }} env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} GH_REPO: ${{ github.repository }} @@ -638,7 +642,7 @@ jobs: --job Builds \ "${artifact_args[@]}" - name: Download WASIX Node-API optional npm packages - if: ${{ fromJSON(needs.plan-candidate.outputs.release_plan).has_release_changes == 'true' && inputs.approval_run_id == '' && contains(fromJson(fromJSON(needs.plan-candidate.outputs.release_plan).products_json), 'oliphaunt-wasix-napi') }} + if: ${{ inputs.approval_run_id == '' && contains(fromJson(fromJSON(needs.plan-candidate.outputs.release_plan).products_json), 'oliphaunt-wasix-napi') }} env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} GH_REPO: ${{ github.repository }} @@ -657,7 +661,7 @@ jobs: --job Builds \ "${artifact_args[@]}" - name: Freeze canonical Apple extension carrier input - if: ${{ fromJSON(needs.plan-candidate.outputs.release_plan).has_release_changes == 'true' && inputs.approval_run_id == '' && (contains(fromJson(fromJSON(needs.plan-candidate.outputs.release_plan).products_json), 'oliphaunt-swift') || contains(fromJson(fromJSON(needs.plan-candidate.outputs.release_plan).products_json), 'oliphaunt-react-native')) }} + if: ${{ inputs.approval_run_id == '' && (contains(fromJson(fromJSON(needs.plan-candidate.outputs.release_plan).products_json), 'oliphaunt-swift') || contains(fromJson(fromJSON(needs.plan-candidate.outputs.release_plan).products_json), 'oliphaunt-react-native')) }} env: PRODUCTS_JSON: ${{ fromJSON(needs.plan-candidate.outputs.release_plan).products_json }} includes_swift: ${{ contains(fromJson(fromJSON(needs.plan-candidate.outputs.release_plan).products_json), 'oliphaunt-swift') }} @@ -750,19 +754,19 @@ jobs: fi - name: Set up pinned npm publisher id: setup_github_stage_npm - if: ${{ fromJSON(needs.plan-candidate.outputs.release_plan).has_release_changes == 'true' && fromJSON(needs.plan-candidate.outputs.registry_needs).needs_npm == 'true' }} + if: ${{ fromJSON(needs.plan-candidate.outputs.registry_needs).needs_npm == 'true' }} timeout-minutes: 3 uses: ./.github/actions/setup-npm-publisher with: npm-version: ${{ env.NPM_VERSION }} - name: Verify unchanged candidate source before assembly id: validate_release - if: ${{ fromJSON(needs.plan-candidate.outputs.release_plan).has_release_changes == 'true' && inputs.approval_run_id == '' }} + if: ${{ inputs.approval_run_id == '' }} env: CANDIDATE_SHA: ${{ fromJSON(needs.plan-candidate.outputs.release_head).sha }} run: bash tools/release/qualified-release-replay.sh "$CANDIDATE_SHA" "$CANDIDATE_SHA" - name: Package public release carriers - if: ${{ fromJSON(needs.plan-candidate.outputs.release_plan).has_release_changes == 'true' && inputs.approval_run_id == '' }} + if: ${{ inputs.approval_run_id == '' }} env: OLIPHAUNT_BROKER_RELEASE_ASSET_INPUT_DIRS: ${{ github.workspace }}/target/oliphaunt-broker/release-assets OLIPHAUNT_NODE_ADDON_ASSET_INPUT_DIRS: ${{ github.workspace }}/target/oliphaunt-node-direct/release-assets @@ -771,7 +775,7 @@ jobs: run: bash tools/release/package-release-carriers.sh --products-json "$PRODUCTS_JSON" - name: Freeze exhaustive publication lock id: freeze_publication_lock - if: ${{ fromJSON(needs.plan-candidate.outputs.release_plan).has_release_changes == 'true' && inputs.approval_run_id == '' }} + if: ${{ inputs.approval_run_id == '' }} env: PRODUCTS_JSON: ${{ fromJSON(needs.plan-candidate.outputs.release_plan).products_json }} run: | @@ -825,7 +829,7 @@ jobs: --head-ref "$RELEASE_HEAD_SHA" - name: Freeze complete publication candidate id: freeze_publication_candidate - if: ${{ inputs.approval_run_id == '' && fromJSON(needs.plan-candidate.outputs.release_plan).has_release_changes == 'true' }} + if: ${{ inputs.approval_run_id == '' }} env: PRODUCTS_JSON: ${{ fromJSON(needs.plan-candidate.outputs.release_plan).products_json }} run: | @@ -837,11 +841,10 @@ jobs: --qualification-run-id "${{ steps.ci_qualification.outputs.run_id }}" \ --output target/release/oliphaunt-publication-candidate.tar - name: Preserve the approved recovery capsule unchanged - if: ${{ fromJSON(needs.plan-candidate.outputs.release_plan).has_release_changes == 'true' && inputs.approval_run_id != '' }} + if: ${{ inputs.approval_run_id != '' }} run: cp "$RUNNER_TEMP/approved-publication/oliphaunt-publication-candidate.tar" target/release/oliphaunt-publication-candidate.tar - name: Upload frozen publication lock id: preserve_publication_lock - if: ${{ fromJSON(needs.plan-candidate.outputs.release_plan).has_release_changes == 'true' }} uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a with: name: oliphaunt-publication-lock @@ -851,7 +854,6 @@ jobs: retention-days: 90 - name: Upload complete frozen publication candidate id: preserve_publication_candidate - if: ${{ fromJSON(needs.plan-candidate.outputs.release_plan).has_release_changes == 'true' }} uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a with: name: oliphaunt-publication-candidate @@ -861,7 +863,6 @@ jobs: retention-days: 90 - name: Check whether first registry identities need credentials id: bootstrap_credentials - if: ${{ fromJSON(needs.plan-candidate.outputs.release_plan).has_release_changes == 'true' }} timeout-minutes: 15 env: PRODUCTS_JSON: ${{ fromJSON(needs.plan-candidate.outputs.release_plan).products_json }} @@ -915,6 +916,7 @@ jobs: - name: Set up Moon uses: ./.github/actions/setup-moon with: + task-cache: "false" install-workspace: "false" - name: Require checked publishing code env: @@ -922,37 +924,20 @@ jobs: GH_REPO: ${{ github.repository }} run: bash .github/scripts/require-workflow-success.sh CI "$GITHUB_SHA" 0 --job Required - name: Prove Release Please PR can complete after bootstrap - if: ${{ fromJSON(needs.plan-candidate.outputs.release_plan).has_release_changes == 'true' }} env: GH_TOKEN: ${{ github.token }} RELEASE_SHA: ${{ fromJSON(needs.plan-candidate.outputs.verify_publication_candidate).release_sha }} run: | tools/dev/bun.sh tools/release/release-please-pr-lifecycle.mts \ assert-markable --release-sha "$RELEASE_SHA" --base main - - name: Resolve registry identity bootstrap scope - id: bootstrap_scope - if: ${{ fromJSON(needs.plan-candidate.outputs.release_plan).has_release_changes == 'true' }} - env: - NEEDS_CARGO: ${{ fromJSON(needs.plan-candidate.outputs.registry_needs).needs_cargo }} - NEEDS_NPM: ${{ fromJSON(needs.plan-candidate.outputs.registry_needs).needs_npm }} - run: | - required=false - if [[ "$NEEDS_CARGO" == true || "$NEEDS_NPM" == true ]]; then - required=true - fi - echo "required=$required" >> "$GITHUB_OUTPUT" - - name: No registry identities require bootstrap - if: ${{ fromJSON(needs.plan-candidate.outputs.release_plan).has_release_changes == 'true' && steps.bootstrap_scope.outputs.required != 'true' }} - run: echo 'The selected release has no Cargo or npm identities; bootstrap is a no-op.' - name: Set up pinned npm publisher id: setup_bootstrap_npm - if: ${{ steps.bootstrap_scope.outputs.required == 'true' && fromJSON(needs.plan-candidate.outputs.registry_needs).needs_npm == 'true' }} + if: ${{ fromJSON(needs.plan-candidate.outputs.registry_needs).needs_npm == 'true' }} timeout-minutes: 3 uses: ./.github/actions/setup-npm-publisher with: npm-version: ${{ env.NPM_VERSION }} - name: Preflight selected product tag and release collisions - if: ${{ steps.bootstrap_scope.outputs.required == 'true' }} env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} PRODUCTS_JSON: ${{ fromJSON(needs.plan-candidate.outputs.release_plan).products_json }} @@ -965,7 +950,6 @@ jobs: --products-json "$PRODUCTS_JSON" \ --head-ref "$RELEASE_HEAD_SHA" - name: Download the frozen candidate from preparation - if: ${{ fromJSON(needs.plan-candidate.outputs.release_plan).has_release_changes == 'true' }} uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c with: artifact-ids: ${{ format('{0},{1}', needs.prepare-candidate.outputs.lock_artifact_id, needs.prepare-candidate.outputs.candidate_artifact_id) }} @@ -973,7 +957,6 @@ jobs: merge-multiple: true - name: Verify and install approved publication candidate id: verify_bootstrap_candidate - if: ${{ steps.bootstrap_scope.outputs.required == 'true' }} env: PRODUCTS_JSON: ${{ fromJSON(needs.plan-candidate.outputs.release_plan).products_json }} APPROVAL_RUN_ID: ${{ inputs.approval_run_id || github.run_id }} @@ -991,7 +974,6 @@ jobs: --workspace-root "$GITHUB_WORKSPACE" - name: Verify external lock equals installed candidate lock id: verify_bootstrap_lock - if: ${{ steps.bootstrap_scope.outputs.required == 'true' }} run: | if ! cmp -s "$RUNNER_TEMP/approved-bootstrap/publication-lock.json" "$PUBLICATION_LOCK_PATH"; then echo 'installed candidate lock differs from the separately downloaded approved publication lock' >&2 @@ -1002,20 +984,17 @@ jobs: --head-ref "$RELEASE_HEAD_SHA" - name: Restore prior bootstrap checkpoint chain id: restore_bootstrap_checkpoint - if: ${{ steps.bootstrap_scope.outputs.required == 'true' }} env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} GH_REPO: ${{ github.repository }} run: bun .github/scripts/download-bootstrap-ledger.mts - name: Classify exact bootstrap credential needs id: bootstrap_credential_needs - if: ${{ steps.bootstrap_scope.outputs.required == 'true' }} env: PRODUCTS_JSON: ${{ fromJSON(needs.plan-candidate.outputs.release_plan).products_json }} REGISTRY_MUTATION_DEADLINE_EPOCH: ${{ env.REGISTRY_JOB_HARD_DEADLINE_EPOCH }} run: bun .github/scripts/bootstrap-registry-identities.mts --credential-needs - name: Require bootstrap credentials before mutation - if: ${{ steps.bootstrap_scope.outputs.required == 'true' }} env: CRATES_IO_BOOTSTRAP_TOKEN: ${{ secrets.CRATES_IO_BOOTSTRAP_TOKEN }} NPM_BOOTSTRAP_TOKEN: ${{ secrets.NPM_BOOTSTRAP_TOKEN }} @@ -1032,7 +1011,6 @@ jobs: fi - name: Create bootstrap transport tag token id: bootstrap_tag_token - if: ${{ steps.bootstrap_scope.outputs.required == 'true' }} uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 with: &release_tag_app client-id: ${{ secrets.RELEASE_TAG_APP_CLIENT_ID }} @@ -1043,7 +1021,6 @@ jobs: permission-workflows: write - name: Ensure exact immutable release transport ref id: ensure_bootstrap_transport_ref - if: ${{ steps.bootstrap_scope.outputs.required == 'true' }} timeout-minutes: 3 env: GH_TOKEN: ${{ steps.bootstrap_tag_token.outputs.token }} @@ -1053,7 +1030,6 @@ jobs: run: bash tools/release/publication-controller.sh "$RELEASE_HEAD_SHA" "$GITHUB_SHA" bun .github/scripts/release-transport-ref.mts ensure "$RELEASE_HEAD_SHA" - name: Start bounded bootstrap mutation window id: bootstrap_mutation_deadline - if: ${{ steps.bootstrap_scope.outputs.required == 'true' }} run: | if [[ ! "$BOOTSTRAP_REGISTRY_MUTATION_WINDOW_SECONDS" =~ ^[1-9][0-9]*$ ]]; then echo 'BOOTSTRAP_REGISTRY_MUTATION_WINDOW_SECONDS must be a positive integer' >&2 @@ -1073,7 +1049,7 @@ jobs: echo "Bootstrap registry mutation must stop before Unix time $deadline." - name: Configure npm identity-bootstrap authentication id: configure_bootstrap_npm_auth - if: ${{ steps.bootstrap_scope.outputs.required == 'true' && steps.bootstrap_credential_needs.outputs.needs_npm_token == 'true' }} + if: ${{ steps.bootstrap_credential_needs.outputs.needs_npm_token == 'true' }} env: NPM_BOOTSTRAP_TOKEN: ${{ secrets.NPM_BOOTSTRAP_TOKEN }} run: | @@ -1082,7 +1058,6 @@ jobs: printf '//registry.npmjs.org/:_authToken=%s\n' "$NPM_BOOTSTRAP_TOKEN" > "$npmrc" - name: Bootstrap missing Cargo and npm identities id: bootstrap_registry_identities - if: ${{ steps.bootstrap_scope.outputs.required == 'true' }} env: CARGO_REGISTRY_TOKEN: ${{ steps.bootstrap_credential_needs.outputs.needs_cargo_token == 'true' && secrets.CRATES_IO_BOOTSTRAP_TOKEN || '' }} NPM_CONFIG_USERCONFIG: ${{ runner.temp }}/oliphaunt-bootstrap.npmrc @@ -1248,6 +1223,7 @@ jobs: - name: Set up Moon uses: ./.github/actions/setup-moon with: + task-cache: "false" install-workspace: "true" - name: Require checked publishing code env: @@ -1258,13 +1234,11 @@ jobs: uses: ./.github/actions/setup-rust - name: Verify direct-workflow OIDC identity id: verify_oidc_identity - if: ${{ fromJSON(needs.plan-candidate.outputs.release_plan).has_release_changes == 'true' }} env: RELEASE_OPERATION: publish run: bun .github/scripts/verify-github-oidc-identity.mts - name: Prove Release Please PR can complete after publication id: assert_release_please_markable - if: ${{ fromJSON(needs.plan-candidate.outputs.release_plan).has_release_changes == 'true' }} timeout-minutes: 1 env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} @@ -1274,7 +1248,6 @@ jobs: --release-sha "${{ fromJSON(needs.plan-candidate.outputs.verify_publication_candidate).release_sha }}" \ --base main - name: Preflight selected product tag and release collisions - if: ${{ fromJSON(needs.plan-candidate.outputs.release_plan).has_release_changes == 'true' }} env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} PRODUCTS_JSON: ${{ fromJSON(needs.plan-candidate.outputs.release_plan).products_json }} @@ -1289,11 +1262,9 @@ jobs: --head-ref "$RELEASE_HEAD_SHA" - name: Check release tag App permissions id: check_release_tag_app - if: ${{ fromJSON(needs.plan-candidate.outputs.release_plan).has_release_changes == 'true' }} uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 with: *release_tag_app - name: Check publish environment - if: ${{ fromJSON(needs.plan-candidate.outputs.release_plan).has_release_changes == 'true' }} env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} @@ -1305,7 +1276,6 @@ jobs: ORG_GRADLE_PROJECT_signingInMemoryKeyPassword: ${{ secrets.MAVEN_GPG_PASSPHRASE }} run: tools/release/check_publish_environment.mts --products-json "${PRODUCTS_JSON}" - name: Verify external registry ownership and trust links - if: ${{ fromJSON(needs.plan-candidate.outputs.release_plan).has_release_changes == 'true' }} env: PRODUCTS_JSON: ${{ fromJSON(needs.plan-candidate.outputs.release_plan).products_json }} ORG_GRADLE_PROJECT_mavenCentralUsername: ${{ secrets.MAVEN_CENTRAL_USERNAME }} @@ -1313,7 +1283,7 @@ jobs: run: bun .github/scripts/verify-external-publish-readiness.mts - name: Import, sign, and verify Maven credentials before mutation id: verify_maven_signing - if: ${{ fromJSON(needs.plan-candidate.outputs.release_plan).has_release_changes == 'true' && fromJSON(needs.plan-candidate.outputs.registry_needs).needs_maven == 'true' }} + if: ${{ fromJSON(needs.plan-candidate.outputs.registry_needs).needs_maven == 'true' }} timeout-minutes: 2 env: ORG_GRADLE_PROJECT_signingInMemoryKey: ${{ secrets.MAVEN_GPG_PRIVATE_KEY }} @@ -1321,14 +1291,12 @@ jobs: ORG_GRADLE_PROJECT_signingInMemoryKeyPassword: ${{ secrets.MAVEN_GPG_PASSPHRASE }} run: bash tools/release/verify-maven-signing-readiness.sh - name: Download the frozen candidate from preparation - if: ${{ fromJSON(needs.plan-candidate.outputs.release_plan).has_release_changes == 'true' }} uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c with: artifact-ids: ${{ format('{0},{1}', needs.prepare-candidate.outputs.lock_artifact_id, needs.prepare-candidate.outputs.candidate_artifact_id) }} path: ${{ runner.temp }}/approved-publication merge-multiple: true - name: Verify and install the complete approved candidate - if: ${{ fromJSON(needs.plan-candidate.outputs.release_plan).has_release_changes == 'true' }} env: PRODUCTS_JSON: ${{ fromJSON(needs.plan-candidate.outputs.release_plan).products_json }} APPROVAL_RUN_ID: ${{ inputs.approval_run_id || github.run_id }} @@ -1342,7 +1310,6 @@ jobs: --workspace-root "$GITHUB_WORKSPACE" - name: Validate product versions and registry state id: validate_release_registry_state - if: ${{ fromJSON(needs.plan-candidate.outputs.release_plan).has_release_changes == 'true' }} env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} @@ -1353,14 +1320,14 @@ jobs: --head-ref "$RELEASE_HEAD_SHA" - name: Set up pinned npm publisher id: setup_github_stage_npm - if: ${{ fromJSON(needs.plan-candidate.outputs.release_plan).has_release_changes == 'true' && fromJSON(needs.plan-candidate.outputs.registry_needs).needs_npm == 'true' }} + if: ${{ fromJSON(needs.plan-candidate.outputs.registry_needs).needs_npm == 'true' }} timeout-minutes: 3 uses: ./.github/actions/setup-npm-publisher with: npm-version: ${{ env.NPM_VERSION }} - name: Assemble and sign the exact Maven Central bundle before release mutation id: preflight_maven_bundle - if: ${{ fromJSON(needs.plan-candidate.outputs.release_plan).has_release_changes == 'true' && fromJSON(needs.plan-candidate.outputs.registry_needs).needs_maven == 'true' }} + if: ${{ fromJSON(needs.plan-candidate.outputs.registry_needs).needs_maven == 'true' }} timeout-minutes: 15 env: PRODUCTS_JSON: ${{ fromJSON(needs.plan-candidate.outputs.release_plan).products_json }} @@ -1374,7 +1341,7 @@ jobs: --release-commit "$RELEASE_HEAD_SHA" - name: Prove the exact SwiftPM source tag is remotely collision-free id: preflight_swift_source_tag - if: ${{ fromJSON(needs.plan-candidate.outputs.release_plan).has_release_changes == 'true' && contains(fromJson(fromJSON(needs.plan-candidate.outputs.release_plan).products_json), 'oliphaunt-swift') }} + if: ${{ contains(fromJson(fromJSON(needs.plan-candidate.outputs.release_plan).products_json), 'oliphaunt-swift') }} timeout-minutes: 2 run: | bash tools/release/publish-swiftpm-source-tag.sh --preflight \ @@ -1382,7 +1349,7 @@ jobs: --release-commit "$RELEASE_HEAD_SHA" - name: Classify pre-tag registry publication state id: bootstrap_ledger_state - if: ${{ fromJSON(needs.plan-candidate.outputs.release_plan).has_release_changes == 'true' && (fromJSON(needs.plan-candidate.outputs.registry_needs).needs_cargo == 'true' || fromJSON(needs.plan-candidate.outputs.registry_needs).needs_npm == 'true') }} + if: ${{ (fromJSON(needs.plan-candidate.outputs.registry_needs).needs_cargo == 'true' || fromJSON(needs.plan-candidate.outputs.registry_needs).needs_npm == 'true') }} env: PRODUCTS_JSON: ${{ fromJSON(needs.plan-candidate.outputs.release_plan).products_json }} RELEASE_HEAD_SHA: ${{ steps.release_head.outputs.sha }} @@ -1411,7 +1378,6 @@ jobs: --require-complete \ --verify-registries - name: Upload publication lock audit evidence - if: ${{ fromJSON(needs.plan-candidate.outputs.release_plan).has_release_changes == 'true' }} uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a with: name: oliphaunt-publication-lock-${{ inputs.operation }} @@ -1421,18 +1387,15 @@ jobs: retention-days: 90 - name: Create release tag token id: release_tag_token - if: ${{ fromJSON(needs.plan-candidate.outputs.release_plan).has_release_changes == 'true' }} uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 with: *release_tag_app - name: Reserve release transport content write - if: ${{ fromJSON(needs.plan-candidate.outputs.release_plan).has_release_changes == 'true' }} timeout-minutes: 3 run: | tools/dev/bun.sh tools/release/github-content-write-pacer.mts reserve \ --label "release transport tag" - name: Ensure exact immutable release transport ref id: ensure_release_transport_ref - if: ${{ fromJSON(needs.plan-candidate.outputs.release_plan).has_release_changes == 'true' }} timeout-minutes: 3 env: GH_TOKEN: ${{ steps.release_tag_token.outputs.token }} @@ -1442,7 +1405,6 @@ jobs: run: bash tools/release/publication-controller.sh "$RELEASE_HEAD_SHA" "$GITHUB_SHA" bun .github/scripts/release-transport-ref.mts ensure "$RELEASE_HEAD_SHA" - name: Stage exact-SHA product tags and draft releases id: stage_github_releases - if: ${{ fromJSON(needs.plan-candidate.outputs.release_plan).has_release_changes == 'true' }} timeout-minutes: 31 env: GH_TOKEN: ${{ steps.release_tag_token.outputs.token }} @@ -1454,7 +1416,6 @@ jobs: --state staged - name: Verify exact product tags id: verify_product_tags - if: ${{ fromJSON(needs.plan-candidate.outputs.release_plan).has_release_changes == 'true' }} timeout-minutes: 5 env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} @@ -1462,7 +1423,6 @@ jobs: run: bash tools/release/verify-product-tags.sh --products-json "${PRODUCTS_JSON}" --target "$RELEASE_HEAD_SHA" - name: Verify exact-SHA GitHub release staging id: verify_github_staging - if: ${{ fromJSON(needs.plan-candidate.outputs.release_plan).has_release_changes == 'true' }} timeout-minutes: 5 env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} @@ -1470,7 +1430,6 @@ jobs: run: bun .github/scripts/manage-release-drafts.mts verify --products-json "$PRODUCTS_JSON" --head-ref "$RELEASE_HEAD_SHA" --state staged - name: Publish all selected GitHub release asset sets concurrently id: publish_github_assets - if: ${{ fromJSON(needs.plan-candidate.outputs.release_plan).has_release_changes == 'true' }} timeout-minutes: 95 env: GITHUB_RELEASE_ASSET_UPLOAD_REPORT_PATH: ${{ runner.temp }}/oliphaunt-github-release-asset-upload-report.json @@ -1479,7 +1438,7 @@ jobs: run: bash tools/release/with-source.sh "$RELEASE_HEAD_SHA" bash tools/dev/bun.sh tools/release/release-publish.mts publish --step github-release-assets --products-json "${PRODUCTS_JSON}" --head-ref "$RELEASE_HEAD_SHA" --publication-lock "$PUBLICATION_LOCK_PATH" - name: Resolve exact selected extension attestation subjects id: extension_attestation_subjects - if: ${{ fromJSON(needs.plan-candidate.outputs.release_plan).has_release_changes == 'true' && fromJSON(needs.plan-candidate.outputs.release_plan).has_extension_products == 'true' }} + if: ${{ fromJSON(needs.plan-candidate.outputs.release_plan).has_extension_products == 'true' }} env: EXTENSION_PRODUCTS_JSON: ${{ fromJSON(needs.plan-candidate.outputs.release_plan).extension_products_json }} run: | @@ -1488,37 +1447,37 @@ jobs: --products-json "$EXTENSION_PRODUCTS_JSON" \ --github-output "$GITHUB_OUTPUT" - name: Reserve extension provenance write (batch 1) - if: ${{ fromJSON(needs.plan-candidate.outputs.release_plan).has_release_changes == 'true' && fromJSON(needs.plan-candidate.outputs.release_plan).has_extension_products == 'true' && steps.extension_attestation_subjects.outputs.nonempty_1 == 'true' }} + if: ${{ fromJSON(needs.plan-candidate.outputs.release_plan).has_extension_products == 'true' && steps.extension_attestation_subjects.outputs.nonempty_1 == 'true' }} run: | tools/dev/bun.sh tools/release/github-content-write-pacer.mts reserve --label "extension provenance batch 1" tools/dev/bun.sh tools/release/github-core-request-journal.mts reserve --label "extension provenance batch 1 API attempt" - name: Attest extension release assets (batch 1) id: attest_extensions_1 - if: ${{ fromJSON(needs.plan-candidate.outputs.release_plan).has_release_changes == 'true' && fromJSON(needs.plan-candidate.outputs.release_plan).has_extension_products == 'true' && steps.extension_attestation_subjects.outputs.nonempty_1 == 'true' }} + if: ${{ fromJSON(needs.plan-candidate.outputs.release_plan).has_extension_products == 'true' && steps.extension_attestation_subjects.outputs.nonempty_1 == 'true' }} timeout-minutes: 5 uses: actions/attest-build-provenance@43d14bc2b83dec42d39ecae14e916627a18bb661 with: subject-path: ${{ steps.extension_attestation_subjects.outputs.paths_1 }} - name: Reserve extension provenance write (batch 2) - if: ${{ fromJSON(needs.plan-candidate.outputs.release_plan).has_release_changes == 'true' && fromJSON(needs.plan-candidate.outputs.release_plan).has_extension_products == 'true' && steps.extension_attestation_subjects.outputs.nonempty_2 == 'true' }} + if: ${{ fromJSON(needs.plan-candidate.outputs.release_plan).has_extension_products == 'true' && steps.extension_attestation_subjects.outputs.nonempty_2 == 'true' }} run: | tools/dev/bun.sh tools/release/github-content-write-pacer.mts reserve --label "extension provenance batch 2" tools/dev/bun.sh tools/release/github-core-request-journal.mts reserve --label "extension provenance batch 2 API attempt" - name: Attest extension release assets (batch 2) id: attest_extensions_2 - if: ${{ fromJSON(needs.plan-candidate.outputs.release_plan).has_release_changes == 'true' && fromJSON(needs.plan-candidate.outputs.release_plan).has_extension_products == 'true' && steps.extension_attestation_subjects.outputs.nonempty_2 == 'true' }} + if: ${{ fromJSON(needs.plan-candidate.outputs.release_plan).has_extension_products == 'true' && steps.extension_attestation_subjects.outputs.nonempty_2 == 'true' }} timeout-minutes: 5 uses: actions/attest-build-provenance@43d14bc2b83dec42d39ecae14e916627a18bb661 with: subject-path: ${{ steps.extension_attestation_subjects.outputs.paths_2 }} - name: Reserve liboliphaunt attestation content write - if: ${{ fromJSON(needs.plan-candidate.outputs.release_plan).has_release_changes == 'true' && contains(fromJson(fromJSON(needs.plan-candidate.outputs.release_plan).products_json), 'liboliphaunt-native') }} + if: ${{ contains(fromJson(fromJSON(needs.plan-candidate.outputs.release_plan).products_json), 'liboliphaunt-native') }} run: | tools/dev/bun.sh tools/release/github-content-write-pacer.mts reserve --label "liboliphaunt native attestation" tools/dev/bun.sh tools/release/github-core-request-journal.mts reserve --label "liboliphaunt native attestation API attempt" - name: Attest liboliphaunt release assets id: attest_liboliphaunt_native - if: ${{ fromJSON(needs.plan-candidate.outputs.release_plan).has_release_changes == 'true' && contains(fromJson(fromJSON(needs.plan-candidate.outputs.release_plan).products_json), 'liboliphaunt-native') }} + if: ${{ contains(fromJson(fromJSON(needs.plan-candidate.outputs.release_plan).products_json), 'liboliphaunt-native') }} timeout-minutes: 5 uses: actions/attest-build-provenance@43d14bc2b83dec42d39ecae14e916627a18bb661 with: @@ -1531,24 +1490,24 @@ jobs: target/extension-artifacts/liboliphaunt-native/oliphaunt-extension-contrib-pg18/release-assets/* - name: Create fresh SwiftPM tag token id: swift_tag_token - if: ${{ fromJSON(needs.plan-candidate.outputs.release_plan).has_release_changes == 'true' && contains(fromJson(fromJSON(needs.plan-candidate.outputs.release_plan).products_json), 'oliphaunt-swift') }} + if: ${{ contains(fromJson(fromJSON(needs.plan-candidate.outputs.release_plan).products_json), 'oliphaunt-swift') }} uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 with: *release_tag_app - name: Publish Swift SDK GitHub release and SwiftPM tags id: publish_swift_source_tag - if: ${{ fromJSON(needs.plan-candidate.outputs.release_plan).has_release_changes == 'true' && contains(fromJson(fromJSON(needs.plan-candidate.outputs.release_plan).products_json), 'oliphaunt-swift') }} + if: ${{ contains(fromJson(fromJSON(needs.plan-candidate.outputs.release_plan).products_json), 'oliphaunt-swift') }} timeout-minutes: 6 env: GH_TOKEN: ${{ steps.swift_tag_token.outputs.token }} run: bash tools/release/publish-swiftpm-source-tag.sh --push --target "$RELEASE_HEAD_SHA" --publication-lock "$PUBLICATION_LOCK_PATH" - name: Reserve broker attestation content write - if: ${{ fromJSON(needs.plan-candidate.outputs.release_plan).has_release_changes == 'true' && contains(fromJson(fromJSON(needs.plan-candidate.outputs.release_plan).products_json), 'oliphaunt-broker') }} + if: ${{ contains(fromJson(fromJSON(needs.plan-candidate.outputs.release_plan).products_json), 'oliphaunt-broker') }} run: | tools/dev/bun.sh tools/release/github-content-write-pacer.mts reserve --label "broker attestation" tools/dev/bun.sh tools/release/github-core-request-journal.mts reserve --label "broker attestation API attempt" - name: Attest broker release assets id: attest_broker - if: ${{ fromJSON(needs.plan-candidate.outputs.release_plan).has_release_changes == 'true' && contains(fromJson(fromJSON(needs.plan-candidate.outputs.release_plan).products_json), 'oliphaunt-broker') }} + if: ${{ contains(fromJson(fromJSON(needs.plan-candidate.outputs.release_plan).products_json), 'oliphaunt-broker') }} timeout-minutes: 5 uses: actions/attest-build-provenance@43d14bc2b83dec42d39ecae14e916627a18bb661 with: @@ -1557,13 +1516,13 @@ jobs: target/oliphaunt-broker/release-assets/*.zip target/oliphaunt-broker/release-assets/*.sha256 - name: Reserve Node direct attestation content write - if: ${{ fromJSON(needs.plan-candidate.outputs.release_plan).has_release_changes == 'true' && contains(fromJson(fromJSON(needs.plan-candidate.outputs.release_plan).products_json), 'oliphaunt-node-direct') }} + if: ${{ contains(fromJson(fromJSON(needs.plan-candidate.outputs.release_plan).products_json), 'oliphaunt-node-direct') }} run: | tools/dev/bun.sh tools/release/github-content-write-pacer.mts reserve --label "Node direct attestation" tools/dev/bun.sh tools/release/github-core-request-journal.mts reserve --label "Node direct attestation API attempt" - name: Attest Node direct release assets id: attest_node_direct - if: ${{ fromJSON(needs.plan-candidate.outputs.release_plan).has_release_changes == 'true' && contains(fromJson(fromJSON(needs.plan-candidate.outputs.release_plan).products_json), 'oliphaunt-node-direct') }} + if: ${{ contains(fromJson(fromJSON(needs.plan-candidate.outputs.release_plan).products_json), 'oliphaunt-node-direct') }} timeout-minutes: 5 uses: actions/attest-build-provenance@43d14bc2b83dec42d39ecae14e916627a18bb661 with: @@ -1572,13 +1531,13 @@ jobs: target/oliphaunt-node-direct/release-assets/*.zip target/oliphaunt-node-direct/release-assets/*.sha256 - name: Reserve WASIX Node-API attestation content write - if: ${{ fromJSON(needs.plan-candidate.outputs.release_plan).has_release_changes == 'true' && contains(fromJson(fromJSON(needs.plan-candidate.outputs.release_plan).products_json), 'oliphaunt-wasix-napi') }} + if: ${{ contains(fromJson(fromJSON(needs.plan-candidate.outputs.release_plan).products_json), 'oliphaunt-wasix-napi') }} run: | tools/dev/bun.sh tools/release/github-content-write-pacer.mts reserve --label "WASIX Node-API attestation" tools/dev/bun.sh tools/release/github-core-request-journal.mts reserve --label "WASIX Node-API attestation API attempt" - name: Attest WASIX Node-API release assets id: attest_wasix_napi - if: ${{ fromJSON(needs.plan-candidate.outputs.release_plan).has_release_changes == 'true' && contains(fromJson(fromJSON(needs.plan-candidate.outputs.release_plan).products_json), 'oliphaunt-wasix-napi') }} + if: ${{ contains(fromJson(fromJSON(needs.plan-candidate.outputs.release_plan).products_json), 'oliphaunt-wasix-napi') }} timeout-minutes: 5 uses: actions/attest-build-provenance@43d14bc2b83dec42d39ecae14e916627a18bb661 with: @@ -1587,13 +1546,13 @@ jobs: target/oliphaunt-wasix-napi/release-assets/*.zip target/oliphaunt-wasix-napi/release-assets/*.sha256 - name: Reserve WASIX attestation content write - if: ${{ fromJSON(needs.plan-candidate.outputs.release_plan).has_release_changes == 'true' && contains(fromJson(fromJSON(needs.plan-candidate.outputs.release_plan).products_json), 'liboliphaunt-wasix') }} + if: ${{ contains(fromJson(fromJSON(needs.plan-candidate.outputs.release_plan).products_json), 'liboliphaunt-wasix') }} run: | tools/dev/bun.sh tools/release/github-content-write-pacer.mts reserve --label "WASIX attestation" tools/dev/bun.sh tools/release/github-core-request-journal.mts reserve --label "WASIX attestation API attempt" - name: Attest WASIX release assets id: attest_wasix - if: ${{ fromJSON(needs.plan-candidate.outputs.release_plan).has_release_changes == 'true' && contains(fromJson(fromJSON(needs.plan-candidate.outputs.release_plan).products_json), 'liboliphaunt-wasix') }} + if: ${{ contains(fromJson(fromJSON(needs.plan-candidate.outputs.release_plan).products_json), 'liboliphaunt-wasix') }} timeout-minutes: 5 uses: actions/attest-build-provenance@43d14bc2b83dec42d39ecae14e916627a18bb661 with: @@ -1602,13 +1561,13 @@ jobs: target/oliphaunt-wasix/release-assets/*.sha256 target/extension-artifacts/liboliphaunt-wasix/oliphaunt-extension-contrib-pg18/release-assets/* - name: Reserve WASIX postmaster attestation content write - if: ${{ fromJSON(needs.plan-candidate.outputs.release_plan).has_release_changes == 'true' && contains(fromJson(fromJSON(needs.plan-candidate.outputs.release_plan).products_json), 'liboliphaunt-wasix-postmaster') }} + if: ${{ contains(fromJson(fromJSON(needs.plan-candidate.outputs.release_plan).products_json), 'liboliphaunt-wasix-postmaster') }} run: | tools/dev/bun.sh tools/release/github-content-write-pacer.mts reserve --label "WASIX postmaster attestation" tools/dev/bun.sh tools/release/github-core-request-journal.mts reserve --label "WASIX postmaster attestation API attempt" - name: Attest WASIX postmaster release assets id: attest_wasix_postmaster - if: ${{ fromJSON(needs.plan-candidate.outputs.release_plan).has_release_changes == 'true' && contains(fromJson(fromJSON(needs.plan-candidate.outputs.release_plan).products_json), 'liboliphaunt-wasix-postmaster') }} + if: ${{ contains(fromJson(fromJSON(needs.plan-candidate.outputs.release_plan).products_json), 'liboliphaunt-wasix-postmaster') }} timeout-minutes: 5 uses: actions/attest-build-provenance@43d14bc2b83dec42d39ecae14e916627a18bb661 with: @@ -1617,7 +1576,6 @@ jobs: target/oliphaunt-wasix-postmaster/release-assets/*.sha256 - name: Freeze exact GitHub release asset and attestation evidence id: freeze_github_evidence - if: ${{ fromJSON(needs.plan-candidate.outputs.release_plan).has_release_changes == 'true' }} timeout-minutes: 10 env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} @@ -1655,7 +1613,6 @@ jobs: "${bundle_args[@]}" - name: Open registry publication window id: registry_publication_window - if: ${{ fromJSON(needs.plan-candidate.outputs.release_plan).has_release_changes == 'true' }} run: | for name in RELEASE_JOB_HARD_DEADLINE_EPOCH POST_REGISTRY_RESERVE_SECONDS; do if [[ ! "${!name:-}" =~ ^[1-9][0-9]*$ ]]; then @@ -1671,7 +1628,6 @@ jobs: echo "REGISTRY_MUTATION_DEADLINE_EPOCH=$mutation_deadline" >> "$GITHUB_ENV" - name: Publish and reconcile every exact-lock registry carrier id: publish_registries - if: ${{ fromJSON(needs.plan-candidate.outputs.release_plan).has_release_changes == 'true' }} timeout-minutes: 240 env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} @@ -1685,7 +1641,6 @@ jobs: --publication-lock "$PUBLICATION_LOCK_PATH" - name: Verify published release id: verify_published_release - if: ${{ fromJSON(needs.plan-candidate.outputs.release_plan).has_release_changes == 'true' }} timeout-minutes: 8 env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} @@ -1702,7 +1657,6 @@ jobs: --github-release-receipt target/release/github-release-attestation-receipt.json - name: Resolve and install exact public consumer surfaces id: public_consumer_smoke - if: ${{ fromJSON(needs.plan-candidate.outputs.release_plan).has_release_changes == 'true' }} timeout-minutes: 15 env: PRODUCTS_JSON: ${{ fromJSON(needs.plan-candidate.outputs.release_plan).products_json }} @@ -1716,7 +1670,6 @@ jobs: --output target/release/public-consumer-smoke.json - name: Preserve public consumer evidence id: preserve_consumer_evidence - if: ${{ fromJSON(needs.plan-candidate.outputs.release_plan).has_release_changes == 'true' }} timeout-minutes: 2 uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a with: @@ -1727,7 +1680,6 @@ jobs: retention-days: 90 - name: Reverify exact publication lock before promotion id: reverify_publication_lock - if: ${{ fromJSON(needs.plan-candidate.outputs.release_plan).has_release_changes == 'true' }} timeout-minutes: 2 run: | bash tools/release/with-source.sh "$RELEASE_HEAD_SHA" bash tools/dev/bun.sh tools/release/publication-lock.mts \ @@ -1736,7 +1688,7 @@ jobs: --head-ref "$RELEASE_HEAD_SHA" - name: Preserve release evidence id: preserve_release_evidence - if: ${{ always() && fromJSON(needs.plan-candidate.outputs.release_plan).has_release_changes == 'true' }} + if: ${{ always() }} continue-on-error: true timeout-minutes: 3 uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a @@ -1754,7 +1706,6 @@ jobs: retention-days: 90 - name: Promote verified GitHub release drafts id: promote_github_releases - if: ${{ fromJSON(needs.plan-candidate.outputs.release_plan).has_release_changes == 'true' }} timeout-minutes: 16 env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.moon/tasks/inputs.yml b/.moon/tasks/inputs.yml index 901c83ac1..ef06d28b5 100644 --- a/.moon/tasks/inputs.yml +++ b/.moon/tasks/inputs.yml @@ -22,6 +22,10 @@ fileGroups: - "/src/third-party/postgres/COPYRIGHT" - "/src/third-party/icu/LICENSE" - "/src/third-party/openssl/LICENSE.txt" + packaging-source: + - "/tools/packaging/*.{mjs,mts,sh}" + - "!/tools/packaging/*.test.{mjs,mts,sh}" + - "!/tools/packaging/test.sh" release-archive-contract: - "/tools/packaging/windows-vc-runtime-policy.json" - "/tools/packaging/release-directory-safety.mts" diff --git a/src/database-resources/moon.yml b/src/database-resources/moon.yml index a2dd33ab8..3ef82322d 100644 --- a/src/database-resources/moon.yml +++ b/src/database-resources/moon.yml @@ -77,7 +77,7 @@ tasks: tags: ["release", "artifact-package", "ci-js-sdk-package"] deps: ["database-resources:build-icu-data"] command: "bash src/database-resources/icu/tools/package.sh" - inputs: ["icu/**/*", "tools/**/*", "contracts/**/*", "!**/*.test.*", "VERSION", "/tools/packaging/**/*.mts", "!/tools/packaging/**/*.test.*"] + inputs: ["icu/**/*", "tools/**/*", "contracts/**/*", "!**/*.test.*", "VERSION", "@group(packaging-source)"] outputs: ["/target/database-resources/release-assets/*icu-data.tar.gz", "/target/database-resources/cargo-artifacts/**/*", "/target/release/npm-packages/oliphaunt-icu/*.tgz", "/target/release/maven-manifests/database-resources.tsv", "/target/release/maven-staging/database-resources/**/*"] options: runFromWorkspaceRoot: true diff --git a/src/extensions/artifacts/native/moon.yml b/src/extensions/artifacts/native/moon.yml index 1d73bebbf..f0d413683 100644 --- a/src/extensions/artifacts/native/moon.yml +++ b/src/extensions/artifacts/native/moon.yml @@ -91,7 +91,7 @@ tasks: - /tools/packaging/check-linux-consumer-baseline.sh - /src/third-party/tools/source-fetch-core.mts - /src/extensions/tools/extension-upstream-licenses.mts - - "/tools/packaging/*.{mjs,mts}" + - "@group(packaging-source)" - /src/extensions/tools/extension-artifact-archive-policy.mts - /src/extensions/artifacts/native/tools/native-extension-asset-index-contract.mts - /src/native/runtime/tools/native-runtime-payload-policy.json diff --git a/src/extensions/artifacts/packages/moon.yml b/src/extensions/artifacts/packages/moon.yml index 359a379bf..a21b7fa40 100644 --- a/src/extensions/artifacts/packages/moon.yml +++ b/src/extensions/artifacts/packages/moon.yml @@ -53,7 +53,7 @@ tasks: - /tools/dev/bun.sh - /src/third-party/tools/source-fetch-core.mts - /src/extensions/artifacts/packages/tools/build-extension-ci-artifacts.mts - - "/tools/packaging/*.{mjs,mts}" + - "@group(packaging-source)" - /tools/packaging/cargo-source-package.mts - /src/extensions/artifacts/packages/tools/check-carriers.mts - /src/extensions/artifacts/packages/tools/extension-runtime-asset-contract.mts @@ -107,7 +107,7 @@ tasks: - /tools/dev/bun.sh - /src/third-party/tools/source-fetch-core.mts - /src/extensions/artifacts/packages/tools/build-extension-ci-artifacts.mts - - "/tools/packaging/*.{mjs,mts}" + - "@group(packaging-source)" - /tools/packaging/cargo-source-package.mts - /src/extensions/artifacts/packages/tools/check-carriers.mts - /src/extensions/artifacts/packages/tools/extension-runtime-asset-contract.mts diff --git a/src/extensions/artifacts/wasix/moon.yml b/src/extensions/artifacts/wasix/moon.yml index 8b2f966bb..d4e23f42e 100644 --- a/src/extensions/artifacts/wasix/moon.yml +++ b/src/extensions/artifacts/wasix/moon.yml @@ -47,7 +47,7 @@ tasks: group: "version" - "tools/package-release-assets.mts" - "tools/package-release-assets.sh" - - "/tools/packaging/*.{mjs,mts}" + - "@group(packaging-source)" - project: "extension-runtime-contract" group: "contract" outputs: diff --git a/src/native/postgres-tools/moon.yml b/src/native/postgres-tools/moon.yml index fe8240524..3462e4809 100644 --- a/src/native/postgres-tools/moon.yml +++ b/src/native/postgres-tools/moon.yml @@ -63,7 +63,7 @@ tasks: - "tools/package-assets.sh" - "/src/native/runtime/tools/runtime-preflight.sh" - "/src/native/runtime/tools/native-runtime-payload*" - - "/tools/packaging/*.{mts,sh}" + - "@group(packaging-source)" - "@group(legal-files)" outputs: ["/target/postgres-tools/native/release-assets/**/*"] options: diff --git a/src/native/runtime/moon.yml b/src/native/runtime/moon.yml index f8f5bfb63..c4ad55fe7 100644 --- a/src/native/runtime/moon.yml +++ b/src/native/runtime/moon.yml @@ -349,7 +349,7 @@ tasks: - /tools/packaging/release-notices.mts - /tools/packaging/strip-native-binaries.sh - /tools/packaging/platform-binary-contract.mts - - "/tools/packaging/*.{mjs,mts}" + - "@group(packaging-source)" outputs: - /target/liboliphaunt/desktop-release-assets/**/* options: @@ -609,7 +609,7 @@ tasks: - project: extensions group: sdk-metadata - /src/native/runtime/moon.yml - - "/tools/packaging/*.{mjs,mts}" + - "@group(packaging-source)" - /src/native/runtime/tools/check-release-assets.mts - /src/native/runtime/tools/package-liboliphaunt-aggregate-assets.sh - "@group(release-target-contract)" diff --git a/src/native/sdks/rust/moon.yml b/src/native/sdks/rust/moon.yml index 268d35e86..ddb72cecc 100644 --- a/src/native/sdks/rust/moon.yml +++ b/src/native/sdks/rust/moon.yml @@ -209,7 +209,7 @@ tasks: - "@group(cargo-workspace)" - "@group(release-archive-contract)" - "@group(release-target-contract)" - - "/tools/packaging/*.{mjs,mts}" + - "@group(packaging-source)" - "/tools/packaging/cargo-source-package.mts" - "/tools/packaging/package-cargo-source.sh" - "/tools/packaging/check-cargo-package-tests.sh" diff --git a/src/wasix/node-addon/moon.yml b/src/wasix/node-addon/moon.yml index 1514d0837..80e622df5 100644 --- a/src/wasix/node-addon/moon.yml +++ b/src/wasix/node-addon/moon.yml @@ -153,7 +153,7 @@ tasks: - "/tools/dev/bun.sh" - "/tools/dev/deno.sh" - "/tools/dev/install-pinned-js-runtime.sh" - - "/tools/packaging/*.{mjs,mts}" + - "@group(packaging-source)" - "/src/wasix/node-addon/tools/check-release-assets.mts" - "/src/extensions/artifacts/packages/tools/build-extension-ci-artifacts.mts" - "/tools/packaging/cargo-source-package.mts" @@ -196,7 +196,7 @@ tasks: group: "code" - project: "liboliphaunt-wasix" group: "crates" - - "/tools/packaging/*.{mjs,mts}" + - "@group(packaging-source)" - "/tools/packaging/finalize-helper-assets.mts" - "/src/wasix/node-addon/tools/check-release-assets.mts" - "/src/wasix/node-addon/tools/build-linux-wasix-napi-baseline.sh" diff --git a/src/wasix/pgwire-server/moon.yml b/src/wasix/pgwire-server/moon.yml index 9f436ba09..b5bec2db1 100644 --- a/src/wasix/pgwire-server/moon.yml +++ b/src/wasix/pgwire-server/moon.yml @@ -64,7 +64,7 @@ tasks: package: command: "bash src/wasix/pgwire-server/tools/package.sh" tags: ["release", "artifact-package", "ci-wasix-rust-package"] - inputs: ["**/*", "@group(legal-files)", "/Cargo.toml", "/Cargo.lock", "/tools/packaging/*.{mts,sh}"] + inputs: ["**/*", "@group(legal-files)", "/Cargo.toml", "/Cargo.lock", "@group(packaging-source)"] outputs: ["/target/sdk-artifacts/oliphaunt-pgwire-server/**/*"] options: runFromWorkspaceRoot: true @@ -77,6 +77,6 @@ tasks: sdk_version="$(bun tools/release/product-version.mts version oliphaunt-wasix-rust)" query_version="$(bun tools/release/product-version.mts version oliphaunt-query)" bash tools/packaging/check-cargo-package-tests.sh --crate "target/sdk-artifacts/oliphaunt-pgwire-server/oliphaunt-pgwire-server-$version.crate" --dependency-crate "target/sdk-artifacts/oliphaunt-wasix-rust/oliphaunt-wasix-$sdk_version.crate" --dependency-crate "target/sdk-artifacts/oliphaunt-query/oliphaunt-query-$query_version.crate" --path-dependencies-from src/wasix/sdks/rust/Cargo.toml - inputs: ["@group(sources)", "/Cargo.toml", "/Cargo.lock", "/tools/packaging/*.{mts,sh}"] + inputs: ["@group(sources)", "/Cargo.toml", "/Cargo.lock", "@group(packaging-source)"] options: runFromWorkspaceRoot: true diff --git a/src/wasix/postgres-tools/moon.yml b/src/wasix/postgres-tools/moon.yml index 944a11d5b..bd6741364 100644 --- a/src/wasix/postgres-tools/moon.yml +++ b/src/wasix/postgres-tools/moon.yml @@ -43,7 +43,7 @@ tasks: command: "bash tools/ci/with-projects.sh src/wasix/postgres-tools/tools/package-assets.mts --target portable" deps: ["compiler-output"] tags: ["artifact", "ci-liboliphaunt-wasix-runtime"] - inputs: ["VERSION", "tools/package-assets.mts", "/tools/packaging/**/*", "/src/wasix/runtime/tools/{check-release-assets.mts,package-release-assets.mts,wasix-aot-manifest.mts,wasix-cargo-artifact-contract.mts}"] + inputs: ["VERSION", "tools/package-assets.mts", "@group(packaging-source)", "/tools/packaging/windows-vc-runtime-policy.json", "/src/wasix/runtime/tools/{check-release-assets.mts,package-release-assets.mts,wasix-aot-manifest.mts,wasix-cargo-artifact-contract.mts}"] outputs: ["/target/postgres-tools/wasix/release-assets/*-portable.tar.gz"] options: runInCI: true @@ -52,7 +52,7 @@ tasks: command: "bash tools/ci/with-projects.sh src/wasix/postgres-tools/tools/package-assets.mts --target aot" deps: ["build-aot"] tags: ["artifact", "ci-liboliphaunt-wasix-aot"] - inputs: ["VERSION", "tools/package-assets.mts", "/tools/packaging/**/*", "/src/wasix/runtime/tools/{check-release-assets.mts,package-release-assets.mts,wasix-aot-manifest.mts,wasix-cargo-artifact-contract.mts}", "$AOT_TARGET"] + inputs: ["VERSION", "tools/package-assets.mts", "@group(packaging-source)", "/tools/packaging/windows-vc-runtime-policy.json", "/src/wasix/runtime/tools/{check-release-assets.mts,package-release-assets.mts,wasix-aot-manifest.mts,wasix-cargo-artifact-contract.mts}", "$AOT_TARGET"] outputs: ["/target/postgres-tools/wasix/release-assets/*-aot-*.tar.gz"] options: runInCI: true diff --git a/src/wasix/postmaster/moon.yml b/src/wasix/postmaster/moon.yml index 28e64dfcb..34ee7db55 100644 --- a/src/wasix/postmaster/moon.yml +++ b/src/wasix/postmaster/moon.yml @@ -363,7 +363,7 @@ tasks: - /rust-toolchain.toml - "@group(production)" - /src/wasix/postmaster/sources/*.toml - - "/tools/packaging/*.{mjs,mts}" + - "@group(packaging-source)" outputs: - /target/oliphaunt-wasix-postmaster/carriers/**/* options: @@ -400,7 +400,7 @@ tasks: - "@group(production)" - /target/oliphaunt-wasix-postmaster/package-docs/README.md - /src/wasix/postmaster/sources/*.toml - - "/tools/packaging/*.{mjs,mts}" + - "@group(packaging-source)" - /target/oliphaunt-wasix-postmaster/carriers/**/* outputs: - /target/oliphaunt-wasix-postmaster/release-assets/**/* @@ -454,7 +454,7 @@ tasks: inputs: - "@group(production)" - /src/wasix/postmaster/sources/*.toml - - "/tools/packaging/*.{mjs,mts}" + - "@group(packaging-source)" - /target/oliphaunt-wasix-postmaster/install/wasix-core-release-o3.current - /target/oliphaunt-wasix-postmaster/install/wasix-core-release-o3.generations/[0-9a-f]*/**/* - /target/oliphaunt-wasix-postmaster/install/wasix-core-release-o3/**/* diff --git a/src/wasix/runtime/moon.yml b/src/wasix/runtime/moon.yml index 445fe19dc..e06430673 100644 --- a/src/wasix/runtime/moon.yml +++ b/src/wasix/runtime/moon.yml @@ -274,9 +274,12 @@ tasks: - tools/package-release-assets.mts - tools/wasix-cargo-artifact-contract.mts - tools/wasix-aot-manifest.mts - - /tools/packaging/**/*.mts + - "@group(packaging-source)" - /src/database-resources/contracts/*.mts - - /tools/release/**/*.mts + - /tools/release/platform-compatibility-policy.mts + - /tools/release/release-artifact-targets.mts + - /tools/release/release-graph.mts + - /tools/release/release-history.mts - assets/build/postgres/patches/**/* - postgres/**/* - /src/third-party/postgres/**/* diff --git a/src/wasix/sdks/rust/moon.yml b/src/wasix/sdks/rust/moon.yml index 3e794857e..95803bc5e 100644 --- a/src/wasix/sdks/rust/moon.yml +++ b/src/wasix/sdks/rust/moon.yml @@ -206,7 +206,7 @@ tasks: - "@group(cargo-workspace)" - "@group(release-archive-contract)" - "@group(release-target-contract)" - - "/tools/packaging/*.{mjs,mts}" + - "@group(packaging-source)" - "/tools/packaging/cargo-source-package.mts" - "/tools/packaging/package-cargo-source.sh" - "/tools/packaging/check-cargo-package-tests.sh" @@ -240,6 +240,6 @@ tasks: bash tools/packaging/check-cargo-package-tests.sh --crate "target/sdk-artifacts/oliphaunt-wasix-rust/oliphaunt-wasix-$version.crate" --no-default-features --features extensions,tools,icu --dependency-crate "target/sdk-artifacts/oliphaunt-query/oliphaunt-query-$query_version.crate" --path-dependencies-from src/wasix/sdks/rust/Cargo.toml env: CARGO_TARGET_DIR: "target" - inputs: ["@group(code)", "@group(cargo-workspace)", "/tools/packaging/*.{mts,sh}"] + inputs: ["@group(code)", "@group(cargo-workspace)", "@group(packaging-source)"] options: runFromWorkspaceRoot: true diff --git a/tools/ci/ci-plan-node-products.test.mts b/tools/ci/ci-plan-node-products.test.mts index 8564c9188..8f809714b 100644 --- a/tools/ci/ci-plan-node-products.test.mts +++ b/tools/ci/ci-plan-node-products.test.mts @@ -30,6 +30,36 @@ const NATIVE_TS_CONSUMER_JOBS = [ 'node-direct', ]; +test('every released product and resource combination keeps its exact scope and required platform matrices', () => { + const products = Object.keys(GRAPH.products).sort(); + const selections = [ + ...products.map((product) => [product]), + ...products + .filter((product) => product !== 'database-resources') + .map((product) => ['database-resources', product]), + products, + ]; + for (const selection of selections) { + const plan = planForReleaseProducts(selection, 'a'.repeat(40)); + assert.deepEqual(plan.qualification_products, [...selection].sort()); + assert.equal(plan.qualification_head_sha, 'a'.repeat(40)); + assert.equal(plan.qualification_mode, 'selected-products'); + for (const [job, matrix] of [ + ['liboliphaunt-native-desktop', plan.liboliphaunt_native_desktop_runtime_matrix], + ['liboliphaunt-native-android', plan.liboliphaunt_native_android_runtime_matrix], + ['liboliphaunt-native-ios', plan.liboliphaunt_native_ios_runtime_matrix], + ['liboliphaunt-wasix-aot', plan.liboliphaunt_wasix_aot_runtime_matrix], + ['wasix-postmaster', plan.liboliphaunt_wasix_postmaster_runtime_matrix], + ]) { + assert.equal(matrix.include.length > 0, plan.jobs.includes(job), `${selection}: ${job}`); + } + } + assert.deepEqual( + planForReleaseProducts(products, 'a'.repeat(40)), + planForReleaseProducts([...products].reverse(), 'a'.repeat(40)), + ); +}); + test('SDK-only release reuses a complete published dependency inventory, while missing or selected dependencies retain producers', () => { const inventory = Object.entries(publishedConsumerDependencies()).map(([name, version]) => ({ name, @@ -122,6 +152,36 @@ function effects(paths) { }; } +test('shared packaging tests and release controllers run checks without scheduling product builds', () => { + for (const file of [ + paths.sharedPackagingTest, + paths.sharedPackagingShellTest, + paths.releaseControllerTest, + paths.releaseControllerSource, + ]) { + const result = effects(file); + assert.deepEqual(result.jobs, ['affected'], file); + assert.deepEqual(result.releaseProducts, [], file); + assert( + result.directTasks.some( + (target) => target === 'artifact-packaging:test' || target === 'release-tools:test', + ), + ); + } + const source = effects(paths.sharedPackagingSource); + for (const target of [ + 'liboliphaunt-native:package-runtime-desktop-target', + 'liboliphaunt-wasix:release-assets', + 'extension-artifacts-native:build-target', + 'oliphaunt-rust:package', + 'oliphaunt-wasix-rust:package', + 'postgres-tools-native:package-assets', + 'postgres-tools-wasix:package-portable', + ]) { + assert(source.directTasks.includes(target), `production helper must affect ${target}`); + } +}); + test('environment input changes retain their producer and runtime qualification', () => { const roots = new Set( triggeringTaskNames({ diff --git a/tools/ci/ci-plan-test-inputs.mts b/tools/ci/ci-plan-test-inputs.mts index d04871667..ffcd445b5 100644 --- a/tools/ci/ci-plan-test-inputs.mts +++ b/tools/ci/ci-plan-test-inputs.mts @@ -1,5 +1,10 @@ // Sample changes exercised by CI planning tests; never used for production affectedness. export const paths = { + sharedPackagingTest: 'tools/packaging/materialize-release-symlinks.test.mts', + sharedPackagingShellTest: 'tools/packaging/strip-native-binaries.test.sh', + sharedPackagingSource: 'tools/packaging/materialize-release-symlinks.mts', + releaseControllerTest: 'tools/release/release-candidate-lib.test.mts', + releaseControllerSource: 'tools/release/publication-lock.mts', postmasterReadme: 'src/wasix/postmaster/README.md', nativeRustRuntimeTests: 'src/native/sdks/rust/tests/native_sql_regression.rs', nativeSwiftRuntimeTests: 'src/native/sdks/swift/Tests/OliphauntTests/NativeRuntimeTests.swift', diff --git a/tools/ci/ci_plan.mts b/tools/ci/ci_plan.mts index 992bc58ac..664d4e9b9 100644 --- a/tools/ci/ci_plan.mts +++ b/tools/ci/ci_plan.mts @@ -50,12 +50,6 @@ const NATIVE_RUNTIME_JOBS = new Set([ 'liboliphaunt-native-desktop', 'liboliphaunt-native-ios', ]); -const NATIVE_RUNTIME_TASKS = new Set([ - 'liboliphaunt-native:package-runtime-desktop-target', - 'liboliphaunt-native:package-runtime-android-arm64-v8a', - 'liboliphaunt-native:package-runtime-android-x86_64', - 'liboliphaunt-native:package-runtime-ios-xcframework', -]); export const WASM_RUNTIME_JOBS = new Set([ 'liboliphaunt-wasix-runtime', 'liboliphaunt-wasix-aot', @@ -338,7 +332,7 @@ export function nativeTargetSubsetForJobs(jobs, tasks) { if (jobs.has('liboliphaunt-native-release-assets')) { return null; } - if (intersects(tasks, NATIVE_RUNTIME_TASKS)) { + if ([...NATIVE_RUNTIME_JOBS].some((job) => intersects(tasks, new Set(CI_JOB_TARGETS[job])))) { return null; } From 60ca7ddd2f658a452025ec819daee354e01d62bd Mon Sep 17 00:00:00 2001 From: Sid Jain Date: Thu, 1 Oct 2026 21:50:16 +0000 Subject: [PATCH 3/3] fix(ci): preserve cached outputs during runtime source preparation --- .../lib/prepare-upstream-checkouts.test.sh | 64 +++++++++++++++++++ .../wasmer/bin/prepare-upstream-checkouts.sh | 17 ++--- 2 files changed, 68 insertions(+), 13 deletions(-) create mode 100644 src/wasix/postmaster/lib/prepare-upstream-checkouts.test.sh diff --git a/src/wasix/postmaster/lib/prepare-upstream-checkouts.test.sh b/src/wasix/postmaster/lib/prepare-upstream-checkouts.test.sh new file mode 100644 index 000000000..6c7fa7030 --- /dev/null +++ b/src/wasix/postmaster/lib/prepare-upstream-checkouts.test.sh @@ -0,0 +1,64 @@ +#!/usr/bin/env bash +set -euo pipefail + +project_root="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +repo_root="$(cd "$project_root/../../.." && pwd)" +scratch="$(mktemp -d)" +mkdir -p "$repo_root/target/oliphaunt-wasix-postmaster" +work="$(mktemp -d "$repo_root/target/oliphaunt-wasix-postmaster/prepare-test.XXXXXX")" +trap 'rm -rf "$scratch" "$work"' EXIT + +# Two real local repositories provide the runtime and all exact-pin gitlinks. +for name in leaf wasmer; do + git init --quiet "$scratch/$name" + git -C "$scratch/$name" config user.name 'Oliphaunt Test' + git -C "$scratch/$name" config user.email test@example.invalid + printf '/target/\n' > "$scratch/$name/.gitignore" + printf '%s\n' "$name" > "$scratch/$name/source.txt" + git -C "$scratch/$name" add . +done +git -C "$scratch/leaf" commit --quiet -m fixture +git -C "$scratch/leaf" commit --quiet --allow-empty -m history +leaf_ref="$(git -C "$scratch/leaf" rev-parse HEAD)" +for path in lib/napi wasmer-test-files tests/wast/spec; do + mkdir -p "$scratch/wasmer/$path" + git -C "$scratch/wasmer" update-index --add --cacheinfo "160000,$leaf_ref,$path" +done +git -C "$scratch/wasmer" commit --quiet -m fixture +git -C "$scratch/wasmer" commit --quiet --allow-empty -m history +wasmer_ref="$(git -C "$scratch/wasmer" rev-parse HEAD)" +for name in leaf wasmer; do + git clone --quiet --depth 1 "file://$scratch/$name" "$scratch/$name-source" +done + +prepare() { + env UPSTREAM_WORK_ROOT="$work" \ + WASMER_SOURCE_ROOT="$scratch/wasmer-source" WASMER_REF="$wasmer_ref" \ + WASMER_NAPI_SOURCE_ROOT="$scratch/leaf-source" WASMER_NAPI_REF="$leaf_ref" \ + WASMER_TEST_FILES_SOURCE_ROOT="$scratch/leaf-source" WASMER_TEST_FILES_REF="$leaf_ref" \ + WASMER_SPEC_SOURCE_ROOT="$scratch/leaf-source" WASMER_SPEC_REF="$leaf_ref" \ + WASIX_LIBC_SOURCE_ROOT="$scratch/leaf-source" WASIX_LIBC_REF="$leaf_ref" \ + bash "$project_root/wasmer/bin/prepare-upstream-checkouts.sh" --skip-patches "$@" +} + +# A restored Cargo cache contains outputs without any checkout or .git metadata. +for path in wasmer wasix-libc wasmer/lib/napi wasmer/wasmer-test-files wasmer/tests/wast/spec; do + mkdir -p "$work/$path/target/release" + printf 'cached output\n' > "$work/$path/target/release/cached.txt" +done +prepare +prepare +prepare --force + +for path in wasmer wasix-libc wasmer/lib/napi wasmer/wasmer-test-files wasmer/tests/wast/spec; do + expected_ref="$leaf_ref" + [ "$path" != wasmer ] || expected_ref="$wasmer_ref" + [ "$(git -C "$work/$path" rev-parse HEAD)" = "$expected_ref" ] + git -C "$work/$path" fsck --no-dangling + [ -z "$(git -C "$work/$path" status --porcelain)" ] + [ "$(cat "$work/$path/target/release/cached.txt")" = 'cached output' ] +done +for name in leaf wasmer; do + [ -z "$(git -C "$scratch/$name-source" status --porcelain)" ] +done +echo 'Runtime preparation preserves restored Cargo outputs and exact local source pins' diff --git a/src/wasix/postmaster/wasmer/bin/prepare-upstream-checkouts.sh b/src/wasix/postmaster/wasmer/bin/prepare-upstream-checkouts.sh index f5c931a4f..2db23e39b 100755 --- a/src/wasix/postmaster/wasmer/bin/prepare-upstream-checkouts.sh +++ b/src/wasix/postmaster/wasmer/bin/prepare-upstream-checkouts.sh @@ -149,8 +149,9 @@ materialize_worktree() { exit 2 fi else - mkdir -p "$(dirname "$root")" - git clone --quiet --no-hardlinks "$source_root" "$root" + # Cargo cache restoration may create target/ before the source worktree. + git init --quiet "$root" + git -C "$root" fetch --quiet --update-shallow "$source_root" "$ref" git -C "$root" checkout --quiet --detach "$ref" fi } @@ -169,17 +170,7 @@ materialize_gitlink() { exit 1 } - if [ -d "$checkout/.git" ]; then - if [ "$FORCE" -eq 1 ]; then - git -C "$checkout" reset --hard "$expected_ref" >/dev/null - git -C "$checkout" clean -fd >/dev/null - fi - else - rmdir "$checkout" 2>/dev/null || true - mkdir -p "$(dirname "$checkout")" - git clone --quiet --no-hardlinks "$source_root" "$checkout" - git -C "$checkout" checkout --quiet --detach "$expected_ref" - fi + materialize_worktree "$label" "$source_root" "$checkout" "$expected_ref" [ "$(git -C "$checkout" rev-parse HEAD)" = "$expected_ref" ] || { printf 'generated Wasmer %s checkout has the wrong revision\n' "$label" >&2 exit 1