fix(ci): authenticate release validation fast paths

This commit is contained in:
Vincent Koc
2026-08-21 02:42:24 -07:00
parent 3329120bc2
commit 8824dcc497
7 changed files with 238 additions and 21 deletions
+72 -2
View File
@@ -33,6 +33,16 @@ on:
required: false
default: ""
type: string
full_release_validation_run_id:
description: Optional authenticated Full Release Validation parent run ID
required: false
default: ""
type: string
full_release_validation_run_attempt:
description: Optional authenticated Full Release Validation parent run attempt
required: false
default: ""
type: string
historical_target_tag:
description: Semver release tag authorizing compatibility fallbacks for its exact commit
required: false
@@ -76,6 +86,7 @@ jobs:
# work fan out from a single source of truth.
preflight:
permissions:
actions: read
contents: read
if: github.event_name != 'pull_request' || !github.event.pull_request.draft
runs-on: ${{ (vars.OPENCLAW_CI_RUNNER_BACKEND == 'github' || vars.OPENCLAW_CI_RUNNER_BACKEND == 'hybrid') && 'ubuntu-24.04' || github.event_name == 'workflow_dispatch' && 'ubuntu-24.04' || (github.repository == 'openclaw/openclaw' && (github.event_name != 'pull_request' || contains(fromJSON('["OWNER","MEMBER","COLLABORATOR","CONTRIBUTOR"]'), github.event.pull_request.author_association)) && 'blacksmith-4vcpu-ubuntu-2404' || 'ubuntu-24.04') }}
@@ -132,11 +143,70 @@ jobs:
run_openclawkit_tests: ${{ steps.manifest.outputs.run_openclawkit_tests }}
run_ios_build: ${{ steps.manifest.outputs.run_ios_build }}
run_ios_screenshots: ${{ steps.changed_scope.outputs.run_ios_screenshots }}
trusted_frv_first_attempt: ${{ steps.frv_parent.outputs.trusted_first_attempt }}
run_android_job: ${{ steps.manifest.outputs.run_android_job }}
use_compatible_android_ci: ${{ steps.manifest.outputs.use_compatible_android_ci }}
run_protocol_event_coverage: ${{ steps.manifest.outputs.run_protocol_event_coverage }}
android_matrix: ${{ steps.manifest.outputs.android_matrix }}
steps:
- name: Validate Full Release Validation parent
id: frv_parent
env:
GH_TOKEN: ${{ github.token }}
PARENT_RUN_ATTEMPT: ${{ inputs.full_release_validation_run_attempt }}
PARENT_RUN_ID: ${{ inputs.full_release_validation_run_id }}
TARGET_REF: ${{ inputs.target_ref }}
WORKFLOW_SHA: ${{ github.sha }}
run: |
set -euo pipefail
echo "trusted_first_attempt=false" >> "$GITHUB_OUTPUT"
if [[ -z "$PARENT_RUN_ID" && -z "$PARENT_RUN_ATTEMPT" ]]; then
exit 0
fi
if [[ ! "$PARENT_RUN_ID" =~ ^[1-9][0-9]*$ || ! "$PARENT_RUN_ATTEMPT" =~ ^[1-9][0-9]*$ ]]; then
echo "::error::Full Release Validation parent run ID and attempt must be positive integers." >&2
exit 1
fi
if [[ ! "$TARGET_REF" =~ ^[0-9a-f]{40}$ ]]; then
echo "::error::Full Release Validation parent routing requires an exact target SHA." >&2
exit 1
fi
parent_run="$(
gh api \
"repos/${GITHUB_REPOSITORY}/actions/runs/${PARENT_RUN_ID}/attempts/${PARENT_RUN_ATTEMPT}"
)"
expected_title="Full Release Validation ${TARGET_REF}"
if ! jq -e \
--arg branch "$GITHUB_REF_NAME" \
--arg repository "$GITHUB_REPOSITORY" \
--arg sha "$WORKFLOW_SHA" \
--arg title "$expected_title" \
--argjson attempt "$PARENT_RUN_ATTEMPT" \
--argjson run_id "$PARENT_RUN_ID" \
'
.id == $run_id
and .run_attempt == $attempt
and .event == "workflow_dispatch"
and .path == ".github/workflows/full-release-validation.yml"
and .head_sha == $sha
and .head_branch == $branch
and .repository.full_name == $repository
and .display_title == $title
and .status == "in_progress"
and .conclusion == null
' <<< "$parent_run" >/dev/null; then
echo "::error::Full Release Validation parent identity did not match this CI target and workflow." >&2
jq '{id, run_attempt, event, path, head_sha, head_branch, repository: .repository.full_name, display_title, status, conclusion}' \
<<< "$parent_run" >&2 || true
exit 1
fi
if [[ "$PARENT_RUN_ATTEMPT" == "1" && "$GITHUB_RUN_ATTEMPT" == "1" ]]; then
echo "trusted_first_attempt=true" >> "$GITHUB_OUTPUT"
fi
- name: Validate release-gate dispatch
if: github.event_name == 'workflow_dispatch' && inputs.release_gate
env:
@@ -3688,9 +3758,9 @@ jobs:
name: "ios-build"
needs: [preflight]
if: needs.preflight.outputs.run_ios_build == 'true'
# The FRV child is a trusted exact-SHA dispatch with an identity-bound nonce.
# Hosted preflight authenticates the exact parent run before this job routes.
# Keep ordinary/manual/release-gate runs and every retry on hosted capacity.
runs-on: ${{ vars.OPENCLAW_CI_RUNNER_BACKEND == 'github' && 'macos-26' || github.run_attempt > 1 && 'macos-26' || (github.event_name == 'workflow_dispatch' && (inputs.release_gate || !startsWith(inputs.dispatch_id, 'full-release-validation-') || !endsWith(inputs.dispatch_id, '-ci'))) && 'macos-26' || (github.repository == 'openclaw/openclaw' && (github.event_name != 'pull_request' || contains(fromJSON('["OWNER","MEMBER","COLLABORATOR","CONTRIBUTOR"]'), github.event.pull_request.author_association)) && 'blacksmith-12vcpu-macos-26' || 'macos-26') }}
runs-on: ${{ vars.OPENCLAW_CI_RUNNER_BACKEND == 'github' && 'macos-26' || github.run_attempt > 1 && 'macos-26' || (github.event_name == 'workflow_dispatch' && (inputs.release_gate || needs.preflight.outputs.trusted_frv_first_attempt != 'true')) && 'macos-26' || (github.repository == 'openclaw/openclaw' && (github.event_name != 'pull_request' || contains(fromJSON('["OWNER","MEMBER","COLLABORATOR","CONTRIBUTOR"]'), github.event.pull_request.author_association)) && 'blacksmith-12vcpu-macos-26' || 'macos-26') }}
timeout-minutes: 150
env:
HISTORICAL_TARGET: ${{ needs.preflight.outputs.compatibility_target }}
@@ -1,4 +1,5 @@
name: Full Release Validation
run-name: Full Release Validation ${{ inputs.expected_sha || inputs.ref }}
on:
workflow_dispatch:
@@ -991,7 +992,7 @@ jobs:
} >> "$GITHUB_STEP_SUMMARY"
dispatch_id="full-release-validation-${GITHUB_RUN_ID}-${GITHUB_RUN_ATTEMPT}-ci"
dispatch_run_name="CI ${dispatch_id}"
args=(-f target_ref="$TARGET_SHA" -f include_android=true -f dispatch_id="$dispatch_id")
args=(-f target_ref="$TARGET_SHA" -f include_android=true -f dispatch_id="$dispatch_id" -f full_release_validation_run_id="$GITHUB_RUN_ID" -f full_release_validation_run_attempt="$GITHUB_RUN_ATTEMPT")
if [[ "$TARGET_REF" =~ ^v[0-9]{4}\.[0-9]+\.[0-9]+(-(alpha|beta)\.[0-9]+)?$ ]]; then
args+=(-f historical_target_tag="$TARGET_REF")
elif [[ "$TARGET_CONTEXT_REF" =~ ^v[0-9]{4}\.[0-9]+\.[0-9]+(-(alpha|beta)\.[0-9]+)?$ ]]; then
@@ -2090,6 +2090,7 @@ jobs:
with:
ref: ${{ needs.resolve_target.outputs.revision }}
expected_sha: ${{ needs.resolve_target.outputs.revision }}
lock_scope: matrix
run_matrix: true
fail_fast: ${{ fromJSON(needs.resolve_target.outputs.fail_fast) }}
secrets:
@@ -2108,6 +2109,7 @@ jobs:
with:
ref: ${{ needs.resolve_target.outputs.revision }}
expected_sha: ${{ needs.resolve_target.outputs.revision }}
lock_scope: buzz
run_buzz: true
buzz_scenario: channel-canary,channel-mention-gating
secrets:
@@ -13,6 +13,11 @@ on:
description: Exact resolved SHA expected from the trusted caller
required: true
type: string
lock_scope:
description: Concurrency scope for a trusted single-lane reusable call
required: false
default: all
type: string
run_mock_parity:
description: Run the mock parity lane
required: false
@@ -118,9 +123,9 @@ permissions:
pull-requests: read
concurrency:
# Release checks call Matrix and Buzz independently. Give those single-lane
# calls separate locks while retaining the shared lock for aggregate runs.
group: qa-lab-${{ github.event_name == 'workflow_call' && inputs.run_buzz && !inputs.run_matrix && 'buzz' || github.event_name == 'workflow_call' && inputs.run_matrix && !inputs.run_buzz && 'matrix' || 'all' }}-${{ github.event_name != 'schedule' && inputs.ref || github.sha }}
# Trusted reusable callers name their single-lane scope explicitly. Manual
# and scheduled aggregate runs retain the shared all-lanes lock.
group: qa-lab-${{ inputs.lock_scope || 'all' }}-${{ github.event_name != 'schedule' && inputs.ref || github.sha }}
cancel-in-progress: false
queue: max
@@ -438,6 +443,10 @@ jobs:
if: inputs.expected_sha == '' || inputs.run_matrix
runs-on: blacksmith-16vcpu-ubuntu-2404
timeout-minutes: 90
concurrency:
group: qa-live-matrix-${{ needs.validate_selected_ref.outputs.selected_revision }}
cancel-in-progress: false
queue: max
environment: qa-live-shared
steps:
- name: Checkout selected ref
@@ -502,6 +511,7 @@ jobs:
concurrency:
group: qa-live-buzz-shared
cancel-in-progress: false
queue: max
environment: qa-live-shared
steps:
- name: Checkout selected ref
+2 -2
View File
@@ -182,7 +182,7 @@ Barnacle treats bug-labeled issues as verification candidates rather than inacti
Ordinary manual CI dispatches run the same job graph as normal CI but force every non-Android scoped lane on: Linux Node shards, bundled-plugin shards, plugin and channel contract shards, Node 22 compatibility, `check-*`, `check-additional-*`, built-artifact smoke checks, docs checks, Python skills, Windows, macOS, iOS build, and Control UI/native app i18n. Node 22 compatibility runs in Full Release Validation and manual dispatches only; push and pull request CI skip it. The exact-head `release_gate` fallback instead keeps the pull request's macOS and iOS scope, including conservative release screenshot capture for screenshot-pipeline owners. Automatic source PRs verify native extraction inventory and Android/Apple localization safety without requiring translated or platform-generated output in the same PR. The serialized Native App Locale Refresh workflow rebuilds those artifacts in one isolated PR and enables exact-head auto-merge after required checks pass. Full native parity remains blocking for generated-artifact PRs, manual CI, Full Release Validation, and release prep. Control UI locale parity remains advisory on automatic PR and `main` runs and blocking on manual/release CI. Standalone manual CI dispatches run Android only with `include_android=true` (the `release_gate` input also forces Android); the full release umbrella enables Android by passing `include_android=true`. Plugin prerelease static checks, the release-only `agentic-plugins` shard, the full extension batch sweep, and plugin prerelease Docker lanes are excluded from CI. The Docker prerelease suite runs only when `Full Release Validation` dispatches the separate `Plugin Prerelease` workflow with the release-validation gate enabled.
The exact Full Release Validation child routes `ios-build` to Blacksmith on its first attempt. Ordinary manual dispatches, exact-head `release_gate` fallbacks, non-canonical repositories, the `github` backend override, and every rerun stay on GitHub-hosted macOS.
An exact-SHA Full Release Validation child routes `ios-build` to Blacksmith only when hosted preflight authenticates the live parent run ID, first attempt, workflow SHA, workflow ref, and target SHA through the Actions API. Ordinary manual dispatches, exact-head `release_gate` fallbacks, non-canonical repositories, the `github` backend override, and every parent or child rerun stay on GitHub-hosted macOS.
PR baseline ratchets derive their comparison state from the checked-out synthetic merge tree and verify its head parent against the event head. The max-lines entry chains the environment-variable budget with the same fork-point ref before the assertion-safety check, so production source growth cannot first surface on `main`. Manual runs use a unique concurrency group so a release-candidate full suite is not cancelled by another push or PR run on the same ref. The optional `target_ref` input lets a trusted caller run that graph against a branch, tag, or full commit SHA while using the workflow file from the selected dispatch ref; ratchet baselines are compared with the target's merge base against the default-branch head resolved for that run. The `release_gate` input is an exact-SHA maintainer fallback for capacity-stalled PR CI: it requires `target_ref` to be a full commit SHA that matches the dispatched branch head and `pull_request_number` to identify the open PR whose merge tree is validated.
@@ -274,7 +274,7 @@ concurrent repositories, retries, and burst overlap.
The changed-target PR plan reduces the common Node test burst from 29 Blacksmith registrations to one. Broad-risk all-Blacksmith PRs keep the 32-registration metadata-complete compact fallback; canonical all-Blacksmith pushes use 24 nondist compact registrations. Hybrid currently uses 53 nondist fallback rows and 46 nondist push rows. The `github` and `hybrid` planner profiles remain capped at 96 compact rows; `github` rows are hosted, while hybrid rows consume Blacksmith registrations on attempt 1 and move to hosted capacity on retries. Even the 96-row cap plus roughly 25 other Blacksmith lanes is about 121 registrations per full run, or 484 for four admitted runs in a five-minute window, far below the 6,000-registration operating target.
Canonical-repo CI keeps Blacksmith as the default runner path for pushes and first-attempt same-repo pull-request runs when the backend is unset or `blacksmith`. Hybrid keeps the heavy set plus the named critical-path plateau lanes on Blacksmith for attempt 1; other light lanes and every rerun Blacksmith lane use GitHub-hosted capacity. Pull-request retries of both UI E2E jobs use GitHub-hosted Ubuntu in every mode; push retries remain on their normal backend unless hybrid fallback applies. All `workflow_dispatch` runs, including `release_gate`, and non-canonical repository runs use GitHub-hosted runners. The [`github` backend](#runner-backend-modes) provides a manual repository-wide fallback; canonical runs do not probe Blacksmith queue health or mutate the variable automatically.
Canonical-repo CI keeps Blacksmith as the default runner path for pushes and first-attempt same-repo pull-request runs when the backend is unset or `blacksmith`. Hybrid keeps the heavy set plus the named critical-path plateau lanes on Blacksmith for attempt 1; other light lanes and every rerun Blacksmith lane use GitHub-hosted capacity. Pull-request retries of both UI E2E jobs use GitHub-hosted Ubuntu in every mode; push retries remain on their normal backend unless hybrid fallback applies. Ordinary `workflow_dispatch` runs, including `release_gate`, and non-canonical repository runs use GitHub-hosted runners. The authenticated first-attempt exact-SHA Full Release Validation iOS child is the sole manual-dispatch exception. The [`github` backend](#runner-backend-modes) provides a manual repository-wide fallback; canonical runs do not probe Blacksmith queue health or mutate the variable automatically.
## Surface ratchets
+129 -13
View File
@@ -93,8 +93,8 @@ function evaluateWorkflowExpression(
repository: string;
runnerBackend?: "" | "blacksmith" | "github" | "hybrid";
runAttempt: number;
trustedFrvFirstAttempt?: boolean;
inputs?: {
dispatchId?: string;
releaseGate?: boolean;
};
},
@@ -116,7 +116,6 @@ function evaluateWorkflowExpression(
Array.isArray(haystack)
? haystack.includes(needle)
: String(haystack).includes(String(needle)),
endsWith: (value: unknown, suffix: unknown) => String(value).endsWith(String(suffix)),
fromJSON: (value: string) => JSON.parse(value) as unknown,
github: {
event_name: context.eventName,
@@ -133,11 +132,16 @@ function evaluateWorkflowExpression(
: {},
},
inputs: {
dispatch_id: context.inputs?.dispatchId ?? "",
release_gate: context.inputs?.releaseGate ?? false,
},
matrix: context.matrix ?? {},
startsWith: (value: unknown, prefix: unknown) => String(value).startsWith(String(prefix)),
needs: {
preflight: {
outputs: {
trusted_frv_first_attempt: context.trustedFrvFirstAttempt ? "true" : "false",
},
},
},
vars: {
OPENCLAW_CI_RUNNER_BACKEND: context.runnerBackend ?? "",
},
@@ -1422,13 +1426,19 @@ describe("ci workflow guards", () => {
expect(workflow.concurrency).toEqual({
group:
"qa-lab-${{ github.event_name == 'workflow_call' && inputs.run_buzz && !inputs.run_matrix && 'buzz' || github.event_name == 'workflow_call' && inputs.run_matrix && !inputs.run_buzz && 'matrix' || 'all' }}-${{ github.event_name != 'schedule' && inputs.ref || github.sha }}",
"qa-lab-${{ inputs.lock_scope || 'all' }}-${{ github.event_name != 'schedule' && inputs.ref || github.sha }}",
"cancel-in-progress": false,
queue: "max",
});
expect(workflow.jobs.run_live_matrix.concurrency).toEqual({
group: "qa-live-matrix-${{ needs.validate_selected_ref.outputs.selected_revision }}",
"cancel-in-progress": false,
queue: "max",
});
expect(workflow.jobs.run_live_buzz.concurrency).toEqual({
group: "qa-live-buzz-shared",
"cancel-in-progress": false,
queue: "max",
});
});
@@ -1728,7 +1738,7 @@ NODE
expect(changedScopeStep.run).toContain(
'node scripts/ci-changed-scope.mjs --base "$BASE" --head "$HEAD_SHA"',
);
expect(workflow.jobs.preflight.permissions).toEqual({ contents: "read" });
expect(workflow.jobs.preflight.permissions).toEqual({ actions: "read", contents: "read" });
expect(workflow.jobs.preflight.outputs.run_ios_screenshots).toBe(
"${{ steps.changed_scope.outputs.run_ios_screenshots }}",
);
@@ -1761,17 +1771,123 @@ NODE
}
});
it("authenticates the exact Full Release Validation parent before fast routing", () => {
const workflow = readCiWorkflow();
const validateStep = expectDefined(
workflow.jobs.preflight.steps.find(
(step: WorkflowStep) => step.name === "Validate Full Release Validation parent",
),
"Full Release Validation parent validation",
);
const targetSha = "a".repeat(40);
const workflowSha = "b".repeat(40);
const baseRun = {
conclusion: null,
display_title: `Full Release Validation ${targetSha}`,
event: "workflow_dispatch",
head_branch: "main",
head_sha: workflowSha,
id: 32441524595,
path: ".github/workflows/full-release-validation.yml",
repository: { full_name: "openclaw/openclaw" },
run_attempt: 1,
status: "in_progress",
};
const runCase = (options?: {
childAttempt?: number;
parentAttempt?: number;
parentRun?: Record<string, unknown>;
provideParent?: boolean;
}) => {
const root = tempDirs.make("openclaw-frv-parent-");
const binDir = path.join(root, "bin");
const callsPath = path.join(root, "gh-calls");
const outputPath = path.join(root, "github-output");
mkdirSync(binDir);
writeExecutable(path.join(binDir, "gh"), [
"#!/usr/bin/env bash",
"set -euo pipefail",
'printf "%s\\n" "$*" >> "$GH_CALLS"',
'printf "%s\\n" "$MOCK_GH_RESPONSE"',
]);
const provideParent = options?.provideParent ?? true;
const parentAttempt = options?.parentAttempt ?? 1;
const parentRun = options?.parentRun ?? { ...baseRun, run_attempt: parentAttempt };
const result = runWorkflowShellScript(expectDefined(validateStep.run, "validation script"), {
cwd: root,
env: {
...process.env,
GH_CALLS: callsPath,
GITHUB_OUTPUT: outputPath,
GITHUB_REF_NAME: "main",
GITHUB_REPOSITORY: "openclaw/openclaw",
GITHUB_RUN_ATTEMPT: String(options?.childAttempt ?? 1),
MOCK_GH_RESPONSE: JSON.stringify(parentRun),
PARENT_RUN_ATTEMPT: provideParent ? String(parentAttempt) : "",
PARENT_RUN_ID: provideParent ? String(baseRun.id) : "",
PATH: `${binDir}:${process.env.PATH ?? ""}`,
TARGET_REF: targetSha,
WORKFLOW_SHA: workflowSha,
},
});
return {
calls: existsSync(callsPath) ? readFileSync(callsPath, "utf8") : "",
outputs: readWorkflowOutputs(outputPath),
result,
};
};
expect(workflow.on.workflow_dispatch.inputs.full_release_validation_run_id).toMatchObject({
default: "",
required: false,
type: "string",
});
expect(workflow.on.workflow_dispatch.inputs.full_release_validation_run_attempt).toMatchObject({
default: "",
required: false,
type: "string",
});
expect(workflow.jobs.preflight.outputs.trusted_frv_first_attempt).toBe(
"${{ steps.frv_parent.outputs.trusted_first_attempt }}",
);
const trusted = runCase();
expect(trusted.result.status, trusted.result.stderr).toBe(0);
expect(trusted.outputs.trusted_first_attempt).toBe("true");
expect(trusted.calls).toContain("repos/openclaw/openclaw/actions/runs/32441524595/attempts/1");
const generic = runCase({ provideParent: false });
expect(generic.result.status, generic.result.stderr).toBe(0);
expect(generic.outputs.trusted_first_attempt).toBe("false");
expect(generic.calls).toBe("");
for (const rerun of [runCase({ childAttempt: 2 }), runCase({ parentAttempt: 2 })]) {
expect(rerun.result.status, rerun.result.stderr).toBe(0);
expect(rerun.outputs.trusted_first_attempt).toBe("false");
}
for (const parentRun of [
{ ...baseRun, id: baseRun.id + 1 },
{ ...baseRun, run_attempt: 2 },
{ ...baseRun, head_sha: "c".repeat(40) },
{ ...baseRun, display_title: "Full Release Validation main" },
{ ...baseRun, path: ".github/workflows/ci.yml" },
{ ...baseRun, status: "completed", conclusion: "success" },
]) {
const rejected = runCase({ parentRun });
expect(rejected.result.status, JSON.stringify(parentRun)).toBe(1);
expect(rejected.outputs.trusted_first_attempt).toBe("false");
}
});
it("routes only trusted first-attempt Full Release Validation iOS to Blacksmith", () => {
const runsOn = readCiWorkflow().jobs["ios-build"]["runs-on"];
const frvInputs = {
dispatchId: "full-release-validation-32441524595-1-ci",
releaseGate: false,
};
const canonicalDispatch = {
eventName: "workflow_dispatch",
inputs: frvInputs,
inputs: { releaseGate: false },
repository: "openclaw/openclaw",
runAttempt: 1,
trustedFrvFirstAttempt: true,
} as const;
expect(evaluateWorkflowExpression(runsOn, canonicalDispatch)).toBe(
@@ -1792,13 +1908,13 @@ NODE
expect(
evaluateWorkflowExpression(runsOn, {
...canonicalDispatch,
inputs: { dispatchId: "", releaseGate: false },
trustedFrvFirstAttempt: false,
}),
).toBe("macos-26");
expect(
evaluateWorkflowExpression(runsOn, {
...canonicalDispatch,
inputs: { ...frvInputs, releaseGate: true },
inputs: { releaseGate: true },
}),
).toBe("macos-26");
expect(
@@ -2771,6 +2771,7 @@ describe("package acceptance workflow", () => {
it("requires full release child workflows to run at the parent workflow SHA", () => {
const workflow = readFileSync(FULL_RELEASE_VALIDATION_WORKFLOW, "utf8");
const parsedWorkflow = readWorkflow(FULL_RELEASE_VALIDATION_WORKFLOW);
const releaseChecksWorkflow = readFileSync(RELEASE_CHECKS_WORKFLOW, "utf8");
const performanceJob = workflowStep(
workflowJob(FULL_RELEASE_VALIDATION_WORKFLOW, "performance"),
@@ -2778,6 +2779,9 @@ describe("package acceptance workflow", () => {
).run;
expect(workflow).toContain("TARGET_SHA: ${{ needs.resolve_target.outputs.sha }}");
expect(parsedWorkflow["run-name"]).toBe(
"Full Release Validation ${{ inputs.expected_sha || inputs.ref }}",
);
expect(workflow).toContain("CHILD_WORKFLOW_REF: ${{ github.ref_name }}");
expect(workflow).toContain("PARENT_WORKFLOW_SHA: ${{ github.sha }}");
expect(workflow).toContain("release_package_spec:");
@@ -2807,6 +2811,8 @@ describe("package acceptance workflow", () => {
);
expect(workflow).toContain('if [[ "$head_sha" != "$PARENT_WORKFLOW_SHA" ]]; then');
expect(workflow).toContain('gh workflow run "$workflow" --ref "$CHILD_WORKFLOW_REF" "$@" 2>&1');
expect(workflow).toContain('-f full_release_validation_run_id="$GITHUB_RUN_ID"');
expect(workflow).toContain('-f full_release_validation_run_attempt="$GITHUB_RUN_ATTEMPT"');
expect(performanceJob).toContain(
'dispatch_id="full-release-validation-${GITHUB_RUN_ID}-${GITHUB_RUN_ATTEMPT}"',
);
@@ -4888,6 +4894,7 @@ describe("package artifact reuse", () => {
expect(releaseJob.with).toMatchObject({
expected_sha: "${{ needs.resolve_target.outputs.revision }}",
fail_fast: "${{ fromJSON(needs.resolve_target.outputs.fail_fast) }}",
lock_scope: "matrix",
run_matrix: true,
});
for (const lane of ["mock_parity", "buzz", "telegram", "discord", "whatsapp", "slack"]) {
@@ -4923,6 +4930,11 @@ describe("package artifact reuse", () => {
expect(qaWorkflow).toContain('if [[ -n "${EXPECTED_SHA}" ]]; then');
const matrixJob = workflowJob(QA_LIVE_TRANSPORTS_WORKFLOW, "run_live_matrix");
expect(matrixJob["timeout-minutes"]).toBe(90);
expect(matrixJob.concurrency).toEqual({
group: "qa-live-matrix-${{ needs.validate_selected_ref.outputs.selected_revision }}",
"cancel-in-progress": false,
queue: "max",
});
expect(workflowStep(matrixJob, "Run Matrix live lane").run).toContain(
"--provider-mode mock-openai",
);
@@ -4962,10 +4974,16 @@ describe("package artifact reuse", () => {
expect(releaseJob.with).toMatchObject({
buzz_scenario: "channel-canary,channel-mention-gating",
expected_sha: "${{ needs.resolve_target.outputs.revision }}",
lock_scope: "buzz",
run_buzz: true,
});
const buzzJob = workflowJob(QA_LIVE_TRANSPORTS_WORKFLOW, "run_live_buzz");
expect(buzzJob.if).toBe("inputs.run_buzz");
expect(buzzJob.concurrency).toEqual({
group: "qa-live-buzz-shared",
"cancel-in-progress": false,
queue: "max",
});
const resolveBuzz = workflowStep(buzzJob, "Resolve Buzz QA runner");
expect(resolveBuzz.run).toContain('runner?.commandName === "buzz"');
expect(resolveBuzz.run).toContain("selected ref does not declare the Buzz QA runner");