bus: a closed decision sends the raising role a message (#1609)

Row 115. When a decision closes by resolve or withdraw, and the closer is
a human or another run, the broker routes a DECISION message to the
raising role's holder: "Decision <id> on <action> was resolved by <by>:
<choice> (<text>)" or "withdrawn by <by>", plus the note if there is one.
A seen op sends nothing. #request falls back to the human.input that
closed the decision, filtered to resolved and withdrawn.

Candidate manifest 1910eb5a672e670cb7c8a197ee3fdd888412dd803fe52a0a15680103c26c078c
from Darkwing's round 2 packet (40e5917f9), on BASE 94ece491f. Filbert
approved in 28428 and Sage in 28427. Sage reran the full gate at the
landing target with the candidate applied.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
2026-10-14 12:09:43 -05:00
co-authored by Claude Opus 5.5
parent 2330074bbe
commit d90482030a
5 changed files with 303 additions and 13 deletions
+36
View File
@@ -266,6 +266,42 @@ test("a message read a day after it was sent shows the time it was sent", async
assert.ok(Date.parse(reply.at) - Date.parse(sentAt) >= 86_000_000);
});
// Row 115 (#1609): the human closing the role's decision gives the role a turn. The runner is unchanged;
// the header cites the decision and the close's human.input answer as the request.
test("a decision the human resolves becomes a turn citing the decision and the answer", async (t) => {
const ctx = await setup(t);
const r = startRunner(ctx.runDir, ctx.cap);
t.after(() => r.child.kill("SIGKILL"));
await until(() => ctx.store.get("SELECT 1 FROM role_claims WHERE role='coder' AND op='claim'"));
const d = ctx.call(ctx.cap, "decision.raise", {
action: "deploy",
target: "release-1",
question: "Ship release-1?",
options: [{ key: "yes", text: "Allow" }, { key: "no", text: "Decline" }],
recommendation: "yes",
blocking: false,
});
assert.equal(d.route_to, "human");
ctx.call(ctx.human, "decision.resolve", { id: d.id, choice: "yes" });
const m = ctx.store.get("SELECT id,at FROM messages WHERE class='DECISION' AND decision=?", d.id);
const answer = ctx.store.get("SELECT id FROM events WHERE kind='human.input' AND subject=?", d.id).id;
const [reply] = await until(() => {
const got = ctx.call(ctx.human, "message.receive");
return got.length ? got : null;
});
assert.equal(reply.from_role, "coder");
assert.equal(reply.class, "RESULT");
assert.equal(reply.in_reply_to, m.id);
assert.ok(
reply.body.endsWith(
`\nMessage ${m.id} from human, sent ${m.at}, class DECISION, citing decision ${d.id}, request ${answer}:\n\n` +
`Decision ${d.id} on deploy was resolved by jason: yes (Allow).`,
),
reply.body,
);
await until(() => ctx.store.get("SELECT 1 FROM deliveries WHERE message=? AND op='read'", m.id));
});
test("a failed state read fails the turn: no adapter runs, the sender hears why, the message is read", async (t) => {
const ctx = await setup(t);
ctx.inbox = [];