This commit is contained in:
+151
-1
@@ -14,6 +14,8 @@ import { bindingKey, bindingSchema, installMultiWork } from "../src/multi-work.j
|
||||
|
||||
class FixtureHerdr extends Herdr {
|
||||
eligible = true;
|
||||
rootWorkspaceID = "w1";
|
||||
rootRepo = "/root/.git";
|
||||
creations = 0;
|
||||
verified = 0;
|
||||
delayCreation = false;
|
||||
@@ -24,7 +26,7 @@ class FixtureHerdr extends Herdr {
|
||||
creationStarted = Promise.withResolvers<void>();
|
||||
override async root(directory: string) {
|
||||
return this.eligible && directory === "/root"
|
||||
? { directory, workspaceID: "w1", repo: "/root/.git" }
|
||||
? { directory, workspaceID: this.rootWorkspaceID, repo: this.rootRepo }
|
||||
: undefined;
|
||||
}
|
||||
override async create(
|
||||
@@ -366,6 +368,128 @@ test("local fast-lo uses native delegation without a worktree", async () => {
|
||||
);
|
||||
});
|
||||
|
||||
test("coordinate can create and resume a root-local background child without Herdr creation or movement", async () => {
|
||||
const f = fixture();
|
||||
f.parent.permissions = [{ action: "herdr_worktree", resource: "*", effect: "deny" }];
|
||||
await Effect.runPromise(
|
||||
Effect.scoped(
|
||||
Effect.gen(function* () {
|
||||
yield* f.install;
|
||||
const created = yield* f.execute({
|
||||
agent: "coordinate",
|
||||
description: "local edit",
|
||||
prompt: "edit assigned files",
|
||||
background: true,
|
||||
});
|
||||
expect(created.metadata).toMatchObject({
|
||||
sessionID: "ses_child1",
|
||||
status: "running",
|
||||
directory: "/root",
|
||||
workspaceID: "w1",
|
||||
branch: "root",
|
||||
});
|
||||
expect(f.herdr.creations).toBe(0);
|
||||
expect(f.order).toEqual(["create", "prompt:/root"]);
|
||||
expect(bindingSchema.parse(f.storage.get(bindingKey("ses_child1")))).toMatchObject({
|
||||
owner: "ses_parent",
|
||||
directory: "/root",
|
||||
workspaceID: "w1",
|
||||
branch: "root",
|
||||
ready: true,
|
||||
});
|
||||
yield* f.execute({
|
||||
agent: "coordinate",
|
||||
description: "resume local",
|
||||
prompt: "continue",
|
||||
sessionID: "ses_child1",
|
||||
});
|
||||
expect(f.herdr.creations).toBe(0);
|
||||
expect(f.herdr.verified).toBe(0);
|
||||
}),
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
test("root-local resume rejects location, binding, and live-root changes", async () => {
|
||||
for (const tamper of ["session", "workspace", "root", "branch", "live-root"] as const) {
|
||||
const f = fixture();
|
||||
await Effect.runPromise(
|
||||
Effect.scoped(
|
||||
Effect.gen(function* () {
|
||||
yield* f.install;
|
||||
yield* f.execute({
|
||||
agent: "coordinate",
|
||||
description: "local edit",
|
||||
prompt: "work",
|
||||
});
|
||||
if (tamper === "session")
|
||||
f.sessions.get("ses_child1")!.location.directory = "/other";
|
||||
else if (tamper === "live-root") f.herdr.rootWorkspaceID = "foreign";
|
||||
else {
|
||||
const binding = bindingSchema.parse(
|
||||
f.storage.get(bindingKey("ses_child1")),
|
||||
);
|
||||
const changed = { ...binding };
|
||||
if (tamper === "workspace") changed.workspaceID = "foreign";
|
||||
if (tamper === "root") {
|
||||
changed.root = { ...binding.root, workspaceID: "foreign" };
|
||||
changed.workspaceID = "foreign";
|
||||
}
|
||||
if (tamper === "branch") changed.branch = "main";
|
||||
f.storage.set(bindingKey("ses_child1"), changed);
|
||||
}
|
||||
expect(
|
||||
(yield* Effect.exit(
|
||||
f.execute({
|
||||
agent: "coordinate",
|
||||
description: "resume",
|
||||
prompt: "continue",
|
||||
sessionID: "ses_child1",
|
||||
}),
|
||||
))._tag,
|
||||
).toBe("Failure");
|
||||
expect(f.prompts).toBe(1);
|
||||
}),
|
||||
),
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
test("interrupted root-local creation retains its binding and cannot be blindly replayed", async () => {
|
||||
const f = fixture();
|
||||
f.hold();
|
||||
const controller = new AbortController();
|
||||
const running = Effect.runPromise(
|
||||
Effect.scoped(
|
||||
Effect.gen(function* () {
|
||||
yield* f.install;
|
||||
return yield* f.execute({
|
||||
agent: "coordinate",
|
||||
description: "local edit",
|
||||
prompt: "work",
|
||||
});
|
||||
}),
|
||||
),
|
||||
{ signal: controller.signal },
|
||||
);
|
||||
await f.entered.promise;
|
||||
controller.abort();
|
||||
await expect(running).rejects.toBeDefined();
|
||||
expect(bindingSchema.parse(f.storage.get(bindingKey("ses_child1"))).ready).toBe(true);
|
||||
expect(f.storage.has(`multi-work-call:${f.call.sessionID}:${f.call.id}`)).toBe(true);
|
||||
const replay = await Effect.runPromise(
|
||||
Effect.exit(
|
||||
f.execute({
|
||||
agent: "coordinate",
|
||||
description: "local edit",
|
||||
prompt: "work",
|
||||
}),
|
||||
),
|
||||
);
|
||||
expect(replay._tag).toBe("Failure");
|
||||
expect(f.prompts).toBe(1);
|
||||
});
|
||||
|
||||
test("custom branch and workspace label pass through delegation", async () => {
|
||||
const f = fixture();
|
||||
await Effect.runPromise(
|
||||
@@ -623,6 +747,32 @@ test("configured build restrictions remain effective for worktree preparation",
|
||||
);
|
||||
});
|
||||
|
||||
test("worktree resumes retain the herdr_worktree permission guard", async () => {
|
||||
const f = fixture();
|
||||
await Effect.runPromise(
|
||||
Effect.scoped(
|
||||
Effect.gen(function* () {
|
||||
yield* f.install;
|
||||
yield* f.invoke();
|
||||
f.parent.permissions = [
|
||||
{ action: "herdr_worktree", resource: "*", effect: "deny" },
|
||||
];
|
||||
const resumed = yield* Effect.exit(
|
||||
f.execute({
|
||||
agent: "coordinate",
|
||||
description: "resume",
|
||||
prompt: "continue",
|
||||
sessionID: "ses_child1",
|
||||
}),
|
||||
);
|
||||
expect(resumed._tag).toBe("Failure");
|
||||
expect(f.herdr.verified).toBe(0);
|
||||
expect(f.prompts).toBe(1);
|
||||
}),
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
test("failed initialization records the attempt and never blindly recreates on replay", async () => {
|
||||
const f = fixture();
|
||||
f.missingDestination();
|
||||
|
||||
Reference in New Issue
Block a user