mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-25 03:45:46 -06:00
fix(ci): fall back when FRV routing auth is unavailable
This commit is contained in:
+28
-13
@@ -152,31 +152,44 @@ jobs:
|
||||
- name: Validate Full Release Validation parent
|
||||
id: frv_parent
|
||||
env:
|
||||
CI_RUNNER_BACKEND: ${{ vars.OPENCLAW_CI_RUNNER_BACKEND }}
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
PARENT_RUN_ATTEMPT: ${{ inputs.full_release_validation_run_attempt }}
|
||||
PARENT_RUN_ID: ${{ inputs.full_release_validation_run_id }}
|
||||
RELEASE_GATE: ${{ inputs.release_gate }}
|
||||
TARGET_REF: ${{ inputs.target_ref }}
|
||||
WORKFLOW_SHA: ${{ github.sha }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
echo "trusted_first_attempt=false" >> "$GITHUB_OUTPUT"
|
||||
|
||||
# Parent authentication only unlocks the attempt-one fast route. Any
|
||||
# hosted-policy case must remain independent of the parent API.
|
||||
if [[ "$GITHUB_RUN_ATTEMPT" != "1" || "$CI_RUNNER_BACKEND" == "github" || "$RELEASE_GATE" == "true" ]]; then
|
||||
exit 0
|
||||
fi
|
||||
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
|
||||
echo "::warning::Full Release Validation parent identity is malformed; routing iOS to GitHub-hosted macOS." >&2
|
||||
exit 0
|
||||
fi
|
||||
if [[ "$PARENT_RUN_ATTEMPT" != "1" ]]; then
|
||||
exit 0
|
||||
fi
|
||||
if [[ ! "$TARGET_REF" =~ ^[0-9a-f]{40}$ ]]; then
|
||||
echo "::error::Full Release Validation parent routing requires an exact target SHA." >&2
|
||||
exit 1
|
||||
echo "::warning::Full Release Validation parent routing requires an exact target SHA; routing iOS to GitHub-hosted macOS." >&2
|
||||
exit 0
|
||||
fi
|
||||
|
||||
parent_run="$(
|
||||
if ! parent_run="$(
|
||||
gh api \
|
||||
"repos/${GITHUB_REPOSITORY}/actions/runs/${PARENT_RUN_ID}/attempts/${PARENT_RUN_ATTEMPT}"
|
||||
)"
|
||||
"repos/${GITHUB_REPOSITORY}/actions/runs/${PARENT_RUN_ID}/attempts/${PARENT_RUN_ATTEMPT}" 2>&1
|
||||
)"; then
|
||||
echo "::warning::Could not authenticate the Full Release Validation parent through the Actions API; routing iOS to GitHub-hosted macOS." >&2
|
||||
exit 0
|
||||
fi
|
||||
expected_title="Full Release Validation ${TARGET_REF}"
|
||||
if ! jq -e \
|
||||
--arg branch "$GITHUB_REF_NAME" \
|
||||
@@ -194,19 +207,21 @@ jobs:
|
||||
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
|
||||
echo "::warning::Full Release Validation parent identity did not match this CI target and workflow; routing iOS to GitHub-hosted macOS." >&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
|
||||
exit 0
|
||||
fi
|
||||
|
||||
if [[ "$PARENT_RUN_ATTEMPT" == "1" && "$GITHUB_RUN_ATTEMPT" == "1" ]]; then
|
||||
echo "trusted_first_attempt=true" >> "$GITHUB_OUTPUT"
|
||||
if ! jq -e '.status == "in_progress" and .conclusion == null' \
|
||||
<<< "$parent_run" >/dev/null; then
|
||||
echo "::warning::Full Release Validation parent is no longer active; routing iOS to GitHub-hosted macOS." >&2
|
||||
exit 0
|
||||
fi
|
||||
|
||||
echo "trusted_first_attempt=true" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Validate release-gate dispatch
|
||||
if: github.event_name == 'workflow_dispatch' && inputs.release_gate
|
||||
env:
|
||||
|
||||
+1
-1
@@ -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.
|
||||
|
||||
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.
|
||||
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. Parent API errors, identity mismatches, and parents that finish before child preflight also fall back to hosted macOS without failing CI.
|
||||
|
||||
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.
|
||||
|
||||
|
||||
@@ -1795,9 +1795,12 @@ NODE
|
||||
};
|
||||
const runCase = (options?: {
|
||||
childAttempt?: number;
|
||||
ghStatus?: number;
|
||||
parentAttempt?: number;
|
||||
parentRun?: Record<string, unknown>;
|
||||
provideParent?: boolean;
|
||||
releaseGate?: boolean;
|
||||
runnerBackend?: string;
|
||||
}) => {
|
||||
const root = tempDirs.make("openclaw-frv-parent-");
|
||||
const binDir = path.join(root, "bin");
|
||||
@@ -1809,6 +1812,7 @@ NODE
|
||||
"set -euo pipefail",
|
||||
'printf "%s\\n" "$*" >> "$GH_CALLS"',
|
||||
'printf "%s\\n" "$MOCK_GH_RESPONSE"',
|
||||
'exit "${MOCK_GH_STATUS:-0}"',
|
||||
]);
|
||||
const provideParent = options?.provideParent ?? true;
|
||||
const parentAttempt = options?.parentAttempt ?? 1;
|
||||
@@ -1817,15 +1821,18 @@ NODE
|
||||
cwd: root,
|
||||
env: {
|
||||
...process.env,
|
||||
CI_RUNNER_BACKEND: options?.runnerBackend ?? "",
|
||||
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),
|
||||
MOCK_GH_STATUS: String(options?.ghStatus ?? 0),
|
||||
PARENT_RUN_ATTEMPT: provideParent ? String(parentAttempt) : "",
|
||||
PARENT_RUN_ID: provideParent ? String(baseRun.id) : "",
|
||||
PATH: `${binDir}:${process.env.PATH ?? ""}`,
|
||||
RELEASE_GATE: options?.releaseGate ? "true" : "false",
|
||||
TARGET_REF: targetSha,
|
||||
WORKFLOW_SHA: workflowSha,
|
||||
},
|
||||
@@ -1861,9 +1868,15 @@ NODE
|
||||
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 hostedFallback of [
|
||||
runCase({ childAttempt: 2 }),
|
||||
runCase({ parentAttempt: 2 }),
|
||||
runCase({ releaseGate: true }),
|
||||
runCase({ runnerBackend: "github" }),
|
||||
]) {
|
||||
expect(hostedFallback.result.status, hostedFallback.result.stderr).toBe(0);
|
||||
expect(hostedFallback.outputs.trusted_first_attempt).toBe("false");
|
||||
expect(hostedFallback.calls).toBe("");
|
||||
}
|
||||
|
||||
for (const parentRun of [
|
||||
@@ -1875,9 +1888,15 @@ NODE
|
||||
{ ...baseRun, status: "completed", conclusion: "success" },
|
||||
]) {
|
||||
const rejected = runCase({ parentRun });
|
||||
expect(rejected.result.status, JSON.stringify(parentRun)).toBe(1);
|
||||
expect(rejected.result.status, JSON.stringify(parentRun)).toBe(0);
|
||||
expect(rejected.outputs.trusted_first_attempt).toBe("false");
|
||||
expect(rejected.result.stderr).toContain("::warning::");
|
||||
}
|
||||
|
||||
const apiFailure = runCase({ ghStatus: 1 });
|
||||
expect(apiFailure.result.status, apiFailure.result.stderr).toBe(0);
|
||||
expect(apiFailure.outputs.trusted_first_attempt).toBe("false");
|
||||
expect(apiFailure.result.stderr).toContain("::warning::");
|
||||
});
|
||||
|
||||
it("routes only trusted first-attempt Full Release Validation iOS to Blacksmith", () => {
|
||||
|
||||
Reference in New Issue
Block a user