fix: make join approval atomic

This commit is contained in:
User
2026-07-14 15:24:06 +02:00
parent bc02213d3e
commit 333874a755
9 changed files with 314 additions and 34 deletions
@@ -33,9 +33,10 @@ const JOIN_OPTIONS = [
},
];
function shellStub(calls) {
return (_file, args) => {
function shellStub(calls, inputs = []) {
return (_file, args, options = {}) => {
calls.push(args.join(" "));
if (options.input) inputs.push(options.input);
if (args[0] === "phase" && args[1] === "meta") {
return JSON.stringify({ phases: [{ num: 4, id: "F4" }] });
}
@@ -46,8 +47,9 @@ function shellStub(calls) {
test("join-only reviewer_decide is read-only and Continue persists every join", async () => {
const calls = [];
const inputs = [];
const original = cp.execFileSync;
cp.execFileSync = shellStub(calls);
cp.execFileSync = shellStub(calls, inputs);
try {
const gate = require(GATE);
const { createFakePi } = require("./fake_pi_runtime.js");
@@ -77,9 +79,48 @@ test("join-only reviewer_decide is read-only and Continue persists every join",
assert.equal(descriptor.widget, "join-review");
assert.equal(descriptor.options[0].detail, JOIN_OPTIONS[0].decision.detail);
assert.equal(descriptor.options[0].rationale, JOIN_OPTIONS[0].decision.rationale);
const decisions = calls.filter((call) => call.startsWith("decision add"));
assert.equal(decisions.length, 2);
assert.ok(decisions.every((call) => call.includes("--type join_modified")));
const decisions = calls.filter((call) => call.startsWith("decision add-join-set"));
assert.equal(decisions.length, 1);
assert.deepEqual(JSON.parse(inputs[0]), JOIN_OPTIONS.map((option) => option.decision));
} finally {
cp.execFileSync = original;
}
});
test("partial join-review responses are rejected and the widget is presented again", async () => {
const calls = [];
const original = cp.execFileSync;
cp.execFileSync = shellStub(calls);
try {
const gate = require(GATE);
const { createFakePi } = require("./fake_pi_runtime.js");
const { pi, ctx, tools } = createFakePi();
ctx.cwd = "/nonexistent-thothii-test-cwd";
gate.default(pi);
let attempts = 0;
ctx.ui.input = async (title) => {
const descriptor = JSON.parse(title);
attempts += 1;
return JSON.stringify({
id: descriptor.id,
kind: attempts === 1 ? "multiselect" : "join-review",
choices: attempts === 2
? [descriptor.options[0].id]
: descriptor.options.map((option) => option.id),
});
};
const tool = tools.get("reviewer_decide");
await tool.def.execute(
"call-partial",
{ session: "s1", title: "Review joins", options: JOIN_OPTIONS, advance: false },
null,
null,
ctx,
);
assert.equal(attempts, 3);
} finally {
cp.execFileSync = original;
}