mirror of
https://github.com/openclaw/openclaw.git
synced 2026-10-03 09:39:25 +00:00
fix(release): reject unrecoverable FRV continuation (#132711)
This commit is contained in:
parent
50e1f091ee
commit
55abb43917
5 changed files with 64 additions and 2 deletions
|
|
@ -58,6 +58,10 @@ Use this with `$release-openclaw-maintainer` and `$openclaw-testing` when a rele
|
||||||
- Same-parent continuation requires the original root to have been dispatched
|
- Same-parent continuation requires the original root to have been dispatched
|
||||||
with `fail_fast=false`. The controller verifies that exact logged input
|
with `fail_fast=false`. The controller verifies that exact logged input
|
||||||
before any rerun mutation.
|
before any rerun mutation.
|
||||||
|
- A parent that produced its own sealed candidate artifacts cannot be continued:
|
||||||
|
GitHub reruns make those prior-attempt artifacts unavailable. Keep the
|
||||||
|
candidate and Tooling SHAs frozen, supersede that parent, and start a fresh
|
||||||
|
all-group Full Release Validation.
|
||||||
- After dispatch, one immutable execution-plan artifact records the original
|
- After dispatch, one immutable execution-plan artifact records the original
|
||||||
parent attempt, exact child tuples and titles, selected coverage, gates, and
|
parent attempt, exact child tuples and titles, selected coverage, gates, and
|
||||||
reuse identity. The same bytes are saved under an exact run-ID cache key.
|
reuse identity. The same bytes are saved under an exact run-ID cache key.
|
||||||
|
|
|
||||||
|
|
@ -1674,8 +1674,8 @@ jobs:
|
||||||
DIAGNOSTIC_DRAIN_PATH: ${{ runner.temp }}/full-release-diagnostics/full-release-diagnostic-manifest.json
|
DIAGNOSTIC_DRAIN_PATH: ${{ runner.temp }}/full-release-diagnostics/full-release-diagnostic-manifest.json
|
||||||
run: node scripts/full-release-validation-state.mjs verify
|
run: node scripts/full-release-validation-state.mjs verify
|
||||||
|
|
||||||
# Artifact v4 keeps prior attempts readable within the same workflow run.
|
# Exact IDs from the sealed plan prevent verification from drifting to a newer upload.
|
||||||
# Exact IDs from the sealed plan prevent recovery from drifting to a newer upload.
|
# Parent-owned candidate plans are rejected before same-parent continuation.
|
||||||
- name: Verify sealed release candidate
|
- name: Verify sealed release candidate
|
||||||
env:
|
env:
|
||||||
GH_TOKEN: ${{ github.token }}
|
GH_TOKEN: ${{ github.token }}
|
||||||
|
|
|
||||||
|
|
@ -46,6 +46,10 @@ still-active child that owns the blocking failure.
|
||||||
Same-parent continuation requires the original root to have been dispatched
|
Same-parent continuation requires the original root to have been dispatched
|
||||||
with `fail_fast=false`. The controller verifies that exact logged input before
|
with `fail_fast=false`. The controller verifies that exact logged input before
|
||||||
any rerun mutation.
|
any rerun mutation.
|
||||||
|
It is also unavailable when that parent produced the sealed candidate
|
||||||
|
artifacts, because GitHub reruns make those prior-attempt artifacts unavailable.
|
||||||
|
Keep the candidate and Tooling SHAs frozen, supersede the parent, and start a
|
||||||
|
fresh all-group Full Release Validation.
|
||||||
|
|
||||||
After dispatch, the parent writes one immutable
|
After dispatch, the parent writes one immutable
|
||||||
`full-release-execution-plan-<run-id>` artifact and preserves the same bytes in
|
`full-release-execution-plan-<run-id>` artifact and preserves the same bytes in
|
||||||
|
|
|
||||||
|
|
@ -249,6 +249,11 @@ export async function preflightContinuation(
|
||||||
client,
|
client,
|
||||||
repository = DEFAULT_REPOSITORY,
|
repository = DEFAULT_REPOSITORY,
|
||||||
) {
|
) {
|
||||||
|
if (plan.candidate?.producer.runId === String(rootRunId)) {
|
||||||
|
throw new Error(
|
||||||
|
"parent-owned sealed candidate artifacts do not survive parent reruns; start a fresh all-group FRV",
|
||||||
|
);
|
||||||
|
}
|
||||||
if (plan.rerunGroup !== "all") {
|
if (plan.rerunGroup !== "all") {
|
||||||
throw new Error("FRV continuation requires an all-group root");
|
throw new Error("FRV continuation requires an all-group root");
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -418,6 +418,55 @@ describe("FRV immutable plan eligibility", () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("FRV continuation preflight", () => {
|
describe("FRV continuation preflight", () => {
|
||||||
|
it("rejects parent-owned candidate artifacts before any GitHub access", async () => {
|
||||||
|
const selected = child("normalCi", "101");
|
||||||
|
const parentOwnedPlan = {
|
||||||
|
...plan([selected]),
|
||||||
|
candidate: { producer: { runId: "77" } },
|
||||||
|
};
|
||||||
|
let reads = 0;
|
||||||
|
let mutations = 0;
|
||||||
|
const read = async () => {
|
||||||
|
reads += 1;
|
||||||
|
throw new Error("unexpected GitHub read");
|
||||||
|
};
|
||||||
|
const mutate = async () => {
|
||||||
|
mutations += 1;
|
||||||
|
};
|
||||||
|
|
||||||
|
await expect(
|
||||||
|
continueFailed(parentOwnedPlan, "77", {
|
||||||
|
getAttemptJobs: read,
|
||||||
|
getJobLog: read,
|
||||||
|
getParentJobs: read,
|
||||||
|
getRun: read,
|
||||||
|
getRunAttempt: read,
|
||||||
|
repository: REPOSITORY,
|
||||||
|
rerunFailed: mutate,
|
||||||
|
rerunParent: mutate,
|
||||||
|
verify: mutate,
|
||||||
|
}),
|
||||||
|
).rejects.toThrow(
|
||||||
|
"parent-owned sealed candidate artifacts do not survive parent reruns; start a fresh all-group FRV",
|
||||||
|
);
|
||||||
|
expect(reads).toBe(0);
|
||||||
|
expect(mutations).toBe(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it.each([
|
||||||
|
["candidate-free", undefined],
|
||||||
|
["externally produced", { producer: { runId: "88" } }],
|
||||||
|
])("allows %s plans through candidate ownership preflight", async (_label, candidate) => {
|
||||||
|
const selected = child("normalCi", "101");
|
||||||
|
await expect(
|
||||||
|
preflightContinuation(
|
||||||
|
{ ...plan([selected]), candidate },
|
||||||
|
"77",
|
||||||
|
preflightMethods([selected], (entry) => runFor(entry, 1, "failure")),
|
||||||
|
),
|
||||||
|
).resolves.toMatchObject({ id: 77 });
|
||||||
|
});
|
||||||
|
|
||||||
it("rejects fail-fast roots before any rerun mutation", async () => {
|
it("rejects fail-fast roots before any rerun mutation", async () => {
|
||||||
const selected = child("normalCi", "101");
|
const selected = child("normalCi", "101");
|
||||||
let mutations = 0;
|
let mutations = 0;
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue