mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-24 19:35:28 -06:00
ci(release): add publication eligibility producer
This commit is contained in:
@@ -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<version> 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
|
||||
}
|
||||
@@ -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.
|
||||
|
||||
@@ -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";
|
||||
|
||||
|
||||
@@ -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"))
|
||||
|
||||
@@ -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"),
|
||||
|
||||
@@ -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<string>();
|
||||
@@ -69,6 +71,7 @@ const MATURITY_GENERATED_PR_PATHS = [
|
||||
];
|
||||
|
||||
type WorkflowStep = {
|
||||
"continue-on-error"?: boolean;
|
||||
env?: Record<string, unknown>;
|
||||
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<Record<string, unknown>>) {
|
||||
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");
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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",
|
||||
|
||||
Reference in New Issue
Block a user