mirror of
https://github.com/nexu-io/open-design.git
synced 2026-10-27 09:51:12 +01:00
The `[P0] cold first Home run` packaged mac case has been red on almost
every beta build for eleven days (1 pass in 12 attempts) and nobody saw
it, because release-beta's smoke step is `continue-on-error: true`.
Four changes, in the order they matter:
1. Capture evidence before cleanup. A failing case uninstalled the app
and the next case deleted the runtime namespace, so the only surviving
trace was `logs: {skipped: true}`. The failure path now writes the
desktop log, a raw `/api/runs` diagnostics snapshot, and the error into
the release report before anything is torn down.
2. Give the wait a budget that matches the chain, split in two. 15s had to
cover cold project creation through rendered output; it is now 60s for
"the daemon owns a finished run" plus 30s for "the output surfaced", so
a timeout says which half broke instead of leaving three observations
that cannot falsify each other. Polling moved inside the page: every
sample used to cost a `tools-pack mac inspect` process, which ate about
a third of the old budget on its own.
3. Drop the injected 503. `POST /api/runs` does not answer
WORKSPACE_AUTHORITY_UNAVAILABLE — local run creation is deliberately
independent of Workspace membership availability — so the case spent
an extra create round trip plus a 500ms backoff on a branch production
cannot reach. Run-create retry semantics stay covered by
apps/web/tests/providers/sse.test.ts.
4. Make the exempted red audible. The exemption stays (at a 1/12 pass rate
a blocking gate would stall the daily beta), but `tools-release
write-report` now turns a failed smoke into a run annotation and a
caution banner at the top of the step summary.
102 lines
3.9 KiB
TypeScript
102 lines
3.9 KiB
TypeScript
import { execFile as execFileCallback } from "node:child_process";
|
|
import { mkdir, mkdtemp, readFile, rm, writeFile } from "node:fs/promises";
|
|
import { createRequire } from "node:module";
|
|
import { tmpdir } from "node:os";
|
|
import { dirname, join, resolve } from "node:path";
|
|
import { fileURLToPath } from "node:url";
|
|
import { promisify } from "node:util";
|
|
|
|
import { describe, expect, it } from "vitest";
|
|
|
|
const execFileAsync = promisify(execFileCallback);
|
|
const require = createRequire(import.meta.url);
|
|
const testDir = dirname(fileURLToPath(import.meta.url));
|
|
const workspaceRoot = resolve(testDir, "..", "..", "..");
|
|
const tsxCliPath = require.resolve("tsx/cli");
|
|
|
|
type WriteReportRun = {
|
|
report: Record<string, unknown>;
|
|
stdout: string;
|
|
summary: string;
|
|
};
|
|
|
|
async function runWriteReport(
|
|
env: Record<string, string>,
|
|
suiteStatus: string,
|
|
): Promise<WriteReportRun> {
|
|
const root = await mkdtemp(join(tmpdir(), "od-write-report-"));
|
|
const reportRoot = join(root, "release-report");
|
|
try {
|
|
await mkdir(reportRoot, { recursive: true });
|
|
await writeFile(
|
|
join(reportRoot, "suite-result.json"),
|
|
`${JSON.stringify({ durationMs: 1234, exitCode: suiteStatus === "failed" ? 1 : 0, status: suiteStatus })}\n`,
|
|
"utf8",
|
|
);
|
|
const result = await execFileAsync(
|
|
process.execPath,
|
|
[tsxCliPath, "tools/release/src/index.ts", "write-report"],
|
|
{
|
|
cwd: workspaceRoot,
|
|
env: {
|
|
...process.env,
|
|
RELEASE_REPORT_DIR: reportRoot,
|
|
RELEASE_REPORT_JSON_PATH: join(reportRoot, "report.json"),
|
|
RELEASE_REPORT_SUMMARY_PATH: join(reportRoot, "summary.md"),
|
|
RELEASE_TARGET: "mac_arm64",
|
|
RELEASE_VERSION: "0.21.6-beta.7",
|
|
REPORT_TITLE: "mac_arm64 beta build",
|
|
...env,
|
|
},
|
|
},
|
|
);
|
|
return {
|
|
report: JSON.parse(await readFile(join(reportRoot, "report.json"), "utf8")) as Record<string, unknown>,
|
|
stdout: result.stdout,
|
|
summary: await readFile(join(reportRoot, "summary.md"), "utf8"),
|
|
};
|
|
} finally {
|
|
await rm(root, { force: true, recursive: true });
|
|
}
|
|
}
|
|
|
|
describe("release report smoke signal", () => {
|
|
it("leads the step summary with the failure and annotates the run", async () => {
|
|
// This is the whole point of the change: the summary is published to
|
|
// $GITHUB_STEP_SUMMARY by the very next workflow step, and the annotation
|
|
// lands on the run page — so a continue-on-error smoke failure can no longer
|
|
// be reduced to one `status` line inside a report nobody scrolls to.
|
|
const run = await runWriteReport(
|
|
{ RELEASE_SMOKE_EXEMPT: "true", RELEASE_SMOKE_OUTCOME: "failure" },
|
|
"failed",
|
|
);
|
|
|
|
expect(run.summary.startsWith("> [!CAUTION]")).toBe(true);
|
|
expect(run.summary).toContain("packaged smoke FAILED");
|
|
expect(run.summary).toContain("0.21.6-beta.7");
|
|
expect(run.summary).toContain("### mac_arm64 beta build");
|
|
expect(run.stdout).toContain("::error title=mac_arm64 packaged smoke failed::");
|
|
expect(run.report.smoke).toEqual({ exempt: true, failed: true, outcome: "failure" });
|
|
}, 60_000);
|
|
|
|
it("keeps a passing smoke summary unchanged", async () => {
|
|
const run = await runWriteReport(
|
|
{ RELEASE_SMOKE_EXEMPT: "true", RELEASE_SMOKE_OUTCOME: "success" },
|
|
"success",
|
|
);
|
|
|
|
expect(run.summary.startsWith("### mac_arm64 beta build")).toBe(true);
|
|
expect(run.summary).not.toContain("[!CAUTION]");
|
|
expect(run.stdout).not.toContain("::error");
|
|
expect(run.report.smoke).toEqual({ exempt: true, failed: false, outcome: "success" });
|
|
}, 60_000);
|
|
|
|
it("still announces a failure for callers that only wire the suite result", async () => {
|
|
const run = await runWriteReport({}, "failed");
|
|
|
|
expect(run.summary.startsWith("> [!CAUTION]")).toBe(true);
|
|
expect(run.stdout).toContain("::error");
|
|
expect(run.report.smoke).toEqual({ exempt: false, failed: true, outcome: "" });
|
|
}, 60_000);
|
|
});
|