Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 32 additions & 1 deletion .github/workflows/release-recovery.yml
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ jobs:
with:
node-version: 24
registry-url: https://registry.npmjs.org
- name: Validate release recovery tooling
run: node --test test/release-tools.test.mjs test/release-test-report.test.mjs test/stress.test.mjs
- name: Validate release environment and npm authentication
env:
NPM_ACCESS_TOKEN: ${{ secrets.NPM_ACCESS_TOKEN }}
Expand All @@ -48,16 +50,24 @@ jobs:
set -euo pipefail
state="$(gh run view "$SOURCE_RUN_ID" --repo "$GITHUB_REPOSITORY" --json jobs --jq '
[.jobs[] | select(.name == "failfast" or .name == "coverage" or .name == "sdk-package" or .name == "stress"
or (.name | startswith("integration (")) or (.name | startswith("package (")))
or (.name | startswith("integration (")) or (.name | startswith("package ("))
or (.name | startswith("install-lifecycle (")))]
| group_by(.name) | map(max_by(.databaseId))
| if length == 14 and all(.conclusion == "success") then "success" else "failed" end')"
test "$state" = success
- name: Check out the immutable release source
env:
GH_TOKEN: ${{ github.token }}
SOURCE_RUN_ID: ${{ inputs.source_run_id }}
RELEASE_TAG: ${{ inputs.tag }}
run: |
set -euo pipefail
[[ "$RELEASE_TAG" =~ ^v(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)$ ]]
test "$(git cat-file -t "refs/tags/$RELEASE_TAG")" = tag
source_run="$(gh api "repos/$GITHUB_REPOSITORY/actions/runs/$SOURCE_RUN_ID")"
test "$(jq -r .path <<< "$source_run")" = .github/workflows/release.yml
test "$(jq -r .status <<< "$source_run")" = completed
test "$(jq -r .head_sha <<< "$source_run")" = "$(git rev-parse "refs/tags/$RELEASE_TAG^{}")"
git checkout --detach "refs/tags/$RELEASE_TAG"
- uses: actions/download-artifact@v8
with:
Expand All @@ -78,6 +88,27 @@ jobs:
path: .temp/release-evidence
run-id: ${{ inputs.source_run_id }}
github-token: ${{ github.token }}
- name: Rebuild the top-level stress suite inventory
run: |
set -euo pipefail
python3 - <<'PY'
import json
from pathlib import Path
path=Path('.temp/release-evidence/stress-qualification/qualification.json')
report=json.loads(path.read_text())
profiles={'smoke','p0-2c4g','scenario','p1-2c4g-peak','p1-2c4g-soak'}
runs=report['runs']
for run in runs:
assert run['profile'] in profiles | {'reconcile'} and run['verdict']=='pass'
assert not run['global_anomalies']
if run['profile']=='reconcile':
for item in [*run['stacks'].values(),*run['scenario'].values()]:
assert item['verdict']=='pass' and not item['anomalies']
stages=[run for run in runs if run['profile'] in profiles]
assert len(stages)==len(profiles) and {run['profile'] for run in stages}==profiles
report['runs']=stages
path.write_text(json.dumps(report,indent=2)+'\n')
PY
- name: Assemble verified release assets
env:
RELEASE_TAG: ${{ inputs.tag }}
Expand Down
2 changes: 1 addition & 1 deletion docs/releases/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ GitHub Releases 是发布状态和二进制的权威来源。本目录保存作

