mirror of
https://github.com/openclaw/openclaw.git
synced 2026-07-19 04:51:37 +00:00
fix(release): gate evidence reuse on trusted lineage
This commit is contained in:
@@ -276,6 +276,7 @@ jobs:
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
TARGET_SHA: ${{ needs.resolve_target.outputs.sha }}
|
||||
WORKFLOW_REF: ${{ github.ref_name }}
|
||||
RELEASE_PROFILE: ${{ inputs.release_profile }}
|
||||
RUN_RELEASE_SOAK: ${{ inputs.run_release_soak || inputs.release_profile == 'stable' || inputs.release_profile == 'full' }}
|
||||
PROVIDER: ${{ inputs.provider }}
|
||||
@@ -309,6 +310,7 @@ jobs:
|
||||
bash workflow/scripts/github/find-reusable-release-validation.sh \
|
||||
--target-sha "$TARGET_SHA" \
|
||||
--workflow-sha "$GITHUB_SHA" \
|
||||
--workflow-ref "$WORKFLOW_REF" \
|
||||
--release-profile "$RELEASE_PROFILE" \
|
||||
--run-release-soak "$RUN_RELEASE_SOAK" \
|
||||
--inputs-json "$inputs_json" \
|
||||
|
||||
@@ -10,6 +10,7 @@ REPO="${GH_REPO:-}"
|
||||
WORKFLOW_FILE="full-release-validation.yml"
|
||||
TARGET_SHA=""
|
||||
VERIFIER_WORKFLOW_SHA=""
|
||||
WORKFLOW_REF=""
|
||||
RELEASE_PROFILE=""
|
||||
RUN_RELEASE_SOAK="false"
|
||||
INPUTS_JSON=""
|
||||
@@ -24,6 +25,7 @@ VALIDATOR="${OPENCLAW_RELEASE_CI_SUMMARY_VALIDATOR:-${REPO_ROOT}/scripts/release
|
||||
usage() {
|
||||
cat >&2 <<'EOF'
|
||||
Usage: find-reusable-release-validation.sh --target-sha <sha> --workflow-sha <sha> \
|
||||
--workflow-ref <main|release-ci/sha12-timestamp> \
|
||||
--release-profile <beta|stable|full> --inputs-json <json> \
|
||||
[--run-release-soak <true|false>] [--repo <owner/repo>] [--repo-dir <path>] \
|
||||
[--workflow <file>] [--max-candidates <n>] [--github-output <file>]
|
||||
@@ -47,6 +49,10 @@ while [[ $# -gt 0 ]]; do
|
||||
VERIFIER_WORKFLOW_SHA="${2:-}"
|
||||
shift 2
|
||||
;;
|
||||
--workflow-ref)
|
||||
WORKFLOW_REF="${2:-}"
|
||||
shift 2
|
||||
;;
|
||||
--release-profile)
|
||||
RELEASE_PROFILE="${2:-}"
|
||||
shift 2
|
||||
@@ -116,6 +122,13 @@ if [[ ! "$VERIFIER_WORKFLOW_SHA" =~ ^[0-9a-f]{40}$ ]]; then
|
||||
echo "Expected --workflow-sha to be a full lowercase commit SHA; got: ${VERIFIER_WORKFLOW_SHA}" >&2
|
||||
exit 2
|
||||
fi
|
||||
if [[ "$WORKFLOW_REF" != "main" ]]; then
|
||||
expected_release_ref="release-ci/${VERIFIER_WORKFLOW_SHA:0:12}-"
|
||||
if [[ ! "$WORKFLOW_REF" =~ ^release-ci/[0-9a-f]{12}-[1-9][0-9]*$ ]] ||
|
||||
[[ "$WORKFLOW_REF" != "$expected_release_ref"* ]]; then
|
||||
no_reuse "workflow ref is not a canonical SHA-pinned release ref"
|
||||
fi
|
||||
fi
|
||||
if [[ -z "$REPO" ]]; then
|
||||
echo "Expected --repo <owner/repo> or GH_REPO." >&2
|
||||
exit 2
|
||||
@@ -134,6 +147,20 @@ if ! expected_inputs="$(jq -Sc 'if type == "object" then . else error("expected
|
||||
exit 2
|
||||
fi
|
||||
|
||||
workflow_lineage=""
|
||||
if ! workflow_lineage="$(
|
||||
gh api "repos/${REPO}/compare/${VERIFIER_WORKFLOW_SHA}...main"
|
||||
)"; then
|
||||
no_reuse "could not verify workflow SHA against trusted main"
|
||||
fi
|
||||
if ! jq -e \
|
||||
--arg workflow_sha "$VERIFIER_WORKFLOW_SHA" '
|
||||
(.status == "ahead" or .status == "identical")
|
||||
and .merge_base_commit.sha == $workflow_sha
|
||||
' <<< "$workflow_lineage" >/dev/null; then
|
||||
no_reuse "workflow SHA is not on trusted main lineage"
|
||||
fi
|
||||
|
||||
# Exact-target reuse still requires internally consistent version stamps
|
||||
# (for example package.json must agree with the macOS plist).
|
||||
if ! (cd "$REPO_DIR" && node "$PREFLIGHT" --macos-versions-only >&2); then
|
||||
|
||||
@@ -409,8 +409,18 @@ function runResolver(args: {
|
||||
runReleaseSoak?: string;
|
||||
targetSha: string;
|
||||
validatorPath: string;
|
||||
verifierOnMain?: boolean;
|
||||
verifierSha?: string;
|
||||
workflowRef?: string;
|
||||
}) {
|
||||
const verifierSha = args.verifierSha ?? VERIFIER_SHA;
|
||||
writeFileSync(
|
||||
fixtureName(args.fixtures, `repos/${REPOSITORY}/compare/${verifierSha}...main`),
|
||||
JSON.stringify({
|
||||
merge_base_commit: { sha: args.verifierOnMain === false ? "f".repeat(40) : verifierSha },
|
||||
status: args.verifierOnMain === false ? "diverged" : "ahead",
|
||||
}),
|
||||
);
|
||||
return spawnSync(
|
||||
"bash",
|
||||
[
|
||||
@@ -418,7 +428,9 @@ function runResolver(args: {
|
||||
"--target-sha",
|
||||
args.targetSha,
|
||||
"--workflow-sha",
|
||||
args.verifierSha ?? VERIFIER_SHA,
|
||||
verifierSha,
|
||||
"--workflow-ref",
|
||||
args.workflowRef ?? "main",
|
||||
"--release-profile",
|
||||
args.releaseProfile ?? "full",
|
||||
"--run-release-soak",
|
||||
@@ -459,6 +471,34 @@ function parseOutput(output: string): Record<string, string> {
|
||||
}
|
||||
|
||||
describe("scripts/github/find-reusable-release-validation.sh", () => {
|
||||
it("rejects noncanonical release refs and workflow SHAs outside trusted main", () => {
|
||||
const { origin, priorSha } = createRepo();
|
||||
const clone = cloneHead(origin);
|
||||
const record = normalizedEvidence({ targetSha: priorSha });
|
||||
const { binDir, fixtures, validatorPath } = setUpFixtures([{ record, runId: "111" }]);
|
||||
|
||||
const forgedRef = runResolver({
|
||||
binDir,
|
||||
fixtures,
|
||||
repoDir: clone,
|
||||
targetSha: priorSha,
|
||||
validatorPath,
|
||||
workflowRef: "release-ci/not-trusted",
|
||||
});
|
||||
expect(parseOutput(forgedRef.stdout)).toMatchObject({ reuse: "false" });
|
||||
|
||||
const untrustedSha = runResolver({
|
||||
binDir,
|
||||
fixtures,
|
||||
repoDir: clone,
|
||||
targetSha: priorSha,
|
||||
validatorPath,
|
||||
verifierOnMain: false,
|
||||
workflowRef: `release-ci/${VERIFIER_SHA.slice(0, 12)}-123`,
|
||||
});
|
||||
expect(parseOutput(untrustedSha.stdout)).toMatchObject({ reuse: "false" });
|
||||
});
|
||||
|
||||
it("reuses pre-tooling trusted-main evidence for the exact target", () => {
|
||||
const { origin, priorSha } = createRepo();
|
||||
const clone = cloneHead(origin);
|
||||
|
||||
Reference in New Issue
Block a user