bus, harness: decision.raise takes one recommendation key and says which field failed (row 112, #1606)

A recommendation that isn't exactly one option key now refuses
invalid-recommendation, checked after the options, instead of sharing
invalid-options with bad option lists. The raise_decision tool text says
recommendation is one key with the reason in question, and that
approvalChoice defaults to yes. The bus README lists the three codes,
the field each names and the check order; a malformed option object
refuses invalid-request. No schema change.

Candidate manifest 013c82c2fd99d23d92f6bed543a788aa04ff97b164019ccb15252ae808b4138e
(Darkwing round 2, packet fee89d52f), BASE 96656dd21. Approved by Filbert
(comment 28383) and Sage (comment 28384). Sage gate rerun at f63fc20e4
plus the candidate passed.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
2026-10-14 11:07:12 -05:00
co-authored by Claude Opus 5.5
parent f63fc20e48
commit cc48b124f1
5 changed files with 52 additions and 5 deletions
+3 -3
View File
@@ -134,12 +134,12 @@ export function typedTools({ resolved, arbiter = false, tracker = false, project
}
add("raise_decision", "decision.raise", null, "Ask for a decision before a gated or cross-role action, or when you need a choice made. The answer arrives as a message.", obj({
action: str("The action the decision is about", { enum: [...ACTIONS] }),
question: str("The question, with enough context to answer it"),
question: str("The question, with enough context to answer it and the reason for your recommendation"),
options: { type: "array", minItems: 2, maxItems: 9, items: obj({ key: str("Short key"), text: str("What this option means") }, ["key", "text"]) },
recommendation: str("Your recommended option key, with a reason"),
recommendation: str("Exactly one option key, as written in options, with nothing added. The reason goes in question"),
blocking: { type: "boolean", description: "True only for a decision that holds a task: it needs task_ref, and the task waits for the answer. A request with no task is false" },
target: str("What the action applies to, such as a role instance or task reference"),
approvalChoice: str("The option key that approves the action"),
approvalChoice: str("The option key that approves the action. It defaults to yes, so pass it whenever no option key is yes"),
task_ref: TASK_REF,
requirement_ref: str("Requirement id"),
}, ["action", "question", "options", "recommendation", "blocking"]));
+5
View File
@@ -50,6 +50,11 @@ test("list_requirements reads the requirement ids, for a task.create holder with
const raise = tools.find((x) => x.name === "raise_decision").parameters.properties;
assert.match(raise.blocking.description, /needs task_ref/);
assert.match(raise.blocking.description, /A request with no task is false/);
assert.match(raise.recommendation.description, /^Exactly one option key, as written in options, with nothing added/);
assert.doesNotMatch(raise.recommendation.description, /with a reason/);
assert.match(raise.recommendation.description, /The reason goes in question/);
assert.match(raise.question.description, /the reason for your recommendation/);
assert.match(raise.approvalChoice.description, /defaults to yes, so pass it whenever no option key is yes/);
});
test("a coder gets no launch, no resolve_decision, and no task tools without a tracker", (t) => {