openclaw/test/scripts/plugin-release-git-lifecycle.test.ts
Peter Steinberger d457b2c611
fix(test): checkout fixture tests fail healthy runs on loaded macOS hosts (#162888)
* fix(test): checkout fixture budgets cut healthy runs on loaded hosts

On a loaded macOS host (load 55-83), ci-platform-checkout.test.ts and the
other checkout fixture consumers failed through three fixture-internal
wall-clock budgets, not through the workflow under test:

- the supervisor's mock-resolution preflight ran bash under its own 2 s
  timeout ("mock command resolution failed: spawnSync bash ETIMEDOUT");
- the POSIX process census shadowed its caller's operation deadline with a
  fresh 1 s budget for all singleton ps queries ("Fixture process census
  failed (ETIMEDOUT)"); the owner-ancestry walk had the same 1 s shape;
- the supervisor cut every run at 45 s ("fixture deadline exceeded"), nested
  under the helper's 50 s close race only because the helper could not see
  the Vitest test timeout.

Measured breakdown: a multi-attempt scenario serially starts 25-36 Node
actors plus the Python owner; at load ~72 Linux git-failure spent 19.4 s,
of which the fixture's own 82 ps calls were 1.6 s. The work scales with
scheduler latency and is the contract under test, so the fix is ownership
of the time bound, not less work.

The owning test's AbortSignal now bounds a supervised run.
withCiCheckoutFixture takes it, rejects its close join on abort, and asks
the live supervisor to cancel over IPC so its own stop() retires the
detached shell group and actors, keeping the SIGKILL fallback for a wedged
supervisor. The supervisor drops its 45 s watchdog; its operation deadline
becomes the existing 60 s actor lifetime ceiling, which only bounds orphans.
Census, preflight, and ancestry queries borrow that operation deadline, as
the Windows census witness already did.

Every runCiGitStep and withCiCheckoutFixture caller passes its test signal;
.each registrations that need the context move to .for, which is the only
form Vitest 5 hands the context to.

Regressions: the shared slow-witness preload now gives POSIX supervisors a
first native query that spends 1.1 s on their clock (fails main's fixture
7/7 with "census failed (unverified)"), and the outer-runner retention
proof gains a cancel fault proving the aborted supervisor retires its shell.

* fix(test): keep checkout signal bindings lint-clean
2026-10-01 20:42:36 +00:00

700 lines
23 KiB
TypeScript

import { expect, it } from "vitest";
import { runCiGitStep } from "./ci-git-owner.test-support.js";
// Plugin publication jobs run on Ubuntu. Native Job Object and POSIX owner
// behavior stays in the shared ci-platform-checkout suites.
const posixIt = it.skipIf(process.platform === "win32");
const sha = "a".repeat(40);
const workflowSha = "b".repeat(40);
const releaseTag = "v2026.8.1";
const alphaBranch = "tideclaw/alpha/2026-08-30-1200Z";
const packageDir = "extensions/fixture";
const packageJson = '{"name":"@openclaw/fixture","version":"2026.8.33"}\n';
type PluginMode =
| "clawhub-resolve"
| "clawhub-oidc"
| "clawhub-trust"
| "npm-resolve"
| "npm-trust"
| "npm-preflight-read"
| "npm-publish-read";
type RunOptions = Partial<Parameters<typeof runCiGitStep>[0]>;
const modes: Record<
PluginMode,
{ workflow: { file: string; job: string; step: string }; env: Record<string, string> }
> = {
"clawhub-resolve": {
workflow: {
file: ".github/workflows/plugin-clawhub-release.yml",
job: "preview_plugins_clawhub",
step: "Resolve checked-out ref",
},
env: { RELEASE_TAG: "", TARGET_REF: "" },
},
"clawhub-oidc": {
workflow: {
file: ".github/workflows/plugin-clawhub-release.yml",
job: "preview_plugins_clawhub",
step: "Validate OIDC source matches workflow ref",
},
env: {
DRY_RUN: "false",
RELEASE_PUBLISH_RUN_ATTEMPT: "",
RELEASE_PUBLISH_RUN_ID: "",
RELEASE_TAG: "",
TARGET_SHA: sha,
WORKFLOW_REF: "refs/heads/main",
WORKFLOW_SHA: sha,
},
},
"clawhub-trust": {
workflow: {
file: ".github/workflows/plugin-clawhub-release.yml",
job: "preview_plugins_clawhub",
step: "Validate ref is on a trusted publish branch",
},
env: { TRUSTED_PUBLISH_BRANCH: "main" },
},
"npm-resolve": {
workflow: {
file: ".github/workflows/plugin-npm-release.yml",
job: "preview_plugins_npm",
step: "Resolve checked-out ref",
},
env: {},
},
"npm-trust": {
workflow: {
file: ".github/workflows/plugin-npm-release.yml",
job: "preview_plugins_npm",
step: "Validate ref is on a trusted publish branch",
},
env: {
RELEASE_CANDIDATE_BRANCH: "",
NPM_DIST_TAG: "default",
PREFLIGHT_ONLY: "false",
// Ancestry cases isolate Git policy; tag admission is asserted with it enabled.
REQUIRE_NPM_PUBLISH_ENVIRONMENT: "false",
PREPARED_ARTIFACT: "",
PUBLISH_SCOPE: "selected",
RELEASE_PLUGINS: "",
RELEASE_PUBLISH_RUN_ATTEMPT: "",
RELEASE_PUBLISH_RUN_ID: "",
SOURCE_REF: sha,
TRUSTED_PUBLISHER_PREFLIGHT: "false",
WORKFLOW_REF: "refs/heads/main",
WORKFLOW_SHA: workflowSha,
},
},
"npm-preflight-read": {
workflow: {
file: ".github/workflows/plugin-npm-release.yml",
job: "verify_plugin_npm_preflight",
step: "Read exact npm preflight source package",
},
env: { EXTENSION_ID: "fixture", PACKAGE_DIR: packageDir, SOURCE_SHA: sha },
},
"npm-publish-read": {
workflow: {
file: ".github/workflows/plugin-npm-release.yml",
job: "publish_plugins_npm",
step: "Read exact npm publication source package",
},
env: { EXTENSION_ID: "fixture", PACKAGE_DIR: packageDir, TARGET_SHA: sha },
},
};
function pluginRun(signal: AbortSignal, mode: PluginMode, options: RunOptions = {}) {
const selected = modes[mode];
return runCiGitStep({
signal,
workflow: selected.workflow,
fetchResults: [],
...options,
env: { ...selected.env, ...options.env },
revisions: { HEAD: sha, ...options.revisions },
});
}
function gitCommands(report: Awaited<ReturnType<typeof pluginRun>>) {
return report.commands.filter(({ tool }) => tool === "git").map(({ args }) => args);
}
posixIt.for([
{
mode: "clawhub-resolve" as const,
commands: [
[
"fetch",
"--no-tags",
"origin",
"+refs/heads/main:refs/remotes/origin/main",
"+refs/heads/release/*:refs/remotes/origin/release/*",
],
["rev-parse", "HEAD"],
],
output: `sha=${sha}\n`,
},
{
mode: "npm-resolve" as const,
commands: [["rev-parse", "HEAD"]],
output: `sha=${sha}\n`,
},
{
mode: "clawhub-trust" as const,
commands: [["merge-base", "--is-ancestor", "HEAD", "origin/main"]],
output: "",
},
{
mode: "npm-trust" as const,
commands: [
[
"fetch",
"--no-tags",
"origin",
"+refs/heads/main:refs/remotes/origin/main",
"+refs/heads/release/*:refs/remotes/origin/release/*",
],
["merge-base", "--is-ancestor", "HEAD", "origin/main"],
],
output: "",
},
])(
"$mode drains every Git tree before success or output",
{ timeout: 55_000 },
async ({ commands, mode, output }, { signal }) => {
const report = await pluginRun(signal, mode);
expect(report.code, report.output).toBe(0);
expect(gitCommands(report)).toEqual(commands);
expect(report.githubOutput).toBe(output);
expect(report.readyAttempts).toHaveLength(commands.length);
if (output) {
expect(report.boundaries.some(({ name }) => name === "output")).toBe(true);
}
},
);
posixIt.for(["npm-preflight-read", "npm-publish-read"] as const)(
"%s preserves exact source package bytes before the next consumer",
{ timeout: 55_000 },
async (mode, { signal }) => {
const sourceRef = mode === "npm-preflight-read" ? "SOURCE_SHA" : "TARGET_SHA";
const report = await pluginRun(signal, mode, {
commandResults: {
[`show ${sha}:${packageDir}/package.json`]: { code: 0, output: packageJson },
},
});
expect(report.code, report.output).toBe(0);
expect(gitCommands(report)).toEqual([
["fetch", "--no-tags", "--depth=1", "--filter=blob:none", "origin", sha],
["show", `${sha}:${packageDir}/package.json`],
]);
expect(report.pluginSourcePackage).toBe(packageJson);
expect(modes[mode].env[sourceRef]).toBe(sha);
},
);
posixIt.for(["npm-preflight-read", "npm-publish-read"] as const)(
"%s rejects partial source package output after ordinary show failure",
{ timeout: 55_000 },
async (mode, { signal }) => {
const report = await pluginRun(signal, mode, {
commandResults: {
[`show ${sha}:${packageDir}/package.json`]: { code: 23, output: "{partial" },
},
});
expect(report.code, report.output).toBe(23);
expect(gitCommands(report).at(-1)?.[0]).toBe("show");
expect(report.pluginSourcePackage).toBe("");
},
);
posixIt.for(
(["npm-preflight-read", "npm-publish-read"] as const).flatMap((mode) =>
([23, 125, "hang"] as const).map((failure) => ({ failure, mode })),
),
)(
"$mode fetch failure $failure stops before source package readback",
{ timeout: 55_000 },
async ({ failure, mode }, { signal }) => {
const report = await pluginRun(signal, mode, { fetchResults: [failure] });
expect(report.code, report.output).toBe(failure === "hang" ? 124 : failure);
expect(gitCommands(report).at(-1)?.[0]).toBe("fetch");
expect(report.pluginSourcePackage).toBe("");
},
);
posixIt.for([1, 125, 143])(
"ClawHub resolves origin fallback after safely drained ordinary local probe failure %s",
{ timeout: 55_000 },
async (code, { signal }) => {
const report = await pluginRun(signal, "clawhub-resolve", {
env: { TARGET_REF: "release/fixture" },
commandResults: {
"rev-parse --verify --quiet release/fixture^{commit}": { code, output: "" },
},
revisions: { "origin/release/fixture^{commit}": sha },
});
expect(report.code, report.output).toBe(0);
expect(gitCommands(report)).toEqual([
[
"fetch",
"--no-tags",
"origin",
"+refs/heads/main:refs/remotes/origin/main",
"+refs/heads/release/*:refs/remotes/origin/release/*",
],
["rev-parse", "--verify", "--quiet", "release/fixture^{commit}"],
["rev-parse", "--verify", "--quiet", "origin/release/fixture^{commit}"],
["rev-parse", "origin/release/fixture^{commit}"],
["checkout", "--detach", sha],
["rev-parse", "HEAD"],
]);
expect(report.githubOutput).toBe(`sha=${sha}\n`);
},
);
posixIt(
"ClawHub release tags retain their second bounded fetch",
async ({ signal }) => {
const report = await pluginRun(signal, "clawhub-resolve", {
env: { RELEASE_TAG: releaseTag },
});
expect(report.code, report.output).toBe(0);
expect(report.fetches.map(({ args }) => args)).toEqual([
[
"fetch",
"--no-tags",
"origin",
"+refs/heads/main:refs/remotes/origin/main",
"+refs/heads/release/*:refs/remotes/origin/release/*",
],
["fetch", "--no-tags", "origin", `+refs/tags/${releaseTag}:refs/tags/${releaseTag}`],
]);
},
55_000,
);
posixIt(
"ClawHub protected tooling validates the exact peeled release target",
async ({ signal }) => {
const report = await pluginRun(signal, "clawhub-oidc", {
env: {
RELEASE_PUBLISH_RUN_ATTEMPT: "2",
RELEASE_PUBLISH_RUN_ID: "123",
RELEASE_TAG: releaseTag,
TARGET_SHA: sha,
WORKFLOW_REF: "refs/tags/release-publish/abcdef123456-2",
WORKFLOW_SHA: workflowSha,
},
revisions: { [`${releaseTag}^{commit}`]: sha },
});
expect(report.code, report.output).toBe(0);
expect(gitCommands(report)).toEqual([["rev-parse", `${releaseTag}^{commit}`]]);
expect(report.output).toContain(
"Protected release tooling is publishing the exact immutable release tag target.",
);
},
55_000,
);
posixIt.for([1, 125])(
"ClawHub protected tag ordinary lookup failure %s retains OIDC rejection",
{ timeout: 55_000 },
async (code, { signal }) => {
const report = await pluginRun(signal, "clawhub-oidc", {
env: {
RELEASE_PUBLISH_RUN_ATTEMPT: "2",
RELEASE_PUBLISH_RUN_ID: "123",
RELEASE_TAG: releaseTag,
TARGET_SHA: sha,
WORKFLOW_REF: "refs/tags/release-publish/abcdef123456-2",
WORKFLOW_SHA: workflowSha,
},
commandResults: { [`rev-parse ${releaseTag}^{commit}`]: { code } },
});
expect(report.code, report.output).toBe(1);
expect(report.output).toContain(
"Plugin ClawHub OIDC publish target is not bound to protected tooling and the exact release tag.",
);
},
);
posixIt.for(["clawhub-trust", "npm-trust"] as const)(
"%s accepts a release branch only after successful enumeration",
{ timeout: 55_000 },
async (mode, { signal }) => {
const report = await pluginRun(signal, mode, {
commandResults: {
"merge-base --is-ancestor HEAD origin/main": { code: 1 },
"for-each-ref --format=%(refname) refs/remotes/origin/release": {
code: 0,
output: "refs/remotes/origin/release/2026.8.1\n",
},
"merge-base --is-ancestor HEAD refs/remotes/origin/release/2026.8.1": { code: 0 },
},
});
expect(report.code, report.output).toBe(0);
expect(gitCommands(report).at(-1)).toEqual([
"merge-base",
"--is-ancestor",
"HEAD",
"refs/remotes/origin/release/2026.8.1",
]);
},
);
posixIt(
"npm-trust rejects a Tideclaw alpha publish after main and release misses",
async ({ signal }) => {
const report = await pluginRun(signal, "npm-trust", {
env: { WORKFLOW_REF: `refs/heads/${alphaBranch}` },
commandResults: {
"merge-base --is-ancestor HEAD origin/main": { code: 1 },
"for-each-ref --format=%(refname) refs/remotes/origin/release": { code: 0, output: "" },
},
});
expect(report.code, report.output).toBe(1);
expect(report.output).toContain(
"Plugin npm publishes must target a commit reachable from main or release/*.",
);
expect(report.fetches.some(({ args }) => args.join(" ").includes(alphaBranch))).toBe(false);
},
55_000,
);
posixIt(
"clawhub-trust rejects a retired Tideclaw alpha branch before ancestry admission",
async ({ signal }) => {
const report = await pluginRun(signal, "clawhub-trust", {
env: { TRUSTED_PUBLISH_BRANCH: alphaBranch },
commandResults: {
"merge-base --is-ancestor HEAD origin/main": { code: 1 },
"for-each-ref --format=%(refname) refs/remotes/origin/release": { code: 0, output: "" },
[`merge-base --is-ancestor HEAD refs/remotes/origin/${alphaBranch}`]: { code: 0 },
},
});
expect(report.code, report.output).toBe(1);
expect(report.output).toContain("Alpha releases are retired;");
expect(report.fetches).toEqual([]);
},
55_000,
);
posixIt.for(["refs/heads/extended-stable/2026.8.33", "refs/heads/main"])(
"npm extended-stable preflight retains exact-tip admission from %s",
{ timeout: 55_000 },
async (workflowRef, { signal }) => {
const branch = "extended-stable/2026.8.33";
const report = await pluginRun(signal, "npm-trust", {
env: {
PREFLIGHT_ONLY: "true",
NPM_DIST_TAG: "extended-stable",
PUBLISH_SCOPE: "all-publishable",
SOURCE_REF: sha,
WORKFLOW_REF: workflowRef,
},
revisions: {
[`${sha}^{commit}`]: sha,
[`refs/heads/${branch}`]: sha,
[`refs/remotes/origin/${branch}`]: sha,
},
});
expect(report.code, report.output).toBe(0);
expect(report.fetches.map(({ args }) => args)).toEqual([
["fetch", "--no-tags", "origin", `+refs/heads/${branch}:refs/remotes/origin/${branch}`],
]);
// Preflight adds its exact-source check before the extended-stable tip check.
expect(gitCommands(report).filter(([operation]) => operation === "rev-parse")).toHaveLength(6);
},
);
const candidateAdmissionCases: Array<{
name: string;
env: Record<string, string>;
commands: RunOptions["commandResults"];
code: number;
message: string;
}> = [
{ name: "qualified protected tooling", env: {}, commands: {}, code: 0, message: "" },
{
name: "wrong candidate month",
env: { RELEASE_CANDIDATE_BRANCH: "extended-stable/2026.7.33" },
commands: {},
code: 1,
message: "release_candidate_branch must be extended-stable/2026.8.33",
},
{
name: "mutable main tooling",
env: { WORKFLOW_REF: "refs/heads/main" },
commands: {},
code: 1,
message: "require a protected release-publish/<sha12>-<n> tooling tag",
},
{
name: "tooling outside main",
env: {},
commands: { [`merge-base --is-ancestor ${workflowSha} origin/main`]: { code: 1 } },
code: 1,
message: "workflow revision is not reachable from current main",
},
{
name: "candidate outside monthly branch",
env: {},
commands: {
"merge-base --is-ancestor HEAD refs/remotes/origin/extended-stable/2026.8.33": { code: 1 },
},
code: 1,
message: "target must be reachable from extended-stable/2026.8.33",
},
{
name: "tooling ancestry Git failure",
env: {},
commands: { [`merge-base --is-ancestor ${workflowSha} origin/main`]: { code: 23 } },
code: 23,
message: "",
},
{
name: "preflight candidate override",
env: { PREFLIGHT_ONLY: "true", REQUIRE_NPM_PUBLISH_ENVIRONMENT: "false" },
commands: {},
code: 1,
message: "preflight must not include release_candidate_branch",
},
{
name: "non-extended candidate override",
env: { NPM_DIST_TAG: "default" },
commands: {},
code: 1,
message: "release_candidate_branch is only valid for extended-stable publication",
},
];
posixIt.for(candidateAdmissionCases)(
"npm canonical candidate admission: $name",
{ timeout: 55_000 },
async ({ env, commands, code, message }, { signal }) => {
const report = await pluginRun(signal, "npm-trust", {
env: {
NPM_DIST_TAG: "extended-stable",
PUBLISH_SCOPE: "all-publishable",
RELEASE_CANDIDATE_BRANCH: "extended-stable/2026.8.33",
REQUIRE_NPM_PUBLISH_ENVIRONMENT: "true",
WORKFLOW_REF: `refs/tags/release-publish/${workflowSha.slice(0, 12)}-123`,
...env,
},
revisions: { [`${sha}^{commit}`]: sha },
commandResults: commands,
});
expect(report.code, report.output).toBe(code);
if (message) {
expect(report.output).toContain(message);
}
if (code === 0) {
expect(gitCommands(report).slice(-2)).toEqual([
["merge-base", "--is-ancestor", workflowSha, "origin/main"],
["merge-base", "--is-ancestor", "HEAD", "refs/remotes/origin/extended-stable/2026.8.33"],
]);
}
},
);
posixIt.for([
["moved canonical tip", "refs/heads/main", "c".repeat(40)],
["untrusted workflow branch", "refs/heads/topic", sha],
["same-name main tag", "refs/tags/main", sha],
] as const)(
"npm extended-stable preflight rejects %s",
{ timeout: 55_000 },
async ([_name, workflowRef, branchSha], { signal }) => {
const branch = "extended-stable/2026.8.33";
const report = await pluginRun(signal, "npm-trust", {
env: {
PREFLIGHT_ONLY: "true",
NPM_DIST_TAG: "extended-stable",
PUBLISH_SCOPE: "all-publishable",
SOURCE_REF: sha,
WORKFLOW_REF: workflowRef,
},
revisions: {
[`${sha}^{commit}`]: sha,
[`refs/remotes/origin/${branch}`]: branchSha,
},
});
expect(report.code, report.output).toBe(1);
expect(report.output).toContain("Extended-stable plugin");
},
);
posixIt(
"npm preflight rejects before Tideclaw fallback after main and release misses",
async ({ signal }) => {
const report = await pluginRun(signal, "npm-trust", {
env: { PREFLIGHT_ONLY: "true", SOURCE_REF: sha, WORKFLOW_REF: `refs/heads/${alphaBranch}` },
revisions: { [`${sha}^{commit}`]: sha },
commandResults: {
"merge-base --is-ancestor HEAD origin/main": { code: 1 },
"for-each-ref --format=%(refname) refs/remotes/origin/release": { code: 0, output: "" },
},
});
expect(report.code, report.output).toBe(1);
expect(report.output).toContain(
"Plugin npm preflight target must be reachable from main or release/*.",
);
expect(report.fetches).toHaveLength(1);
expect(report.output).not.toContain("matching Tideclaw alpha branch");
},
55_000,
);
posixIt.for(["clawhub-trust", "npm-trust"] as const)(
"%s treats merge-base errors other than ordinary 1 as terminal",
{ timeout: 55_000 },
async (mode, { signal }) => {
const report = await pluginRun(signal, mode, {
commandResults: { "merge-base --is-ancestor HEAD origin/main": { code: 23 } },
});
expect(report.code, report.output).toBe(23);
expect(gitCommands(report)).toHaveLength(mode === "npm-trust" ? 2 : 1);
expect(report.fetches).toHaveLength(mode === "npm-trust" ? 1 : 0);
},
);
posixIt.for(["clawhub-trust", "npm-trust"] as const)(
"%s treats release-ref enumeration failure as terminal",
{ timeout: 55_000 },
async (mode, { signal }) => {
const report = await pluginRun(signal, mode, {
commandResults: {
"merge-base --is-ancestor HEAD origin/main": { code: 1 },
"for-each-ref --format=%(refname) refs/remotes/origin/release": { code: 23 },
},
});
expect(report.code, report.output).toBe(23);
expect(gitCommands(report).at(-1)?.[0]).toBe("for-each-ref");
expect(report.fetches).toHaveLength(mode === "npm-trust" ? 1 : 0);
},
);
const terminalCases: Array<{
commandResults?: RunOptions["commandResults"];
env?: Record<string, string>;
match: string;
mode: PluginMode;
operation: string;
revisions?: Record<string, string>;
}> = [
{ mode: "clawhub-resolve" as const, operation: "fetch", match: "^fetch " },
{
mode: "clawhub-resolve" as const,
operation: "rev-parse",
match: "^rev-parse --verify --quiet ",
env: { TARGET_REF: "release/fixture" },
revisions: { "origin/release/fixture^{commit}": sha },
},
{
mode: "clawhub-resolve" as const,
operation: "checkout",
match: "^checkout ",
env: { TARGET_REF: sha },
revisions: { [`${sha}^{commit}`]: sha },
},
{
mode: "clawhub-oidc" as const,
operation: "rev-parse",
match: "^rev-parse ",
env: {
RELEASE_PUBLISH_RUN_ATTEMPT: "1",
RELEASE_PUBLISH_RUN_ID: "1",
RELEASE_TAG: releaseTag,
TARGET_SHA: sha,
WORKFLOW_REF: "refs/tags/release-publish/abcdef123456-1",
WORKFLOW_SHA: workflowSha,
},
},
{
mode: "clawhub-trust" as const,
operation: "merge-base",
match: "^merge-base ",
},
{
mode: "clawhub-trust" as const,
operation: "for-each-ref",
match: "^for-each-ref ",
commandResults: { "merge-base --is-ancestor HEAD origin/main": { code: 1 } },
},
{ mode: "npm-resolve" as const, operation: "rev-parse", match: "^rev-parse " },
{ mode: "npm-trust" as const, operation: "fetch", match: "^fetch " },
{
mode: "npm-trust" as const,
operation: "merge-base",
match: "^merge-base ",
},
{
mode: "npm-trust" as const,
operation: "for-each-ref",
match: "^for-each-ref ",
commandResults: { "merge-base --is-ancestor HEAD origin/main": { code: 1 } },
},
{ mode: "npm-preflight-read" as const, operation: "fetch", match: "^fetch " },
{
mode: "npm-preflight-read" as const,
operation: "show",
match: "^show ",
commandResults: {
[`show ${sha}:${packageDir}/package.json`]: { code: 0, output: packageJson },
},
},
{ mode: "npm-publish-read" as const, operation: "fetch", match: "^fetch " },
{
mode: "npm-publish-read" as const,
operation: "show",
match: "^show ",
commandResults: {
[`show ${sha}:${packageDir}/package.json`]: { code: 0, output: packageJson },
},
},
];
posixIt.for(
terminalCases.flatMap((entry) =>
(["cleanup-failure", "cancel"] as const).map((failure) =>
Object.assign({}, entry, { failure }),
),
),
)(
"$mode $operation $failure fences every later Git/output/consumer boundary",
{ timeout: 55_000 },
async ({ commandResults, env, failure, match, mode, operation, revisions }, { signal }) => {
const report = await pluginRun(signal, mode, {
commandResults,
env,
revisions,
gitFault: { match, code: failure },
});
expect(report.code, report.output).toBe(failure === "cancel" ? 143 : 125);
expect(gitCommands(report).at(-1)?.[0]).toBe(operation);
expect(
gitCommands(report).filter((args) => new RegExp(match).test(args.join(" "))),
).toHaveLength(1);
expect(report.githubOutput).toBe("");
expect(report.commands.some(({ tool }) => ["node", "pnpm"].includes(tool))).toBe(false);
},
);
posixIt.for(
(["clawhub-resolve", "npm-resolve", "npm-preflight-read", "npm-publish-read"] as const).flatMap(
(mode) => (["owner", "python", "git"] as const).map((setupFailure) => ({ mode, setupFailure })),
),
)(
"$mode setup failure $setupFailure cannot publish or consume Git output",
{ timeout: 55_000 },
async ({ mode, setupFailure }, { signal }) => {
const report = await pluginRun(signal, mode, { setupFailure });
expect(report.code, report.output).not.toBe(0);
expect(report.githubOutput).toBe("");
expect(report.commands.some(({ tool }) => ["node", "pnpm"].includes(tool))).toBe(false);
},
);