diff --git a/docs/plans/2026-10-14_decision-raise-recommendation.md b/docs/plans/2026-10-14_decision-raise-recommendation.md new file mode 100644 index 000000000..0bc4e26c1 --- /dev/null +++ b/docs/plans/2026-10-14_decision-raise-recommendation.md @@ -0,0 +1,97 @@ +# decision.raise recommendation fix (2026-10-14) + +Filed by Sage on Mos's report of 2026-10-14. Gitea #1606, Vikunja #205. + +## Decision raise takes one recommendation key and says which field failed + +### Problem + +The PM couldn't raise Jason's Q139/Q140 organization direction as a +`prd.approve` decision. The broker refused it four times with +`invalid-options` (bus events 335-338, 15:32:34Z-15:32:54Z; PM reply message +d6070b4f), so nothing reached the Inbox and no tasks were made. + +`packages/harness/src/tools.mjs:139` describes `recommendation` as "Your +recommended option key, with a reason". `packages/bus/src/broker.mjs:784-785` +refuses unless `recommendation` equals an option key exactly. The PM run's +transcript shows every call sent "key: reason", for example +`"yes: it matches Jason's direction"`. The refusal code names `options`, so +the PM changed its options three times and never the field that failed. + +A second trap sits next to it. `approvalChoice` defaults to `yes` +(broker.mjs:786-787, bus README "Authorization requires..."), but the tool +text only says "The option key that approves the action". Attempt 1 used +keys amend/rename-only/hold with no approvalChoice, so it would have been +refused `invalid-approval-choice` next. + +The same lines are in badfa52b9 (live), 179697a8a (staged copy) and +refactor at 29d34fd1a. + +### Owner and reviewer + +- Owner: darkwing. +- Reviewers: filbert (decision 130, not a sandbox row) and sage. + +### Files owned + +- `packages/harness/src/tools.mjs` +- `packages/harness/tests/tools.test.mjs` +- `packages/bus/src/broker.mjs` +- `packages/bus/tests/broker.test.mjs` +- `packages/bus/README.md` + +### What ships + +1. Tool text, `raise_decision` in tools.mjs: + - `recommendation`: exactly one option key, as written in `options`, + with nothing added; the reason goes in `question`. + - `approvalChoice`: the option key that approves the action; it defaults + to `yes`, so pass it whenever no option key is `yes`. + - The tool description or the `question` text says the question carries + the context and the reason for the recommendation. +2. Broker, `decision.raise` in broker.mjs: + - A `recommendation` that isn't one of the option keys refuses + `invalid-recommendation`. + - Fewer than 2 or more than 9 options, a malformed option, or duplicate + keys still refuse `invalid-options`. Check the options before the + recommendation, so a bad option list never reports as a bad + recommendation. + - `invalid-approval-choice` is unchanged. + - No schema change: `decisions.recommendation` stays one key, which the + CLI, notifier and Inbox already print as "recommended: KEY". +3. Bus README: the `decision.raise` row or the decision section lists the + three refusal codes and which field each one names. +4. Tests: + - broker.test.mjs: "key: reason" refuses `invalid-recommendation` and + writes no decision row and no message; duplicate keys still refuse + `invalid-options`, including when the recommendation also misses; + a plain key is accepted. Split the existing duplicate-key case at + broker.test.mjs:144-154 only as far as needed. + - tools.test.mjs: the `raise_decision` schema text for `recommendation` + says exactly one option key and no longer asks for a reason; + `approvalChoice` names the `yes` default. + - A mutant that restores the old shared code (recommendation miss + refusing `invalid-options`) fails the broker test. Record it in the + packet. +5. Suites: every package suite (`node --test tests/*.test.mjs` in each + `packages/*/`) and every `scripts/test-*.sh`, on the packet gate. + +### Out of scope + +- A separate recommendation-reason field. It would change the decision + schema and the Inbox; if the question text proves too thin, that is a + new row. +- Detail beyond the code in broker refusals generally (Vikunja #205 + dogfood list). +- The PRD amendment the PM proposed (REQ-ORG-1 and the rest). That waits + for this row to go live and then runs through the PM and Jason's Inbox. +- Going live. A new copy, re-pin and bus restart happen under Mos's go, + shared with 179697a8a (row 110). + +### Gate + +Filbert and Sage approve on #1606 naming the candidate manifest. Sage +reruns the full gate at the landing target. Mos gets notices before and +after the landing (decision 109). Live only through `stack-copy.sh copy` +and a bus restart under Mos's go; Mos re-raises the Q139/Q140 decision +through the PM after the restart and confirms it reached the Inbox.