| Version | Published | Notes | GitHub |
| ------- | ---------- | -------------------------- | ------------------------------------------------------------------------- |
| 0.3.0 | Pending | [Release notes](0.3.0.md) | [v0.3.0](https://github.com/elliothux/open-compute/releases/tag/v0.3.0) |
| 0.3.0 | 2026-10-10 | [Release notes](0.3.0.md) | [v0.3.0](https://github.com/elliothux/open-compute/releases/tag/v0.3.0) |
| 0.2.4 | 2026-10-05 | [Release notes](0.2.4.md) | [v0.2.4](https://github.com/elliothux/open-compute/releases/tag/v0.2.4) |
| 0.2.3 | 2026-10-01 | [Release notes](0.2.3.md) | [v0.2.3](https://github.com/elliothux/open-compute/releases/tag/v0.2.3) |
| 0.2.2 | 2026-09-25 | [Release notes](0.2.2.md) | [v0.2.2](https://github.com/elliothux/open-compute/releases/tag/v0.2.2) |
Expand Down
2 changes: 1 addition & 1 deletion test/conformance/baseline.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"schemaVersion": 1,
"sourceDigest": "bc7d576b5f1dbcdabefa0dd1ee912e91740b4d800d281a56bdf3f9924cbca418",
"sourceDigest": "b99adb70658a312c6b4fa85e64f42aac23d82c2fcc706de29098c182ac893b0a",
"sourceDigestScope": "git-visible production, test, toolchain, manifest, and maintained reference inputs; excludes generated Python caches, test/conformance/baseline.json, and non-reference docs",
"workerdLockSha256": "d685331dc96e294b10a1b2365065ab6934d920c037d071b020552c7fc4f0d37f",
"workerd": {
Expand Down
107 changes: 107 additions & 0 deletions test/release-tools.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -437,6 +437,113 @@ test("release qualification and local Docker diagnostic keep their exact boundar
assert.doesNotMatch(workflow, /tolerate-republish/);
});

test("recovery rebuilds the stress index without accepting failed or unknown results", async () => {
const workflow = await readFile(recoveryWorkflowPath, "utf8");
const qualification = workflow.match(/--json jobs --jq '([\s\S]*?)'/)?.[1];
assert.ok(qualification);
const names = [
"failfast",
"coverage",
"sdk-package",
"stress",
"integration (macos)",
"integration (linux)",
"package (macos)",
"package (arm64)",
"package (x64)",
...Array.from({ length: 5 }, (_, index) => `install-lifecycle (${index})`),
];
const jobs = names.map((name, databaseId) => ({
name,
databaseId,
conclusion: "success",
}));
for (const [items, expected] of [
[jobs, "success"],
[jobs.slice(1), "failed"],
[[{ ...jobs[0], conclusion: "failure" }, ...jobs.slice(1)], "failed"],
[
[...jobs, { ...jobs[0], databaseId: 100, conclusion: "failure" }],
"failed",
],
]) {
const { stdout } = await execFileAsync("jq", [
"-nr",
"--argjson",
"evidence",
JSON.stringify({ jobs: items }),
`$evidence | ${qualification}`,
]);
assert.equal(stdout.trim(), expected);
}
assert.match(
workflow,
/\.head_sha[\s\S]*?git rev-parse "refs\/tags\/\$RELEASE_TAG\^\{\}"/,
);
const script = workflow
.match(/ python3 - <<'PY'\n([\s\S]*?) PY/)?.[1]
.replace(/^ /gm, "");
assert.ok(script);
const directory = await mkdtemp(join(tmpdir(), "oc-recovery-"));
try {
const evidence = join(
directory,
".temp/release-evidence/stress-qualification",
);
await mkdir(evidence, { recursive: true });
const path = join(evidence, "qualification.json");
const profiles = [
"smoke",
"p0-2c4g",
"scenario",
"p1-2c4g-peak",
"p1-2c4g-soak",
];
const events = [{ event: "restart_reconcile_ok" }];
const runs = profiles.map((profile) => ({
profile,
verdict: "pass",
global_anomalies: [],
...(profile.endsWith("soak") ? { soak: { events } } : {}),
}));
const reconcile = {
profile: "reconcile",
verdict: "pass",
global_anomalies: [],
stacks: { kv: { verdict: "pass", anomalies: [] } },
scenario: {},
};
const original = {
revision: "fixture",
runs: [...runs, reconcile, reconcile, reconcile],
};
await writeFile(path, JSON.stringify(original));
await execFileAsync("python3", ["-c", script], { cwd: directory });
assert.deepEqual(JSON.parse(await readFile(path, "utf8")), {
revision: "fixture",
runs,
});
for (const invalid of [
{ ...reconcile, verdict: "fail" },
{ ...reconcile, profile: "unknown" },
{ ...reconcile, global_anomalies: ["failure"] },
{ ...reconcile, stacks: { kv: { verdict: "fail", anomalies: [] } } },
runs[0],
]) {
await writeFile(path, JSON.stringify({ runs: [...runs, invalid] }));
await assert.rejects(
execFileAsync("python3", ["-c", script], { cwd: directory }),
);
}
await writeFile(path, JSON.stringify({ runs: runs.slice(1) }));
await assert.rejects(
execFileAsync("python3", ["-c", script], { cwd: directory }),
);
} finally {
await rm(directory, { recursive: true });
}
});

test("release assembly requires and describes the exact three native executables", async () => {
const root = await mkdtemp(join(tmpdir(), "oc-release-assembly-test-"));
const evidenceDirectory = await mkdtemp(
Expand Down
74 changes: 74 additions & 0 deletions test/stress.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,80 @@ done
await rm(directory, { recursive: true });
});

test("release qualification selects five stages and retains soak recovery evidence", async () => {
const directory = await fixture();
try {
const source = await readFile(
join(root, "test/stress/qualify-release.sh"),
"utf8",
);
const collector = source.match(
/python3 - "\$STRESS_RUN_ROOT" "\$package_report" <<'PY'\n([\s\S]*?)\nPY/,
)?.[1];
assert.ok(collector);
const packagePath = join(directory, "package.json");
await writeFile(
packagePath,
JSON.stringify({
version: "1.2.3",
revision: "fixture",
sha256: "fixture",
workerdLockSha256: "fixture",
}),
);
const profiles = [
"smoke",
"p0-2c4g",
"scenario",
"p1-2c4g-peak",
"p1-2c4g-soak",
];
const events = [{ event: "restart_reconcile_ok" }];
for (const [index, profile] of [
...profiles,
"reconcile",
"reconcile",
"reconcile",
].entries()) {
await mkdir(join(directory, String(index)));
await writeFile(
join(directory, String(index), "result.json"),
JSON.stringify({
profile,
verdict: "pass",
...(profile.endsWith("soak") ? { soak: { events } } : {}),
}),
);
}
const collect = () =>
exec("python3", ["-c", collector, directory, packagePath]);
await collect();
const qualification = JSON.parse(
await readFile(join(directory, "qualification.json"), "utf8"),
);
assert.deepEqual(
qualification.runs.map((run) => run.profile),
profiles,
);
assert.deepEqual(qualification.runs.at(-1).soak.events, events);
const first = join(directory, "0/result.json");
await writeFile(
first,
JSON.stringify({ profile: "smoke", verdict: "fail" }),
);
await assert.rejects(collect());
await writeFile(
first,
JSON.stringify({ profile: "scenario", verdict: "pass" }),
);
await assert.rejects(collect());
await rm(first);
await assert.rejects(collect());
} finally {
await rm(directory, { recursive: true });
}
});

test("final qualification exits unsuccessfully and preserves failed evidence", async () => {
const directory = await fixture();
const result = join(directory, "result.json");
Expand Down
8 changes: 7 additions & 1 deletion test/stress/qualify-release.sh
Original file line number Diff line number Diff line change
Expand Up @@ -85,9 +85,15 @@ python3 - "$STRESS_RUN_ROOT" "$package_report" <<'PY'
import json,sys
from pathlib import Path
root,package=Path(sys.argv[1]),json.loads(Path(sys.argv[2]).read_text())
profiles=['smoke','p0-2c4g','scenario','p1-2c4g-peak','p1-2c4g-soak']
results=[json.loads(p.read_text()) for p in sorted(root.glob('*/result.json'))]
# Reconcile results are internal soak evidence; verified recovery stays in soak.events.
runs=[r for r in results if r.get('profile') in profiles]
assert len(runs)==len(profiles) and {r['profile'] for r in runs}==set(profiles)
assert all(r['verdict']=='pass' for r in runs)
report={'schemaVersion':1,'status':'passed','version':package['version'],'revision':package['revision'],
'candidateSha256':package['sha256'],'workerdLockSha256':package['workerdLockSha256'],
'cpuLimit':2,'memoryLimitBytes':4*1024**3,'runs':[json.loads(p.read_text()) for p in sorted(root.glob('*/result.json'))]}
'cpuLimit':2,'memoryLimitBytes':4*1024**3,'runs':runs}
(root/'qualification.json').write_text(json.dumps(report,indent=2)+'\n')
PY
printf 'release stress qualification passed: %s\n' "$STRESS_RUN_ROOT/qualification.json"