@botbuddy/cli 1.5.0 → 1.5.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +3 -2
- package/src/botbuddy-release-repair.json +1 -0
- package/src/publish-equal.mjs +154 -6
- package/src/run.mjs +5 -1
- package/src/auth.test.mjs +0 -404
- package/src/discovery.test.mjs +0 -195
- package/src/docker-hygiene.test.mjs +0 -790
- package/src/locks.test.mjs +0 -60
- package/src/profile-bootstrap.test.mjs +0 -205
- package/src/publish-equal.test.mjs +0 -176
- package/src/publish-workflow.test.mjs +0 -122
- package/src/quiet-runner.test.mjs +0 -109
- package/src/run.test.mjs +0 -173
- package/src/stack.test.mjs +0 -756
- package/src/wait-profile.test.mjs +0 -30
- package/src/wait.test.mjs +0 -266
|
@@ -1,790 +0,0 @@
|
|
|
1
|
-
// BOT-1405 — safe, explicit-context OrbStack/Supabase hygiene.
|
|
2
|
-
import test from "node:test";
|
|
3
|
-
import assert from "node:assert/strict";
|
|
4
|
-
import { spawnSync } from "node:child_process";
|
|
5
|
-
import { fileURLToPath } from "node:url";
|
|
6
|
-
|
|
7
|
-
import {
|
|
8
|
-
DOCKER_HELP,
|
|
9
|
-
EXIT,
|
|
10
|
-
classifyInventory,
|
|
11
|
-
branchMatchesTicket,
|
|
12
|
-
evaluatePressure,
|
|
13
|
-
formatHumanReceipt,
|
|
14
|
-
parseDockerArgs,
|
|
15
|
-
projectMatchesTicket,
|
|
16
|
-
runDockerCommand,
|
|
17
|
-
runDockerWorkflow,
|
|
18
|
-
} from "./docker-hygiene.mjs";
|
|
19
|
-
|
|
20
|
-
const CLI_BIN = fileURLToPath(new URL("../bin/botbuddy.mjs", import.meta.url));
|
|
21
|
-
const OWNED_BRANCH = () => "jmo-codex/bot-1405-orbstack-hygiene";
|
|
22
|
-
const OWNED_LOCK = {
|
|
23
|
-
held: true,
|
|
24
|
-
host: "test-mac",
|
|
25
|
-
slot: "botbuddy-web-bot-1405",
|
|
26
|
-
resourceId: "00000000-0000-4000-8000-000000001405",
|
|
27
|
-
resourceName: "supabase_local:test-mac:botbuddy-web-bot-1405",
|
|
28
|
-
};
|
|
29
|
-
const QUEUED_LOCK_ID = "00000000-0000-4000-8000-000000001406";
|
|
30
|
-
const GRANTED_LOCK_RAW = `locked (resource: ${OWNED_LOCK.resourceName}) [resource_id: ${OWNED_LOCK.resourceId}] ⏳ Lock TTL 15m: expires_at 2026-08-28T18:00:00Z (~900s).`;
|
|
31
|
-
|
|
32
|
-
const ORBSTACK_CONTEXT = [{
|
|
33
|
-
Name: "orbstack",
|
|
34
|
-
Metadata: { Description: "OrbStack" },
|
|
35
|
-
Endpoints: { docker: { Host: "unix:///Users/test/.orbstack/run/docker.sock" } },
|
|
36
|
-
}];
|
|
37
|
-
|
|
38
|
-
const ORBSTACK_INFO = {
|
|
39
|
-
ID: "orb-server-id",
|
|
40
|
-
Name: "orbstack",
|
|
41
|
-
OperatingSystem: "OrbStack",
|
|
42
|
-
KernelVersion: "7.0.14-orbstack-test",
|
|
43
|
-
ServerVersion: "29.4.0",
|
|
44
|
-
};
|
|
45
|
-
|
|
46
|
-
function container(id, name, project, state, size = 0) {
|
|
47
|
-
return {
|
|
48
|
-
Id: id,
|
|
49
|
-
Name: `/${name}`,
|
|
50
|
-
Config: { Labels: project ? { "com.supabase.cli.project": project, "com.docker.compose.project": project } : {} },
|
|
51
|
-
State: {
|
|
52
|
-
Running: state === "running",
|
|
53
|
-
Paused: state === "paused",
|
|
54
|
-
Restarting: state === "restarting",
|
|
55
|
-
Status: state,
|
|
56
|
-
},
|
|
57
|
-
SizeRw: size,
|
|
58
|
-
};
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
function network(id, name, project, attached = {}, driver = "bridge") {
|
|
62
|
-
return {
|
|
63
|
-
Id: id,
|
|
64
|
-
Name: name,
|
|
65
|
-
Driver: driver,
|
|
66
|
-
Containers: attached,
|
|
67
|
-
Labels: project ? { "com.supabase.cli.project": project, "com.docker.compose.project": project } : {},
|
|
68
|
-
};
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
function baseState() {
|
|
72
|
-
return {
|
|
73
|
-
containers: [
|
|
74
|
-
container("c-stale", "supabase_db_stale", "botbuddy-web-bot-1405", "exited", 1536),
|
|
75
|
-
container("c-live-stopped", "supabase_vector_live", "live", "exited", 2048),
|
|
76
|
-
container("c-live", "supabase_db_live", "live", "running"),
|
|
77
|
-
container("c-starting", "supabase_db_starting", "starting", "created"),
|
|
78
|
-
container("c-starting-old", "supabase_vector_starting", "starting", "exited", 1024),
|
|
79
|
-
container("c-unlabelled", "supabase_db_unknown", null, "exited", 4096),
|
|
80
|
-
container("c-other", "unrelated", "other", "exited", 8192),
|
|
81
|
-
],
|
|
82
|
-
networks: [
|
|
83
|
-
network("builtin", "bridge", null),
|
|
84
|
-
network("n-stale", "supabase_network_stale", "botbuddy-web-bot-1405"),
|
|
85
|
-
network("n-live", "supabase_network_live", "live", { "c-live": { Name: "supabase_db_live" } }),
|
|
86
|
-
network("n-unlabelled", "supabase_network_unknown", null),
|
|
87
|
-
network("n-other", "other_empty", "other"),
|
|
88
|
-
],
|
|
89
|
-
};
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
function fakeDocker({ info = ORBSTACK_INFO, mutateBeforeRevalidation } = {}) {
|
|
93
|
-
const state = baseState();
|
|
94
|
-
const calls = [];
|
|
95
|
-
let inventoryReads = 0;
|
|
96
|
-
|
|
97
|
-
const run = (args) => {
|
|
98
|
-
calls.push([...args]);
|
|
99
|
-
const command = ["--context", "--host"].includes(args[0]) ? args.slice(2) : args;
|
|
100
|
-
|
|
101
|
-
if (command[0] === "context" && command[1] === "inspect") {
|
|
102
|
-
return { status: 0, stdout: JSON.stringify(ORBSTACK_CONTEXT), stderr: "" };
|
|
103
|
-
}
|
|
104
|
-
if (command[0] === "info") return { status: 0, stdout: JSON.stringify(info), stderr: "" };
|
|
105
|
-
if (command[0] === "container" && command[1] === "ls") {
|
|
106
|
-
inventoryReads += 1;
|
|
107
|
-
if (inventoryReads === 2 && mutateBeforeRevalidation) mutateBeforeRevalidation(state);
|
|
108
|
-
return { status: 0, stdout: state.containers.map((item) => item.Id).join("\n"), stderr: "" };
|
|
109
|
-
}
|
|
110
|
-
if (command[0] === "container" && command[1] === "inspect") {
|
|
111
|
-
const requested = new Set(command.slice(command.indexOf("--size") + 1));
|
|
112
|
-
return { status: 0, stdout: JSON.stringify(state.containers.filter((item) => requested.has(item.Id))), stderr: "" };
|
|
113
|
-
}
|
|
114
|
-
if (command[0] === "network" && command[1] === "ls") {
|
|
115
|
-
return { status: 0, stdout: state.networks.map((item) => item.Id).join("\n"), stderr: "" };
|
|
116
|
-
}
|
|
117
|
-
if (command[0] === "network" && command[1] === "inspect") {
|
|
118
|
-
const requested = new Set(command.slice(2));
|
|
119
|
-
return { status: 0, stdout: JSON.stringify(state.networks.filter((item) => requested.has(item.Id))), stderr: "" };
|
|
120
|
-
}
|
|
121
|
-
if (command[0] === "container" && command[1] === "rm") {
|
|
122
|
-
state.containers = state.containers.filter((item) => item.Id !== command[2]);
|
|
123
|
-
return { status: 0, stdout: `${command[2]}\n`, stderr: "" };
|
|
124
|
-
}
|
|
125
|
-
if (command[0] === "network" && command[1] === "rm") {
|
|
126
|
-
state.networks = state.networks.filter((item) => item.Id !== command[2]);
|
|
127
|
-
return { status: 0, stdout: `${command[2]}\n`, stderr: "" };
|
|
128
|
-
}
|
|
129
|
-
return { status: 125, stdout: "", stderr: `unexpected docker argv: ${args.join(" ")}` };
|
|
130
|
-
};
|
|
131
|
-
|
|
132
|
-
return { run, calls, state };
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
test("parseDockerArgs requires exactly one explicit context or endpoint", () => {
|
|
136
|
-
assert.match(parseDockerArgs(["hygiene"]).errors.join(" "), /--context.*--endpoint/i);
|
|
137
|
-
assert.match(
|
|
138
|
-
parseDockerArgs(["hygiene", "--context", "orbstack", "--endpoint", "unix:///tmp/docker.sock"]).errors.join(" "),
|
|
139
|
-
/exactly one/i,
|
|
140
|
-
);
|
|
141
|
-
assert.deepEqual(parseDockerArgs(["hygiene", "--context", "orbstack"]).errors, []);
|
|
142
|
-
});
|
|
143
|
-
|
|
144
|
-
test("missing explicit selector fails closed before Docker is called", () => {
|
|
145
|
-
const calls = [];
|
|
146
|
-
const result = runDockerWorkflow(["hygiene", "--json"], {
|
|
147
|
-
runDocker: (args) => {
|
|
148
|
-
calls.push(args);
|
|
149
|
-
return { status: 0, stdout: "", stderr: "" };
|
|
150
|
-
},
|
|
151
|
-
platform: "darwin",
|
|
152
|
-
});
|
|
153
|
-
|
|
154
|
-
assert.equal(result.exitCode, EXIT.INVALID);
|
|
155
|
-
assert.equal(result.receipt.outcome, "error");
|
|
156
|
-
assert.deepEqual(calls, []);
|
|
157
|
-
});
|
|
158
|
-
|
|
159
|
-
test("parseDockerArgs rejects invalid pressure boundaries and unknown options", () => {
|
|
160
|
-
assert.match(parseDockerArgs(["preflight", "--context", "orbstack", "--warn-pressure", "10", "--fail-pressure", "9"]).errors.join(" "), /fail-pressure.*greater/i);
|
|
161
|
-
assert.match(parseDockerArgs(["hygiene", "--context", "orbstack", "--bogus"]).errors.join(" "), /unknown option/i);
|
|
162
|
-
});
|
|
163
|
-
|
|
164
|
-
test("parseDockerArgs preserves a following flag when an option value is missing", () => {
|
|
165
|
-
const parsed = parseDockerArgs(["hygiene", "--context", "--json"]);
|
|
166
|
-
assert.equal(parsed.opts.json, true);
|
|
167
|
-
assert.match(parsed.errors.join(" "), /--context needs a value/i);
|
|
168
|
-
});
|
|
169
|
-
|
|
170
|
-
test("ticket authority matches only explicit project and worktree ticket tokens", () => {
|
|
171
|
-
assert.equal(projectMatchesTicket("botbuddy-web-bot-1405", "BOT-1405"), true);
|
|
172
|
-
assert.equal(projectMatchesTicket("bot1405", "BOT-1405"), true);
|
|
173
|
-
assert.equal(projectMatchesTicket("sgent3836", "ENT-3836"), true);
|
|
174
|
-
assert.equal(projectMatchesTicket("sgent38360", "ENT-3836"), false);
|
|
175
|
-
assert.equal(projectMatchesTicket("xymjddoobrfnsmawmtbc", "BOT-1405"), false);
|
|
176
|
-
assert.equal(branchMatchesTicket("jmo-codex/bot-1405-orbstack-hygiene", "BOT-1405"), true);
|
|
177
|
-
assert.equal(branchMatchesTicket("jmo-codex/bot-14050-other", "BOT-1405"), false);
|
|
178
|
-
});
|
|
179
|
-
|
|
180
|
-
test("classifyInventory discovers only inactive stopped Supabase containers and empty labelled bridge networks", () => {
|
|
181
|
-
const state = baseState();
|
|
182
|
-
const result = classifyInventory(state);
|
|
183
|
-
|
|
184
|
-
assert.deepEqual(result.active_projects, ["live", "starting"]);
|
|
185
|
-
assert.deepEqual(result.candidates.map((item) => [item.type, item.id, item.name, item.reclaimable_bytes]), [
|
|
186
|
-
["container", "c-stale", "supabase_db_stale", 1536],
|
|
187
|
-
["network", "n-stale", "supabase_network_stale", 0],
|
|
188
|
-
]);
|
|
189
|
-
assert.ok(result.skipped.some((item) => item.id === "c-live-stopped" && item.reason === "active_stack"));
|
|
190
|
-
assert.ok(result.skipped.some((item) => item.id === "c-starting-old" && item.reason === "active_stack"));
|
|
191
|
-
assert.ok(result.skipped.some((item) => item.id === "c-unlabelled" && item.reason === "missing_project_identity"));
|
|
192
|
-
assert.ok(result.skipped.some((item) => item.id === "n-live" && item.reason === "attached_containers"));
|
|
193
|
-
assert.ok(result.skipped.some((item) => item.id === "n-unlabelled" && item.reason === "missing_project_identity"));
|
|
194
|
-
assert.equal(result.skipped.some((item) => item.id === "builtin" || item.id === "n-other" || item.id === "c-other"), false);
|
|
195
|
-
});
|
|
196
|
-
|
|
197
|
-
test("dry-run inventories exact candidates but invokes no mutation command", () => {
|
|
198
|
-
const fake = fakeDocker();
|
|
199
|
-
const result = runDockerWorkflow(["hygiene", "--context", "orbstack", "--json"], {
|
|
200
|
-
runDocker: fake.run,
|
|
201
|
-
platform: "darwin",
|
|
202
|
-
now: () => new Date("2026-08-28T12:00:00.000Z"),
|
|
203
|
-
});
|
|
204
|
-
|
|
205
|
-
assert.equal(result.exitCode, EXIT.OK);
|
|
206
|
-
assert.equal(result.receipt.apply, false);
|
|
207
|
-
assert.deepEqual(result.receipt.candidates.map((item) => item.id), ["c-stale", "n-stale"]);
|
|
208
|
-
assert.deepEqual(result.receipt.deleted, []);
|
|
209
|
-
assert.equal(result.receipt.reclaimed_space_bytes, 0);
|
|
210
|
-
assert.equal(fake.calls.some((args) => args.includes("rm") || args.includes("prune")), false);
|
|
211
|
-
assert.ok(fake.calls.every((args) => args[0] === "--context" || args[0] === "context"));
|
|
212
|
-
});
|
|
213
|
-
|
|
214
|
-
test("discovery inspects large container inventories in bounded batches", () => {
|
|
215
|
-
const fake = fakeDocker();
|
|
216
|
-
for (let i = 0; i < 22; i += 1) {
|
|
217
|
-
fake.state.containers.push(container(`c-scale-${i}`, `supabase_db_scale_${i}`, `scale-${i}`, "running"));
|
|
218
|
-
}
|
|
219
|
-
const result = runDockerWorkflow(["hygiene", "--context", "orbstack", "--json"], {
|
|
220
|
-
runDocker: fake.run,
|
|
221
|
-
platform: "darwin",
|
|
222
|
-
});
|
|
223
|
-
|
|
224
|
-
assert.equal(result.exitCode, EXIT.OK);
|
|
225
|
-
const inspectCalls = fake.calls.filter((args) => args.includes("container") && args.includes("inspect"));
|
|
226
|
-
assert.ok(inspectCalls.length >= 3);
|
|
227
|
-
assert.ok(inspectCalls.every((args) => args.slice(args.indexOf("--size") + 1).length <= 10));
|
|
228
|
-
});
|
|
229
|
-
|
|
230
|
-
test("apply removes only revalidated IDs without force, prune, or volume flags", () => {
|
|
231
|
-
const fake = fakeDocker();
|
|
232
|
-
const result = runDockerWorkflow([
|
|
233
|
-
"hygiene", "--context", "orbstack", "--apply", "--ticket", "BOT-1405",
|
|
234
|
-
"--lock-slot", "botbuddy-web-bot-1405",
|
|
235
|
-
"--candidate-id", "c-stale", "--candidate-id", "n-stale", "--json",
|
|
236
|
-
], {
|
|
237
|
-
runDocker: fake.run,
|
|
238
|
-
resolveBranch: OWNED_BRANCH,
|
|
239
|
-
lockAuthority: OWNED_LOCK,
|
|
240
|
-
platform: "darwin",
|
|
241
|
-
});
|
|
242
|
-
|
|
243
|
-
assert.equal(result.exitCode, EXIT.OK);
|
|
244
|
-
assert.deepEqual(result.receipt.deleted.map((item) => [item.type, item.id]), [["container", "c-stale"], ["network", "n-stale"]]);
|
|
245
|
-
assert.equal(result.receipt.reclaimed_space_bytes, 1536);
|
|
246
|
-
|
|
247
|
-
const mutations = fake.calls.filter((args) => args.includes("rm"));
|
|
248
|
-
assert.deepEqual(mutations, [
|
|
249
|
-
["--context", "orbstack", "container", "rm", "c-stale"],
|
|
250
|
-
["--context", "orbstack", "network", "rm", "n-stale"],
|
|
251
|
-
]);
|
|
252
|
-
for (const args of fake.calls) {
|
|
253
|
-
assert.equal(args.some((arg) => arg === "prune" || arg === "--volumes" || arg === "-v" || arg === "--force"), false, args.join(" "));
|
|
254
|
-
assert.equal(args[0] === "volume" || args.includes("volume"), false, args.join(" "));
|
|
255
|
-
}
|
|
256
|
-
});
|
|
257
|
-
|
|
258
|
-
test("apply refuses without a ticket and exact reviewed candidate IDs before Docker is called", () => {
|
|
259
|
-
const calls = [];
|
|
260
|
-
const result = runDockerWorkflow(["hygiene", "--context", "orbstack", "--apply", "--json"], {
|
|
261
|
-
runDocker: (args) => {
|
|
262
|
-
calls.push(args);
|
|
263
|
-
return { status: 0, stdout: "", stderr: "" };
|
|
264
|
-
},
|
|
265
|
-
platform: "darwin",
|
|
266
|
-
});
|
|
267
|
-
|
|
268
|
-
assert.equal(result.exitCode, EXIT.INVALID);
|
|
269
|
-
assert.match(result.receipt.errors.join(" "), /--ticket/i);
|
|
270
|
-
assert.match(result.receipt.errors.join(" "), /--candidate-id/i);
|
|
271
|
-
assert.deepEqual(calls, []);
|
|
272
|
-
});
|
|
273
|
-
|
|
274
|
-
test("apply refuses when the current worktree branch does not own the ticket", () => {
|
|
275
|
-
const calls = [];
|
|
276
|
-
const result = runDockerWorkflow([
|
|
277
|
-
"hygiene", "--context", "orbstack", "--apply", "--ticket", "BOT-1405",
|
|
278
|
-
"--lock-slot", "botbuddy-web-bot-1405",
|
|
279
|
-
"--candidate-id", "c-stale", "--json",
|
|
280
|
-
], {
|
|
281
|
-
runDocker: (args) => {
|
|
282
|
-
calls.push(args);
|
|
283
|
-
return { status: 0, stdout: "", stderr: "" };
|
|
284
|
-
},
|
|
285
|
-
resolveBranch: () => "jmo-codex/bot-9999-other-work",
|
|
286
|
-
platform: "darwin",
|
|
287
|
-
});
|
|
288
|
-
|
|
289
|
-
assert.equal(result.exitCode, EXIT.INVALID);
|
|
290
|
-
assert.equal(result.receipt.outcome, "refused");
|
|
291
|
-
assert.match(result.receipt.errors.join(" "), /does not own BOT-1405/i);
|
|
292
|
-
assert.deepEqual(calls, []);
|
|
293
|
-
});
|
|
294
|
-
|
|
295
|
-
test("apply refuses before Docker inventory when no confirmed stack lock is present", () => {
|
|
296
|
-
const calls = [];
|
|
297
|
-
const result = runDockerWorkflow([
|
|
298
|
-
"hygiene", "--context", "orbstack", "--apply", "--ticket", "BOT-1405",
|
|
299
|
-
"--lock-slot", "botbuddy-web-bot-1405", "--candidate-id", "c-stale", "--json",
|
|
300
|
-
], {
|
|
301
|
-
runDocker: (args) => {
|
|
302
|
-
calls.push(args);
|
|
303
|
-
return { status: 0, stdout: "", stderr: "" };
|
|
304
|
-
},
|
|
305
|
-
resolveBranch: OWNED_BRANCH,
|
|
306
|
-
platform: "darwin",
|
|
307
|
-
});
|
|
308
|
-
|
|
309
|
-
assert.equal(result.exitCode, EXIT.DOCKER);
|
|
310
|
-
assert.equal(result.receipt.outcome, "refused");
|
|
311
|
-
assert.match(result.receipt.errors.join(" "), /confirmed BotBuddy supabase_local lock/i);
|
|
312
|
-
assert.deepEqual(calls, []);
|
|
313
|
-
});
|
|
314
|
-
|
|
315
|
-
test("runDockerCommand holds and releases the ticket stack lock around apply", async () => {
|
|
316
|
-
const fake = fakeDocker();
|
|
317
|
-
const events = [];
|
|
318
|
-
const callTool = async (tool, args) => {
|
|
319
|
-
events.push([tool, args]);
|
|
320
|
-
if (tool === "acquire_lock") {
|
|
321
|
-
return {
|
|
322
|
-
ok: true,
|
|
323
|
-
isError: false,
|
|
324
|
-
data: { raw: GRANTED_LOCK_RAW },
|
|
325
|
-
};
|
|
326
|
-
}
|
|
327
|
-
if (tool === "release_lock") return { ok: true, isError: false, data: { raw: "released" } };
|
|
328
|
-
throw new Error(`unexpected tool ${tool}`);
|
|
329
|
-
};
|
|
330
|
-
const result = await runDockerCommand([
|
|
331
|
-
"hygiene", "--context", "orbstack", "--apply", "--ticket", "BOT-1405",
|
|
332
|
-
"--lock-slot", "botbuddy-web-bot-1405", "--candidate-id", "c-stale", "--json",
|
|
333
|
-
], {
|
|
334
|
-
callTool,
|
|
335
|
-
machineHost: "test-mac",
|
|
336
|
-
workflowOptions: { runDocker: fake.run, resolveBranch: OWNED_BRANCH, platform: "darwin" },
|
|
337
|
-
});
|
|
338
|
-
|
|
339
|
-
assert.equal(result.exitCode, EXIT.OK);
|
|
340
|
-
assert.deepEqual(events.map(([tool]) => tool), ["acquire_lock", "release_lock"]);
|
|
341
|
-
assert.deepEqual(events[0][1], {
|
|
342
|
-
resource_type: "container",
|
|
343
|
-
subtype: "supabase_local",
|
|
344
|
-
host: "test-mac",
|
|
345
|
-
slot: "botbuddy-web-bot-1405",
|
|
346
|
-
mode: "lock",
|
|
347
|
-
ticket_id: "BOT-1405",
|
|
348
|
-
no_pr_reason: "local OrbStack hygiene",
|
|
349
|
-
});
|
|
350
|
-
assert.deepEqual(events[1][1], { resource_id: OWNED_LOCK.resourceId });
|
|
351
|
-
assert.equal(result.receipt.authority.stack_lock.released, true);
|
|
352
|
-
});
|
|
353
|
-
|
|
354
|
-
test("runDockerCommand takes the shared project file lock before the BotBuddy lock and Docker", async () => {
|
|
355
|
-
const fake = fakeDocker();
|
|
356
|
-
const events = [];
|
|
357
|
-
const result = await runDockerCommand([
|
|
358
|
-
"hygiene", "--context", "orbstack", "--apply", "--ticket", "BOT-1405",
|
|
359
|
-
"--lock-slot", "botbuddy-web-bot-1405", "--candidate-id", "c-stale", "--json",
|
|
360
|
-
], {
|
|
361
|
-
resolveProjectId: () => "xymjddoobrfnsmawmtbc",
|
|
362
|
-
fileLockPathForProject: (projectId) => `/tmp/${projectId}.lock`,
|
|
363
|
-
acquireFileLock: async (path) => {
|
|
364
|
-
events.push(["file_acquire", path]);
|
|
365
|
-
return { path, release: () => events.push(["file_release", path]) };
|
|
366
|
-
},
|
|
367
|
-
callTool: async (tool) => {
|
|
368
|
-
events.push([tool]);
|
|
369
|
-
return tool === "acquire_lock"
|
|
370
|
-
? { ok: true, isError: false, data: { raw: GRANTED_LOCK_RAW } }
|
|
371
|
-
: { ok: true, isError: false, data: { raw: "released" } };
|
|
372
|
-
},
|
|
373
|
-
machineHost: "test-mac",
|
|
374
|
-
workflowOptions: {
|
|
375
|
-
runDocker: (args, options) => { events.push(["docker", args]); return fake.run(args, options); },
|
|
376
|
-
resolveBranch: OWNED_BRANCH,
|
|
377
|
-
platform: "darwin",
|
|
378
|
-
},
|
|
379
|
-
});
|
|
380
|
-
|
|
381
|
-
assert.equal(result.exitCode, EXIT.OK);
|
|
382
|
-
assert.deepEqual(events[0], ["file_acquire", "/tmp/xymjddoobrfnsmawmtbc.lock"]);
|
|
383
|
-
assert.equal(events[1][0], "acquire_lock");
|
|
384
|
-
assert.ok(events.findIndex(([kind]) => kind === "docker") > 1);
|
|
385
|
-
assert.ok(events.findIndex(([kind]) => kind === "release_lock") < events.findIndex(([kind]) => kind === "file_release"));
|
|
386
|
-
assert.deepEqual(result.receipt.authority.file_lock, {
|
|
387
|
-
project_id: "xymjddoobrfnsmawmtbc", path: "/tmp/xymjddoobrfnsmawmtbc.lock", held: false, released: true,
|
|
388
|
-
});
|
|
389
|
-
});
|
|
390
|
-
|
|
391
|
-
test("runDockerCommand refuses before BotBuddy or Docker when the project file lock cannot be acquired", async () => {
|
|
392
|
-
const calls = [];
|
|
393
|
-
const result = await runDockerCommand([
|
|
394
|
-
"hygiene", "--context", "orbstack", "--apply", "--ticket", "BOT-1405",
|
|
395
|
-
"--lock-slot", "botbuddy-web-bot-1405", "--candidate-id", "c-stale", "--json",
|
|
396
|
-
], {
|
|
397
|
-
resolveProjectId: () => "xymjddoobrfnsmawmtbc",
|
|
398
|
-
acquireFileLock: async () => { throw new Error("held by another worktree"); },
|
|
399
|
-
callTool: async (tool) => { calls.push(tool); throw new Error("must not call BotBuddy"); },
|
|
400
|
-
workflowOptions: { runDocker: (args) => { calls.push(args); throw new Error("must not call Docker"); } },
|
|
401
|
-
});
|
|
402
|
-
assert.equal(result.exitCode, EXIT.DOCKER);
|
|
403
|
-
assert.match(result.receipt.errors.join(" "), /file lock.*another worktree/i);
|
|
404
|
-
assert.deepEqual(calls, []);
|
|
405
|
-
});
|
|
406
|
-
|
|
407
|
-
test("runDockerCommand fails closed on a busy stack lock without Docker inventory", async () => {
|
|
408
|
-
const fake = fakeDocker();
|
|
409
|
-
const calls = [];
|
|
410
|
-
const result = await runDockerCommand([
|
|
411
|
-
"hygiene", "--context", "orbstack", "--apply", "--ticket", "BOT-1405",
|
|
412
|
-
"--lock-slot", "botbuddy-web-bot-1405", "--candidate-id", "c-stale", "--json",
|
|
413
|
-
], {
|
|
414
|
-
callTool: async (tool, args) => {
|
|
415
|
-
calls.push([tool, args]);
|
|
416
|
-
return tool === "acquire_lock"
|
|
417
|
-
? {
|
|
418
|
-
ok: true,
|
|
419
|
-
isError: true,
|
|
420
|
-
data: {
|
|
421
|
-
success: false,
|
|
422
|
-
queued: true,
|
|
423
|
-
message: "held by another session",
|
|
424
|
-
resource_id: OWNED_LOCK.resourceId,
|
|
425
|
-
resource_lock_id: QUEUED_LOCK_ID,
|
|
426
|
-
resource_name: OWNED_LOCK.resourceName,
|
|
427
|
-
},
|
|
428
|
-
}
|
|
429
|
-
: { ok: true, isError: false, data: { success: true, retired: true } };
|
|
430
|
-
},
|
|
431
|
-
machineHost: "test-mac",
|
|
432
|
-
workflowOptions: { runDocker: fake.run, resolveBranch: OWNED_BRANCH, platform: "darwin" },
|
|
433
|
-
});
|
|
434
|
-
|
|
435
|
-
assert.equal(result.exitCode, EXIT.DOCKER);
|
|
436
|
-
assert.equal(result.receipt.outcome, "refused");
|
|
437
|
-
assert.match(result.receipt.errors.join(" "), /held by another session/i);
|
|
438
|
-
assert.deepEqual(calls.map(([tool]) => tool), ["acquire_lock", "retire_queued_lock_attempt"]);
|
|
439
|
-
assert.deepEqual(calls[1][1], { resource_id: OWNED_LOCK.resourceId, resource_lock_id: QUEUED_LOCK_ID });
|
|
440
|
-
assert.deepEqual(result.receipt.authority.queued_lock_attempt, {
|
|
441
|
-
resource_id: OWNED_LOCK.resourceId,
|
|
442
|
-
resource_lock_id: QUEUED_LOCK_ID,
|
|
443
|
-
resource_name: OWNED_LOCK.resourceName,
|
|
444
|
-
retired: true,
|
|
445
|
-
already_absent: false,
|
|
446
|
-
});
|
|
447
|
-
assert.deepEqual(fake.calls, []);
|
|
448
|
-
});
|
|
449
|
-
|
|
450
|
-
test("runDockerCommand reports a failed queued-attempt retirement and still refuses Docker", async () => {
|
|
451
|
-
const fake = fakeDocker();
|
|
452
|
-
const result = await runDockerCommand([
|
|
453
|
-
"hygiene", "--context", "orbstack", "--apply", "--ticket", "BOT-1405",
|
|
454
|
-
"--lock-slot", "botbuddy-web-bot-1405", "--candidate-id", "c-stale", "--json",
|
|
455
|
-
], {
|
|
456
|
-
callTool: async (tool) => tool === "acquire_lock"
|
|
457
|
-
? {
|
|
458
|
-
ok: true,
|
|
459
|
-
isError: true,
|
|
460
|
-
data: { success: false, queued: true, message: "busy", resource_id: OWNED_LOCK.resourceId, resource_lock_id: QUEUED_LOCK_ID },
|
|
461
|
-
}
|
|
462
|
-
: { ok: false, isError: true, error: "relay unavailable" },
|
|
463
|
-
machineHost: "test-mac",
|
|
464
|
-
workflowOptions: { runDocker: fake.run, resolveBranch: OWNED_BRANCH, platform: "darwin" },
|
|
465
|
-
});
|
|
466
|
-
|
|
467
|
-
assert.equal(result.exitCode, EXIT.DOCKER);
|
|
468
|
-
assert.match(result.receipt.errors.join(" "), /retire queued lock attempt.*relay unavailable/i);
|
|
469
|
-
assert.equal(result.receipt.authority.queued_lock_attempt.retired, false);
|
|
470
|
-
assert.equal(result.receipt.authority.queued_lock_attempt.already_absent, false);
|
|
471
|
-
assert.deepEqual(fake.calls, []);
|
|
472
|
-
});
|
|
473
|
-
|
|
474
|
-
test("runDockerCommand refuses a reentrant already-held lock without releasing its owner", async () => {
|
|
475
|
-
const fake = fakeDocker();
|
|
476
|
-
const calls = [];
|
|
477
|
-
const result = await runDockerCommand([
|
|
478
|
-
"hygiene", "--context", "orbstack", "--apply", "--ticket", "BOT-1405",
|
|
479
|
-
"--lock-slot", "botbuddy-web-bot-1405", "--candidate-id", "c-stale", "--json",
|
|
480
|
-
], {
|
|
481
|
-
callTool: async (tool, args) => {
|
|
482
|
-
calls.push([tool, args]);
|
|
483
|
-
return {
|
|
484
|
-
ok: true,
|
|
485
|
-
isError: false,
|
|
486
|
-
data: {
|
|
487
|
-
raw: `Exclusive lock already held (resource: ${OWNED_LOCK.resourceName}) [resource_id: ${OWNED_LOCK.resourceId}]`,
|
|
488
|
-
},
|
|
489
|
-
};
|
|
490
|
-
},
|
|
491
|
-
machineHost: "test-mac",
|
|
492
|
-
workflowOptions: { runDocker: fake.run, resolveBranch: OWNED_BRANCH, platform: "darwin" },
|
|
493
|
-
});
|
|
494
|
-
|
|
495
|
-
assert.equal(result.exitCode, EXIT.DOCKER);
|
|
496
|
-
assert.equal(result.receipt.outcome, "refused");
|
|
497
|
-
assert.match(result.receipt.errors.join(" "), /already held/i);
|
|
498
|
-
assert.deepEqual(calls.map(([tool]) => tool), ["acquire_lock"]);
|
|
499
|
-
assert.deepEqual(fake.calls, []);
|
|
500
|
-
});
|
|
501
|
-
|
|
502
|
-
test("apply stops Docker work before the advertised lock budget can lapse", () => {
|
|
503
|
-
const fake = fakeDocker();
|
|
504
|
-
let elapsed = 0;
|
|
505
|
-
const timedRun = (args, options) => {
|
|
506
|
-
assert.ok(options.timeoutMs > 0 && options.timeoutMs <= 30_000);
|
|
507
|
-
elapsed += 10_000;
|
|
508
|
-
return fake.run(args);
|
|
509
|
-
};
|
|
510
|
-
const result = runDockerWorkflow([
|
|
511
|
-
"hygiene", "--context", "orbstack", "--apply", "--ticket", "BOT-1405",
|
|
512
|
-
"--lock-slot", "botbuddy-web-bot-1405", "--candidate-id", "c-stale", "--json",
|
|
513
|
-
], {
|
|
514
|
-
runDocker: timedRun,
|
|
515
|
-
resolveBranch: OWNED_BRANCH,
|
|
516
|
-
lockAuthority: OWNED_LOCK,
|
|
517
|
-
deadlineAt: 55_000,
|
|
518
|
-
monotonicNow: () => elapsed,
|
|
519
|
-
platform: "darwin",
|
|
520
|
-
});
|
|
521
|
-
|
|
522
|
-
assert.equal(result.exitCode, EXIT.DELETE_FAILED);
|
|
523
|
-
assert.match(result.receipt.errors.join(" "), /lock safety budget expired/i);
|
|
524
|
-
assert.equal(fake.calls.some((args) => args.includes("rm")), false);
|
|
525
|
-
});
|
|
526
|
-
|
|
527
|
-
test("apply preserves a successful deletion receipt when its Docker call crosses the lock budget", () => {
|
|
528
|
-
const fake = fakeDocker();
|
|
529
|
-
let elapsed = 0;
|
|
530
|
-
const timedRun = (args, options) => {
|
|
531
|
-
assert.ok(options.timeoutMs > 0 && options.timeoutMs <= 30_000);
|
|
532
|
-
const result = fake.run(args);
|
|
533
|
-
if (args.includes("rm")) elapsed = 1_001;
|
|
534
|
-
return result;
|
|
535
|
-
};
|
|
536
|
-
const result = runDockerWorkflow([
|
|
537
|
-
"hygiene", "--context", "orbstack", "--apply", "--ticket", "BOT-1405",
|
|
538
|
-
"--lock-slot", "botbuddy-web-bot-1405", "--candidate-id", "c-stale",
|
|
539
|
-
"--candidate-id", "n-stale", "--json",
|
|
540
|
-
], {
|
|
541
|
-
runDocker: timedRun,
|
|
542
|
-
resolveBranch: OWNED_BRANCH,
|
|
543
|
-
lockAuthority: OWNED_LOCK,
|
|
544
|
-
deadlineAt: 1_000,
|
|
545
|
-
monotonicNow: () => elapsed,
|
|
546
|
-
platform: "darwin",
|
|
547
|
-
});
|
|
548
|
-
|
|
549
|
-
assert.equal(result.exitCode, EXIT.DELETE_FAILED);
|
|
550
|
-
assert.equal(result.receipt.outcome, "partial_failure");
|
|
551
|
-
assert.deepEqual(result.receipt.deleted.map((item) => item.id), ["c-stale"]);
|
|
552
|
-
assert.equal(result.receipt.reclaimed_space_bytes, 1536);
|
|
553
|
-
assert.match(result.receipt.errors.join(" "), /budget expired after successful.*c-stale/i);
|
|
554
|
-
assert.equal(fake.calls.some((args) => args.includes("network") && args.includes("rm")), false);
|
|
555
|
-
});
|
|
556
|
-
|
|
557
|
-
test("runDockerCommand attempts fallback release when a grant has no identity", async () => {
|
|
558
|
-
const fake = fakeDocker();
|
|
559
|
-
const calls = [];
|
|
560
|
-
const result = await runDockerCommand([
|
|
561
|
-
"hygiene", "--context", "orbstack", "--apply", "--ticket", "BOT-1405",
|
|
562
|
-
"--lock-slot", "botbuddy-web-bot-1405", "--candidate-id", "c-stale", "--json",
|
|
563
|
-
], {
|
|
564
|
-
callTool: async (tool, args) => {
|
|
565
|
-
calls.push([tool, args]);
|
|
566
|
-
return tool === "acquire_lock"
|
|
567
|
-
? { ok: true, isError: false, data: { raw: "lock granted" } }
|
|
568
|
-
: { ok: true, isError: false, data: { raw: "released" } };
|
|
569
|
-
},
|
|
570
|
-
machineHost: "test-mac",
|
|
571
|
-
workflowOptions: { runDocker: fake.run, resolveBranch: OWNED_BRANCH, platform: "darwin" },
|
|
572
|
-
});
|
|
573
|
-
|
|
574
|
-
assert.equal(result.exitCode, EXIT.DOCKER);
|
|
575
|
-
assert.match(result.receipt.errors.join(" "), /without a releasable identity/i);
|
|
576
|
-
assert.deepEqual(calls.map(([tool]) => tool), ["acquire_lock", "release_lock"]);
|
|
577
|
-
assert.deepEqual(calls[1][1], { resource_name: "supabase_local:test-mac:botbuddy-web-bot-1405" });
|
|
578
|
-
assert.deepEqual(fake.calls, []);
|
|
579
|
-
});
|
|
580
|
-
|
|
581
|
-
test("runDockerCommand releases and refuses a grant without safe TTL metadata", async () => {
|
|
582
|
-
const fake = fakeDocker();
|
|
583
|
-
const calls = [];
|
|
584
|
-
const result = await runDockerCommand([
|
|
585
|
-
"hygiene", "--context", "orbstack", "--apply", "--ticket", "BOT-1405",
|
|
586
|
-
"--lock-slot", "botbuddy-web-bot-1405", "--candidate-id", "c-stale", "--json",
|
|
587
|
-
], {
|
|
588
|
-
callTool: async (tool, args) => {
|
|
589
|
-
calls.push([tool, args]);
|
|
590
|
-
return tool === "acquire_lock"
|
|
591
|
-
? { ok: true, isError: false, data: { raw: `locked (resource: ${OWNED_LOCK.resourceName}) [resource_id: ${OWNED_LOCK.resourceId}]` } }
|
|
592
|
-
: { ok: true, isError: false, data: { raw: "released" } };
|
|
593
|
-
},
|
|
594
|
-
machineHost: "test-mac",
|
|
595
|
-
workflowOptions: { runDocker: fake.run, resolveBranch: OWNED_BRANCH, platform: "darwin" },
|
|
596
|
-
});
|
|
597
|
-
|
|
598
|
-
assert.equal(result.exitCode, EXIT.DOCKER);
|
|
599
|
-
assert.match(result.receipt.errors.join(" "), /safe remaining TTL/i);
|
|
600
|
-
assert.deepEqual(calls.map(([tool]) => tool), ["acquire_lock", "release_lock"]);
|
|
601
|
-
assert.deepEqual(calls[1][1], { resource_id: OWNED_LOCK.resourceId });
|
|
602
|
-
assert.deepEqual(fake.calls, []);
|
|
603
|
-
});
|
|
604
|
-
|
|
605
|
-
test("runDockerCommand reports lock-release failure as non-zero after cleanup", async () => {
|
|
606
|
-
const fake = fakeDocker();
|
|
607
|
-
const result = await runDockerCommand([
|
|
608
|
-
"hygiene", "--context", "orbstack", "--apply", "--ticket", "BOT-1405",
|
|
609
|
-
"--lock-slot", "botbuddy-web-bot-1405", "--candidate-id", "c-stale", "--json",
|
|
610
|
-
], {
|
|
611
|
-
callTool: async (tool) => tool === "acquire_lock"
|
|
612
|
-
? { ok: true, isError: false, data: { raw: GRANTED_LOCK_RAW } }
|
|
613
|
-
: { ok: false, isError: true, error: "release unavailable" },
|
|
614
|
-
machineHost: "test-mac",
|
|
615
|
-
workflowOptions: { runDocker: fake.run, resolveBranch: OWNED_BRANCH, platform: "darwin" },
|
|
616
|
-
});
|
|
617
|
-
|
|
618
|
-
assert.equal(result.exitCode, EXIT.DELETE_FAILED);
|
|
619
|
-
assert.equal(result.receipt.outcome, "partial_failure");
|
|
620
|
-
assert.match(result.receipt.errors.join(" "), /release unavailable/i);
|
|
621
|
-
assert.equal(result.receipt.authority.stack_lock.held, true);
|
|
622
|
-
});
|
|
623
|
-
|
|
624
|
-
test("apply refuses candidate IDs outside the ticket-owned project without deleting anything", () => {
|
|
625
|
-
const fake = fakeDocker();
|
|
626
|
-
const result = runDockerWorkflow([
|
|
627
|
-
"hygiene", "--context", "orbstack", "--apply", "--ticket", "ENT-9999",
|
|
628
|
-
"--lock-slot", "supplyspark-ent-sgent9999",
|
|
629
|
-
"--candidate-id", "c-stale", "--json",
|
|
630
|
-
], {
|
|
631
|
-
runDocker: fake.run,
|
|
632
|
-
resolveBranch: () => "jmo-codex/ent-9999-hygiene",
|
|
633
|
-
lockAuthority: { ...OWNED_LOCK, slot: "supplyspark-ent-sgent9999" },
|
|
634
|
-
platform: "darwin",
|
|
635
|
-
});
|
|
636
|
-
|
|
637
|
-
assert.equal(result.exitCode, EXIT.INVALID);
|
|
638
|
-
assert.equal(result.receipt.outcome, "refused");
|
|
639
|
-
assert.match(result.receipt.errors.join(" "), /does not belong to ENT-9999/i);
|
|
640
|
-
assert.equal(fake.calls.some((args) => args.includes("rm")), false);
|
|
641
|
-
});
|
|
642
|
-
|
|
643
|
-
test("apply never adds a newly eligible or unselected candidate to the reviewed set", () => {
|
|
644
|
-
const fake = fakeDocker();
|
|
645
|
-
fake.state.containers.push(container("c-other-stale", "supabase_db_other", "botbuddy-web-bot-1405", "exited", 99));
|
|
646
|
-
const result = runDockerWorkflow([
|
|
647
|
-
"hygiene", "--context", "orbstack", "--apply", "--ticket", "BOT-1405",
|
|
648
|
-
"--lock-slot", "botbuddy-web-bot-1405",
|
|
649
|
-
"--candidate-id", "c-stale", "--json",
|
|
650
|
-
], { runDocker: fake.run, resolveBranch: OWNED_BRANCH, lockAuthority: OWNED_LOCK, platform: "darwin" });
|
|
651
|
-
|
|
652
|
-
assert.equal(result.exitCode, EXIT.OK);
|
|
653
|
-
assert.deepEqual(result.receipt.deleted.map((item) => item.id), ["c-stale"]);
|
|
654
|
-
assert.equal(fake.calls.some((args) => args.includes("rm") && args.includes("c-other-stale")), false);
|
|
655
|
-
assert.equal(fake.calls.some((args) => args.includes("rm") && args.includes("n-stale")), false);
|
|
656
|
-
});
|
|
657
|
-
|
|
658
|
-
test("apply refuses a candidate whose stack becomes active before deletion", () => {
|
|
659
|
-
const fake = fakeDocker({
|
|
660
|
-
mutateBeforeRevalidation: (state) => state.containers.push(container("c-new-live", "supabase_rest_stale", "botbuddy-web-bot-1405", "running")),
|
|
661
|
-
});
|
|
662
|
-
const result = runDockerWorkflow([
|
|
663
|
-
"hygiene", "--context", "orbstack", "--apply", "--ticket", "BOT-1405",
|
|
664
|
-
"--lock-slot", "botbuddy-web-bot-1405",
|
|
665
|
-
"--candidate-id", "c-stale", "--candidate-id", "n-stale", "--json",
|
|
666
|
-
], {
|
|
667
|
-
runDocker: fake.run,
|
|
668
|
-
resolveBranch: OWNED_BRANCH,
|
|
669
|
-
lockAuthority: OWNED_LOCK,
|
|
670
|
-
platform: "darwin",
|
|
671
|
-
});
|
|
672
|
-
|
|
673
|
-
assert.equal(result.receipt.deleted.some((item) => item.id === "c-stale"), false);
|
|
674
|
-
assert.equal(result.receipt.deleted.some((item) => item.id === "n-stale"), false);
|
|
675
|
-
assert.equal(result.exitCode, EXIT.DELETE_FAILED);
|
|
676
|
-
assert.equal(result.receipt.outcome, "refused");
|
|
677
|
-
assert.ok(result.receipt.skipped.some((item) => item.id === "c-stale" && item.reason === "active_stack"));
|
|
678
|
-
assert.ok(result.receipt.skipped.some((item) => item.id === "n-stale" && item.reason === "active_stack"));
|
|
679
|
-
assert.equal(fake.calls.some((args) => args.includes("rm") && args.includes("c-stale")), false);
|
|
680
|
-
assert.equal(fake.calls.some((args) => args.includes("rm") && args.includes("n-stale")), false);
|
|
681
|
-
});
|
|
682
|
-
|
|
683
|
-
test("malformed Docker inventory fails closed without mutation", () => {
|
|
684
|
-
const fake = fakeDocker();
|
|
685
|
-
const run = (args) => {
|
|
686
|
-
if (args.includes("container") && args.includes("inspect")) {
|
|
687
|
-
fake.calls.push([...args]);
|
|
688
|
-
return { status: 0, stdout: "not-json", stderr: "" };
|
|
689
|
-
}
|
|
690
|
-
return fake.run(args);
|
|
691
|
-
};
|
|
692
|
-
const result = runDockerWorkflow([
|
|
693
|
-
"hygiene", "--context", "orbstack", "--apply", "--ticket", "BOT-1405",
|
|
694
|
-
"--lock-slot", "botbuddy-web-bot-1405",
|
|
695
|
-
"--candidate-id", "c-stale", "--json",
|
|
696
|
-
], {
|
|
697
|
-
runDocker: run,
|
|
698
|
-
resolveBranch: OWNED_BRANCH,
|
|
699
|
-
lockAuthority: OWNED_LOCK,
|
|
700
|
-
platform: "darwin",
|
|
701
|
-
});
|
|
702
|
-
|
|
703
|
-
assert.equal(result.exitCode, EXIT.DOCKER);
|
|
704
|
-
assert.match(result.receipt.errors.join(" "), /malformed JSON/i);
|
|
705
|
-
assert.equal(fake.calls.some((args) => args.includes("rm") || args.includes("prune")), false);
|
|
706
|
-
});
|
|
707
|
-
|
|
708
|
-
test("wrong Docker server refuses before inventory or mutation", () => {
|
|
709
|
-
const fake = fakeDocker({ info: { ...ORBSTACK_INFO, OperatingSystem: "Docker Desktop", KernelVersion: "linuxkit" } });
|
|
710
|
-
const result = runDockerWorkflow([
|
|
711
|
-
"hygiene", "--context", "desktop-linux", "--apply", "--ticket", "BOT-1405",
|
|
712
|
-
"--lock-slot", "botbuddy-web-bot-1405",
|
|
713
|
-
"--candidate-id", "c-stale", "--json",
|
|
714
|
-
], {
|
|
715
|
-
runDocker: fake.run,
|
|
716
|
-
resolveBranch: OWNED_BRANCH,
|
|
717
|
-
lockAuthority: OWNED_LOCK,
|
|
718
|
-
platform: "darwin",
|
|
719
|
-
});
|
|
720
|
-
|
|
721
|
-
assert.equal(result.exitCode, EXIT.DOCKER);
|
|
722
|
-
assert.equal(result.receipt.outcome, "error");
|
|
723
|
-
assert.match(result.receipt.errors.join(" "), /not OrbStack/i);
|
|
724
|
-
assert.equal(fake.calls.some((args) => args.includes("container") || args.includes("network")), false);
|
|
725
|
-
});
|
|
726
|
-
|
|
727
|
-
test("endpoint mode pins every daemon call to the exact endpoint", () => {
|
|
728
|
-
const fake = fakeDocker();
|
|
729
|
-
const endpoint = "unix:///Users/test/.orbstack/run/docker.sock";
|
|
730
|
-
const result = runDockerWorkflow(["hygiene", "--endpoint", endpoint, "--json"], {
|
|
731
|
-
runDocker: fake.run,
|
|
732
|
-
platform: "darwin",
|
|
733
|
-
});
|
|
734
|
-
assert.equal(result.exitCode, EXIT.OK);
|
|
735
|
-
assert.ok(fake.calls.every((args) => args[0] === "--host" && args[1] === endpoint));
|
|
736
|
-
});
|
|
737
|
-
|
|
738
|
-
test("preflight boundary returns ok, warn, and fail without mutation", () => {
|
|
739
|
-
assert.equal(evaluatePressure({ bridgeNetworks: 2, attachedEndpoints: 1, projectedEndpoints: 1, warn: 8, fail: 10 }).status, "ok");
|
|
740
|
-
assert.equal(evaluatePressure({ bridgeNetworks: 2, attachedEndpoints: 1, projectedEndpoints: 1, warn: 7, fail: 10 }).status, "warn");
|
|
741
|
-
const failed = evaluatePressure({ bridgeNetworks: 2, attachedEndpoints: 1, projectedEndpoints: 1, warn: 6, fail: 7 });
|
|
742
|
-
assert.equal(failed.status, "fail");
|
|
743
|
-
assert.equal(failed.projected_pressure_units, 7);
|
|
744
|
-
|
|
745
|
-
const fake = fakeDocker();
|
|
746
|
-
const result = runDockerWorkflow(["preflight", "--context", "orbstack", "--projected-endpoints", "1", "--warn-pressure", "6", "--fail-pressure", "7", "--json"], {
|
|
747
|
-
runDocker: fake.run,
|
|
748
|
-
platform: "darwin",
|
|
749
|
-
});
|
|
750
|
-
assert.equal(result.exitCode, EXIT.PRESSURE);
|
|
751
|
-
assert.equal(result.receipt.pressure.status, "fail");
|
|
752
|
-
assert.equal(fake.calls.some((args) => args.includes("rm") || args.includes("prune")), false);
|
|
753
|
-
});
|
|
754
|
-
|
|
755
|
-
test("human receipt contains context, candidates, skips, reclaim, and recommendation", () => {
|
|
756
|
-
const fake = fakeDocker();
|
|
757
|
-
const { receipt } = runDockerWorkflow(["hygiene", "--context", "orbstack", "--ticket", "BOT-1405"], {
|
|
758
|
-
runDocker: fake.run,
|
|
759
|
-
platform: "darwin",
|
|
760
|
-
now: () => new Date("2026-08-28T12:00:00.000Z"),
|
|
761
|
-
});
|
|
762
|
-
const human = formatHumanReceipt(receipt);
|
|
763
|
-
for (const value of ["orbstack", "BOT-1405", "supabase_db_stale", "supabase_network_stale", "active_stack", "0 B", "--apply", "--candidate-id c-stale"]) {
|
|
764
|
-
assert.ok(human.includes(value), `human receipt missing ${value}`);
|
|
765
|
-
}
|
|
766
|
-
});
|
|
767
|
-
|
|
768
|
-
test("help documents dry-run, apply, explicit selectors, and no-volume guarantee", () => {
|
|
769
|
-
for (const value of ["docker hygiene", "docker preflight", "--context", "--endpoint", "--apply", "dry-run", "never removes volumes"]) {
|
|
770
|
-
assert.ok(DOCKER_HELP.toLowerCase().includes(value.toLowerCase()), `help missing ${value}`);
|
|
771
|
-
}
|
|
772
|
-
});
|
|
773
|
-
|
|
774
|
-
test("published CLI route exposes docker help and one-line JSON failures", () => {
|
|
775
|
-
const help = spawnSync(process.execPath, [CLI_BIN, "docker", "help"], { encoding: "utf8" });
|
|
776
|
-
assert.equal(help.status, 0, help.stderr);
|
|
777
|
-
assert.match(help.stdout, /botbuddy docker hygiene/);
|
|
778
|
-
|
|
779
|
-
const invalid = spawnSync(process.execPath, [CLI_BIN, "docker", "hygiene", "--json"], { encoding: "utf8" });
|
|
780
|
-
assert.equal(invalid.status, EXIT.INVALID);
|
|
781
|
-
assert.equal(invalid.stderr, "");
|
|
782
|
-
const lines = invalid.stdout.trim().split("\n");
|
|
783
|
-
assert.equal(lines.length, 1);
|
|
784
|
-
assert.equal(JSON.parse(lines[0]).outcome, "error");
|
|
785
|
-
|
|
786
|
-
const missingValue = spawnSync(process.execPath, [CLI_BIN, "docker", "hygiene", "--context", "--json"], { encoding: "utf8" });
|
|
787
|
-
assert.equal(missingValue.status, EXIT.INVALID);
|
|
788
|
-
assert.equal(missingValue.stdout.trim().split("\n").length, 1);
|
|
789
|
-
assert.match(JSON.parse(missingValue.stdout).errors.join(" "), /--context needs a value/i);
|
|
790
|
-
});
|