diff --git a/.github/workflows/release-publication-eligibility.yml b/.github/workflows/release-publication-eligibility.yml new file mode 100644 index 000000000000..02f42522ba3f --- /dev/null +++ b/.github/workflows/release-publication-eligibility.yml @@ -0,0 +1,250 @@ +name: Release Publication Eligibility +run-name: Release publication eligibility ${{ inputs.target_sha }} + +on: + workflow_dispatch: + inputs: + target_sha: + description: Exact candidate commit SHA whose publication inventory will be checked + required: true + type: string + target_context_ref: + description: Canonical refs/tags/v context for the unpublished candidate + required: true + type: string + tooling_sha: + description: Exact trusted release-publish workflow commit SHA + required: true + type: string + tooling_ref: + description: Exact trusted refs/tags/release-publish/... workflow ref + required: true + type: string + +permissions: + actions: read + contents: read + +concurrency: + group: release-publication-eligibility-${{ inputs.target_sha }}-${{ inputs.tooling_sha }} + cancel-in-progress: false + +env: + FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true" + NODE_VERSION: "24.16.0" + +jobs: + eligibility: + name: Collect publication eligibility + runs-on: ubuntu-24.04 + timeout-minutes: 15 + steps: + - name: Initialize evidence bundle + run: | + mkdir -p "$RUNNER_TEMP/publication-eligibility" + printf '%s\n' '{"publication_authorized":false,"status":"pending"}' \ + > "$RUNNER_TEMP/publication-eligibility/producer-result.json" + + - name: Checkout exact release tooling + id: checkout + continue-on-error: true + uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 + with: + ref: ${{ inputs.tooling_sha }} + fetch-depth: 0 + filter: blob:none + persist-credentials: false + submodules: false + + - name: Verify exact target and tooling identity + id: identity + if: steps.checkout.outcome == 'success' + continue-on-error: true + env: + TARGET_CONTEXT_REF: ${{ inputs.target_context_ref }} + TARGET_SHA: ${{ inputs.target_sha }} + TOOLING_REF: ${{ inputs.tooling_ref }} + TOOLING_SHA: ${{ inputs.tooling_sha }} + WORKFLOW_REF: ${{ github.workflow_ref }} + WORKFLOW_SHA: ${{ github.workflow_sha }} + run: | + set -euo pipefail + [[ "$TARGET_SHA" =~ ^[0-9a-f]{40}$ ]] || { + echo "target_sha must be an exact lowercase commit SHA." >&2 + exit 1 + } + [[ "$TOOLING_SHA" =~ ^[0-9a-f]{40}$ ]] || { + echo "tooling_sha must be an exact lowercase commit SHA." >&2 + exit 1 + } + [[ "$TARGET_CONTEXT_REF" =~ ^refs/tags/v[^[:space:]]+$ ]] || { + echo "target_context_ref must be a qualified release tag context." >&2 + exit 1 + } + [[ "$TOOLING_REF" =~ ^refs/tags/release-publish/[0-9a-f]{12}-[1-9][0-9]*$ ]] || { + echo "tooling_ref must be an exact release-publish tag." >&2 + exit 1 + } + expected_workflow_ref="${GITHUB_REPOSITORY}/.github/workflows/release-publication-eligibility.yml@${TOOLING_REF}" + [[ "$TOOLING_REF" == "$GITHUB_REF" && + "$TOOLING_SHA" == "$GITHUB_SHA" && + "$TOOLING_SHA" == "$WORKFLOW_SHA" && + "$WORKFLOW_REF" == "$expected_workflow_ref" && + "$(git rev-parse HEAD)" == "$TOOLING_SHA" ]] || { + echo "publication eligibility must run from the exact requested tooling ref and SHA." >&2 + exit 1 + } + git cat-file -e "${TARGET_SHA}^{commit}" || { + echo "target_sha is not available from the trusted repository checkout." >&2 + exit 1 + } + + - name: Setup trusted Node environment + id: setup + if: steps.identity.outcome == 'success' + continue-on-error: true + uses: ./.github/actions/setup-node-env + with: + cache-mode: restore + node-version: ${{ env.NODE_VERSION }} + install-bun: "false" + install-deps: "true" + frozen-lockfile: "true" + + - name: Produce canonical ReleasePlan lock + id: release_plan + if: steps.setup.outcome == 'success' + continue-on-error: true + env: + GH_TOKEN: ${{ github.token }} + TARGET_CONTEXT_REF: ${{ inputs.target_context_ref }} + TARGET_SHA: ${{ inputs.target_sha }} + TOOLING_REF: ${{ inputs.tooling_ref }} + TOOLING_SHA: ${{ inputs.tooling_sha }} + run: | + set -euo pipefail + mkdir -p "$RUNNER_TEMP/release-plan" + node --import tsx scripts/release-plan-producer.mts \ + --intent publish \ + --candidate-sha "$TARGET_SHA" \ + --candidate-ref "$TARGET_CONTEXT_REF" \ + --tooling-sha "$TOOLING_SHA" \ + --tooling-full-ref "$TOOLING_REF" \ + > "$RUNNER_TEMP/release-plan/release-plan-lock.json" + + - name: Upload immutable ReleasePlan + id: release_plan_upload + if: steps.release_plan.outcome == 'success' + continue-on-error: true + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 + with: + name: release-publication-plan-${{ github.run_id }}-${{ github.run_attempt }} + path: ${{ runner.temp }}/release-plan/release-plan-lock.json + if-no-files-found: error + retention-days: 30 + + - name: Bind ReleasePlan artifact digest + id: release_plan_identity + if: steps.release_plan_upload.outcome == 'success' + continue-on-error: true + env: + RAW_DIGEST: ${{ steps.release_plan_upload.outputs.artifact-digest }} + run: | + set -euo pipefail + [[ "$RAW_DIGEST" =~ ^[0-9a-f]{64}$ ]] || { + echo "ReleasePlan artifact digest must be 64 lowercase hex characters." >&2 + exit 1 + } + echo "digest=sha256:${RAW_DIGEST}" >> "$GITHUB_OUTPUT" + + - name: Collect publication eligibility receipt + id: collect + if: steps.release_plan_identity.outcome == 'success' + continue-on-error: true + env: + GH_TOKEN: ${{ github.token }} + run: | + set -euo pipefail + node --import tsx scripts/release-publication-eligibility.mts \ + --release-plan-lock "$RUNNER_TEMP/release-plan/release-plan-lock.json" \ + --receipt "$RUNNER_TEMP/publication-eligibility/release-publication-eligibility.json" \ + --repo-root "$GITHUB_WORKSPACE" \ + --run-id "$GITHUB_RUN_ID" \ + --run-attempt "$GITHUB_RUN_ATTEMPT" \ + --job eligibility \ + --artifact-id "${{ steps.release_plan_upload.outputs.artifact-id }}" \ + --artifact-digest "${{ steps.release_plan_identity.outputs.digest }}" \ + 2> >(tee "$RUNNER_TEMP/publication-eligibility/collector-error.txt" >&2) + + - name: Record producer outcome + if: always() + env: + CHECKOUT_OUTCOME: ${{ steps.checkout.outcome }} + COLLECT_OUTCOME: ${{ steps.collect.outcome }} + IDENTITY_OUTCOME: ${{ steps.identity.outcome }} + RELEASE_PLAN_IDENTITY_OUTCOME: ${{ steps.release_plan_identity.outcome }} + RELEASE_PLAN_OUTCOME: ${{ steps.release_plan.outcome }} + RELEASE_PLAN_UPLOAD_OUTCOME: ${{ steps.release_plan_upload.outcome }} + SETUP_OUTCOME: ${{ steps.setup.outcome }} + run: | + set -euo pipefail + receipt="$RUNNER_TEMP/publication-eligibility/release-publication-eligibility.json" + receipt_present=false + [[ -f "$receipt" ]] && receipt_present=true + jq -n \ + --arg checkout "$CHECKOUT_OUTCOME" \ + --arg identity "$IDENTITY_OUTCOME" \ + --arg setup "$SETUP_OUTCOME" \ + --arg release_plan "$RELEASE_PLAN_OUTCOME" \ + --arg release_plan_upload "$RELEASE_PLAN_UPLOAD_OUTCOME" \ + --arg release_plan_identity "$RELEASE_PLAN_IDENTITY_OUTCOME" \ + --arg collect "$COLLECT_OUTCOME" \ + --argjson receipt_present "$receipt_present" \ + '{ + schema: "openclaw.release-publication-eligibility-producer-result.v1", + publication_authorized: false, + checkout: $checkout, + identity: $identity, + setup: $setup, + release_plan: $release_plan, + release_plan_upload: $release_plan_upload, + release_plan_identity: $release_plan_identity, + collect: $collect, + receipt_present: $receipt_present + }' > "$RUNNER_TEMP/publication-eligibility/producer-result.json" + + - name: Upload receipt or blocker bundle + id: evidence_upload + if: always() + continue-on-error: true + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 + with: + name: release-publication-eligibility-${{ github.run_id }}-${{ github.run_attempt }} + path: ${{ runner.temp }}/publication-eligibility/* + if-no-files-found: error + retention-days: 30 + + - name: Fail blocked producer after evidence upload + if: always() + env: + CHECKOUT_OUTCOME: ${{ steps.checkout.outcome }} + COLLECT_OUTCOME: ${{ steps.collect.outcome }} + EVIDENCE_UPLOAD_OUTCOME: ${{ steps.evidence_upload.outcome }} + IDENTITY_OUTCOME: ${{ steps.identity.outcome }} + RELEASE_PLAN_IDENTITY_OUTCOME: ${{ steps.release_plan_identity.outcome }} + RELEASE_PLAN_OUTCOME: ${{ steps.release_plan.outcome }} + RELEASE_PLAN_UPLOAD_OUTCOME: ${{ steps.release_plan_upload.outcome }} + SETUP_OUTCOME: ${{ steps.setup.outcome }} + run: | + set -euo pipefail + [[ "$CHECKOUT_OUTCOME" == "success" && + "$IDENTITY_OUTCOME" == "success" && + "$SETUP_OUTCOME" == "success" && + "$RELEASE_PLAN_OUTCOME" == "success" && + "$RELEASE_PLAN_UPLOAD_OUTCOME" == "success" && + "$RELEASE_PLAN_IDENTITY_OUTCOME" == "success" && + "$COLLECT_OUTCOME" == "success" && + "$EVIDENCE_UPLOAD_OUTCOME" == "success" ]] || { + echo "publication eligibility was blocked; inspect the uploaded evidence bundle." >&2 + exit 1 + } diff --git a/CHANGELOG.md b/CHANGELOG.md index c2e8007a9efb..22cc4481d5e9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,7 +8,6 @@ Docs: https://docs.openclaw.ai - **Secret egress host binding:** bind each shared-store secret to exact HTTPS destination hosts across CLI, Gateway RPC, and Control UI so unbound sentinel substitution fails closed before plaintext egress. - **Release validation:** defer beta candidate Parallels smoke to postpublish `release:beta-smoke` by default, keep stable/full prepublish coverage, and bound nested release workflow monitors with explicit job timeouts. -- **Release publication eligibility:** collect npm and ClawHub version, trust, and vacancy blockers before validation from an immutable candidate snapshot, and emit five-minute validation-only receipts bound to the verified ReleasePlan and exact GitHub run provenance. - **macOS app profiles:** isolate named app instances across state, preferences, Keychain, Gateway services, and duplicate-instance ownership while keeping host-global login and node services untouched. - **Developer workflow:** remove the obsolete scoped-commit helper and use standard Git commands in isolated worktrees. - **Plugin uninstall cleanup:** remove exact recorded install paths from `plugins.load.paths` for marketplace, npm, and other managed installs while preserving parent, child, prefix, and unrelated paths. diff --git a/scripts/release-publication-eligibility-contract.d.mts b/scripts/release-publication-eligibility-contract.d.mts index 3878b955fdeb..b7dfbffda523 100644 --- a/scripts/release-publication-eligibility-contract.d.mts +++ b/scripts/release-publication-eligibility-contract.d.mts @@ -60,6 +60,7 @@ export type ReleasePublicationEligibilityReceiptBody = Omit< export const RELEASE_PUBLICATION_ELIGIBILITY_CANONICALIZATION: "ascii-sorted-compact-json-trailing-newline-v1"; export const RELEASE_PUBLICATION_ELIGIBILITY_MAX_AGE_MS: number; export const RELEASE_PUBLICATION_ELIGIBILITY_EVIDENCE_SCOPE: "validation-start-only"; +export const RELEASE_PUBLICATION_ELIGIBILITY_WORKFLOW_PATH: ".github/workflows/release-publication-eligibility.yml"; export const RELEASE_PUBLICATION_NPM_REGISTRY: "https://registry.npmjs.org"; export const RELEASE_PUBLICATION_CLAWHUB_REGISTRY: "https://clawhub.ai"; diff --git a/scripts/release-publication-eligibility-contract.mjs b/scripts/release-publication-eligibility-contract.mjs index 65f5e2f46536..1f0d9abf1cb2 100644 --- a/scripts/release-publication-eligibility-contract.mjs +++ b/scripts/release-publication-eligibility-contract.mjs @@ -9,6 +9,8 @@ export const RELEASE_PUBLICATION_ELIGIBILITY_CANONICALIZATION = export const RELEASE_PUBLICATION_ELIGIBILITY_MAX_AGE_MS = 5 * 60_000; const RELEASE_PUBLICATION_ELIGIBILITY_MAX_BYTES = 512 * 1024; export const RELEASE_PUBLICATION_ELIGIBILITY_EVIDENCE_SCOPE = "validation-start-only"; +export const RELEASE_PUBLICATION_ELIGIBILITY_WORKFLOW_PATH = + ".github/workflows/release-publication-eligibility.yml"; export const RELEASE_PUBLICATION_NPM_REGISTRY = "https://registry.npmjs.org"; export const RELEASE_PUBLICATION_CLAWHUB_REGISTRY = "https://clawhub.ai"; @@ -504,11 +506,11 @@ export function verifyReleasePublicationEligibilityReceipt( } if ( provenance.repository !== lock.plan.tooling.repository || - provenance.workflow_path !== lock.plan.tooling.workflow_path || + provenance.workflow_path !== RELEASE_PUBLICATION_ELIGIBILITY_WORKFLOW_PATH || provenance.workflow_ref !== lock.plan.tooling.ref || provenance.workflow_sha !== lock.plan.tooling.sha ) { - fail("publication eligibility provenance does not match ReleasePlan tooling"); + fail("publication eligibility provenance does not match its producer and ReleasePlan tooling"); } const expectedNpm = lock.plan.inventory.packages .filter((entry) => entry.targets.includes("npm")) diff --git a/scripts/release-publication-eligibility.mts b/scripts/release-publication-eligibility.mts index 1545266fbc96..29128342f76c 100644 --- a/scripts/release-publication-eligibility.mts +++ b/scripts/release-publication-eligibility.mts @@ -33,6 +33,7 @@ import { RELEASE_PUBLICATION_CLAWHUB_REGISTRY, RELEASE_PUBLICATION_ELIGIBILITY_EVIDENCE_SCOPE, RELEASE_PUBLICATION_ELIGIBILITY_MAX_AGE_MS, + RELEASE_PUBLICATION_ELIGIBILITY_WORKFLOW_PATH, RELEASE_PUBLICATION_NPM_REGISTRY, verifyReleasePublicationEligibilityReceipt, type ReleasePublicationEligibilityProvenance, @@ -925,7 +926,7 @@ export async function runReleasePublicationEligibilityCli( } const provenance: ReleasePublicationEligibilityProvenance = { repository: lock.plan.tooling.repository, - workflow_path: lock.plan.tooling.workflow_path, + workflow_path: RELEASE_PUBLICATION_ELIGIBILITY_WORKFLOW_PATH, workflow_ref: lock.plan.tooling.ref, workflow_sha: lock.plan.tooling.sha, run_id: requiredOption(args, "--run-id"), diff --git a/test/scripts/ci-workflow-guards.test.ts b/test/scripts/ci-workflow-guards.test.ts index 74d932f74383..352aedf29a96 100644 --- a/test/scripts/ci-workflow-guards.test.ts +++ b/test/scripts/ci-workflow-guards.test.ts @@ -48,6 +48,8 @@ const CREATE_GENERATED_PR_TOKENS_ACTION = ".github/actions/create-generated-pr-t const PUBLISH_GENERATED_PR_ACTION = ".github/actions/publish-generated-pr/action.yml"; const SETUP_ANDROID_TOOLCHAIN_ACTION = ".github/actions/setup-android-toolchain/action.yml"; const MATURITY_SCORECARD_WORKFLOW = ".github/workflows/maturity-scorecard.yml"; +const RELEASE_PUBLICATION_ELIGIBILITY_WORKFLOW = + ".github/workflows/release-publication-eligibility.yml"; const MATURITY_SCORECARD_WORKFLOW_REF = "openclaw/openclaw/.github/workflows/maturity-scorecard.yml@refs/heads/main"; const OIDC_BOUND_MAIN_REUSABLE_WORKFLOWS = new Set(); @@ -69,6 +71,7 @@ const MATURITY_GENERATED_PR_PATHS = [ ]; type WorkflowStep = { + "continue-on-error"?: boolean; env?: Record; id?: string; if?: string; @@ -1504,6 +1507,91 @@ ${actionRun}`; } describe("ci workflow guards", () => { + it("keeps publication eligibility read-only, exact, and evidence-preserving", () => { + const source = readFileSync(RELEASE_PUBLICATION_ELIGIBILITY_WORKFLOW, "utf8"); + const workflow = parse(source); + const inputs = workflow.on.workflow_dispatch.inputs; + const eligibility = workflow.jobs.eligibility; + const eligibilitySteps = eligibility.steps as WorkflowStep[]; + const exactIdentity = expectDefined( + eligibilitySteps.find((step) => step.name === "Verify exact target and tooling identity"), + "publication eligibility exact identity step", + ); + const setup = expectDefined( + eligibilitySteps.find((step) => step.name === "Setup trusted Node environment"), + "publication eligibility trusted Node setup step", + ); + const produce = expectDefined( + eligibilitySteps.find((step) => step.name === "Produce canonical ReleasePlan lock"), + "publication eligibility ReleasePlan producer step", + ); + const planUpload = expectDefined( + eligibilitySteps.find((step) => step.name === "Upload immutable ReleasePlan"), + "publication eligibility ReleasePlan upload step", + ); + const collect = expectDefined( + eligibilitySteps.find((step) => step.name === "Collect publication eligibility receipt"), + "publication eligibility collector step", + ); + const upload = expectDefined( + eligibilitySteps.find((step) => step.name === "Upload receipt or blocker bundle"), + "publication eligibility evidence upload step", + ); + const fail = expectDefined( + eligibilitySteps.find((step) => step.name === "Fail blocked producer after evidence upload"), + "publication eligibility final failure step", + ); + + expect(Object.keys(inputs).toSorted()).toEqual([ + "target_context_ref", + "target_sha", + "tooling_ref", + "tooling_sha", + ]); + for (const input of Object.values(inputs) as Array>) { + expect(input).toMatchObject({ required: true, type: "string" }); + } + expect(workflow.permissions).toEqual({ actions: "read", contents: "read" }); + expect(source).not.toMatch(/\b(?:id-token|packages|statuses|contents):\s*write\b/u); + expect(source).not.toContain("environment:"); + expect(source).not.toContain("secrets."); + expect(source).not.toContain("npm publish"); + expect(source).not.toContain("clawhub publish"); + expect(source).not.toContain("release-candidate-checklist"); + expect(source).not.toContain("full-release-validation"); + + expect(exactIdentity.run).toContain('"$TOOLING_REF" == "$GITHUB_REF"'); + expect(exactIdentity.run).toContain('"$TOOLING_SHA" == "$WORKFLOW_SHA"'); + expect(exactIdentity.run).toContain( + ".github/workflows/release-publication-eligibility.yml@${TOOLING_REF}", + ); + expect(setup.with?.["cache-mode"]).toBe("restore"); + expect(produce.run).toContain("scripts/release-plan-producer.mts"); + expect(produce.run).toContain('--candidate-sha "$TARGET_SHA"'); + expect(produce.run).toContain('--candidate-ref "$TARGET_CONTEXT_REF"'); + expect(produce.run).toContain('--tooling-sha "$TOOLING_SHA"'); + expect(produce.run).toContain('--tooling-full-ref "$TOOLING_REF"'); + expect(planUpload.uses).toBe(UPLOAD_ARTIFACT_V7); + expect(planUpload.with?.["if-no-files-found"]).toBe("error"); + + expect(collect["continue-on-error"]).toBe(true); + expect(collect.run).toContain("scripts/release-publication-eligibility.mts"); + expect(collect.run).toContain('--run-id "$GITHUB_RUN_ID"'); + expect(collect.run).toContain('--run-attempt "$GITHUB_RUN_ATTEMPT"'); + expect(collect.run).toContain("--job eligibility"); + expect(collect.run).toContain( + '--artifact-id "${{ steps.release_plan_upload.outputs.artifact-id }}"', + ); + expect(collect.run).toContain( + '--artifact-digest "${{ steps.release_plan_identity.outputs.digest }}"', + ); + expect(upload.if).toBe("always()"); + expect(upload.uses).toBe(UPLOAD_ARTIFACT_V7); + expect(upload.with?.["if-no-files-found"]).toBe("error"); + expect(fail.if).toBe("always()"); + expect(eligibilitySteps.indexOf(upload)).toBeLessThan(eligibilitySteps.indexOf(fail)); + }); + it("retains pending same-SHA QA calls in the shared concurrency group", () => { const workflowPath = ".github/workflows/qa-live-transports-convex.yml"; const workflowSource = readFileSync(workflowPath, "utf8"); diff --git a/test/scripts/release-publication-eligibility-contract.test.ts b/test/scripts/release-publication-eligibility-contract.test.ts index d72473efb7e7..aa1a3fd1d41f 100644 --- a/test/scripts/release-publication-eligibility-contract.test.ts +++ b/test/scripts/release-publication-eligibility-contract.test.ts @@ -9,6 +9,7 @@ import { parseReleasePublicationEligibilityReceiptJson, RELEASE_PUBLICATION_ELIGIBILITY_CANONICALIZATION, RELEASE_PUBLICATION_ELIGIBILITY_MAX_AGE_MS, + RELEASE_PUBLICATION_ELIGIBILITY_WORKFLOW_PATH, verifyReleasePublicationEligibilityReceipt, } from "../../scripts/release-publication-eligibility-contract.mjs"; @@ -17,7 +18,7 @@ const releasePlanLock = JSON.parse( ) as VerifiedReleasePlanLock; const provenance = { repository: releasePlanLock.plan.tooling.repository, - workflow_path: releasePlanLock.plan.tooling.workflow_path, + workflow_path: RELEASE_PUBLICATION_ELIGIBILITY_WORKFLOW_PATH, workflow_ref: releasePlanLock.plan.tooling.ref, workflow_sha: releasePlanLock.plan.tooling.sha, run_id: "123456", @@ -193,7 +194,22 @@ describe("release publication eligibility receipt contract", () => { wrongToolingReceipt.provenance, Date.parse("2026-08-21T00:00:03.000Z"), ), - ).toThrow("ReleasePlan tooling"); + ).toThrow("producer and ReleasePlan tooling"); + const wrongProducerReceipt = createReleasePublicationEligibilityReceipt({ + ...body, + provenance: { + ...provenance, + workflow_path: releasePlanLock.plan.tooling.workflow_path, + }, + }); + expect(() => + verifyReleasePublicationEligibilityReceipt( + wrongProducerReceipt, + releasePlanLock, + wrongProducerReceipt.provenance, + Date.parse("2026-08-21T00:00:03.000Z"), + ), + ).toThrow("producer and ReleasePlan tooling"); expect(() => createReleasePublicationEligibilityReceipt({ ...body, diff --git a/test/scripts/release-publication-eligibility.test.ts b/test/scripts/release-publication-eligibility.test.ts index d80d14d5d449..455bdd5f103e 100644 --- a/test/scripts/release-publication-eligibility.test.ts +++ b/test/scripts/release-publication-eligibility.test.ts @@ -12,6 +12,7 @@ import { parseReleasePublicationEligibilityReceiptJson, RELEASE_PUBLICATION_CLAWHUB_REGISTRY, RELEASE_PUBLICATION_ELIGIBILITY_MAX_AGE_MS, + RELEASE_PUBLICATION_ELIGIBILITY_WORKFLOW_PATH, RELEASE_PUBLICATION_NPM_REGISTRY, type ReleasePublicationEligibilityReceipt, } from "../../scripts/release-publication-eligibility-contract.mjs"; @@ -38,7 +39,7 @@ function verified(lock: ReleasePlanLock): VerifiedReleasePlanLock { function provenance(lock: ReleasePlanLock) { return { repository: lock.plan.tooling.repository, - workflow_path: lock.plan.tooling.workflow_path, + workflow_path: RELEASE_PUBLICATION_ELIGIBILITY_WORKFLOW_PATH, workflow_ref: lock.plan.tooling.ref, workflow_sha: lock.plan.tooling.sha, run_id: "123456",