test(scripts): remove low-value tests (batch d152) (#163549)

* test(scripts): deslop s1015 tests

* test(scripts): deslop s1023 tests

* test(scripts): deslop s1026 tests

* test(scripts): deslop s1027 tests

* test(scripts): deslop s1020 tests

* test(scripts): deslop s1031 tests

* test(scripts): deslop s1038 tests

* test(scripts): deslop s1025 tests

* test(scripts): deslop s1028 tests

* test(scripts): deslop s1037 tests

* test(scripts): retain rollout ancestry coverage
This commit is contained in:
Peter Steinberger 2026-10-02 07:23:04 -07:00 • committed by GitHub
parent 779c5aae5e
commit 65cb5ab547
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
47 changed files with 668 additions and 1749 deletions

View file

@ -26,7 +26,7 @@ type OxlintDiagnostic = {
help?: string;
};
export function compareLineCapViolations(
function compareLineCapViolations(
head: ReadonlyMap<string, LineCapViolation>,
base: ReadonlyMap<string, LineCapViolation>,
renames: readonly { from: string; to: string }[] = [],

View file

@ -2,7 +2,7 @@ import { execFileSync } from "node:child_process";
import fs from "node:fs";
import path from "node:path";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import { compareLineCapViolations, main } from "../../scripts/check-line-cap-ratchet.mts";
import { main } from "../../scripts/check-line-cap-ratchet.mts";
import { useAutoCleanupTempDirTracker } from "../helpers/temp-dir.js";
const tempDirs = useAutoCleanupTempDirTracker(afterEach);
@ -102,18 +102,6 @@ describe("line-cap growth ratchet", () => {
expect(errors).not.toHaveBeenCalled();
});
it.each([
{ label: "over-cap shrinking", before: 705, after: 703, fails: false },
{ label: "over-cap growing", before: 705, after: 706, fails: true },
{ label: "newly over-cap", before: 700, after: 701, fails: true },
{ label: "under-cap growth", before: 698, after: 700, fails: false },
{ label: "unchanged over-cap", before: 705, after: 705, fails: false },
])("$label", ({ before, after, fails }) => {
const violations = (count: number) =>
new Map(count > 700 ? [["src/file.ts", { count, cap: 700 }]] : []);
expect(compareLineCapViolations(violations(after), violations(before)).length > 0).toBe(fails);
});
it.skipIf(process.platform === "win32")("preserves native filenames beginning with file:", () => {
vi.stubEnv("TERMINAL_EMULATOR", "");
const root = fixture();

View file

@ -300,29 +300,16 @@ describe("bundled browser MCP package", () => {
change: "modify",
error: "bundled chrome-devtools-mcp must be ESM version 1.10.1",
},
...[
"build/src/TextSnapshot.js",
"build/src/McpPage.js",
"build/src/third_party/index.js",
"build/src/OPENCLAW_PATCH_NOTICE.md",
].map((file) => ({
file,
{
file: "build/src/TextSnapshot.js",
change: "modify",
error: `unpatched or changed runtime entry ${file}`,
})),
...[
MCP_CLI,
"build/src/bin/chrome-devtools-mcp-main.js",
"build/src/third_party/devtools-formatter-worker.js",
"build/src/third_party/devtools-heap-snapshot-worker.js",
"build/src/third_party/lighthouse-devtools-mcp-bundle.js",
"LICENSE",
"build/src/third_party/THIRD_PARTY_NOTICES",
].map((file) => ({
file,
error: "unpatched or changed runtime entry build/src/TextSnapshot.js",
},
{
file: MCP_CLI,
change: "remove",
error: `missing required runtime entry ${file}`,
})),
error: `missing required runtime entry ${MCP_CLI}`,
},
{
file: "build/src/third_party/issue-descriptions",
change: "remove",

View file

@ -12,42 +12,14 @@ import {
const tempDirs = useAutoCleanupTempDirTracker(afterEach);
describe.skipIf(process.platform === "win32")("architecture CI Go memory environment", () => {
it.each([
{
name: "defaults",
gogc: undefined,
limit: undefined,
expectedGc: "30",
expectedLimit: "3GiB",
exit: 0,
},
{
name: "caller overrides",
gogc: "70",
limit: "8GiB",
expectedGc: "70",
expectedLimit: "8GiB",
exit: 0,
},
{
name: "partial override",
gogc: "off",
limit: undefined,
expectedGc: "off",
expectedLimit: "3GiB",
exit: 0,
},
{
name: "blocking command failure",
gogc: undefined,
limit: undefined,
expectedGc: "30",
expectedLimit: "3GiB",
exit: 17,
},
it.each<[string, string | undefined, string | undefined, string, string, number]>([
["defaults", undefined, undefined, "30", "3GiB", 0],
["caller overrides", "70", "8GiB", "70", "8GiB", 0],
["partial override", "off", undefined, "off", "3GiB", 0],
["blocking command failure", undefined, undefined, "30", "3GiB", 17],
])(
"passes $name to the architecture command",
({ gogc, limit, expectedGc, expectedLimit, exit }) => {
"passes %s to the architecture command",
(_name, gogc, limit, expectedGc, expectedLimit, exit) => {
const script = readCiWorkflow().jobs["check-additional-shard"].steps.find(
(step: WorkflowStep) => step.name === "Run additional check shard",
)?.run;

View file

@ -21,6 +21,16 @@ import { useAutoCleanupTempDirTracker } from "../helpers/temp-dir.js";
const tempDirs = useAutoCleanupTempDirTracker(afterEach);
function fixture(prefix: string) {
const cwd = tempDirs.make(prefix);
const write = (file: string, source: string) => {
const target = path.join(cwd, file);
mkdirSync(path.dirname(target), { recursive: true });
writeFileSync(target, source);
};
return { cwd, write };
}
vi.mock("../../scripts/test-projects.test-support.mts", async (importOriginal) => {
const original =
await importOriginal<typeof import("../../scripts/test-projects.test-support.mts")>();
@ -39,12 +49,7 @@ vi.mock("../../scripts/test-projects.test-support.mts", async (importOriginal) =
describe("CI changed lint", () => {
it("includes transitive and aliased type consumers without widening to their packages", async () => {
const cwd = tempDirs.make("ci-file-lint-");
const write = (file: string, content: string) => {
const target = path.join(cwd, file);
mkdirSync(path.dirname(target), { recursive: true });
writeFileSync(target, content);
};
const { cwd, write } = fixture("ci-file-lint-");
write("pnpm-workspace.yaml", "packages:\n - .\n - ui\n - packages/*\n - extensions/*\n");
for (const root of [
".",
@ -145,23 +150,17 @@ describe("CI changed lint", () => {
])(
"retains full lint when an affected consumer augments globals through %s",
async (file, source) => {
const cwd = tempDirs.make("ci-ambient-consumer-");
mkdirSync(path.join(cwd, "src"));
writeFileSync(path.join(cwd, "package.json"), '{"type":"module"}');
writeFileSync(path.join(cwd, "src/value.ts"), "export type Work = () => Promise<void>;\n");
writeFileSync(path.join(cwd, "src", file), source);
writeFileSync(path.join(cwd, "src/reader.ts"), "window.work();\n");
const { cwd, write } = fixture("ci-ambient-consumer-");
write("package.json", '{"type":"module"}');
write("src/value.ts", "export type Work = () => Promise<void>;\n");
write(`src/${file}`, source);
write("src/reader.ts", "window.work();\n");
expect(await resolveChangedOxlintFileScope(["src/value.ts"], cwd)).toBeUndefined();
},
);
it("runs native rules on a changed file and unchanged consumers of its changed type", () => {
const cwd = tempDirs.make("ci-file-lint-native-");
const write = (file: string, source: string) => {
const target = path.join(cwd, file);
mkdirSync(path.dirname(target), { recursive: true });
writeFileSync(target, source);
};
const { cwd, write } = fixture("ci-file-lint-native-");
write("package.json", '{"private":true,"type":"module"}');
write("pnpm-workspace.yaml", "packages: [.]\n");
write(

View file

@ -32,6 +32,18 @@ function selectedFiles(shards: ReturnType<typeof createChangedNodeTestShards>) {
);
}
function canonicalOwner(jobs: CompactNodeTestShard[], shardName: string) {
const job = expectDefined(
jobs.find((candidate) => candidate.groups.some((group) => group.shard_name === shardName)),
`canonical job for ${shardName}`,
);
const group = expectDefined(
job.groups.find((candidate) => candidate.shard_name === shardName),
`canonical group for ${shardName}`,
);
return { job, group };
}
it("keeps the aggressive fixed smoke within two Node rows", () => {
let smoke: string[] = [];
resolveChangedNodeTestTargets(["src/infra/new-unlisted-module.ts"], {
@ -282,16 +294,7 @@ it("keeps UI and core changes with exact owners and direct consumers", () => {
const owners = group.configs.includes("test/vitest/vitest.tooling.config.ts")
? canonicalTooling
: canonical;
const ownerJob = expectDefined(
owners.find((candidate) =>
candidate.groups.some((owner) => owner.shard_name === group.shard_name),
),
`canonical UI consumer job for ${group.shard_name}`,
);
const owner = expectDefined(
ownerJob.groups.find((candidate) => candidate.shard_name === group.shard_name),
"canonical UI consumer group",
);
const { group: owner } = canonicalOwner(owners, group.shard_name);
if (group.includePatterns) {
expect(group.includePatterns.length).toBeGreaterThan(0);
} else {
@ -299,15 +302,9 @@ it("keeps UI and core changes with exact owners and direct consumers", () => {
}
expect(group.configs.every((config) => owner.configs.includes(config))).toBe(true);
// Tooling capacity follows selected files; an excluded compiler can require a larger full job.
const selectedJob = expectDefined(
selectedCanonical.find((candidate) =>
candidate.groups.some((selected) => selected.shard_name === group.shard_name),
),
`selected UI consumer job for ${group.shard_name}`,
);
const selectedGroup = expectDefined(
selectedJob.groups.find((selected) => selected.shard_name === group.shard_name),
"selected UI consumer group",
const { job: selectedJob, group: selectedGroup } = canonicalOwner(
selectedCanonical,
group.shard_name,
);
for (const key of [
"configs",

View file

@ -73,15 +73,10 @@ describe("changed CI compiler graph selection", () => {
it.for([
[],
["src/missing.ts"],
["src/types/runtime.d.ts"],
["test/tsconfig/tsconfig.test.root.json"],
["package.json"],
["unclassified/module.ts"],
["docs/plugins/sdk-subpaths.md", "ui/src/styles/chat.css"],
[sharedType, "src/config/catalog.json"],
["extensions/example/value.ts", "extensions/example/types.d.ts"],
["extensions/example/value.ts", "extensions/example/tsconfig.json"],
])("retains all compilers without discovery for uncertain input %j", async (paths) => {
expect(selectChangedCiTsgoGraphs(paths, graphs())).toBeUndefined();
inspectGraphs.mockRejectedValue(new Error("Full plans must not enumerate compiler inputs"));
@ -151,14 +146,13 @@ describe("changed CI compiler graph selection", () => {
});
});
it.each(["leaf", "directory", "extension-alias", "broken", "traversal", "directory-file"])(
it.each(["leaf", "directory", "traversal", "directory-file"])(
"retains every compiler for a nonphysical extension input (%s)",
async (kind) => {
const cwd = tempDirs.make("ci-type-extension-alias-");
mkdirSync(join(cwd, "src"));
mkdirSync(join(cwd, "extensions", "example"), { recursive: true });
writeFileSync(join(cwd, "src/value.ts"), "export type Value = number;\n");
writeFileSync(join(cwd, "extensions/example/other.ts"), "export type Value = number;\n");
const file =
kind === "traversal"
? "extensions/example/../../src/value.ts"
@ -170,18 +164,7 @@ describe("changed CI compiler graph selection", () => {
} else if (kind === "directory-file") {
mkdirSync(join(cwd, file));
} else if (kind !== "traversal") {
symlinkSync(
join(
cwd,
kind === "broken"
? "src/missing.ts"
: kind === "extension-alias"
? "extensions/example/other.ts"
: "src/value.ts",
),
join(cwd, file),
"file",
);
symlinkSync(join(cwd, "src/value.ts"), join(cwd, file), "file");
}
inspectGraphs.mockRejectedValue(
new Error("Aliases must retain all graphs without discovery"),
@ -215,7 +198,7 @@ describe("changed CI compiler graph selection", () => {
},
);
it.each(["missing", "duplicate", "unexpected", "unmatched", "mixed", "ambient", "config"])(
it.each(["missing", "duplicate", "unmatched", "mixed"])(
"refuses incomplete or inapplicable noncore discovery (%s)",
(kind) => {
const file = "extensions/example/value.ts";
@ -227,29 +210,19 @@ describe("changed CI compiler graph selection", () => {
inventory.pop();
} else if (kind === "duplicate") {
inventory[inventory.length - 1] = inventory[0]!;
} else if (kind === "unexpected") {
inventory[0] = { config: "tsconfig.core.json", files: [file] };
} else if (kind === "unmatched") {
paths.push("extensions/example/other.ts");
} else if (kind === "mixed") {
paths.push("src/value.ts");
} else if (kind === "ambient") {
paths.push("extensions/example/types.d.ts");
} else if (kind === "config") {
paths.push("extensions/example/package.json");
}
expect(selectChangedCiTsgoGraphs(paths, inventory, { scope: "noncore" })).toBeUndefined();
},
);
it.each(["missing", "duplicate"])("refuses an incomplete %s compiler inventory", (kind) => {
it("refuses an incomplete full compiler inventory", () => {
const inventory = graphs();
inventory[0]!.files.push(sharedType);
if (kind === "missing") {
inventory.pop();
} else {
inventory[inventory.length - 1] = inventory[0]!;
}
inventory.pop();
expect(selectChangedCiTsgoGraphs([sharedType], inventory)).toBeUndefined();
});
});

View file

@ -37,8 +37,9 @@ describe("narrow PR check families", () => {
"packages/media-core/src/types.d.ts",
])("keeps runtime and declaration consumers for %s", (path) => {
const scope = resolveCiCheckFamilyScope([path]);
expect(scope.checkTasks).not.toContain("bundled-channel-config-metadata");
expect(scope.checkTasks).toEqual(["guards", "dependencies"]);
expect(scope.fastTasks).toEqual([]);
expect(scope.additionalGroups).toContain("boundaries");
expect(scope.additionalGroups).toContain("extension-package-boundary");
expect(scope).toMatchObject({
baselineRatchets: true,
@ -47,15 +48,6 @@ describe("narrow PR check families", () => {
});
});
it("keeps extension correctness checks while exact runtime tests use the Node owner", () => {
const scope = resolveCiCheckFamilyScope(["extensions/telegram/src/send.ts"]);
expect(scope.fastTasks).toEqual([]);
expect(scope.checkTasks).toEqual(["guards", "dependencies"]);
expect(scope).toMatchObject({ types: true, lint: true });
expect(scope.additionalGroups).toContain("boundaries");
expect(scope.additionalGroups).toContain("extension-package-boundary");
});
it.each(["src/config/zod-schema.core.ts", "extensions/telegram/src/config-schema.ts"])(
"selects bundled metadata through its schema owner for %s",
(file) => {
@ -70,7 +62,6 @@ describe("narrow PR check families", () => {
"src/agents/session.test-support.ts",
"scripts/lib/source-file-scan-cache.mts",
"config/knip.all-exports.config.ts",
"config/test-timeout-race-baseline.txt",
"extensions/telegram/tsconfig.json",
"extensions/telegram/package.json",
"pnpm-lock.yaml",

View file

@ -66,7 +66,6 @@ function sealDiagnostic(before: CompilerInputSnapshot, after: CompilerInputSnaps
it.each([
['{"compilerOptions":{"target":"invalid"},"include":["src/**/*.ts"]}', "TS6046"],
['{"include":"src/**/*.ts"}', "TS5024"],
['{"files":"src/index.ts"}', "TS5024"],
['{"extends":"./missing.json","include":["src/**/*.ts"]}', "TS5083"],
['{"compilerOptions": {', "TS1005"],
])("rejects invalid native compiler configuration %s", (config, diagnostic) => {
@ -245,12 +244,15 @@ it.each([
["config-bytes", "base.json", '{ "compilerOptions": {"target":"ES2023","types":[]} }\n'],
])(
"identifies a %s rejection without exposing configuration or tool bytes",
(category, file, bytes) => {
async (category, file, bytes) => {
const f = fixture();
const before = f.snapshot();
await before.prepare();
f.signature(before);
f.write(file!, bytes!);
expect(sealDiagnostic(before, f.snapshot()).detail).toEqual({ category });
const after = f.snapshot();
await after.prepare();
expect(sealDiagnostic(before, after).detail).toEqual({ category });
},
);
@ -294,16 +296,6 @@ it.each(["ascii", "unicode", "controls"])(
},
);
it("prepares the same ordered source and installed-alias namespace as synchronous readers", async () => {
const f = fixture();
const synchronous = f.snapshot();
const prepared = f.snapshot();
await prepared.prepare();
for (const outputRoot of [undefined, path.join(f.root, "packages/local/dist")]) {
expect(f.signature(prepared, outputRoot)).toBe(f.signature(synchronous, outputRoot));
}
});
it("ignores checkout scratch packages that disappear during preparation", async () => {
const f = fixture();
const original = f.signature(f.snapshot());
@ -545,21 +537,20 @@ it("preloads sibling subtrees while the ordered visitor waits on a deeper direct
expect(active).toBe(0);
expect(observed.has(path.join(f.root, ".artifacts"))).toBe(false);
expect(observed.has(path.join(f.root, ".cache/vitest"))).toBe(false);
expect(f.signature(snapshot)).toBe(f.signature(f.snapshot()));
const synchronous = f.snapshot();
for (const outputRoot of [undefined, path.join(f.root, "packages/local/dist")]) {
expect(f.signature(snapshot, outputRoot)).toBe(f.signature(synchronous, outputRoot));
}
});
it.each([
["source addition", "src/shadow.ts", "export const shadow = 1;\n"],
["package addition", "src/package.json", '{"type":"commonjs"}'],
["nested workspace metadata", "packages/local/.tmp/package.json", '{"type":"commonjs"}'],
[
"installed package metadata",
"packages/local/package.json",
'{"name":"fixture-package","type":"commonjs"}',
],
["inherited config", "base.json", '{"compilerOptions":{"target":"ES2022","types":[]}}'],
["generator input", "scripts/generator.mts", "export const generator = 2;\n"],
["compiler input", "tools/compiler.js", "export const compiler = 2;\n"],
])("retains invalidation after a %s change", async (_label, filename, bytes) => {
const f = fixture();
const before = f.snapshot();

View file

@ -169,7 +169,6 @@ afterEach(() => {
describe("syncControlUiCatalogFallbackBaseline", () => {
it("checks and writes ordered fallback bytes without rewriting a matching baseline", async () => {
const stdout = vi.spyOn(process.stdout, "write").mockReturnValue(true);
const expected = baseline({ "group.second": ["de"], missing: ["de", "fr"] });
await expect(
@ -179,9 +178,6 @@ describe("syncControlUiCatalogFallbackBaseline", () => {
await syncControlUiCatalogFallbackBaseline(writeOptions);
expect(fixture.writes).toEqual([{ path: baselinePath, data: expected }]);
expect(fixture.files.get(baselinePath)).toBe(expected);
expect(stdout).toHaveBeenCalledWith(
"control-ui-i18n: catalog: fallback_keys=2 fallback_pairs=3\n",
);
fixture.writes.length = 0;
await syncControlUiCatalogFallbackBaseline(writeOptions);
@ -231,26 +227,22 @@ describe("syncControlUiCatalogFallbackBaseline", () => {
);
});
it.each([false, true])(
"preserves the first missing/malformed memory error (allowCatalogDrift=%s)",
async (allowCatalogDrift) => {
fixture.files.delete(memoryPath("fr"));
fixture.files.set(memoryPath("de"), "{");
await expect(
syncControlUiCatalogFallbackBaseline({ ...writeOptions, allowCatalogDrift }),
).rejects.toThrow("ui/src/i18n/.i18n/fr.tm.jsonl does not contain fr translations");
it("preserves the first missing/malformed memory error during scoped sync", async () => {
fixture.files.delete(memoryPath("fr"));
fixture.files.set(memoryPath("de"), "{");
await expect(
syncControlUiCatalogFallbackBaseline({ ...writeOptions, allowCatalogDrift: true }),
).rejects.toThrow("ui/src/i18n/.i18n/fr.tm.jsonl does not contain fr translations");
fixture.files.set(memoryPath("fr"), "{");
fixture.files.delete(memoryPath("de"));
await expect(
syncControlUiCatalogFallbackBaseline({ ...writeOptions, allowCatalogDrift }),
).rejects.toBeInstanceOf(SyntaxError);
expect(fixture.writes).toEqual([]);
},
);
fixture.files.set(memoryPath("fr"), "{");
fixture.files.delete(memoryPath("de"));
await expect(
syncControlUiCatalogFallbackBaseline({ ...writeOptions, allowCatalogDrift: true }),
).rejects.toBeInstanceOf(SyntaxError);
expect(fixture.writes).toEqual([]);
});
it("only tolerates analyzer drift during scoped sync", async () => {
const stdout = vi.spyOn(process.stdout, "write").mockReturnValue(true);
fixture.loadSource.mockReturnValue({ greeting: "Hello {count}" });
setMemory("fr", [
row({
@ -268,23 +260,17 @@ describe("syncControlUiCatalogFallbackBaseline", () => {
expect(fixture.writes).toEqual([]);
await syncControlUiCatalogFallbackBaseline({ ...writeOptions, allowCatalogDrift: true });
expect(fixture.files.get(baselinePath)).toBe(baseline({ greeting: ["de"] }));
expect(stdout).toHaveBeenCalledWith(
"control-ui-i18n: catalog: tolerated_errors=1 during scoped sync\n",
);
});
it.each([false, true])(
"rejects terminology errors before later locale errors (allowCatalogDrift=%s)",
async (allowCatalogDrift) => {
fixture.loadSource.mockReturnValue({ sessionsView: { subagentPrefix: "abc" } });
setMemory("fr", [
row({ segment_id: "sessionsView.subagentPrefix", segment_ids: [], translated: "Cron" }),
]);
fixture.files.set(memoryPath("de"), "{");
await expect(
syncControlUiCatalogFallbackBaseline({ ...writeOptions, allowCatalogDrift }),
).rejects.toThrow("fr: sessionsView.subagentPrefix");
expect(fixture.writes).toEqual([]);
},
);
it("rejects terminology errors before later locale errors during scoped sync", async () => {
fixture.loadSource.mockReturnValue({ sessionsView: { subagentPrefix: "abc" } });
setMemory("fr", [
row({ segment_id: "sessionsView.subagentPrefix", segment_ids: [], translated: "Cron" }),
]);
fixture.files.set(memoryPath("de"), "{");
await expect(
syncControlUiCatalogFallbackBaseline({ ...writeOptions, allowCatalogDrift: true }),
).rejects.toThrow("fr: sessionsView.subagentPrefix");
expect(fixture.writes).toEqual([]);
});
});

View file

@ -34,52 +34,54 @@ function scan(css: string) {
return scanIconGridFit(css, [fixture], base);
}
const cramped =
'html`<header class="toolbar"><button class="icon">${icons.refresh}</button></header>`';
function createAuditFixture(sheets: Record<string, string> = {}, markup = cramped) {
const root = tempDirs.make("openclaw-icon-grid-");
const styles = path.join(root, "ui/src/styles");
const source = path.join(root, "ui/src/control.ts");
fs.mkdirSync(styles, { recursive: true });
for (const [name, css] of Object.entries({
"base.css": base,
"components.css": "",
"control.css": original,
...sheets,
})) {
fs.writeFileSync(path.join(styles, name), css);
}
fs.writeFileSync(source, markup);
return { root, styles, source };
}
describe("fixed icon-grid fit", () => {
it("catches the shipped size override rather than flagging the safe base control", () => {
const findings = scan(original).findings;
expect(findings).toHaveLength(2);
expect(findings).toEqual(
expect.arrayContaining([
for (const axis of ["width", "height"]) {
expect(findings).toContainEqual(
expect.objectContaining({
kind: "overflow",
axis: "width",
axis,
size: 26,
padding: 12,
border: 2,
icon: 17,
available: 12,
}),
expect.objectContaining({
kind: "overflow",
axis: "height",
size: 26,
padding: 12,
border: 2,
icon: 17,
available: 12,
}),
]),
);
);
}
expect(scan(original + ".toolbar > button {padding:0}").findings).toEqual([]);
expect(scan(original.replace("width:26px; height:26px;", "")).findings).toEqual([]);
});
it("requires authored padding instead of trusting jsdom's zero native default", () => {
const css = original.replace("padding:6px;", "");
expect(scan(css).findings).toEqual([
expect.objectContaining({
kind: "native-padding",
axis: "width",
padding: null,
available: null,
}),
expect.objectContaining({
kind: "native-padding",
axis: "height",
padding: null,
available: null,
}),
]);
expect(scan(css).findings).toEqual(
["width", "height"].map((axis) =>
expect.objectContaining({ kind: "native-padding", axis, padding: null, available: null }),
),
);
expect(scan(css + ".icon {padding:0}").findings).toEqual([]);
});
@ -115,7 +117,6 @@ describe("fixed icon-grid fit", () => {
".toolbar .icon {width:40px!important}.icon {width:24px!important}",
],
["always-applicable media correction", "@media all {.icon {padding:0}}"],
["supports correction", "@supports (display:grid) {.icon {padding:0}}"],
["nested-only padding", ".icon {&:hover {padding:0}}"],
])("defers %s rather than inventing a resolved geometry", (_name, correction) => {
const result = scan(original + correction);
@ -123,9 +124,9 @@ describe("fixed icon-grid fit", () => {
expect(result.unresolved).toBeGreaterThan(0);
});
it.each(["hover", "focus-visible", "active"])("defers top-level %s geometry", (state) => {
it("defers top-level hover geometry", () => {
const result = scan(
original + ".toolbar > button {padding:0}.toolbar > button:" + state + " {padding:8px}",
original + ".toolbar > button {padding:0}.toolbar > button:hover {padding:8px}",
);
expect(result.findings).toHaveLength(0);
expect(result.checked).toBe(0);
@ -133,14 +134,8 @@ describe("fixed icon-grid fit", () => {
});
it("defers cross-sheet ancestor, tag, ID, attribute, and SVG overrides", () => {
const root = tempDirs.make("openclaw-icon-grid-cross-sheet-");
const styles = path.join(root, "ui/src/styles");
fs.mkdirSync(styles, { recursive: true });
fs.writeFileSync(path.join(styles, "base.css"), base);
fs.writeFileSync(path.join(styles, "components.css"), "");
fs.writeFileSync(path.join(styles, "control.css"), original);
fs.writeFileSync(
path.join(root, "ui/src/control.ts"),
const { root, styles } = createAuditFixture(
{},
'html`<header class="toolbar"><button class="icon" id="action" title="Preview">${icons.refresh}</button></header>`',
);
for (const correction of [
@ -249,17 +244,7 @@ describe("fixed icon-grid fit", () => {
});
it("audits current source and shared styles on each manual invocation", () => {
const root = tempDirs.make("openclaw-icon-grid-");
const styles = path.join(root, "ui/src/styles");
fs.mkdirSync(styles, { recursive: true });
fs.writeFileSync(path.join(styles, "base.css"), base);
fs.writeFileSync(path.join(styles, "components.css"), "");
const source = path.join(root, "ui/src/control.ts");
fs.writeFileSync(
source,
'html`<header class="toolbar"><button class="icon">${icons.refresh}</button></header>`',
);
fs.writeFileSync(path.join(styles, "control.css"), original);
const { root, styles, source } = createAuditFixture();
const audit = () => auditIconButtons(root, ["ui/src/styles/control.css"]);
const first = audit();
expect(first.findings).toHaveLength(2);
@ -267,8 +252,6 @@ describe("fixed icon-grid fit", () => {
fs.writeFileSync(source, 'html`<button class="icon">${icons.refresh}</button>`');
expect(audit().findings).toHaveLength(0);
const added = path.join(root, "ui/src/added.ts");
const cramped =
'html`<header class="toolbar"><button class="icon">${icons.refresh}</button></header>`';
fs.writeFileSync(added, cramped);
expect(audit().findings).toHaveLength(2);
fs.unlinkSync(added);
@ -288,18 +271,11 @@ describe("fixed icon-grid fit", () => {
});
it("does not append the base sheet again after component overrides", () => {
const root = tempDirs.make("openclaw-icon-grid-order-");
const styles = path.join(root, "ui/src/styles");
fs.mkdirSync(styles, { recursive: true });
fs.writeFileSync(path.join(styles, "base.css"), base + original);
fs.writeFileSync(
path.join(styles, "components.css"),
".toolbar > button {width:32px;height:32px}",
);
fs.writeFileSync(
path.join(root, "ui/src/control.ts"),
'html`<header class="toolbar"><button class="icon">${icons.refresh}</button></header>`',
);
const { root } = createAuditFixture({
"base.css": base + original,
"components.css": ".toolbar > button {width:32px;height:32px}",
"control.css": "",
});
const result = auditIconButtons(root, ["ui/src/styles/base.css"]);
expect(result.findings).toHaveLength(0);
expect(result.checkedAxes).toBe(2);

View file

@ -23,19 +23,6 @@ describe("Control UI plugin discovery preview", () => {
const result = buildPluginDiscoveryCategoriesMock();
expect(Value.Check(PluginsCatalogCategoriesResultSchema, result)).toBe(true);
expect(result.categories.map((category) => category.slug)).toEqual([
"channels",
"models",
"memory",
"context",
"voice",
"media",
"web",
"tools",
"runtime",
"gateway",
"security",
"other",
]);
expect(result.categories.length).toBeGreaterThan(0);
});
});

View file

@ -97,10 +97,8 @@ fi
["shared setup action", "fresh"],
["default hydration", "legacy"],
["GitHub hydration", "legacy"],
["default hydration", "unknown"],
["GitHub hydration", "unknown"],
["default hydration", "unknown-newline"],
["default hydration", "fallback"],
["default hydration", "fallback-dangling"],
["default hydration", "configured-fallback"],
["default hydration", "unknown-fallback"],
@ -116,7 +114,6 @@ fi
const store = path.join(root, "store");
const runnerTemp = path.join(root, "runner");
const usesFallback = [
"fallback",
"fallback-dangling",
"configured-fallback",
"unknown-fallback",
@ -303,26 +300,7 @@ fi
rmSync(path.join(workspace, "node_modules"), { recursive: true, force: true });
symlinkSync(linkedModules, path.join(workspace, "node_modules"));
const handoff = path.join(env.HOME!, ".crabbox/actions/hydration-proof.env");
const exports = `${handoff}.sh`;
const legacyExports =
job === "hydrate-github"
? `export PNPM_CONFIG_MODULES_DIR=${shellQuote(externalModules)}\nexport PNPM_CONFIG_VIRTUAL_STORE_DIR=${shellQuote(path.join(externalRoot, "virtual-store"))}\n`
: `export CRABBOX_PNPM_MODULES_DIR=${shellQuote(externalModules)}\n`;
write(
root,
path.relative(root, handoff),
`WORKSPACE=${workspace}\nRUN_ID=fixture\nJOB=${job}\nENV_FILE=${exports}\nSERVICES_FILE=${handoff.replace(/\.env$/u, ".services")}\nREADY_AT=2026-09-14T00:00:00Z\n`,
);
write(
root,
path.relative(root, exports),
`export CI=true\nexport GITHUB_WORKSPACE=${shellQuote(workspace)}\nexport GITHUB_RUN_ID=fixture\nexport PNPM_CONFIG_STORE_DIR=${shellQuote(legacyStore)}\n${usesFallback ? `export XDG_CACHE_HOME=${shellQuote(cacheRoot)}\n` : ""}${legacyExports}`,
);
// Released Crabbox clears both native handoff markers before starting rehydration.
rmSync(handoff);
rmSync(exports);
expect(existsSync(handoff) || existsSync(exports)).toBe(false);
if (initialState === "fallback-dangling") {
// Native rehydration recreates the same lease's runner root before workflow steps.
rmSync(runnerTemp, { recursive: true });

View file

@ -247,51 +247,37 @@ describe.skipIf(process.platform === "win32")("persistent Crabbox source capsule
}
});
it.each(["empty", "changed source"])(
"reuses unchanged mirror files after a same-ref %s commit and seals the new witness",
(change) => {
const f = fixture();
const first = f.prepare();
const stable = fileIdentity(join(first.directory, "stable.txt"));
first.cleanup();
if (change === "changed source") {
writeFileSync(join(f.repository, "change.txt"), "committed newer bytes\r\n");
f.git(f.repository, "add", "change.txt");
}
f.git(
f.repository,
"-c",
"commit.gpgsign=false",
"commit",
"--quiet",
"--allow-empty",
"-m",
"next head",
it("reuses unchanged mirror files after a same-ref source commit and seals the new witness", () => {
const f = fixture();
const first = f.prepare();
const stable = fileIdentity(join(first.directory, "stable.txt"));
first.cleanup();
writeFileSync(join(f.repository, "change.txt"), "committed newer bytes\r\n");
f.git(f.repository, "add", "change.txt");
f.git(f.repository, "-c", "commit.gpgsign=false", "commit", "--quiet", "-m", "next head");
const head = f.git(f.repository, "rev-parse", "HEAD");
expect(head).not.toBe(first.sourceSha);
const next = f.prepare();
try {
expect(next.directory).toBe(first.directory);
expect(fileIdentity(join(next.directory, "stable.txt"))).toEqual(stable);
expect(next.sourceSha).toBe(head);
expect(readFileSync(join(next.directory, "change.txt"), "utf8")).toBe(
"committed newer bytes\r\n",
);
const head = f.git(f.repository, "rev-parse", "HEAD");
expect(head).not.toBe(first.sourceSha);
const next = f.prepare();
try {
expect(next.directory).toBe(first.directory);
expect(fileIdentity(join(next.directory, "stable.txt"))).toEqual(stable);
expect(next.sourceSha).toBe(head);
expect(readFileSync(join(next.directory, "change.txt"), "utf8")).toBe(
change === "changed source" ? "committed newer bytes\r\n" : "original bytes\n",
);
const receipt: unknown = JSON.parse(
readFileSync(join(next.staging.root, "staging.json"), "utf8"),
);
expect(receipt).toHaveProperty("witness", {
gitDir: f.git(f.repository, "rev-parse", "--path-format=absolute", "--git-common-dir"),
ref: "refs/heads/main",
commit: head,
});
f.expectColdEquivalent(next);
} finally {
next.cleanup();
}
},
);
const receipt: unknown = JSON.parse(
readFileSync(join(next.staging.root, "staging.json"), "utf8"),
);
expect(receipt).toHaveProperty("witness", {
gitDir: f.git(f.repository, "rev-parse", "--path-format=absolute", "--git-common-dir"),
ref: "refs/heads/main",
commit: head,
});
f.expectColdEquivalent(next);
} finally {
next.cleanup();
}
});
it.each(["ref", "Git directory"])(
"rebuilds the mirror when its retained source %s changes",

View file

@ -133,23 +133,9 @@ it.runIf(process.platform === "linux")(
{ command: "busctl", args: ["--machine", "testuser@", ...versionArgs] },
{ command: "systemctl", args: ["--system", "is-system-running"] },
]);
console.info("original Doctor argv:", JSON.stringify(invocations[0]?.args));
},
);
it.runIf(process.platform === "linux")("still rejects an unavailable synthetic bus", async () => {
const env = scenarioEnvironment();
vi.mocked(execFileUtf8).mockResolvedValue({
code: 1,
termination: "exit",
stdout: "",
stderr: "Failed to connect to bus: No such file or directory",
});
await expect(resolveSystemdUserTransport(env)).rejects.toMatchObject({
reason: "systemd-user-bus-unavailable",
});
});
it("keeps machine scope, auto-start, and foreign-manager probes outside the shim contract", () => {
const env = scenarioEnvironment();
for (const args of [

View file

@ -20,7 +20,6 @@ describe("extended-stable live publication eligibility", () => {
});
it.skipIf(process.platform === "win32").each([
{ mainVersion: "2026.8.1", expectedStatus: 42, expectedError: "" },
{ mainVersion: "2026.9.1", expectedStatus: 42, expectedError: "" },
{
mainVersion: "2026.10.1",

View file

@ -38,7 +38,7 @@ describe("cold-import resource observations", () => {
it.each([
"not a resource record",
RESOURCE_MARKER + "{",
...[undefined, 0, -1, 1.5].map(
...[undefined, 0, 1.5].map(
(pid) =>
RESOURCE_MARKER + JSON.stringify({ ...observation, pid, userCpuUs: 1, systemCpuUs: 0 }),
),

View file

@ -679,7 +679,6 @@ describe("publication status real CLI", () => {
"duplicate-name",
"duplicate-id",
"count-gap",
"attempt-limit",
])("rejects independently observed %s", async (kind) => {
const result = await runPublicationCli(publicationFixture(), undefined, (responses) => {
const prefix = `repos/${REPOSITORY}/actions/`;
@ -711,32 +710,11 @@ describe("publication status real CLI", () => {
if (kind === "count-gap") {
list.total_count++;
}
if (kind === "attempt-limit") {
Object.assign(responses[`${prefix}runs/101`] as object, { run_attempt: 100000000 });
}
});
expect(result.status, result.stdout).toBe(1);
expect(JSON.parse(result.stdout).publication.collection.complete).toBe(false);
});
it.each(["88", "77", "101"])(
"refuses advancing run %s without restarting observation",
async (runId) => {
const result = await runPublicationCli(publicationFixture(), undefined, (responses) => {
const path = `repos/${REPOSITORY}/actions/runs/${runId}`;
const before = responses[path] as { run_attempt: number };
responses[path] = {
sequence: [before, { ...before, run_attempt: before.run_attempt + 1 }],
};
});
expect(result.status).toBe(1);
expect(JSON.parse(result.stdout).publication.collection.error).toBe("attempt-changed");
expect(
result.calls.filter((call) => call.includes(`repos/${REPOSITORY}/actions/runs/${runId}`)),
).toHaveLength(2);
},
);
it.each(["missing", "expired", "legacy-only", "unsupported", "incomplete-link"])(
"keeps authenticated historical %s unknown, not failed publication",
async (kind) => {
@ -773,24 +751,21 @@ describe("publication status real CLI", () => {
},
);
it.each(["403 quota", "404 unavailable", "network timeout"])(
"classifies %s as unavailable, never absence",
async (failure) => {
const result = await runPublicationCli(publicationFixture(), undefined, (responses) => {
responses[`repos/${REPOSITORY}/actions/runs/88/artifacts?per_page=100&page=1`] = {
failure: `${failure}: /private/fixture/credential synthetic-secret`,
};
});
expect(result.status).toBe(1);
expect(JSON.parse(result.stdout).publication.collection).toEqual({
complete: false,
error: "transport",
});
expect(result.stdout + result.stderr).not.toMatch(
/synthetic-secret|private\/fixture|quota|404 unavailable/u,
);
},
);
it("classifies failed artifact reads as unavailable, never absence", async () => {
const result = await runPublicationCli(publicationFixture(), undefined, (responses) => {
responses[`repos/${REPOSITORY}/actions/runs/88/artifacts?per_page=100&page=1`] = {
failure: "404 unavailable: /private/fixture/credential synthetic-secret",
};
});
expect(result.status).toBe(1);
expect(JSON.parse(result.stdout).publication.collection).toEqual({
complete: false,
error: "transport",
});
expect(result.stdout + result.stderr).not.toMatch(
/synthetic-secret|private\/fixture|quota|404 unavailable/u,
);
});
it("retains partial package successes and truncation without declaring a complete observation", async () => {
const fixture = publicationFixture();
@ -973,6 +948,9 @@ describe("publication status real CLI", () => {
expect(result.status).toBe(1);
expect(JSON.parse(result.stdout).publication.collection.error).toBe("attempt-changed");
expect(JSON.parse(result.stdout).publication.relationship.status).toBe("verified");
expect(
result.calls.filter((call) => call.includes(`repos/${REPOSITORY}/actions/runs/101`)),
).toHaveLength(2);
});
it.each(["transport", "workflow", "attempt-limit"])(
@ -1077,6 +1055,10 @@ describe("publication status real CLI", () => {
});
expect(result.status).toBe(1);
expect(JSON.parse(result.stdout).publication.relationship.status).toBe("invalid");
expect(JSON.parse(result.stdout).publication.collection.error).toBe("attempt-changed");
expect(
result.calls.filter((call) => call.includes(`repos/${REPOSITORY}/actions/runs/${runId}`)),
).toHaveLength(2);
},
);
@ -1317,11 +1299,11 @@ describe("publication status real CLI", () => {
},
);
it.each(["traversal", "unexpected-entry", "expanded", "truncated", "corrupt", "duplicate-entry"])(
it.each(["unexpected-entry", "expanded"])(
"refuses %s archives before projecting diagnostics",
async (kind) => {
const fixture = publicationFixture();
const result = await runPublicationCli(fixture, undefined, async (responses, artifact) => {
const result = await runPublicationCli(fixture, undefined, async (_responses, artifact) => {
await artifact(
3,
fixture.publisher,
@ -1329,41 +1311,14 @@ describe("publication status real CLI", () => {
DIAGNOSTIC_FILE,
fixture.diagnostic,
(zip) => {
if (kind === "traversal") {
zip.file("../escape.json", "{}");
}
if (kind === "unexpected-entry") {
zip.file("extra.json", "{}");
}
if (kind === "expanded") {
zip.file(DIAGNOSTIC_FILE, " ".repeat(128 * 1024 + 1));
}
if (kind === "duplicate-entry") {
zip.file("x".repeat(DIAGNOSTIC_FILE.length), "{}");
}
},
);
const archive = responses[`repos/${REPOSITORY}/actions/artifacts/3/zip`] as {
binary: string;
};
let bytes = Buffer.from(archive.binary, "base64");
if (kind === "truncated") {
bytes = bytes.subarray(0, -10);
}
if (kind === "corrupt") {
bytes[0] = 0;
}
if (kind === "duplicate-entry") {
const needle = Buffer.from("x".repeat(DIAGNOSTIC_FILE.length));
for (let offset = bytes.indexOf(needle); offset !== -1; offset = bytes.indexOf(needle)) {
bytes.set(Buffer.from(DIAGNOSTIC_FILE), offset);
}
}
archive.binary = bytes.toString("base64");
Object.assign(responses[`repos/${REPOSITORY}/actions/artifacts/3`] as object, {
size_in_bytes: bytes.length,
digest: `sha256:${createHash("sha256").update(bytes).digest("hex")}`,
});
});
expect(result.status).toBe(1);
expect(JSON.parse(result.stdout).publication.verification.state).toBe("unknown");
@ -1418,7 +1373,6 @@ describe("publication status real CLI", () => {
});
it.each([
[1, true],
[100, true],
[100, false],
] as const)(

View file

@ -123,15 +123,6 @@ describe("FreeBSD CLI runtime installation", () => {
},
);
it("refuses private Node recovery before linking or changing packages", () => {
const { bin, prefix } = fixture();
const result = install(bin, prefix, "NODE_ONLY=1");
expect(result.status).toBe(1);
expect(result.stdout).toContain("Private Node.js recovery is unavailable on FreeBSD");
expect(result.stdout).not.toContain("unexpected");
expect(existsSync(prefix)).toBe(false);
});
it("explains how to install missing Git without invoking pkg", () => {
const result = run(`
uname() { printf 'FreeBSD\\n'; }
@ -146,11 +137,14 @@ describe("FreeBSD CLI runtime installation", () => {
});
describe("FreeBSD source-install admission", () => {
it.each(
["--install-method git", "--method git", "--git", "--github", "--npm --git", ""].flatMap(
(args) => [false, true].map((json) => ({ args, json })),
),
)("refuses $args before installation side effects (JSON: $json)", ({ args, json }) => {
it.each([
{ args: "--install-method git", json: false },
{ args: "--method git", json: true },
{ args: "--git", json: false },
{ args: "--github", json: true },
{ args: "--npm --git", json: true },
{ args: "", json: false },
])("refuses $args before installation side effects (JSON: $json)", ({ args, json }) => {
const { root, prefix } = fixture();
const oldRuntime = join(root, "old-runtime");
const checkout = join(root, "checkout");
@ -209,11 +203,7 @@ describe("FreeBSD source-install admission", () => {
it.each([
["freebsd", "--git --npm"],
["freebsd", "--github --install-method npm"],
["freebsd", "--method npm"],
["linux", "--git"],
["darwin", "--git"],
["linux", "--npm"],
["darwin", "--npm"],
])("keeps %s %s on its selected install route", (os, args) => {
const result = run(

View file

@ -336,37 +336,6 @@ try {
`,
].join("\n"),
},
{
name: "openclaw-native-command-exit",
source: [
scriptWithoutEntryPoint,
"",
"function Get-OpenClawCommandPath { return (Get-Process -Id $PID).Path }",
"$caught = $false",
"try {",
" Invoke-OpenClawCommand -NoLogo -NoProfile -Command 'exit 17'",
"} catch {",
" if ($_.Exception.Message -notmatch 'failed with exit code 17') { throw }",
" $caught = $true",
"}",
"if (-not $caught) { throw 'nonzero native exit was accepted' }",
"",
].join("\n"),
},
{
name: "doctor-failure-output",
source: [
scriptWithoutEntryPoint,
"",
"function Invoke-OpenClawCommand { throw 'doctor failed' }",
"$output = @(Run-Doctor *>&1 | ForEach-Object { $_.ToString() })",
'$text = $output -join "`n"',
"if ($text -match 'Migration complete') { throw 'doctor failure reported success' }",
"if ($text -notmatch 'Migration failed') { throw \"missing error: $text\" }",
"if ($output[-1] -ne $false) { throw 'doctor failure did not propagate' }",
"",
].join("\n"),
},
{
name: "npm-lifecycle-policy",
source: [
@ -894,31 +863,6 @@ try {
"",
].join("\n"),
},
{
name: "package-manager-node-validation-failure",
source: [
scriptWithoutEntryPoint,
"",
"function Get-Command {",
" [CmdletBinding()]",
" param([string]$Name)",
" if ($Name -eq 'choco') { return $true }",
" return $null",
"}",
"filter Out-Host { }",
"function choco {",
" $global:LASTEXITCODE = 0",
" Write-Output 'Chocolatey output'",
"}",
"$script:portableCalled = $false",
"function Install-PortableNode { $script:portableCalled = $true }",
"function Check-Node { return $script:portableCalled }",
"$result = @(Install-Node)",
'if ($result.Count -ne 1 -or $result[0] -ne $true) { throw "Install-Node returned $result" }',
"if (-not $script:portableCalled) { throw 'Portable Node fallback was not attempted' }",
"",
].join("\n"),
},
{
name: "package-manager-node-command-failures",
source: [
@ -1798,7 +1742,6 @@ try {
expectBatchedPowerShellCase("winget-node-delayed-path");
expectBatchedPowerShellCase("chocolatey-node-upgrade");
expectBatchedPowerShellCase("scoop-node-update");
expectBatchedPowerShellCase("package-manager-node-validation-failure");
});
runIfPowerShell("recovers from package-manager failures and preserves installer refusal", () => {
@ -2090,19 +2033,12 @@ describe("install.ps1 stale Winget repair", () => {
repair: true,
success: true,
},
{
name: "accepts a normal successful install without repair",
installExit: 0,
afterInstall: "healthy",
success: true,
},
{
name: "accepts a healthy no-upgrade result without repair",
afterInstall: "healthy",
success: true,
},
{ name: "does not repair generic Winget failure", installExit: 1 },
{ name: "does not repair another HRESULT", installExit: -1978335188 },
{ name: "does not repair successful install with missing Node", installExit: 0 },
{
name: "recovers unsupported repair through Chocolatey",
@ -2125,13 +2061,6 @@ describe("install.ps1 stale Winget repair", () => {
fallback: "portable",
success: true,
},
{
name: "rejects unusable Chocolatey fallback after failed repair",
repairExit: 1,
repair: true,
fallback: "choco",
afterFallback: "old-sqlite",
},
{
name: "rejects unusable portable fallback after failed repair",
repairExit: 1,
@ -2139,24 +2068,6 @@ describe("install.ps1 stale Winget repair", () => {
fallback: "portable",
afterFallback: "text",
},
{
name: "recovers a generic Winget failure through portable Node",
installExit: 1,
fallback: "portable",
success: true,
},
{
name: "recovers a thrown Winget invocation through portable Node",
installThrows: true,
fallback: "portable",
success: true,
},
{
name: "recovers a generic Winget failure through the next package manager",
installExit: 1,
fallback: "choco",
success: true,
},
{
name: "rejects failed repair even if Node becomes healthy",
repairExit: 1,
@ -2164,8 +2075,6 @@ describe("install.ps1 stale Winget repair", () => {
repair: true,
},
{ name: "rejects repair that leaves Node missing", repair: true },
{ name: "rejects old Node after repair", afterRepair: "old-node", repair: true },
{ name: "rejects old SQLite after repair", afterRepair: "old-sqlite", repair: true },
...["text", "blob", "json", "probe-error"].map((capability) => ({
name: `rejects broken SQLite ${capability} after repair`,
afterRepair: capability,
@ -2186,7 +2095,6 @@ describe("install.ps1 stale Winget repair", () => {
afterRepair: "missing",
repair: false,
success: false,
installThrows: false,
fallback: "none",
afterFallback: "healthy",
...testCase,
@ -2237,7 +2145,6 @@ function Get-Command {
function Invoke-FixtureNode {
$global:LASTEXITCODE = 0
if ($args[0] -eq '-v') {
if ($global:State -eq 'old-node') { return 'v22.15.0' }
return 'v26.1.0'
}
$probe = @($input) -join [Environment]::NewLine
@ -2253,7 +2160,6 @@ function winget {
$global:Events.Add($args[0])
$global:WingetCalls.Add(@($args))
if ($args[0] -eq 'install') {
if ($case.installThrows) { throw 'fixture Winget invocation failed' }
$global:LASTEXITCODE = $case.installExit
$global:PendingState = $case.afterInstall
} elseif ($args[0] -eq 'repair') {
@ -2347,8 +2253,7 @@ Write-Output ('RESULT:' + (@{ direct = $direct; main = $main; healthy = $healthy
expect(run.calls).toEqual(options.repair ? [installArgs, repairArgs] : [installArgs]);
const repaired =
options.repair && options.repairExit === 0 && options.afterRepair === "healthy";
const fallbackExpected =
!repaired && (options.installThrows || options.afterInstall !== "healthy");
const fallbackExpected = !repaired && options.afterInstall !== "healthy";
const fallbacks: string[] = [];
if (fallbackExpected) {
if (options.fallback === "choco") {

View file

@ -71,40 +71,21 @@ describe.each([
expect(stdout).toHaveBeenCalledWith(expect.stringContaining("Usage:"));
expect(scan).not.toHaveBeenCalled();
});
});
describe("shared inventory argument validation", () => {
it.each([
{ argv: ["--limit"], error: "--limit expects a non-negative integer" },
...[
"",
" ",
" 1",
"1 ",
"+1",
"-1",
"1.0",
"1e3",
"0x10",
"١",
"1",
"9007199254740992",
"9".repeat(400),
"--",
"-h",
].map((value) => ({
...["-1", "9007199254740992", "--"].map((value) => ({
argv: ["--limit", value],
error: "--limit expects a non-negative integer",
})),
{ argv: ["--repo-root"], error: "--repo-root expects a path" },
...["", "-", "-h", "--"].map((value) => ({
argv: ["--repo-root", value],
error: "--repo-root expects a path",
{ argv: ["--repo-root", "-h"], error: "--repo-root expects a path" },
...["--limit=1", "--repo-root=repo", "--json=true"].map((arg) => ({
argv: [arg],
error: `Unknown argument: ${arg}`,
})),
...["--limit=1", "--repo-root=repo", "--json=true", "--unknown", "positional", ""].map(
(arg) => ({
argv: [arg],
error: `Unknown argument: ${arg}`,
}),
),
{ argv: ["--limit", "1", "--limit", "bad"], error: "--limit expects a non-negative integer" },
{ argv: ["--limit", "bad", "--limit", "1"], error: "--limit expects a non-negative integer" },
{ argv: ["--repo-root", "valid", "--repo-root", ""], error: "--repo-root expects a path" },
@ -115,7 +96,7 @@ describe.each([
const scan = vi.spyOn(fileUtils, "listRepoFilesSync");
const stdout = vi.spyOn(process.stdout, "write").mockReturnValue(true);
for (const args of [argv, ["--help", ...argv], [...argv, "--help"]]) {
expect(() => main(args)).toThrow(new Error(error));
expect(() => runEnvReport(args)).toThrow(new Error(error));
}
expect(stdout).not.toHaveBeenCalled();
expect(scan).not.toHaveBeenCalled();

View file

@ -108,7 +108,6 @@ describe("iOS release test identity", () => {
["skipped", { result: "Skipped" }],
["failed", { result: "Failed" }],
["failed child", { children: [{ nodeType: "Test Case Run", result: "Failed" }] }],
["wrong class", { nodeIdentifier: "OtherTests/testLiveGatewayPairChatAndRelaunch()" }],
[
"retry to green",
{
@ -204,7 +203,6 @@ describe("sampled simulator-tree footprint", () => {
it.each([
{},
{ ...sample, bytes: 0 },
{ ...sample, bytes: -1 },
{ ...sample, bytes: "1024" },
{ ...sample, processes: 0 },
{ ...sample, cpu: Number.NaN },
@ -234,7 +232,7 @@ describe("sampled simulator-tree footprint", () => {
function fixture(
options: {
fail?: "prepare" | "test" | "reader" | "cleanup";
fail?: "test" | "cleanup";
measure?: boolean;
invalidMeasurement?: boolean;
cancel?: boolean;
@ -262,9 +260,6 @@ function fixture(
prepare: async () => {
trace.push(`prepare:${index}`);
time += 100;
if (index === 1 && options.fail === "prepare") {
throw new Error("private preparation diagnostics");
}
},
test: async (test: TestIdentity) => {
trace.push(`test:${index}:${test}`);
@ -278,11 +273,7 @@ function fixture(
if (options.cancel) {
abort.abort();
}
if (
index === 1 &&
(options.fail === "test" ||
(options.fail === "reader" && test === IOS_RELEASE_TESTS[1]))
) {
if (index === 1 && options.fail === "test") {
throw Object.assign(new Error("private timeout diagnostics"), { code: "ETIMEDOUT" });
}
return result(test);
@ -361,45 +352,11 @@ describe("fresh trial ownership", () => {
}
expect(JSON.stringify(report)).not.toContain("private");
});
it.each(["test", "reader"] as const)(
"fails the arm after its %s failure without repeating either test",
async (fail) => {
const { deps, trace } = fixture({ fail });
const report = await runTrials("stock", deps);
expect(report.trials).toHaveLength(1);
expect(report.trials[0]).toMatchObject({
status: "failed",
errors: ["test-timeout"],
tests:
fail === "test"
? [{ test: IOS_RELEASE_TESTS[0], status: "failed" }]
: [
{ test: IOS_RELEASE_TESTS[0], status: "passed" },
{ test: IOS_RELEASE_TESTS[1], status: "failed" },
],
});
expect(trace.filter((entry) => entry.startsWith("test:"))).toEqual(
(fail === "test" ? [IOS_RELEASE_TESTS[0]] : IOS_RELEASE_TESTS).map(
(test) => `test:1:${test}`,
),
);
expect(trace.at(-1)).toBe("cleanup:1");
},
);
it("refuses comparison without a meter", async () => {
const { deps } = fixture();
await expect(runTrials("compare", deps)).rejects.toThrow("comparison-meter-required");
expect(deps.create).not.toHaveBeenCalled();
});
it("does not retry failed preparation or start its test/meter", async () => {
const { deps, trace } = fixture({ fail: "prepare" });
const report = await runTrials("stock", deps);
expect(report.trials[0]?.errors).toEqual(["preparation-failed"]);
expect(trace.filter((entry) => entry === "prepare:1")).toHaveLength(1);
expect(trace.some((entry) => entry.startsWith("test:"))).toBe(false);
expect(report.trials[0]?.tests).toEqual([]);
expect(trace).toContain("cleanup:1");
});
it("joins the collector and fails incomplete measurement without discarding the trial", async () => {
const { deps, trace } = fixture({ measure: true, invalidMeasurement: true });
const report = await runTrials("stock", deps);
@ -521,9 +478,7 @@ describe("release qualification workflow authority", () => {
});
it.each([
["manual current revision", {}, true],
["CI current revision", { caller: "ci" }, true],
["manual arbitrary target", { target: "b".repeat(40) }, false],
["CI arbitrary target", { caller: "ci", target: "b".repeat(40) }, false],
["invalid SHA", { target: "main" }, false],
["invalid mode", { mode: "unknown" }, false],
])("checks %s before checkout", (_name, options, admitted) => {
@ -533,7 +488,6 @@ describe("release qualification workflow authority", () => {
const target = "target" in options ? options.target : sha;
const repository = "openclaw/openclaw";
const ref = "refs/heads/main";
const caller = "caller" in options ? options.caller : "ios-release-e2e";
const first = workflow.jobs.qualify.steps[0];
expect(first.id).toBe("start");
const execution = spawnSync("/bin/bash", ["-c", first.run], {
@ -546,7 +500,7 @@ describe("release qualification workflow authority", () => {
GITHUB_SHA: sha,
GITHUB_REPOSITORY: repository,
GITHUB_REF: ref,
GITHUB_WORKFLOW_REF: `${repository}/.github/workflows/${caller}.yml@${ref}`,
GITHUB_WORKFLOW_REF: `${repository}/.github/workflows/ios-release-e2e.yml@${ref}`,
GITHUB_EVENT_NAME: "workflow_dispatch",
TARGET_SHA: target,
E2E_MODE: "mode" in options ? options.mode : "stock",
@ -637,7 +591,6 @@ describe("release qualification workflow authority", () => {
["full", "a".repeat(40), true],
["full", "b".repeat(40), false],
["main", "a".repeat(40), false],
["main", "b".repeat(40), false],
])(
"selects %s-tier target %s for required native qualification: %s",
(tier, target, selected) => {
@ -671,15 +624,12 @@ describe("release qualification workflow authority", () => {
describe("native command adapter", () => {
it.each([
"success",
"dirty-tracked",
"dirty-untracked",
"source-late-dirty",
"source-late-head-change",
"different-xcode",
"different-xcode-build",
"invalid-xcode-output",
"different-runtime",
"newest-compatible-runtime",
"unavailable-runtime",
"unsupported-runtime-device",
@ -709,8 +659,6 @@ describe("native command adapter", () => {
"reader-failure",
"fixture-exit",
"gateway-exit",
"missing-first",
"missing-second",
"missing-relaunch",
"provider-duplicate",
"provider-out-of-order",
@ -718,7 +666,6 @@ describe("native command adapter", () => {
"test-timeout-output",
"reply-failure-evidence",
"reply-failure-history-error",
"reply-failure-submission",
"reply-failure-source-only",
"reply-failure-app-log-error",
])("owns admission, build, test and cleanup for %s", async (scenario) => {
@ -951,11 +898,9 @@ describe("native command adapter", () => {
stdout.write(
scenario === "different-xcode"
? "Xcode 26.6\nBuild version 17F113\n"
: scenario === "different-xcode-build"
? "Xcode 27.0\nBuild version 27A000\n"
: scenario === "invalid-xcode-output"
? "unrecognized toolchain\n"
: "Xcode 27.0\nBuild version 27A266a\n",
: scenario === "invalid-xcode-output"
? "unrecognized toolchain\n"
: "Xcode 27.0\nBuild version 27A266a\n",
);
} else if (args.includes("--print-path")) {
stdout.write(`${developerDir}\n`);
@ -964,13 +909,11 @@ describe("native command adapter", () => {
} else if (args.includes("runtimes")) {
const runtime = {
isAvailable: scenario !== "unavailable-runtime",
version: scenario === "different-runtime" ? "27.0" : "26.5",
version: "26.5",
identifier:
scenario === "different-runtime"
? "com.apple.CoreSimulator.SimRuntime.iOS-27-0"
: scenario === "non-ios-runtime"
? "com.apple.CoreSimulator.SimRuntime.watchOS-26-5"
: "com.apple.CoreSimulator.SimRuntime.iOS-26-5",
scenario === "non-ios-runtime"
? "com.apple.CoreSimulator.SimRuntime.watchOS-26-5"
: "com.apple.CoreSimulator.SimRuntime.iOS-26-5",
supportedArchitectures:
scenario === "unsupported-runtime-architecture" ? ["x86_64"] : ["arm64"],
supportedDeviceTypes: [
@ -1014,11 +957,9 @@ describe("native command adapter", () => {
expect(lifecycle).toContain("setup-status-ready");
lifecycle.push("simulator-create");
expect(args.at(-1)).toBe(
scenario === "different-runtime"
? "com.apple.CoreSimulator.SimRuntime.iOS-27-0"
: scenario === "newest-compatible-runtime"
? "com.apple.CoreSimulator.SimRuntime.iOS-26-10"
: "com.apple.CoreSimulator.SimRuntime.iOS-26-5",
scenario === "newest-compatible-runtime"
? "com.apple.CoreSimulator.SimRuntime.iOS-26-10"
: "com.apple.CoreSimulator.SimRuntime.iOS-26-5",
);
if (scenario === "gateway-exit-during-create") {
gatewayChild.exitCode = 17;
@ -1181,7 +1122,7 @@ describe("native command adapter", () => {
const failureMessage =
scenario === "reply-failure-source-only"
? ""
: `IOS_RELEASE_CHAT_FAILURE relaunch ${scenario === "reply-failure-submission" ? "submission" : "reply"} draft=false keyboard=true reply=false writing=false jump=true foreground=true input=true transcript=true send=false`;
: "IOS_RELEASE_CHAT_FAILURE relaunch reply draft=false keyboard=true reply=false writing=false jump=true foreground=true input=true transcript=true send=false";
stdout.write(
"Test Case '-[OpenClawUITests.OpenClawSnapshotUITests testLiveGatewayPairChatAndRelaunch]' started.\n" +
`/private/checkout/OpenClawSnapshotUITests.swift:1913: error: private ${failureMessage}\n` +
@ -1338,24 +1279,12 @@ describe("native command adapter", () => {
const native = await admission;
expect(proof).toMatchObject({
xcode: scenario === "different-xcode" ? "26.6" : "27.0",
xcodeBuild:
scenario === "different-xcode"
? "17F113"
: scenario === "different-xcode-build"
? "27A000"
: "27A266a",
runtime:
scenario === "different-runtime"
? "27.0"
: scenario === "newest-compatible-runtime"
? "26.10"
: "26.5",
xcodeBuild: scenario === "different-xcode" ? "17F113" : "27A266a",
runtime: scenario === "newest-compatible-runtime" ? "26.10" : "26.5",
runtimeIdentifier:
scenario === "different-runtime"
? "com.apple.CoreSimulator.SimRuntime.iOS-27-0"
: scenario === "newest-compatible-runtime"
? "com.apple.CoreSimulator.SimRuntime.iOS-26-10"
: "com.apple.CoreSimulator.SimRuntime.iOS-26-5",
scenario === "newest-compatible-runtime"
? "com.apple.CoreSimulator.SimRuntime.iOS-26-10"
: "com.apple.CoreSimulator.SimRuntime.iOS-26-5",
});
try {
if (scenario === "native-build-only") {
@ -1426,7 +1355,7 @@ describe("native command adapter", () => {
? []
: [
"chat-stage:relaunch",
`chat-checkpoint:${scenario === "reply-failure-submission" ? "submission" : "reply"}`,
"chat-checkpoint:reply",
"chat-draft-retained:false",
"chat-keyboard:true",
"chat-reply-present:false",

View file

@ -81,7 +81,7 @@ describe("Kitchen Sink resource phase receipts", () => {
expect(phase.processCpuMsPerCompletedOperation).toBeCloseTo(1.21 / 20);
});
it.each([0, 1, 3])(
it.each([0, 3])(
"retains partial split counts when original operation %i fails",
async (failedIndex) => {
let step = 0;
@ -183,49 +183,46 @@ describe("Kitchen Sink resource phase receipts", () => {
},
);
it.each(invalidSamples)(
"preserves the first receipt and all completions on a %s final sample",
async (_name, invalidSample) => {
const before = snapshot(1);
const midpoint = snapshot(2, 90);
const sample = vi
.fn()
.mockResolvedValueOnce(before)
.mockResolvedValueOnce(midpoint)
.mockImplementationOnce(invalidSample);
const run = vi.fn(async () => {});
const phase = await measureResourceOperations({
name: "plugin-tool",
count: 20,
splitFirst: true,
sample,
run,
});
expect(run).toHaveBeenCalledTimes(20);
expect(sample).toHaveBeenCalledTimes(3);
expect(phase).toMatchObject({
status: "failed",
operations: { attempted: 20, completed: 20, failed: 0 },
after: null,
cpu: null,
});
expect(phase.before).toBe(before);
expect(phase.breakdown![0]).toEqual(
summarizeResourcePhase("plugin-tool-first", before, midpoint, {
attempted: 1,
completed: 1,
failed: 0,
}),
);
expect(phase.breakdown![1]).toMatchObject({
name: "plugin-tool-warm",
status: "failed",
operations: { attempted: 19, completed: 19, failed: 0 },
after: null,
cpu: null,
});
},
);
it("preserves the first receipt and all completions on a missing final sample", async () => {
const before = snapshot(1);
const midpoint = snapshot(2, 90);
const sample = vi
.fn()
.mockResolvedValueOnce(before)
.mockResolvedValueOnce(midpoint)
.mockRejectedValueOnce(new Error("sample unavailable"));
const run = vi.fn(async () => {});
const phase = await measureResourceOperations({
name: "plugin-tool",
count: 20,
splitFirst: true,
sample,
run,
});
expect(run).toHaveBeenCalledTimes(20);
expect(sample).toHaveBeenCalledTimes(3);
expect(phase).toMatchObject({
status: "failed",
operations: { attempted: 20, completed: 20, failed: 0 },
after: null,
cpu: null,
});
expect(phase.before).toBe(before);
expect(phase.breakdown![0]).toEqual(
summarizeResourcePhase("plugin-tool-first", before, midpoint, {
attempted: 1,
completed: 1,
failed: 0,
}),
);
expect(phase.breakdown![1]).toMatchObject({
name: "plugin-tool-warm",
status: "failed",
operations: { attempted: 19, completed: 19, failed: 0 },
after: null,
cpu: null,
});
});
it("counts only asserted responses and stops on the first failure without retrying", async () => {
const sample = vi
@ -327,16 +324,6 @@ describe("Kitchen Sink resource phase receipts", () => {
).toEqual([]);
});
it("rejects mixed process identities and unavailable memory rather than reporting zero", () => {
const ops = { attempted: 0, completed: 0, failed: 0 };
expect(() =>
summarizeResourcePhase("idle", snapshot(1), { ...snapshot(2), pid: 999 }, ops),
).toThrow("one process");
const invalid = snapshot(2);
invalid.memory.rss = Number.NaN;
expect(() => summarizeResourcePhase("idle", snapshot(1), invalid, ops)).toThrow("invalid rss");
});
it("retains signed resource changes and rejects missing observations", () => {
const before = { ...snapshot(1), activeResources: { Timeout: 2, TCPServerWrap: 1 } };
const after = { ...snapshot(2), activeResources: { Timeout: 1, Immediate: 1 } };

View file

@ -1028,27 +1028,20 @@ it("accepts a successful completed publisher for finalization without a Linux ch
it("rejects retired Tideclaw alpha finalization before writes", () => {
const f = fixture("tideclaw/alpha/2026-09-13-0400Z");
const tag = "v2026.9.4-alpha.1";
f.addDraft(tag, true);
const result = f.run("finalize-core", tag, "false");
const alphaTag = "v2026.9.4-alpha.1";
f.addDraft(alphaTag, true);
const result = f.run("finalize-core", alphaTag, "false");
expect(result.status, result.stderr).toBe(1);
expect(result.stderr).toContain("Alpha releases are retired;");
expect(f.mutations()).toEqual([]);
});
it.each([
{ ref: "unreviewed/branch", tag: "v2026.9.4-alpha.1", latest: "false" },
{ ref: "tideclaw/alpha/2026-09-13-0400Z", tag: nextTag, latest: "false" },
{ ref: "tideclaw/alpha/2026-09-13-0400Z", tag: "v2026.9.4-alpha.1", latest: "true" },
])(
"rejects unapproved branch finalization $ref/$tag/$latest",
({ ref, tag: selectedTag, latest }) => {
const f = fixture(ref);
f.addDraft(selectedTag, selectedTag.includes("-alpha."));
expect(f.run("finalize-core", selectedTag, latest).status).toBe(1);
expect(f.mutations()).toEqual([]);
},
);
it("rejects a retired alpha workflow even when finalizing a stable tag", () => {
const f = fixture("tideclaw/alpha/2026-09-13-0400Z");
f.addDraft(nextTag);
failed(f.run("finalize-core", nextTag, "false"), "Alpha releases are retired;");
expect(f.mutations()).toEqual([]);
});
it("publishes opt-in desktop metadata only after successful Linux and legacy readback", () => {
const f = fixture(toolingRef, true);
@ -1485,7 +1478,7 @@ it("refuses mirroring before canonical initialization without creating a release
expect(f.mutations()).toEqual([]);
});
it.each(["v2026.9.3-alpha.1", "v2026.9.3-beta.1", "v2026.6.33"])(
it.each(["v2026.9.3-beta.1", "v2026.6.33"])(
"rejects non-regular target %s before touching GitHub",
(releaseTag) => {
const f = fixture();
@ -1556,7 +1549,6 @@ it.each([null, tag])(
it.each([
{ releaseTag: nextTag, prerelease: false },
{ releaseTag: "v2026.6.33", prerelease: false },
{ releaseTag: "v2026.8.35", prerelease: false },
{ releaseTag: "v2026.9.4-beta.1", prerelease: true },
])("honors explicit non-latest finalization of $releaseTag", ({ releaseTag, prerelease }) => {

View file

@ -46,7 +46,7 @@ type Remote = {
requestReadRejected?: boolean;
uploadBehavior?: "accepted-error" | "rejected-error" | "deleted-error" | "success-noop";
dispatchRejected?: boolean;
dispatchResponse?: "missing-id" | "null-id";
missingDispatchId?: boolean;
toolingStatus?: string;
parentAttempt?: number;
currentRun?: ActionRun;
@ -175,8 +175,7 @@ if (args[0] === 'api') {
(state.requestRuns ||= []).unshift({id: 456, head_sha: '${toolingSha}', head_branch: request.ref,
event: 'workflow_dispatch', status: 'queued', conclusion: null,
display_title: 'Linux App Release Request [' + request.inputs.tag + '] desktop=' + request.inputs['desktop-test-bundles']});
result({...(state.dispatchResponse === 'missing-id' ? {} :
{workflow_run_id: state.dispatchResponse === 'null-id' ? null : 456}),
result({...(state.missingDispatchId ? {} : {workflow_run_id: 456}),
html_url: 'https://github.com/${repository}/actions/runs/456'});
} else if (endpoint?.startsWith('repos/${repository}/commits/')) {
result('${sourceSha}');
@ -380,7 +379,7 @@ process.stdout.write('${sourceSha}\\trefs/tags/v2026.9.4\\n');
};
}
it.each(["2026.9.3", "2026.9.4", "2026.9.5"])(
it.each(["2026.9.3", "2026.9.5"])(
"preserves an equal or newer valid target manifest byte-for-byte (%s)",
(version) => {
const remote = fixture();
@ -418,7 +417,6 @@ it.each(["missing-manifest", "missing-release"])(
it.each([
"cross-repository",
"wrong-version-url",
"empty-signature",
"invalid-base64",
"invalid-json",
@ -436,9 +434,6 @@ it.each([
if (kind === "cross-repository") {
platform.url = platform.url.replace(repository, "foreign/repository");
}
if (kind === "wrong-version-url") {
platform.url = platform.url.replaceAll("2026.9.3", "2026.9.2");
}
if (kind === "empty-signature") {
platform.signature = "";
}
@ -565,7 +560,7 @@ it("preserves identity CLI behavior when no writer tuple is requested", () => {
});
it.each([
...writerCliFields.map(([name, value]) => ({ name, arguments: [name, value] })),
{ name: "incomplete writer tuple", arguments: ["--writer-run-id", "200"] },
{ name: "missing writer value", arguments: ["--writer-run-id"] },
])("refuses partial identity CLI writer arguments: $name", (scenario) => {
const remote = fixture();
@ -811,10 +806,7 @@ it.each(["absent", "complete", "partial", "propagation"])(
);
it("reports a refused Linux workflow dispatch without a success receipt or retry", () => {
const remote = fixture({ dispatchRejected: true });
remote.update((state) => {
state.releases["v2026.9.4"] = release("2026.9.4", { assets: [], manifest: undefined });
});
const remote = pendingLinuxFixture({ dispatchRejected: true });
const result = remote.dispatch();
expect(result.status).toBe(1);
expect(result.evidence.state).toBe("dispatch-unconfirmed");
@ -847,7 +839,7 @@ function linuxRequest(overrides: Partial<ActionRun> = {}): ActionRun {
};
}
it.each(["requested", "waiting", "pending", "queued", "in_progress", "completed"])(
it.each(["queued", "completed"])(
"reuses a manual same-tag Linux request in %s state without another build request",
(status) => {
const remote = pendingLinuxFixture({
@ -894,7 +886,6 @@ it("finds a desktop-inclusive Linux request beyond the first history page", () =
it.each([
{ reason: "failed", overrides: { conclusion: "failure" } },
{ reason: "cancelled", overrides: { conclusion: "cancelled" } },
{ reason: "another branch", overrides: { head_branch: "feature" } },
{ reason: "another event", overrides: { event: "push" } },
{
@ -919,10 +910,7 @@ it("refuses another Linux request when request history cannot be read", () => {
});
it("refuses a moved tag even when the dispatch caller suppresses shell errexit", () => {
const remote = fixture();
remote.update((state) => {
state.releases["v2026.9.4"] = release("2026.9.4", { assets: [], manifest: undefined });
});
const remote = pendingLinuxFixture();
const result = remote.dispatch("c".repeat(40));
expect(result.status).toBe(1);
expect(result.stderr).toContain("Release tag v2026.9.4 moved");
@ -933,20 +921,14 @@ it("refuses a moved tag even when the dispatch caller suppresses shell errexit",
).toEqual([]);
});
it.each(["missing-id", "null-id"] as const)(
"does not confirm or repeat an accepted workflow dispatch with %s",
(dispatchResponse) => {
const remote = fixture({ dispatchResponse });
remote.update((state) => {
state.releases["v2026.9.4"] = release("2026.9.4", { assets: [], manifest: undefined });
});
const result = remote.dispatch();
expect(result.status).toBe(1);
expect(result.evidence.state).toBe("dispatch-unconfirmed");
expect(remote.read().requests).toHaveLength(1);
expect(remote.read().calls.filter((args) => args[0] === "run")).toEqual([]);
expect(
remote.read().calls.filter((args) => args.some((arg) => arg.endsWith("/dispatches"))),
).toHaveLength(1);
},
);
it("does not confirm or repeat an accepted workflow dispatch without a run ID", () => {
const remote = pendingLinuxFixture({ missingDispatchId: true });
const result = remote.dispatch();
expect(result.status).toBe(1);
expect(result.evidence.state).toBe("dispatch-unconfirmed");
expect(remote.read().requests).toHaveLength(1);
expect(remote.read().calls.filter((args) => args[0] === "run")).toEqual([]);
expect(
remote.read().calls.filter((args) => args.some((arg) => arg.endsWith("/dispatches"))),
).toHaveLength(1);
});

View file

@ -208,7 +208,6 @@ it.each([false, true])(
it.each([
["win32", true, true],
["win32", false, true],
["darwin", true, true],
["darwin", false, true],
["win32", true, false],
] as const)(
@ -297,12 +296,7 @@ it.each([
if (terminates && closes) {
expect(descendantOutput.destroyed).toBe(true);
owner.assertReleased();
// POSIX strict normal-exit policy still reports unexpected group survivors.
if (platform === "win32") {
expect(outcome).toBe(0);
} else {
expect(outcome).toMatchObject({ processTreeState: "terminated" });
}
expect(outcome).toBe(0);
} else {
expect(hasUnjoinedWork(outcome)).toBe(true);
expect(outcome).toMatchObject({ code: "EPROCESSGROUP_CLEANUP_FAILED" });

View file

@ -1,4 +1,4 @@
import { execFileSync, spawnSync } from "node:child_process";
import { spawnSync } from "node:child_process";
import { randomUUID } from "node:crypto";
import { mkdirSync, readdirSync, realpathSync, readFileSync, lstatSync } from "node:fs";
import path from "node:path";
@ -73,23 +73,6 @@ const tempDirs = useAutoCleanupTempDirTracker(afterEach);
afterEach(() => vi.restoreAllMocks());
it("loads the worker compiler with native Node before preparing artifacts", () => {
const output = execFileSync(
process.execPath,
[
"--input-type=module",
"--eval",
`
await import("./scripts/lib/vitest-worker-compiler.mts");
console.log("native worker compiler import verified");
`,
],
{ encoding: "utf8", timeout: 30_000 },
);
expect(output.trim()).toBe("native worker compiler import verified");
});
it.each(
(["managed", "package"] as const).filter(
(kind) => kind === "managed" || process.platform !== "win32",
@ -246,9 +229,6 @@ it.each(
"createManagedHandoffLeaseStore",
"resolveUpdateRestartNoticeMeta",
"shouldPublishUpdateRestartNotice",
"extractSqliteTableSchema",
"readRestartSentinelRowSync",
"writeRestartSentinelRowIfRevisionSync",
]) {
assert.equal(typeof runtime[name], "function", name);
}
@ -330,12 +310,7 @@ it.each(
expect(stale.error).toBeUndefined();
expect(stale.status).toBe(1);
expect(stale.stderr).toContain("different operation");
const after = snapshot();
expect(after).toHaveLength(before.length);
for (const [index, original] of before.entries()) {
expect(after[index]!.ino).toBe(original.ino);
expect(after[index]!.bytes.equals(original.bytes)).toBe(true);
}
expect(snapshot()).toEqual(before);
}
// The replacement's temporary command is deliberately one-phase, never
// another locator for the next operation after its helper has moved.

View file

@ -755,16 +755,6 @@ describe("mobile release CI tools", () => {
expect(JSON.stringify(steps)).not.toContain("candidate/");
const tooling = steps[toolingIndex]?.run ?? "";
expect(tooling).toContain('apt_source="/etc/apt/sources.list.d/ubuntu.sources"');
expect(tooling).toContain(
'apt_source_parts="$RUNNER_TEMP/openclaw-android-apt-sourceparts-disabled"',
);
expect(tooling).toContain('test -s "$apt_source"');
expect(tooling).toContain('[[ -e "$apt_source_parts" || -L "$apt_source_parts" ]]');
expect(tooling).toContain('/usr/bin/apt-get "${apt_options[@]}" update');
expect(tooling).toMatch(
/\/usr\/bin\/apt-get "\$\{apt_options\[@\]\}" install \\\n\s+-y --no-install-recommends imagemagick/u,
);
expect(tooling).toContain(
'test "$(git -C "$trusted_root" rev-parse HEAD)" = "$GITHUB_WORKFLOW_SHA"',
);
@ -955,18 +945,10 @@ describe("mobile release CI tools", () => {
expect(diagnostic).toContain(
'emulator_args=(-avd "$AVD_NAME" -no-window -no-audio -no-boot-anim -verbose -show-kernel)',
);
expect(diagnostic).toContain("capture_accel_check() {");
expect(diagnostic).toContain("accel_check_timeout_seconds=10");
expect(diagnostic).toContain('emulator -accel-check >"$accel_raw" 2>&1 &');
expect(diagnostic).toContain(
'head -c 16384 "$accel_raw" >"$DIAGNOSTIC_DIR/emulator-accel-check.txt"',
);
expect(diagnostic).toContain(
'printf \'exit_status=%s\\n\' "$accel_status" >>"$DIAGNOSTIC_DIR/emulator-accel-check.txt"',
);
expect(diagnostic).toContain(
'printf \'timed_out=%s\\n\' "$accel_timed_out" >>"$DIAGNOSTIC_DIR/emulator-accel-check.txt"',
);
expect(diagnostic).toContain("sample_owned_qemu() {");
expect(diagnostic).toContain(
'printf \'\\n[%s] owned_emulator_pid=%s\\n\' "$(date -u +%Y-%m-%dT%H:%M:%SZ)" "$emulator_pid"',
@ -1021,18 +1003,15 @@ describe("mobile release CI tools", () => {
expect(timedOutAccel.output).toContain("exit_status=124");
expect(timedOutAccel.output).toContain("timed_out=true");
expect(diagnostic).toContain("observe_after_readiness_timeout() {");
expect(diagnostic).toContain("final_cold_boot_observation_seconds=900");
expect(diagnostic).toContain("probe_timeout_seconds=5");
expect(diagnostic).toContain("final_snapshot_lead_seconds=15");
expect(diagnostic).toContain("snapshot_properties_max_bytes=65536");
expect(diagnostic).toContain("snapshot_logcat_max_bytes=262144");
expect(diagnostic).toContain("capture_cold_boot_snapshot() {");
expect(diagnostic).toContain(
"emulator_observation_deadline=$((emulator_launch_seconds + final_cold_boot_observation_seconds))",
);
expect(diagnostic).not.toContain("post_deadline_observation_seconds");
expect(diagnostic).toContain("fail_after_readiness_timeout() {");
const observationFunctionStart = diagnostic.indexOf("run_bounded_probe() {");
const observationFunctionEnd = diagnostic.indexOf(
"\n\nfail_after_readiness_timeout()",
@ -1127,34 +1106,11 @@ describe("mobile release CI tools", () => {
};
};
const lateReady = await runPostDeadlineObservation(`#!/bin/bash
set -euo pipefail
if [[ "\${1:-}" == "devices" ]]; then
printf 'List of devices attached\\nemulator-5554\\tdevice product:sdk model:sdk\\n'
elif [[ "\${1:-}" == "-s" && "\${3:-}" == "shell" ]]; then
printf '1\\n'
elif [[ "\${1:-}" == "-s" && "\${3:-}" == "emu" ]]; then
printf '%s\\nOK\\n' "\${AVD_NAME:?}"
fi
`);
expect(lateReady.result.status).toBe(1);
expect(lateReady.result.stderr).toContain("::error::latched readiness failure");
expect(lateReady.observations).toContain("late_adb_online_at=");
expect(lateReady.observations).toContain("late_boot_completed_at=");
expect(lateReady.observations).toContain("observation_stop=late-boot-completed");
expect(lateReady.snapshots).toEqual(["first-online"]);
expect(
fs.readFileSync(
path.join(lateReady.snapshotsRoot, "first-online", "boot-properties.txt"),
"utf8",
),
).toContain("probe_exit_status=0");
const lateReadyNearCeilingFunctions = observationFunctions.replace(
"final_snapshot_lead_seconds=4",
"final_snapshot_lead_seconds=60",
);
const lateReadyNearCeiling = await runPostDeadlineObservation(
const lateReady = await runPostDeadlineObservation(
`#!/bin/bash
set -euo pipefail
if [[ "\${1:-}" == "devices" ]]; then
@ -1167,10 +1123,18 @@ fi
`,
{ functions: lateReadyNearCeilingFunctions },
);
expect(lateReadyNearCeiling.result.status).toBe(1);
expect(lateReadyNearCeiling.observations).toContain("late_boot_completed_at=");
expect(lateReadyNearCeiling.observations).toContain("observation_stop=late-boot-completed");
expect(lateReadyNearCeiling.snapshots).toEqual(["first-online"]);
expect(lateReady.result.status).toBe(1);
expect(lateReady.result.stderr).toContain("::error::latched readiness failure");
expect(lateReady.observations).toContain("late_adb_online_at=");
expect(lateReady.observations).toContain("late_boot_completed_at=");
expect(lateReady.observations).toContain("observation_stop=late-boot-completed");
expect(lateReady.snapshots).toEqual(["first-online"]);
expect(
fs.readFileSync(
path.join(lateReady.snapshotsRoot, "first-online", "boot-properties.txt"),
"utf8",
),
).toContain("probe_exit_status=0");
const [failedBootProbeResult, boundedSnapshotsResult] = await Promise.allSettled([
runPostDeadlineObservation(
@ -1256,21 +1220,6 @@ fi
expect(changedDevice.observations).toContain("observation_stop=unexpected-device-change");
expect(changedDevice.snapshots).toEqual([]);
const capped = await runPostDeadlineObservation(
`#!/bin/bash
set -euo pipefail
if [[ "\${1:-}" == "devices" ]]; then
printf 'List of devices attached\\n\\n'
fi
`,
{ deadlineSeconds: 2 },
);
expect(capped.result.status).toBe(1);
expect(capped.elapsedSeconds).toBe(2);
expect(capped.result.stderr).toContain("::error::latched readiness failure");
expect(capped.observations).toContain("observation_cap_seconds=900");
expect(capped.observations).toContain("observation_stop=observation-cap-reached");
const absoluteCap = await runPostDeadlineObservation(
`#!/bin/bash
set -euo pipefail
@ -1282,6 +1231,8 @@ fi
);
expect(absoluteCap.result.status).toBe(1);
expect(absoluteCap.elapsedSeconds).toBe(3);
expect(absoluteCap.result.stderr).toContain("::error::latched readiness failure");
expect(absoluteCap.observations).toContain("observation_cap_seconds=900");
expect(absoluteCap.durationMs).toBeLessThan(5_000);
expect(absoluteCap.observations).toContain("observation_stop=observation-cap-reached");
@ -1454,7 +1405,6 @@ fi
expect(diagnostic).toContain("adb devices -l");
expect(diagnostic).toContain('>>"$DIAGNOSTIC_DIR/adb-observations.log" 2>&1');
expect(diagnostic).toContain('ps -p "$emulator_pid"');
expect(diagnostic).toContain('kill "$emulator_pid"');
expect(diagnostic).toContain("adb kill-server");
expect(diagnostic).toContain("trap cleanup EXIT");
expect(diagnostic).toMatch(
@ -1906,8 +1856,6 @@ fi
"bundle:_4.0.21_ exec fastlane ios signing_check",
"probe:root-cwd",
]);
expect(signingProof).toContain("source ./scripts/lib/ios-fastlane.sh");
expect(signingProof).toContain("(cd apps/ios && run_ios_fastlane ios signing_check)");
const failedCheck = runSigningProof({ FIXTURE_FAIL_CHECK: "1" });
expect(failedCheck.result.status).not.toBe(0);

View file

@ -179,104 +179,90 @@ describe("generated mobile store notes", () => {
await expect(generateMobileReleaseNotes(f)).rejects.toThrow("different production baseline");
});
it.each(["legacy", "v2"])(
"keeps phone and Wear baselines and text separate with %s source records",
async (scheme) => {
const f = fixture("android");
const sourceRef =
"refs/openclaw/mobile-releases/android/v2/2026.7.3/0/1/2026070449-2026070450";
const wearBase = f.sourceSha;
git(
f.rootDir,
"update-ref",
"refs/openclaw/mobile-releases/android/2026.7.3-2026070302",
wearBase,
);
f.plan.releaseNotesBaselines[1] = {
audience: "wear",
version: "2026.7.3",
build: "2026070352",
};
const wearFile = "apps/android/wear/src/main/java/Watch.kt";
f.write(wearFile, 'val label = "Start talking"\n');
git(f.rootDir, "add", wearFile);
git(f.rootDir, "commit", "-m", "Clarify watch voice action");
f.sourceSha = git(f.rootDir, "rev-parse", "HEAD");
f.plan.sourceSha = f.sourceSha;
fs.writeFileSync(f.planPath, JSON.stringify(f.plan));
if (scheme === "v2") {
git(f.rootDir, "update-ref", sourceRef, f.base);
f.plan.releaseNotesBaselines[0] = {
audience: "phone",
version: "2026.7.30",
build: "2026070449",
};
fs.writeFileSync(
f.planPath,
JSON.stringify({
...f.plan,
releaseNotesBaselines: [
{ ...f.plan.releaseNotesBaselines[0], sourceRef },
f.plan.releaseNotesBaselines[1],
],
}),
);
}
accept([{ file: f.file, focus: ["label"] }]);
accept(
[{ file: wearFile, focus: ["label"] }],
[{ text: "Clearer watch voice controls.", evidenceIds: ["e1"] }],
);
const saved = await generateMobileReleaseNotes(f);
const wearInventory = JSON.parse(api.parse.mock.calls[3]![0].input);
expect(wearInventory.files).toEqual(
expect.arrayContaining([expect.objectContaining({ file: wearFile })]),
);
expect(wearInventory.files).not.toEqual(
expect.arrayContaining([expect.objectContaining({ file: f.file })]),
);
for (const index of [2, 5]) {
const review = JSON.parse(api.parse.mock.calls[index]![0].input);
expect(review.evidence).toEqual(
expect.arrayContaining([
expect.objectContaining({
file: "apps/android/app/src/play/java/ai/openclaw/app/SensitiveFeatureConfig.kt",
kind: "context",
patch: expect.stringContaining("smsEnabled = false"),
}),
]),
);
}
expect(
saved.entries.map((entry) => [
entry.audience,
entry.baseline.build,
entry.baseline.sourceSha,
entry.text,
]),
).toEqual([
[
"phone",
scheme === "v2" ? "2026070449" : "2026070301",
f.base,
"- Clearer labels when sending messages.",
it("keeps v2 phone and legacy Wear baselines and text separate", async () => {
const f = fixture("android");
const sourceRef = "refs/openclaw/mobile-releases/android/v2/2026.7.3/0/1/2026070449-2026070450";
const wearBase = f.sourceSha;
git(
f.rootDir,
"update-ref",
"refs/openclaw/mobile-releases/android/2026.7.3-2026070302",
wearBase,
);
f.plan.releaseNotesBaselines[1] = {
audience: "wear",
version: "2026.7.3",
build: "2026070352",
};
const wearFile = "apps/android/wear/src/main/java/Watch.kt";
f.write(wearFile, 'val label = "Start talking"\n');
git(f.rootDir, "add", wearFile);
git(f.rootDir, "commit", "-m", "Clarify watch voice action");
f.sourceSha = git(f.rootDir, "rev-parse", "HEAD");
f.plan.sourceSha = f.sourceSha;
git(f.rootDir, "update-ref", sourceRef, f.base);
f.plan.releaseNotesBaselines[0] = {
audience: "phone",
version: "2026.7.30",
build: "2026070449",
};
fs.writeFileSync(
f.planPath,
JSON.stringify({
...f.plan,
releaseNotesBaselines: [
{ ...f.plan.releaseNotesBaselines[0], sourceRef },
f.plan.releaseNotesBaselines[1],
],
["wear", "2026070352", wearBase, "- Clearer watch voice controls."],
]);
vi.stubEnv("OPENAI_API_KEY", "");
expect(await generateMobileReleaseNotes(f)).toEqual(saved);
if (scheme === "v2") {
fs.rmSync(f.outputPath);
vi.stubEnv("OPENAI_API_KEY", "synthetic-key");
const plan = JSON.parse(fs.readFileSync(f.planPath, "utf8"));
plan.releaseNotesBaselines[0].build = "2026070450";
fs.writeFileSync(f.planPath, JSON.stringify(plan));
await expect(generateMobileReleaseNotes(f)).rejects.toThrow(
"does not match its recorded store identity",
);
}
},
);
}),
);
accept([{ file: f.file, focus: ["label"] }]);
accept(
[{ file: wearFile, focus: ["label"] }],
[{ text: "Clearer watch voice controls.", evidenceIds: ["e1"] }],
);
const saved = await generateMobileReleaseNotes(f);
const wearInventory = JSON.parse(api.parse.mock.calls[3]![0].input);
expect(wearInventory.files).toEqual(
expect.arrayContaining([expect.objectContaining({ file: wearFile })]),
);
expect(wearInventory.files).not.toEqual(
expect.arrayContaining([expect.objectContaining({ file: f.file })]),
);
for (const index of [2, 5]) {
const review = JSON.parse(api.parse.mock.calls[index]![0].input);
expect(review.evidence).toEqual(
expect.arrayContaining([
expect.objectContaining({
file: "apps/android/app/src/play/java/ai/openclaw/app/SensitiveFeatureConfig.kt",
kind: "context",
patch: expect.stringContaining("smsEnabled = false"),
}),
]),
);
}
expect(
saved.entries.map((entry) => [
entry.audience,
entry.baseline.build,
entry.baseline.sourceSha,
entry.text,
]),
).toEqual([
["phone", "2026070449", f.base, "- Clearer labels when sending messages."],
["wear", "2026070352", wearBase, "- Clearer watch voice controls."],
]);
vi.stubEnv("OPENAI_API_KEY", "");
expect(await generateMobileReleaseNotes(f)).toEqual(saved);
fs.rmSync(f.outputPath);
vi.stubEnv("OPENAI_API_KEY", "synthetic-key");
const plan = JSON.parse(fs.readFileSync(f.planPath, "utf8"));
plan.releaseNotesBaselines[0].build = "2026070450";
fs.writeFileSync(f.planPath, JSON.stringify(plan));
await expect(generateMobileReleaseNotes(f)).rejects.toThrow(
"does not match its recorded store identity",
);
});
it("does not invent notes for a fully reverted app change", async () => {
const f = fixture();

View file

@ -55,13 +55,6 @@ describe("installed-package execution identity proof", () => {
expect(existsSync(path.join(state, "state/openclaw.sqlite"))).toBe(false);
});
it("accepts the persisted run id when it differs from the caller session id", () => {
const result = projection();
expect(
assertIdentityProjection(result, JSON.stringify(result.identity.context), [privateMarker]),
).toBe("execution-1");
});
it.each([
[
"missing identity",
@ -109,15 +102,10 @@ describe("installed-package execution identity proof", () => {
).toThrow();
});
it.each(["export", "storage", "receipt"])("rejects a private canary in %s", (where) => {
it.each(["export", "storage"])("rejects a private canary in %s", (where) => {
const result = projection();
const stored = JSON.stringify(result.identity.context);
const exported =
where === "export"
? { ...result, prompt: privateMarker }
: where === "receipt"
? { ...result, decisions: [{ body: privateMarker }] }
: result;
const exported = where === "export" ? { ...result, prompt: privateMarker } : result;
expect(() =>
assertIdentityProjection(exported, where === "storage" ? stored + privateMarker : stored, [
privateMarker,
@ -152,7 +140,7 @@ describe("installed-package execution identity proof", () => {
insert.run(JSON.stringify(before.identity.context));
writeFileSync(beforePath, JSON.stringify(before));
writeFileSync(afterPath, JSON.stringify(before));
expect(verify().status).toBe(0);
expect(verify()).toMatchObject({ status: 0, stdout: "execution-1" });
const after = projection();
after.identity.context.runtimeInstance.runtimeRef = domainRef.replace(/b/gu, "c");
db.prepare("UPDATE execution_identity_contexts SET context_json = ?").run(

View file

@ -8,13 +8,9 @@ const helper = "scripts/e2e/lib/onboard/first-agent-flow.sh";
describe.skipIf(process.platform === "win32")("guided first-agent prompt handshake", () => {
it.each([
["legacy", "plain", "provider", 5],
["legacy", "fragmented", "provider", 5],
["team", "plain", "provider", 6],
["team", "fragmented", "provider", 6],
["legacy", "plain", "configured", 4],
["legacy", "fragmented", "configured", 4],
["team", "plain", "configured", 5],
["team", "fragmented", "configured", 5],
] as const)(
"drives the real guided sender through %s %s %s prompts",
(layout, rendering, modelPrompt, count) => {

View file

@ -292,12 +292,8 @@ describe("OpenClaw performance Crabbox boundary", () => {
},
);
it.each([
{ name: "IPv6 rule readback", fault: "6:-C" },
{ name: "IPv4 reachable metadata", family: 4, code: 0, output: "401" },
{ name: "IPv6 reachable metadata", family: 6, code: 0, output: "401" },
])("blocks runner handoff without its own metadata denial: $name", (options) => {
const run = prepareSut({ ...options, runner: true });
it("blocks runner handoff when metadata is reachable", () => {
const run = prepareSut({ runner: true, family: 6, code: 0, output: "401" });
expect(run.result.error).toBeUndefined();
expect(run.result.status, run.result.stderr).not.toBe(0);
expect(run.handoff).toBe(false);
@ -310,21 +306,19 @@ describe("OpenClaw performance Crabbox boundary", () => {
fault: `missing:${tool}`,
})),
...["http", "ipv6", "version"].map((fault) => ({ name: `curl ${fault}`, fault })),
...[4, 6].flatMap((family) =>
["401", "404"].map((output) => ({
name: `IPv${family} HTTP ${output}`,
family,
output,
code: 0,
})),
),
...[0, 1, 2, 3, 4, 5, 6, 22, 23, 26, 28, 52, 55, 56, 126, 127, 143].map((code) => ({
...[4, 6].map((family) => ({
name: `IPv${family} HTTP 401`,
family,
output: "401",
code: 0,
})),
...[0, 22, 28, 127].map((code) => ({
name: `IPv6 curl exit ${code}`,
code,
})),
{ name: "IPv4 timeout", family: 4, code: 28 },
{ name: "HTTP response despite connection failure", output: "401", code: 7 },
...["", "00", "000000", "000\n401", " 000", "000\n"].map((output) => ({
...["", "000\n401", "000\n"].map((output) => ({
name: `malformed status ${JSON.stringify(output)}`,
output,
})),
@ -446,7 +440,6 @@ collect_diagnostics "$SOURCE" "$DESTINATION" "$SUBTREE"
it.each([
{ name: "success", expected: 0 },
{ name: "advisory matrix 17", matrixExit: 17, expected: 0 },
{ name: "adapted gated matrix 17", matrixExit: 17, gated: true, adapted: true, expected: 0 },
{ name: "rejected gated matrix 17", matrixExit: 17, gated: true, expected: 17 },
{ name: "setup failure", setupExit: 23, expected: 23 },
@ -823,75 +816,58 @@ fi
it.each([
{
name: "advisory BLOCKED",
gated: false,
sutExit: 17,
records: true,
planFilter: "scenario:probe",
expected: 0,
},
{
name: "unadaptable gated BLOCKED",
gated: true,
sutExit: 17,
records: true,
planFilter: "scenario:probe",
expected: 17,
},
{
name: "missing requested records",
gated: false,
sutExit: 0,
records: false,
planFilter: "scenario:probe",
expected: 1,
},
{
name: "wrong plan filters",
gated: false,
sutExit: 0,
records: true,
planFilter: "scenario:wrong",
expected: 1,
},
{
name: "ambiguous full reports",
gated: false,
sutExit: 0,
records: true,
planFilter: "scenario:probe",
ambiguous: true,
expected: 1,
},
{
name: "custom Kova diagnostics",
gated: false,
sutExit: 0,
records: true,
planFilter: "scenario:probe",
admitted: false,
expected: 0,
},
{
name: "custom Kova cannot approve a gate",
gated: true,
sutExit: 0,
records: true,
planFilter: "scenario:probe",
admitted: false,
expected: 1,
},
{
name: "custom Kova invalid evidence",
gated: false,
sutExit: 0,
records: false,
planFilter: "scenario:probe",
admitted: false,
expected: 1,
},
])(
"enforces native Kova evidence and gate semantics: $name",
({ gated, sutExit, records, planFilter, expected, admitted = true, ambiguous = false }) => {
({
gated = false,
sutExit = 0,
records = true,
planFilter = "scenario:probe",
expected,
admitted = true,
ambiguous = false,
}) => {
const root = tempDirs.make("openclaw-performance-kova-contract-");
const openclaw = join(root, "openclaw");
mkdirSync(join(openclaw, ".artifacts/kova/reports/mock-provider"), { recursive: true });
@ -1051,10 +1027,8 @@ validate_kova mock-provider "$ROOT" diagnostic 1 scenario:probe - "$GATED" "$HEL
);
chmodSync(crabbox, 0o755);
for (let attempt = 0; attempt < 2; attempt += 1) {
const result = spawnSync("bash", [SCRIPT, "confirm-stop", crabbox, "cbx_0123456789ab"]);
expect(result.status).toBe(status);
}
const result = spawnSync("bash", [SCRIPT, "confirm-stop", crabbox, "cbx_0123456789ab"]);
expect(result.status).toBe(status);
},
);

View file

@ -157,7 +157,6 @@ describe("release readiness contract", () => {
["retired soak waiver", { stable_soak_waiver: "2026.9.2 approved" }],
["retired lane waiver", { lane_waiver: "2026.9.2 approved" }],
["moving source", { tag: "main" }],
["missing source", { tag: "" }],
["wrong beta channel", { npm_dist_tag: "latest" }],
["alpha owner", { tag: "v2026.9.2-alpha.1", npm_dist_tag: "alpha" }],
["extended-stable owner", { tag: "v2026.9.33", npm_dist_tag: "latest" }],
@ -1844,28 +1843,25 @@ describe("release preparation recovery", () => {
expect(existsSync(fixture.env.GITHUB_OUTPUT)).toBe(false);
});
it.each([1, 2])(
"adopts exact identified preparations without dispatch on attempt %s",
async (attempt) => {
const fixture = await preparationFixture();
const request = preparationRequest();
const result = fixture.prepare(request, attempt);
expect(result.status, result.stderr).toBe(0);
expect(JSON.parse(readFileSync(fixture.requestPath, "utf8"))).toEqual(request);
expect(fixture.trace().filter((entry) => entry.args?.includes("POST"))).toEqual([]);
expect(
fixture
.trace()
.filter((entry) => /\/actions\/runs\/(?:300|400)$/u.test(entry.args?.[1] ?? "")),
).toEqual([
{ event: "gh", args: ["api", `repos/${REPOSITORY}/actions/runs/300`] },
{ event: "gh", args: ["api", `repos/${REPOSITORY}/actions/runs/400`] },
]);
expect(readFileSync(fixture.env.GITHUB_OUTPUT, "utf8")).toContain(
`request=${JSON.stringify(request)}\n`,
);
},
);
it("adopts exact identified preparations without dispatch on a rerun", async () => {
const fixture = await preparationFixture();
const request = preparationRequest();
const result = fixture.prepare(request, 2);
expect(result.status, result.stderr).toBe(0);
expect(JSON.parse(readFileSync(fixture.requestPath, "utf8"))).toEqual(request);
expect(fixture.trace().filter((entry) => entry.args?.includes("POST"))).toEqual([]);
expect(
fixture
.trace()
.filter((entry) => /\/actions\/runs\/(?:300|400)$/u.test(entry.args?.[1] ?? "")),
).toEqual([
{ event: "gh", args: ["api", `repos/${REPOSITORY}/actions/runs/300`] },
{ event: "gh", args: ["api", `repos/${REPOSITORY}/actions/runs/400`] },
]);
expect(readFileSync(fixture.env.GITHUB_OUTPUT, "utf8")).toContain(
`request=${JSON.stringify(request)}\n`,
);
});
it.each([
["schema", { schema: "different" }],
@ -1893,9 +1889,6 @@ describe("release preparation recovery", () => {
it.each([
["workflow", { path: ".github/workflows/openclaw-release-publish.yml" }],
["event", { event: "push" }],
["protected ref", { head_branch: "main" }],
["tooling SHA", { head_sha: "c".repeat(40) }],
["attempt", { run_attempt: 0 }],
])("rejects a recovered producer with the wrong %s", async (_label, preparationProducer) => {
const fixture = await preparationFixture({ preparationProducer });
@ -1952,7 +1945,6 @@ process.exitCode = 1;
] as const)(
"preserves sealed inputs with resume override %s (sealed=%s)",
(resumeRunId, sealedResumeRunId) => {
const fixture = finalizationFixture();
const ready = readyRelease();
ready.inputs = validateReleaseButtonInputs(
inputs({
@ -1967,38 +1959,9 @@ process.exitCode = 1;
...(sealedResumeRunId ? { openclaw_npm_resume_run_id: sealedResumeRunId } : {}),
}),
);
writeFixtureFile(
fixture.scripts,
"lib/actions-artifact-archive.mjs",
`
export { readBoundedRegularFile } from ${JSON.stringify(pathToFileURL(resolve("scripts/lib/actions-artifact-archive.mjs")).href)};
export async function downloadActionsArtifactArchive() { return { archiveBytes: Buffer.from('verified fixture archive') }; }
export function inspectActionsArtifactZipWithPolicy() {
return new Map([['release-ready.json', Buffer.from(${JSON.stringify(JSON.stringify(ready))})]]);
}
`,
);
const artifact = {
...descriptor("npm"),
workflowPath: ".github/workflows/openclaw-release-prepare.yml",
artifactName: readyArtifactName(SOURCE_SHA, 300, 1),
};
const workflow = parse(
readFileSync(".github/workflows/openclaw-release-promote.yml", "utf8"),
);
const dispatch = workflow.jobs.publish.steps.find(
(step: { id?: string }) => step.id === "dispatch",
);
const result = spawnSync("bash", ["-c", dispatch.run], {
cwd: dirname(fixture.scripts),
env: {
...fixture.env,
PREPARED_ARTIFACT: JSON.stringify(artifact),
OPENCLAW_NPM_RESUME_RUN_ID: resumeRunId,
},
encoding: "utf8",
timeout: 10_000,
});
const fixture = publicationFixture({}, ready);
const { workflow } = fixture;
const result = fixture.publish({ OPENCLAW_NPM_RESUME_RUN_ID: resumeRunId });
expect(result.status, result.stderr).toBe(0);
const outputs = Object.fromEntries(
readFileSync(fixture.env.GITHUB_OUTPUT, "utf8")
@ -2063,7 +2026,7 @@ process.exitCode = 1;
},
);
it.each(["0", "-1", "1.5", " 800", "9007199254740992"])(
it.each(["0", " 800", "9007199254740992"])(
"rejects malformed resume run %s without dispatching publication",
(resumeRunId) => {
const fixture = finalizationFixture();
@ -2133,7 +2096,6 @@ process.exitCode = 1;
it.each([
["v2026.9.2-beta.1", "beta", true],
["v2026.9.2", "beta", false],
["v2026.9.2", "latest", false],
] as const)(
"refuses parent activation of %s on %s with a mismatched draft classification",
@ -2168,12 +2130,10 @@ process.exitCode = 1;
describe("prepared Windows handoff", () => {
it.each([
["stable on beta", "v2026.9.2", "beta", "success", true, true, false, true],
["stable on latest", "v2026.9.2", "latest", "success", true, true, false, true],
["absent", "v2026.9.2", "beta", "success", false, false, false, false],
["incomplete", "v2026.9.2", "beta", "success", true, false, false, true],
["beta", "v2026.9.2-beta.1", "beta", "success", true, true, false, false],
["failed activation", "v2026.9.2", "beta", "failure", true, true, false, false],
["skipped activation", "v2026.9.2", "beta", "skipped", true, true, false, false],
["dispatch failed", "v2026.9.2", "beta", "success", true, true, true, true],
] as const)(
"uses the frozen optional selection after activation: %s",

View file

@ -127,8 +127,6 @@ describePosix("native merge outcome with real Git and supervised lock recovery",
it.each([
{ auto: false, mergeStateStatus: "CLEAN", route: "immediate" },
{ auto: true, mergeStateStatus: "CLEAN", route: "immediate" },
{ auto: true, mergeStateStatus: "BEHIND", route: "auto" },
{ auto: true, mergeStateStatus: "BLOCKED", route: "auto" },
{ auto: false, mergeStateStatus: "CLEAN", route: "immediate", statusFirst: true },
])(
@ -209,18 +207,14 @@ describePosix("native merge outcome with real Git and supervised lock recovery",
});
it.each([
"persistent UNKNOWN",
"persistent UNKNOWN mergeable",
"persistent UNKNOWN status",
"known mergeable reverts",
"known status reverts",
"known status changes",
"invalid metadata",
"API error",
"PR identity",
"head",
"base",
"closed",
"merged",
"draft",
"auto request",
@ -231,74 +225,31 @@ describePosix("native merge outcome with real Git and supervised lock recovery",
"known HAS_HOOKS",
"final UNKNOWN mergeable",
"final UNKNOWN status",
"final changed status",
])("stops initial settlement without dispatch on %s", (fault) => {
const f = fixture();
const next = f.state();
const { author: _author, headRefName: _headRefName, ...observedPr } = next.pr;
const step: (typeof next.observations)[number] = {};
switch (fault) {
case "invalid metadata":
step.invalid = true;
break;
case "API error":
step.unavailable = true;
break;
case "PR identity":
step.pr = { id: "other-pr" };
break;
case "head":
step.pr = { headRefOid: f.base };
break;
case "base":
step.pr = { baseRefName: "release" };
break;
case "closed":
step.pr = { state: "CLOSED" };
break;
case "merged":
step.main = f.commit(f.tree("after\n"), [f.base]);
step.pr = { state: "MERGED", mergeCommit: { oid: step.main } };
break;
case "draft":
step.pr = { isDraft: true };
break;
case "auto request":
step.pr = { autoMergeRequest: { mergeMethod: "SQUASH" } };
break;
case "queue policy":
step.pr = { isMergeQueueEnabled: true };
break;
case "queue membership":
step.pr = { isInMergeQueue: true };
break;
case "invalid receipt":
step.pr = { mergeCommit: { oid: f.head } };
break;
case "conflicting":
step.pr = { mergeable: "CONFLICTING", mergeStateStatus: "DIRTY" };
break;
case "known HAS_HOOKS":
step.pr = { mergeable: "MERGEABLE", mergeStateStatus: "HAS_HOOKS" };
break;
case "known mergeable reverts":
step.pr = { mergeable: "UNKNOWN" };
break;
case "known status reverts":
step.pr = { mergeStateStatus: "UNKNOWN" };
break;
case "known status changes":
step.pr = { mergeStateStatus: "BEHIND" };
break;
case "final UNKNOWN mergeable":
step.pr = { mergeable: "UNKNOWN" };
break;
case "final UNKNOWN status":
step.pr = { mergeStateStatus: "UNKNOWN" };
break;
case "final changed status":
step.pr = { mergeStateStatus: "BEHIND" };
break;
const steps: Record<string, (typeof next.observations)[number]> = {
"invalid metadata": { invalid: true },
"API error": { unavailable: true },
"PR identity": { pr: { id: "other-pr" } },
head: { pr: { headRefOid: f.base } },
draft: { pr: { isDraft: true } },
"auto request": { pr: { autoMergeRequest: { mergeMethod: "SQUASH" } } },
"queue policy": { pr: { isMergeQueueEnabled: true } },
"queue membership": { pr: { isInMergeQueue: true } },
"invalid receipt": { pr: { mergeCommit: { oid: f.head } } },
conflicting: { pr: { mergeable: "CONFLICTING", mergeStateStatus: "DIRTY" } },
"known HAS_HOOKS": { pr: { mergeable: "MERGEABLE", mergeStateStatus: "HAS_HOOKS" } },
"known mergeable reverts": { pr: { mergeable: "UNKNOWN" } },
"known status reverts": { pr: { mergeStateStatus: "UNKNOWN" } },
"final UNKNOWN mergeable": { pr: { mergeable: "UNKNOWN" } },
"final UNKNOWN status": { pr: { mergeStateStatus: "UNKNOWN" } },
};
const step = steps[fault] ?? {};
if (fault === "merged") {
step.main = f.commit(f.tree("after\n"), [f.base]);
step.pr = { state: "MERGED", mergeCommit: { oid: step.main } };
}
next.observations = [{ pr: unknownProjection }];
const persistent = fault.startsWith("persistent ");
@ -348,7 +299,7 @@ describePosix("native merge outcome with real Git and supervised lock recovery",
expect(run.output).toContain(
fault === "final UNKNOWN mergeable"
? 'mergeable: observed="UNKNOWN"; expected="MERGEABLE"'
: `mergeStateStatus: observed="${fault === "final UNKNOWN status" ? "UNKNOWN" : "BEHIND"}"; expected="CLEAN"`,
: 'mergeStateStatus: observed="UNKNOWN"; expected="CLEAN"',
);
for (const [label, expected] of [
["observation", { main: f.base, pr: observedPr, transport: "graphql" }],
@ -448,7 +399,7 @@ describePosix("native merge outcome with real Git and supervised lock recovery",
f.git(["merge-base", "--is-ancestor", f.head, f.record().landed]);
});
it.each(["review", "ready", "checks", "pending", "existing-auto", "auto-ineligible"])(
it.each(["review", "ready", "checks", "pending"])(
"keeps %s admission ahead of intent",
(gate) => {
const f = fixture();
@ -465,12 +416,6 @@ describePosix("native merge outcome with real Git and supervised lock recovery",
if (gate === "pending") {
next.gates = "pending";
}
if (gate === "existing-auto") {
next.pr.autoMergeRequest = { mergeMethod: "MERGE" };
}
if (gate === "auto-ineligible") {
next.pr.mergeStateStatus = "HAS_HOOKS";
}
f.save(next);
const run = f.run(true);
expect(run.status, run.output).toBe(1);
@ -499,39 +444,23 @@ describePosix("native merge outcome with real Git and supervised lock recovery",
expect(() => f.record()).toThrow();
});
it.each([
{
name: "removed",
reason: "completed review is missing.",
comments: [
it("revalidates removed review evidence immediately before intent", () => {
const f = fixture();
f.save({
...f.state(),
issueCommentsAfterFirst: [
{
id: 2,
body: "<!-- clawsweeper-pr-ack:opened item=123 -->",
user: { id: 274271284, login: "clawsweeper[bot]", type: "Bot" },
},
],
},
{
name: "malformed",
reason: "trusted review-version field values are invalid.",
comments: [
{
id: 2,
body: `<!-- clawsweeper-review-version item=123 reviewed_at=invalid sha=${"a".repeat(40)} source_revision=${"c".repeat(64)} lease_owner=github-run-2 lease_comment_id=2 v=1 -->
<!-- clawsweeper-review item=123 -->`,
user: { id: 274271284, login: "clawsweeper[bot]", type: "Bot" },
},
],
},
])("revalidates $name review evidence immediately before intent", ({ comments, reason }) => {
const f = fixture();
f.save({ ...f.state(), issueCommentsAfterFirst: comments });
});
const run = f.run();
expect(run.status, run.output).toBe(1);
expect(run.output).toContain(`ClawSweeper review gate failed: ${reason}`);
expect(run.output).toContain("ClawSweeper review gate failed: completed review is missing.");
expect(f.state().issueCommentReads).toBe(2);
expect(f.state().mutations).toBe(0);
expect(() => f.record()).toThrow();

View file

@ -41,7 +41,6 @@ describePosix("native auto-merge recovery", () => {
{ mode: "pending", cancellation: "lost", absent: false },
{ mode: "pending-error", cancellation: "success", absent: false },
{ mode: "pending", cancellation: "success", absent: true },
{ mode: "pending-error", cancellation: "success", absent: true },
])(
"retires auto before recovering a reviewed replacement (submission=$mode, cancellation=$cancellation, absent=$absent)",
({ mode, cancellation, absent }) => {

View file

@ -28,7 +28,7 @@ describePosix("native merge outcome with real Git and supervised lock recovery",
expect(f.state().graphqlMergePayloads[0]?.expectedHeadOid).toBe(f.head);
});
it.each(["tamper", "head", "queue", "merge", "review"])(
it.each(["tamper", "head", "queue", "merge"])(
"keeps explicit body admission closed for %s",
(fault) => {
const f = fixture();
@ -44,9 +44,6 @@ describePosix("native merge outcome with real Git and supervised lock recovery",
if (fault === "queue") {
state.observations = [{ pr: { isMergeQueueEnabled: true } }];
}
if (fault === "review") {
state.ready = false;
}
f.save(state);
const result = f.run(false, f.repo, fault === "merge" ? "merge" : "squash", "", "", body);
expect(result.status, result.output).not.toBe(0);
@ -55,60 +52,18 @@ describePosix("native merge outcome with real Git and supervised lock recovery",
},
);
it.each([
{
route: "immediate",
access: "external",
auto: false,
admin: false,
queue: false,
mergeStateStatus: "CLEAN",
},
{
route: "auto",
access: "unknown",
auto: true,
admin: false,
queue: false,
mergeStateStatus: "BEHIND",
},
{
route: "queue",
access: "external",
auto: false,
admin: false,
queue: true,
mergeStateStatus: "CLEAN",
},
{
route: "admin",
access: "unknown",
auto: false,
admin: true,
queue: false,
mergeStateStatus: "BLOCKED",
},
])(
"blocks rewritten $access squash before $route intent",
({ access, auto, admin, queue, mergeStateStatus }) => {
const f = fixture();
f.setPrivacyProvenance("true", access);
f.save({
...f.state(),
admin,
gates: admin ? "fail" : "pass",
pr: { ...f.state().pr, isMergeQueueEnabled: queue, mergeStateStatus },
});
it.each(["external", "unknown"])("blocks rewritten %s squash before intent", (access) => {
const f = fixture();
f.setPrivacyProvenance("true", access);
const run = f.run(auto);
const run = f.run();
expect(run.status, run.output).toBe(1);
expect(run.output).toContain("maintainer-owned replacement PR");
expect(f.state().mutations).toBe(0);
expect(f.captures()).toEqual([]);
expect(() => f.record()).toThrow();
},
);
expect(run.status, run.output).toBe(1);
expect(run.output).toContain("maintainer-owned replacement PR");
expect(f.state().mutations).toBe(0);
expect(f.captures()).toEqual([]);
expect(() => f.record()).toThrow();
});
it.each([
{ rewrite: "true", access: "maintainer" },
@ -126,11 +81,6 @@ describePosix("native merge outcome with real Git and supervised lock recovery",
it.each([
["missing", "PREP_REPLACED_HOSTED_ANCESTRY=false\n"],
[
"malformed rewrite",
"PREP_REPLACED_HOSTED_ANCESTRY=false",
"PREP_REPLACED_HOSTED_ANCESTRY=yes",
],
["malformed access", "PREP_AUTHOR_ACCESS=external", "PREP_AUTHOR_ACCESS=write"],
])("requires prepare rerun for %s squash provenance", (_label, from, to = "") => {
const f = fixture();
@ -145,11 +95,11 @@ describePosix("native merge outcome with real Git and supervised lock recovery",
expect(() => f.record()).toThrow();
});
it.each(["merge", "rebase"])("leaves %s mechanics independent of squash provenance", (method) => {
it("leaves merge mechanics independent of squash provenance", () => {
const f = fixture();
f.setPrivacyProvenance(null, null);
const run = f.run(false, f.repo, method);
const run = f.run(false, f.repo, "merge");
expect(run.status, run.output).toBe(0);
expect(f.state().mutations).toBe(1);
@ -170,14 +120,9 @@ describePosix("native merge outcome with real Git and supervised lock recovery",
});
it.each([
{ auto: false, admin: false, mergeState: "CLEAN", route: "immediate" },
{ auto: false, admin: false, mergeState: "HAS_HOOKS", route: "immediate" },
{ auto: false, admin: false, mergeState: "UNSTABLE", route: "immediate" },
{ auto: false, admin: true, mergeState: "CLEAN", route: "admin" },
{ auto: false, admin: true, mergeState: "BLOCKED", route: "admin" },
{ auto: false, admin: true, mergeState: "BEHIND", route: "admin" },
{ auto: true, admin: false, mergeState: "BEHIND", route: "auto" },
{ auto: true, admin: false, mergeState: "BLOCKED", route: "auto" },
{ auto: true, admin: false, mergeState: "CLEAN", route: "immediate", pendingGates: true },
])(
"submits verified attribution with pinned head for %j",

View file

@ -63,13 +63,7 @@ function correctionFixture() {
}
describePosix("correction authority through native merge admission", () => {
it("merges an exactly reviewed and qualified correction while retaining original NEEDS WORK", () => {
const f = correctionFixture();
const result = f.run();
expect(result.status, result.output).toBe(0);
expect(f.state().mutations).toBe(1);
});
it.each(["correction-review.json", "prep-context.env", "gates.env", "prep.env", "pr-meta.env"])(
it.each(["correction-review.json", "prep-context.env", "gates.env"])(
"refuses changed %s after CI checks and before intent",
(artifact) => {
const f = correctionFixture();

View file

@ -101,7 +101,6 @@ describePosix("native merge outcome with real Git and supervised lock recovery",
});
it.each([
"empty",
"timeout",
"extra-capture",
"wrong-hash",
@ -109,10 +108,7 @@ describePosix("native merge outcome with real Git and supervised lock recovery",
"wrong-base",
"missing-proof",
"symlink",
"auto",
"queue",
"closed",
"pending",
"ci-proof",
"changed-capture",
"no-head",
@ -122,8 +118,8 @@ describePosix("native merge outcome with real Git and supervised lock recovery",
const approvedHead = f.replacePreparedHead();
const next = f.state();
const capture = join(f.worktree, ".local/merge-output.log");
if (fault === "empty" || fault === "timeout") {
const text = fault === "empty" ? "" : "502 after dispatch\n";
if (fault === "timeout") {
const text = "502 after dispatch\n";
writeFileSync(capture, text);
writeFileSync(join(legacy.directory, "merge-output.log"), text);
legacy.oid = f.git(["hash-object", "--no-filters", capture]);
@ -151,18 +147,9 @@ describePosix("native merge outcome with real Git and supervised lock recovery",
rmSync(capture);
symlinkSync(join(legacy.directory, "merge-output.log"), capture);
}
if (fault === "auto") {
next.pr.autoMergeRequest = { mergeMethod: "SQUASH" };
}
if (fault === "queue") {
next.pr.isMergeQueueEnabled = true;
}
if (fault === "closed") {
next.pr.state = "CLOSED";
}
if (fault === "pending") {
next.gates = "pending";
}
if (fault === "ci-proof") {
next.ciExit = 15;
}

View file

@ -45,10 +45,27 @@ const hash = (text: string) =>
execFileSync("git", ["hash-object", "--stdin"], { input: text, encoding: "utf8" }).trim();
const describePosix = process.platform === "win32" ? describe.skip : describe;
function refusalEvidence(contents: string, proof: unknown) {
const root = temps.make("pr-refusal-evidence-");
const directory = join(root, "evidence");
mkdirSync(directory);
mkdirSync(join(root, ".local"));
writeFileSync(join(root, ".local", capture), contents);
writeFileSync(join(directory, capture), contents);
writeFileSync(join(directory, "qualification.json"), JSON.stringify(proof));
return {
root,
directory,
run: (attempt = record) =>
spawnSync(node, [helper, directory, outcome, JSON.stringify(attempt)], {
cwd: root,
encoding: "utf8",
}),
};
}
describePosix("operator-qualified pre-dispatch evidence", () => {
it.each([
"approved",
"uninspected",
"diagnostics-not-requested",
"wrong-producer",
"wrong-command",
@ -57,20 +74,13 @@ describePosix("operator-qualified pre-dispatch evidence", () => {
"wrong-executable",
"missing-source",
"extra-source",
...Object.keys(policyTimeoutQualification.sourceSha256).map((path) => `source:${path}`),
"policy-denial",
"source:cmd/octopool/string_rewrites_pr.go",
"other-class",
"extra-text",
"extra-newline",
"missing-newline",
"diagnostic",
"child-started",
"response-headers",
])("bounds the inspected initial policy timeout: %s", (fault) => {
const root = temps.make("pr-policy-timeout-evidence-");
const directory = join(root, "evidence");
mkdirSync(directory);
mkdirSync(join(root, ".local"));
const sourceSha256: Record<string, string> = { ...policyTimeoutQualification.sourceSha256 };
if (fault.startsWith("source:")) {
sourceSha256[fault.slice("source:".length)] = "0".repeat(64);
@ -81,36 +91,19 @@ describePosix("operator-qualified pre-dispatch evidence", () => {
if (fault === "extra-source") {
sourceSha256["cmd/octopool/unknown.go"] = "0".repeat(64);
}
let contents = policyTimeoutCapture;
if (fault === "policy-denial") {
contents = refusal;
}
if (fault === "other-class") {
contents = contents.replace("class=timeout", "class=server_validation");
}
if (fault === "extra-text") {
contents += "mutation accepted\n";
}
if (fault === "extra-newline") {
contents += "\n";
}
if (fault === "missing-newline") {
contents = contents.trimEnd();
}
if (fault === "diagnostic" || fault === "child-started") {
contents +=
fault === "diagnostic"
? diagnostic
: diagnostic.replace("child_started=false", "child_started=true");
}
if (fault === "response-headers") {
contents = contents.replace(")\n", " http_status=504)\n");
}
const captures: Record<string, string> = {
"other-class": policyTimeoutCapture.replace("class=timeout", "class=server_validation"),
"extra-newline": policyTimeoutCapture + "\n",
"missing-newline": policyTimeoutCapture.trimEnd(),
diagnostic: policyTimeoutCapture + diagnostic,
"response-headers": policyTimeoutCapture.replace(")\n", " http_status=504)\n"),
};
const contents = captures[fault] ?? policyTimeoutCapture;
const proof = {
...policyTimeoutQualification,
outcome,
capture: hash(contents),
inspected: fault !== "uninspected",
inspected: true,
diagnosticsEnabled: fault !== "diagnostics-not-requested",
producer: fault === "wrong-producer" ? "gh" : policyTimeoutQualification.producer,
command: fault === "wrong-command" ? "pr view" : policyTimeoutQualification.command,
@ -121,38 +114,20 @@ describePosix("operator-qualified pre-dispatch evidence", () => {
fault === "wrong-executable" ? "a".repeat(64) : policyTimeoutQualification.executableSha256,
sourceSha256,
};
writeFileSync(join(root, ".local", capture), contents);
writeFileSync(join(directory, capture), contents);
writeFileSync(join(directory, "qualification.json"), JSON.stringify(proof));
const result = spawnSync(node, [helper, directory, outcome, JSON.stringify(record)], {
cwd: root,
encoding: "utf8",
});
if (fault === "approved") {
expect(result.status, result.stderr).toBe(0);
expect(JSON.parse(result.stdout)).toMatchObject({
kind: proof.kind,
capture,
files: { [capture]: proof.capture },
});
} else {
expect(result.status, result.stderr).toBe(1);
expect(result.stdout).toBe("");
}
const result = refusalEvidence(contents, proof).run();
expect(result.status, result.stderr).toBe(1);
expect(result.stdout).toBe("");
});
it.each([
"historical",
"historical-0.7.1",
"0.7.1-wrong-version",
"0.7.1-wrong-source",
"0.7.1-wrong-parser",
"0.7.1-subject",
"0.7.1-subject-equals",
"0.7.1-altered-stderr",
"diagnostic",
"generic-only",
"wrong-source",
"changed-capture",
"symlink",
"accepted",
@ -163,10 +138,6 @@ describePosix("operator-qualified pre-dispatch evidence", () => {
"extra-evidence",
"wrong-outcome",
])("qualifies only intact, inspected no-dispatch evidence: %s", (fault) => {
const root = temps.make("pr-refusal-evidence-");
const directory = join(root, "evidence");
mkdirSync(directory);
mkdirSync(join(root, ".local"));
const diagnostics = ["diagnostic", "started", "duplicate"].includes(fault);
const historical =
historicalRefusals[
@ -195,9 +166,6 @@ describePosix("operator-qualified pre-dispatch evidence", () => {
"--body-file",
".local/merge-body.fixture",
];
if (fault === "0.7.1-subject") {
args.push("--subject", "Fixture subject");
}
if (fault === "0.7.1-subject-equals") {
args.push("--subject=Fixture subject");
}
@ -212,16 +180,14 @@ describePosix("operator-qualified pre-dispatch evidence", () => {
version: fault === "0.7.1-wrong-version" ? "0.7.0" : historical.version,
sourceRevision:
fault === "0.7.1-wrong-source" ? "a".repeat(40) : historical.sourceRevision,
parserSha256:
fault === "wrong-source" || fault === "0.7.1-wrong-parser"
? "a".repeat(64)
: historical.parserSha256,
parserSha256: fault === "0.7.1-wrong-parser" ? "a".repeat(64) : historical.parserSha256,
args,
}),
};
writeFileSync(join(root, ".local", capture), contents);
writeFileSync(join(directory, capture), fault === "changed-capture" ? "changed\n" : contents);
writeFileSync(join(directory, "qualification.json"), JSON.stringify(proof));
const { root, directory, run } = refusalEvidence(contents, proof);
if (fault === "changed-capture") {
writeFileSync(join(directory, capture), "changed\n");
}
if (fault === "symlink") {
rmSync(join(directory, capture));
symlinkSync(join(root, ".local", capture), join(directory, capture));
@ -237,11 +203,8 @@ describePosix("operator-qualified pre-dispatch evidence", () => {
accepted: fault === "accepted",
route: fault === "queue" ? "queue" : record.route,
};
const result = spawnSync(node, [helper, directory, outcome, JSON.stringify(attempt)], {
cwd: root,
encoding: "utf8",
});
if (fault === "historical" || fault === "historical-0.7.1" || fault === "diagnostic") {
const result = run(attempt);
if (fault === "historical-0.7.1" || fault === "diagnostic") {
expect(result.status, result.stderr).toBe(0);
expect(JSON.parse(result.stdout)).toMatchObject({
kind: proof.kind,

View file

@ -9,24 +9,9 @@ import {
const { fixture, outcomeRef, describePosix } = createMergeOutcomeFixtureHarness();
const historicalRefusals = {
"0.6.10": {
kind: "octopool-0.6.10-auto-refusal",
version: "0.6.10",
sourceRevision: "00c442d8084ad26eb5a5003f7372170e75a20c8a",
parserSha256: "f6ff8cd7e59503f71f94fefd561b671193df11b3aac9ba0986a0dc3ba91ca32b",
},
"0.7.1": {
kind: "octopool-0.7.1-missing-subject-refusal",
version: "0.7.1",
sourceRevision: "7ab9b348c99a7be4fdc82c75cb06ebce44e0007e",
parserSha256: "b32cb960537f5ffa1336a7689674afba9b4a2485b05e449acd2684a251ff8970",
},
} as const;
function qualifiedAutoRefusal(
f: ReturnType<typeof fixture>,
version: keyof typeof historicalRefusals | "policy-timeout" = "0.6.10",
version: "0.6.10" | "policy-timeout" = "0.6.10",
) {
f.save({
...f.state(),
@ -49,7 +34,10 @@ function qualifiedAutoRefusal(
...(version === "policy-timeout"
? policyTimeoutQualification
: {
...historicalRefusals[version],
kind: "octopool-0.6.10-auto-refusal",
version,
sourceRevision: "00c442d8084ad26eb5a5003f7372170e75a20c8a",
parserSha256: "f6ff8cd7e59503f71f94fefd561b671193df11b3aac9ba0986a0dc3ba91ca32b",
args: [
"pr",
"merge",
@ -68,14 +56,18 @@ function qualifiedAutoRefusal(
writeFileSync(join(directory, "qualification.json"), JSON.stringify(qualification));
f.recover();
f.save({ ...f.state(), mode: "success", pr: { ...f.state().pr, mergeStateStatus: "CLEAN" } });
return { outcome, directory, capture, qualification };
return {
outcome,
capture,
qualification,
runRecovery: (replacement = "") =>
f.run(false, f.repo, "squash", outcome, replacement, "", "", "", false, directory),
};
}
describePosix("qualified pre-dispatch merge recovery", () => {
it.each([
{ version: "0.6.10", replaceHead: false },
{ version: "0.6.10", replaceHead: true },
{ version: "0.7.1", replaceHead: true },
{ version: "policy-timeout", replaceHead: true },
] as const)(
"recovers a qualified $version pre-dispatch refusal with retained evidence (replacement=$replaceHead)",
@ -89,18 +81,7 @@ describePosix("qualified pre-dispatch merge recovery", () => {
`PR_NUMBER=123\nGATES_MODE=github_pending\nHOSTED_GATES_TARGET_HEAD_SHA=${preparedHead}\n`,
);
f.save({ ...f.state(), requiredCheckName: "openclaw/ci-gate" });
const run = f.run(
false,
f.repo,
"squash",
proof.outcome,
replacement,
"",
"",
"",
false,
proof.directory,
);
const run = proof.runRecovery(replacement);
expect(run.status, run.output).toBe(0);
expect(f.state().mutations).toBe(1);
expect(f.record()).toMatchObject({
@ -125,18 +106,7 @@ describePosix("qualified pre-dispatch merge recovery", () => {
);
expect(f.run().status).toBe(0);
expect(f.state().mutations).toBe(1);
const replay = f.run(
false,
f.repo,
"squash",
proof.outcome,
replacement,
"",
"",
"",
false,
proof.directory,
);
const replay = proof.runRecovery(replacement);
expect(replay.status, replay.output).toBe(1);
expect(f.state().mutations).toBe(1);
},
@ -176,18 +146,7 @@ describePosix("qualified pre-dispatch merge recovery", () => {
next.duringChecks = { preparedHead: movedHead };
}
f.save(next);
const run = f.run(
false,
f.repo,
"squash",
proof.outcome,
"",
"",
"",
"",
false,
proof.directory,
);
const run = proof.runRecovery();
expect(run.error, run.output).toBeUndefined();
expect(run.status, run.output).toBe(1);
expect(f.state()).toMatchObject({ mutations: 0, posts: 0 });
@ -226,18 +185,7 @@ describePosix("qualified pre-dispatch merge recovery", () => {
next.pr.mergeStateStatus = "BEHIND";
}
f.save(next);
const run = f.run(
false,
f.repo,
"squash",
proof.outcome,
replacement,
"",
"",
"",
false,
proof.directory,
);
const run = proof.runRecovery(replacement);
expect(run.status, `${fault}: ${run.output}`).toBe(1);
expect(f.state().mutations).toBe(0);
expect(f.git(["rev-parse", outcomeRef])).toBe(proof.outcome);

View file

@ -128,25 +128,19 @@ describe("security review rollout", () => {
expect(result.requests).toEqual([`/repos/openclaw/openclaw/pulls/${rolloutNumber}`]);
});
it.each(["2026-09-19T12:00:00Z", "2026-09-20T00:00:00Z"])(
"enforces PRs created at or after rollout (%s), even on an old branch",
(createdAt) => {
const result = evaluate({ pullRequest: { ...pullRequest, created_at: createdAt } });
expect(result.mode).toBe("enforced");
expect(result.requests).toHaveLength(1);
},
);
it("enforces PRs created at rollout, even on an old branch", () => {
const result = evaluate({ pullRequest: { ...pullRequest, created_at: mergedAt } });
expect(result.mode).toBe("enforced");
expect(result.requests).toHaveLength(1);
});
it.each(["behind", "diverged"])(
"exempts an older PR whose head has not incorporated the rollout (%s)",
(status) => {
const result = evaluate({ comparison: { ...comparison, status } });
expect(result.mode).toBe("grandfathered");
expect(result.requests[1]).toBe(
`/repos/openclaw/openclaw/compare/${mergeCommit}...${head}?per_page=1&page=2`,
);
},
);
it("exempts an older PR whose head is behind the rollout", () => {
const result = evaluate({ comparison: { ...comparison, status: "behind" } });
expect(result.mode).toBe("grandfathered");
expect(result.requests[1]).toBe(
`/repos/openclaw/openclaw/compare/${mergeCommit}...${head}?per_page=1&page=2`,
);
});
it("enforces an older PR after a rebase or merge incorporates the rollout", () => {
const result = evaluate({
@ -181,12 +175,10 @@ describe("security review rollout", () => {
{ ...rollout, number: 1 },
{ ...rollout, base: { ref: "stable", repo: { full_name: "openclaw/openclaw" } } },
{ ...rollout, base: { ref: "main", repo: { full_name: "contributor/fork" } } },
{ ...rollout, merged: undefined },
{ ...rollout, merged: false },
{ ...rollout, state: "open" },
{ ...rollout, merged_at: null },
{ ...rollout, merged_at: "2026-02-30T12:00:00Z" },
{ ...rollout, merge_commit_sha: "main" },
])("does not grant an exemption for invalid rollout metadata (%j)", (value) => {
const result = evaluate({ rollout: value });
expect(result.status).toBe(1);
@ -216,12 +208,9 @@ describe("security review rollout", () => {
expect(result.error).toContain("Cannot determine security review rollout");
});
it.each(["rollout", "comparison"] as const)(
"does not convert a GitHub %s error into an exemption",
(apiError) => {
const result = evaluate({ apiError });
expect(result.status).toBe(1);
expect(result.error).toBe("GitHub unavailable");
},
);
it("does not convert a GitHub comparison error into an exemption", () => {
const result = evaluate({ apiError: "comparison" });
expect(result.status).toBe(1);
expect(result.error).toBe("GitHub unavailable");
});
});

View file

@ -63,6 +63,26 @@ const otherReview = {
creator: { login: "github-actions[bot]", type: "Bot" },
};
const lockfilePr = {
...pr,
changed_files: 1,
head: { ...pr.head, repo: { id: 1, full_name: "openclaw/openclaw" } },
};
const lockfileContents = {
type: "file",
encoding: "base64",
content: Buffer.from("lockfileVersion: '9.0'\n").toString("base64"),
};
const lockfileRoutes = {
[`GET ${pullPath}/files`]: [{ filename: "pnpm-lock.yaml", status: "modified" }],
[rolePath]: { role_name: "read" },
[`GET /repos/openclaw/openclaw/dependency-graph/compare/${pr.base.sha}...${head}`]: [],
[`GET /repos/openclaw/openclaw/compare/${pr.base.sha}...${head}`]: {
base_commit: { sha: pr.base.sha },
merge_base_commit: { sha: pr.base.sha },
},
};
function evaluate(routes: Record<string, unknown> = {}, mode = "enforce", deadline?: number) {
const root = tempDirs.make("security-review-");
const logPath = path.join(root, "requests.jsonl");
@ -148,20 +168,9 @@ function evaluate(routes: Record<string, unknown> = {}, mode = "enforce", deadli
}
describe("combined security review entry point", () => {
it("recovers a temporary HTTP 500 without failing the review", () => {
const result = evaluate({
[`GET ${pullPath}`]: { responses: [{ httpError: 500 }, pr] },
});
expect(result.status, result.stderr).toBe(0);
expect(result.waits).toEqual([1_000]);
expect(result.combined.at(-1)).toBe("success");
expect(result.reviews.filter((entry) => entry.body?.state === "success")).toHaveLength(2);
});
it.each([
{ route: `GET ${pullPath}`, response: pr, requestTimeout: "fetch" },
{ route: rolePath, response: { role_name: "maintain" }, requestTimeout: "body" },
{ route: jobsPath, response: jobs, requestTimeout: "fetch" },
])(
"restarts evaluation after a $requestTimeout deadline on $route",
({ route, response, requestTimeout }) => {
@ -213,27 +222,10 @@ describe("combined security review entry point", () => {
it("recovers a lockfile read deadline before submitting one cleanup commit", () => {
const result = evaluate(
{
[`GET ${pullPath}`]: {
...pr,
changed_files: 1,
head: { ...pr.head, repo: { id: 1, full_name: "openclaw/openclaw" } },
},
[`GET ${pullPath}/files`]: [{ filename: "pnpm-lock.yaml", status: "modified" }],
[rolePath]: { role_name: "read" },
[`GET /repos/openclaw/openclaw/dependency-graph/compare/${pr.base.sha}...${head}`]: [],
[`GET /repos/openclaw/openclaw/compare/${pr.base.sha}...${head}`]: {
base_commit: { sha: pr.base.sha },
merge_base_commit: { sha: pr.base.sha },
},
...lockfileRoutes,
[`GET ${pullPath}`]: lockfilePr,
[`GET /repos/openclaw/openclaw/contents/pnpm-lock.yaml?ref=${pr.base.sha}`]: {
responses: [
{ requestTimeout: "fetch" },
{
type: "file",
encoding: "base64",
content: Buffer.from("lockfileVersion: '9.0'\n").toString("base64"),
},
],
responses: [{ requestTimeout: "fetch" }, lockfileContents],
},
"POST /graphql": { data: { createCommitOnBranch: { commit: { oid: "e".repeat(40) } } } },
},
@ -268,8 +260,6 @@ describe("combined security review entry point", () => {
unavailable: true,
},
{ name: "unknown HTTP 403", response: { httpError: 403 }, unavailable: false },
{ name: "invalid credentials", response: { httpError: 401 }, unavailable: false },
{ name: "missing repository", response: { httpError: 404 }, unavailable: false },
{
name: "stale head",
response: { errors: [{ type: "STALE_DATA", message: "Expected branch head to match" }] },
@ -307,26 +297,15 @@ describe("combined security review entry point", () => {
const { response, unavailable } = scenario;
const baseReadDenied = "baseReadDenied" in scenario && scenario.baseReadDenied;
const routes = {
...lockfileRoutes,
[`GET ${pullPath}`]: {
...pr,
changed_files: 1,
...lockfilePr,
maintainer_can_modify: true,
head: { ...pr.head, repo: { id: 2, full_name: "contributor/openclaw" } },
},
[`GET ${pullPath}/files`]: [{ filename: "pnpm-lock.yaml", status: "modified" }],
[rolePath]: { role_name: "read" },
[`GET /repos/openclaw/openclaw/dependency-graph/compare/${pr.base.sha}...${head}`]: [],
[`GET /repos/openclaw/openclaw/compare/${pr.base.sha}...${head}`]: {
base_commit: { sha: pr.base.sha },
merge_base_commit: { sha: pr.base.sha },
},
[`GET /repos/openclaw/openclaw/contents/pnpm-lock.yaml`]: baseReadDenied
? response
: {
type: "file",
encoding: "base64",
content: Buffer.from("lockfileVersion: '9.0'\n").toString("base64"),
},
: lockfileContents,
"POST /graphql": response,
};
const cleanup = evaluate(routes, "autoscrub");
@ -357,34 +336,19 @@ describe("combined security review entry point", () => {
});
it("preserves a failed cleanup mutation when the PR then closes", () => {
const cleanupPr = {
...pr,
changed_files: 1,
head: { ...pr.head, repo: { id: 1, full_name: "openclaw/openclaw" } },
};
const result = evaluate(
{
...lockfileRoutes,
[`GET ${pullPath}`]: {
responses: [
cleanupPr,
cleanupPr,
cleanupPr,
cleanupPr,
{ ...cleanupPr, state: "closed" },
lockfilePr,
lockfilePr,
lockfilePr,
lockfilePr,
{ ...lockfilePr, state: "closed" },
],
},
[`GET ${pullPath}/files`]: [{ filename: "pnpm-lock.yaml", status: "modified" }],
[rolePath]: { role_name: "read" },
[`GET /repos/openclaw/openclaw/dependency-graph/compare/${pr.base.sha}...${head}`]: [],
[`GET /repos/openclaw/openclaw/compare/${pr.base.sha}...${head}`]: {
base_commit: { sha: pr.base.sha },
merge_base_commit: { sha: pr.base.sha },
},
[`GET /repos/openclaw/openclaw/contents/pnpm-lock.yaml`]: {
type: "file",
encoding: "base64",
content: Buffer.from("lockfileVersion: '9.0'\n").toString("base64"),
},
[`GET /repos/openclaw/openclaw/contents/pnpm-lock.yaml`]: lockfileContents,
"POST /graphql": { httpError: 500 },
},
"autoscrub",
@ -444,32 +408,6 @@ describe("combined security review entry point", () => {
expect(result.combined).not.toContain("success");
});
it("exhausts HTTP 500 retries without publishing approval", () => {
const result = evaluate({ [`GET ${pullPath}`]: { httpError: 500 } });
expect(result.status).toBe(1);
expect(result.waits).toEqual([1_000, 2_000, 4_000]);
expect(result.requests.filter((entry) => entry.path === pullPath)).toHaveLength(4);
expect(result.stderr).toContain(`GitHub API GET ${pullPath} failed: 500`);
expect(
result.requests.every((entry) => entry.method === "GET" || entry.method === "WAIT"),
).toBe(true);
});
it.each([
{ name: "partial file list", initialPr: pr, initialFiles: files.slice(0, 1) },
{ name: "stale file count", initialPr: { ...pr, changed_files: 3 }, initialFiles: files },
])("recovers a $name before evaluating either guard", ({ initialPr, initialFiles }) => {
const result = evaluate({
[`GET ${pullPath}`]: { responses: [initialPr, pr] },
[`GET ${pullPath}/files`]: { responses: [initialFiles, files] },
});
expect(result.status, result.stderr).toBe(0);
expect(result.waits).toEqual([30_000, 30_000]);
expect(result.requests.filter((entry) => entry.path === `${pullPath}/files`)).toHaveLength(2);
expect(result.combined.at(-1)).toBe("success");
expect(result.reviews.filter((entry) => entry.body?.state === "success")).toHaveLength(2);
});
it.each(["detect", "enforce"])(
"recovers diff data that takes longer than seven seconds to settle in %s mode",
(mode) => {
@ -724,22 +662,19 @@ describe("combined security review entry point", () => {
},
);
it.each([
{ httpError: 500 },
{ httpError: 502 },
{ httpError: 503 },
{ httpError: 504 },
{ transportError: "ECONNRESET" },
])("restarts evaluation after a transient status publication failure: %j", (failure) => {
const result = evaluate({ [statusPath]: { responses: [failure, {}] } });
expect(result.status, result.stderr).toBe(0);
expect(result.waits).toEqual([1_000]);
const afterWait = result.requests.slice(
result.requests.findIndex((entry) => entry.method === "WAIT") + 1,
);
expect(afterWait[0]).toMatchObject({ method: "GET", path: pullPath });
expect(result.combined.at(-1)).toBe("success");
});
it.each([{ httpError: 500 }, { transportError: "ECONNRESET" }])(
"restarts evaluation after a transient status publication failure: %j",
(failure) => {
const result = evaluate({ [statusPath]: { responses: [failure, {}] } });
expect(result.status, result.stderr).toBe(0);
expect(result.waits).toEqual([1_000]);
const afterWait = result.requests.slice(
result.requests.findIndex((entry) => entry.method === "WAIT") + 1,
);
expect(afterWait[0]).toMatchObject({ method: "GET", path: pullPath });
expect(result.combined.at(-1)).toBe("success");
},
);
it.each([
{ httpError: 429 },
@ -1129,29 +1064,14 @@ describe("combined security review entry point", () => {
it.each(["before cleanup", "before commit"])(
"preserves merged lockfiles %s and still reports their missing approval",
(phase) => {
const cleanupPr = {
...pr,
changed_files: 1,
head: { ...pr.head, repo: { id: 1, full_name: "openclaw/openclaw" } },
};
const merged = { ...cleanupPr, state: "closed", merged: true };
const merged = { ...lockfilePr, state: "closed", merged: true };
const routes = {
...lockfileRoutes,
[`GET ${pullPath}`]: {
responses:
phase === "before cleanup" ? [merged] : [cleanupPr, cleanupPr, cleanupPr, merged],
},
[`GET ${pullPath}/files`]: [{ filename: "pnpm-lock.yaml", status: "modified" }],
[rolePath]: { role_name: "read" },
[`GET /repos/openclaw/openclaw/dependency-graph/compare/${pr.base.sha}...${head}`]: [],
[`GET /repos/openclaw/openclaw/compare/${pr.base.sha}...${head}`]: {
base_commit: { sha: pr.base.sha },
merge_base_commit: { sha: pr.base.sha },
},
[`GET /repos/openclaw/openclaw/contents/pnpm-lock.yaml`]: {
type: "file",
encoding: "base64",
content: Buffer.from("lockfileVersion: '9.0'\n").toString("base64"),
phase === "before cleanup" ? [merged] : [lockfilePr, lockfilePr, lockfilePr, merged],
},
[`GET /repos/openclaw/openclaw/contents/pnpm-lock.yaml`]: lockfileContents,
};
const cleanup = evaluate(routes, "autoscrub");
expect(cleanup.status, cleanup.stderr).toBe(0);
@ -1216,15 +1136,12 @@ describe("combined security review entry point", () => {
expect(result.combined).not.toContain("success");
});
it.each([undefined, "", "main"])(
"does not treat an invalid live head %s as superseded",
(sha) => {
const result = evaluate({ [`GET ${pullPath}`]: { ...pr, head: { ...pr.head, sha } } });
expect(result.status).toBe(1);
expect(result.stdout).not.toContain("Superseded");
expect(result.requests.some((entry) => entry.method !== "GET")).toBe(false);
},
);
it.each([undefined, "main"])("does not treat an invalid live head %s as superseded", (sha) => {
const result = evaluate({ [`GET ${pullPath}`]: { ...pr, head: { ...pr.head, sha } } });
expect(result.status).toBe(1);
expect(result.stdout).not.toContain("Superseded");
expect(result.requests.some((entry) => entry.method !== "GET")).toBe(false);
});
it("accepts the existing exact-head CI fallback without a manual guard run", () => {
const fallback = {
@ -1329,7 +1246,6 @@ describe("combined security review entry point", () => {
it.each([
{ status: "completed", conclusion: "failure", expected: "failure" },
{ status: "completed", conclusion: "cancelled", expected: "failure" },
{ status: "in_progress", conclusion: null, expected: "pending" },
])(
"keeps newer substantive CI authoritative before a skipped PR workflow: $status/$conclusion",
@ -1401,7 +1317,6 @@ describe("combined security review entry point", () => {
});
it.each([
{ field: "status", value: undefined },
{ field: "status", value: "unknown" },
{ field: "run_attempt", value: undefined },
{ field: "run_attempt", value: 0 },
@ -1419,7 +1334,7 @@ describe("combined security review entry point", () => {
}
});
it.each([0, -1, undefined, "invalid"])(
it.each([0, undefined])(
"rejects malformed eligible run ID %s before selecting older successful CI",
(id) => {
const result = evaluate({
@ -1434,14 +1349,11 @@ describe("combined security review entry point", () => {
},
);
it.each(["failure", "cancelled", "skipped", "neutral"])(
"does not hide a %s CI gate",
(conclusion) => {
const result = evaluate({ [jobsPath]: { ...jobs, jobs: [{ ...jobs.jobs[0], conclusion }] } });
expect(result.status, result.stderr).toBe(0);
expect(result.combined).not.toContain("success");
},
);
it.each(["failure", "skipped"])("does not hide a %s CI gate", (conclusion) => {
const result = evaluate({ [jobsPath]: { ...jobs, jobs: [{ ...jobs.jobs[0], conclusion }] } });
expect(result.status, result.stderr).toBe(0);
expect(result.combined).not.toContain("success");
});
it("does not accept a previously successful CI attempt while a new attempt runs", () => {
const result = evaluate({
@ -1460,17 +1372,6 @@ describe("combined security review entry point", () => {
expect(result.combined).toEqual(["pending", "failure"]);
});
it("settles after CI completes without leaving either evaluation failed", () => {
const waiting = evaluate({
[runsPath]: { total_count: 1, workflow_runs: [{ ...run, status: "in_progress" }] },
});
const completed = evaluate();
expect(waiting.status, waiting.stderr).toBe(0);
expect(waiting.combined.at(-1)).toBe("pending");
expect(completed.status, completed.stderr).toBe(0);
expect(completed.combined.at(-1)).toBe("success");
});
it.each([
{ name: "CI API failure", routes: { [runsPath]: { httpError: 403 } } },
{ name: "invalid CI metadata", routes: { [runsPath]: { workflow_runs: null } } },

View file

@ -1,15 +1,6 @@
import { execFileSync, spawnSync } from "node:child_process";
import {
cpSync,
existsSync,
mkdirSync,
readFileSync,
realpathSync,
rmSync,
writeFileSync,
} from "node:fs";
import { cpSync, existsSync, mkdirSync, readFileSync, realpathSync, writeFileSync } from "node:fs";
import { dirname, join, resolve } from "node:path";
import { pathToFileURL } from "node:url";
import { runInNewContext } from "node:vm";
import ignore from "ignore";
import { afterEach, describe, expect, it } from "vitest";
@ -592,26 +583,6 @@ describe("security review workflow trust boundaries", () => {
expect(result.stderr.trim()).toBe(
"GitHub token, event, event name, and repository are required.",
);
rmSync(join(workspace, "scripts/lib/bounded-response.mjs"));
const entryUrl = pathToFileURL(join(workspace, "scripts/github", entry)).href;
const missingModule = spawnSync(
process.execPath,
[
"--input-type=module",
"--eval",
`await import(${JSON.stringify(entryUrl)}).catch(error => {
console.error(JSON.stringify({ code: error.code, message: error.message }));
process.exitCode = 1;
});`,
],
{ cwd: workspace, env: {}, encoding: "utf8" },
);
expect(missingModule.status).toBe(1);
expect(JSON.parse(missingModule.stderr)).toMatchObject({
code: "ERR_MODULE_NOT_FOUND",
message: expect.stringContaining("bounded-response.mjs"),
});
});
it.skipIf(process.platform === "win32")(
@ -695,16 +666,6 @@ for (const [name, target] of Object.entries(${JSON.stringify(packages)})) {
expect(loaded.stderr.trim()).toBe(
"GITHUB_TOKEN, GITHUB_EVENT_PATH, and GITHUB_REPOSITORY are required.",
);
rmSync(join(workspace, ".github/security-review-policy.yml"));
const missingPolicy = spawnSync(process.execPath, [probe], {
cwd: workspace,
env: {},
encoding: "utf8",
});
expect(missingPolicy.status).toBe(1);
expect(missingPolicy.stderr).toContain("ENOENT");
expect(missingPolicy.stderr).toContain("security-review-policy.yml");
},
);
@ -777,14 +738,11 @@ describe("security review ownership", () => {
it.each([
".github/CODEOWNERS",
"SECURITY.md",
".github/codeql/codeql-core-auth-secrets-critical-security.yml",
".github/codeql/openclaw-boundary/queries/managed-proxy-runtime-mutation.ql",
".github/workflows/codeql-macos-critical-security.yml",
".github/workflows/security-review.yml",
".github/security-review-policy.yml",
".github/actions/setup-security-review/action.yml",
".github/actions/setup-security-review/package.json",
".github/actions/setup-security-review/package-lock.json",
"scripts/github/security-review-policy.mjs",
"scripts/github/security-review-event.mjs",
"scripts/github/security-review.mjs",

View file

@ -29,7 +29,7 @@ describe("pinned pnpm cold bootstrap", () => {
for (const name of archives) {
fs.copyFileSync(path.join(f.registry, name), path.join(f.image, name));
}
for (const source of ["image", "store", "registry"]) {
for (const [index, source] of ["image", "store", "registry"].entries()) {
if (source === "store") {
for (const name of archives) {
fs.writeFileSync(path.join(f.image, name), "corrupt image");
@ -46,10 +46,13 @@ describe("pinned pnpm cold bootstrap", () => {
expect(
fs.readFileSync(path.join(root, "node_modules/@pnpm/exe.linux-x64/pnpm"), "utf8"),
).toBe("native-fixture\n");
expect(JSON.parse(fs.readFileSync(path.join(root, ".corepack"), "utf8")).hash).toBe(
f.spec.split("+")[1],
);
expect(JSON.parse(fs.readFileSync(path.join(root, ".corepack"), "utf8"))).toEqual({
locator: { name: "pnpm", reference: f.spec.slice(5) },
bin: { pnpm: "./bin/pnpm.mjs", pnpx: "./bin/pnpx.mjs" },
hash: f.spec.split("+")[1],
});
expect(fs.existsSync(f.calls)).toBe(source === "registry");
expect(fs.readdirSync(f.runner)).toHaveLength(index + 1);
for (const name of archives) {
expect(fs.readFileSync(path.join(f.store, "toolchain", name))).toEqual(
fs.readFileSync(path.join(f.registry, name)),
@ -151,27 +154,6 @@ describe("pinned pnpm cold bootstrap", () => {
});
});
it("downloads authenticated registry archives when both the store and image are empty", async ({
command,
}) => {
const f = createPnpmArchiveFixture(command);
const result = await f.run();
expect(result.status, result.stderr).toBe(0);
expect(result.stdout.trim()).not.toBe("");
const root = path.join(result.stdout.trim(), "v1/pnpm/12.5.1");
expect(fs.readFileSync(path.join(root, "pnpm"), "utf8")).toBe("wrapper-fixture\n");
expect(fs.readFileSync(path.join(root, "node_modules/@pnpm/exe.linux-x64/pnpm"), "utf8")).toBe(
"native-fixture\n",
);
expect(JSON.parse(fs.readFileSync(path.join(root, ".corepack"), "utf8"))).toEqual({
locator: { name: "pnpm", reference: f.spec.slice(5) },
bin: { pnpm: "./bin/pnpm.mjs", pnpx: "./bin/pnpx.mjs" },
hash: f.spec.split("+")[1],
});
expect(fs.readFileSync(f.calls, "utf8").trim().split("\n")).toHaveLength(2);
expect(fs.readdirSync(f.runner)).toHaveLength(1);
});
it("uses authenticated image bytes without making a network request", async ({ command }) => {
const f = createPnpmArchiveFixture(command);
for (const name of fs.readdirSync(f.registry)) {

View file

@ -192,8 +192,6 @@ describe.skipIf(process.platform === "win32")("iOS simulator preparation", () =>
{ env: { OPENCLAW_CI_SIMSLIM_BINARY: "/missing-simslim" } },
{ args: [] },
{ args: ["booted"] },
{ args: ["all"] },
{ args: [simulatorId, "extra"] },
])("rejects invalid admission before any tool call: %j", (options) => {
const { result, commands } = runFixture("ios-simulator-prepare.sh", options);
expect(result.status).not.toBe(0);

View file

@ -32,26 +32,6 @@ describe("SQLite reliability worker messages", () => {
vi.useRealTimers();
});
it("listens before sending, ignores other messages, and preserves other listeners", async () => {
const child = new ChildProcess();
const observed: unknown[] = [];
const observe = (message: unknown) => observed.push(message);
child.on("message", observe);
const ready = waitForReady(child, {
action: () => {
child.emit("message", "unrelated");
expect(child.listenerCount("message")).toBe(2);
child.emit("message", "ready");
},
});
await expect(ready).resolves.toBe("ready");
expect(observed).toEqual(["unrelated", "ready"]);
expect(child.listeners("message")).toEqual([observe]);
child.off("message", observe);
expectWaitCleanedUp(child);
});
it("rejects child errors without replacing the original error", async () => {
const child = new ChildProcess();
const error = new Error("IPC failed");
@ -75,25 +55,23 @@ describe("SQLite reliability worker messages", () => {
expectWaitCleanedUp(child);
});
it.each([30_000, 120_000])(
"honors a %i ms timeout and reads final diagnostics",
async (timeoutMs) => {
const child = new ChildProcess();
let stderr = "before";
const ready = waitForReady(child, {
timeoutMs,
timeoutMessage: () => `timeout: ${stderr}`,
});
const rejected = expect(ready).rejects.toThrow("timeout: last stderr");
await vi.advanceTimersByTimeAsync(timeoutMs - 1);
expect(child.listenerCount("message")).toBe(1);
stderr = "last stderr";
await vi.advanceTimersByTimeAsync(1);
it("honors the configured timeout and reads final diagnostics", async () => {
const timeoutMs = 120_000;
const child = new ChildProcess();
let stderr = "before";
const ready = waitForReady(child, {
timeoutMs,
timeoutMessage: () => `timeout: ${stderr}`,
});
const rejected = expect(ready).rejects.toThrow("timeout: last stderr");
await vi.advanceTimersByTimeAsync(timeoutMs - 1);
expect(child.listenerCount("message")).toBe(1);
stderr = "last stderr";
await vi.advanceTimersByTimeAsync(1);
await rejected;
expectWaitCleanedUp(child);
},
);
await rejected;
expectWaitCleanedUp(child);
});
it.each(["action", "matches"] as const)("cleans up when %s throws", async (source) => {
const child = new ChildProcess();
@ -123,19 +101,28 @@ describe("SQLite reliability worker messages", () => {
expectWaitCleanedUp(child);
});
it("returns the requested writer payload from a synchronous action reply", async () => {
it("waits for the synchronous writer reply while preserving other listeners", async () => {
const child = new ChildProcess();
const observed: unknown[] = [];
const observe = (message: unknown) => observed.push(message);
child.on("message", observe);
const payload = { kind: "result", batchesCommitted: 2, rowsCommitted: 16 };
const result = await waitForWriterMessage(
{ child, stderr: [], stopped: false },
"result",
() => {
child.emit("message", { kind: "ready" });
child.emit("message", { kind: "result", batchesCommitted: 2, rowsCommitted: 16 });
expect(child.listenerCount("message")).toBe(2);
child.emit("message", payload);
},
);
expect(result.batchesCommitted).toBe(2);
expect(result.rowsCommitted).toBe(16);
expect(result).toBe(payload);
expect(observed).toEqual([{ kind: "ready" }, payload]);
expect(child.listeners("message")).toEqual([observe]);
child.off("message", observe);
expectWaitCleanedUp(child);
});
});

View file

@ -16,20 +16,19 @@ const { createTempDir } = createScriptTestHarness();
const chunk = previousReleaseInventory.releases[0]!.chunks.find((entry) =>
/-[A-Za-z0-9_-]{8}\.m?js$/.test(entry.path),
)!;
const externalSources = [
{ kind: "named export", source: 'export { helper as NAME } from "external-package";' },
{ kind: "namespace export", source: 'export * as NAME from "external-package";' },
{
kind: "namespace import",
source: 'import * as NAME from "external-package"; export { NAME };',
},
];
const namedExport = {
kind: "named export",
source: 'export { helper as NAME } from "external-package";',
};
const namespaceImport = {
kind: "namespace import",
source: 'import * as NAME from "external-package"; export { NAME };',
};
it.each([
'import { safePath as helper } from "@openclaw/fs-safe/path"; export { helper };',
'import helper from "external-package"; export { helper };',
'export { safePath as helper } from "@openclaw/fs-safe/path";',
'import { basename as helper } from "node:path"; export { helper };',
'export * as helper from "external-package";',
'import * as helper from "external-package"; export { helper };',
])("keeps published updater bridges usable beside %s", async (external) => {
@ -51,7 +50,7 @@ it.each([
}
});
it.each(externalSources)(
it.each([namedExport, namespaceImport])(
"refuses a required updater implementation replaced with an external $kind",
({ source: template }) => {
const root = createTempDir("update-compat-required-external-");
@ -71,7 +70,10 @@ it.each(externalSources)(
},
);
it.each(externalSources)("refuses to omit a published external $kind", ({ source }) => {
it.each([
namedExport,
{ kind: "namespace export", source: 'export * as NAME from "external-package";' },
])("refuses to omit a published external $kind", ({ source }) => {
const root = createTempDir("update-compat-published-external-");
fs.mkdirSync(path.join(root, "dist"));
fs.writeFileSync(