It had no writer, and its prompt did not know that.
A workflow step running `toolMode: "readonly"` is limited to read/grep/find/ls, fn_web_fetch
and a few read-only task reads; `fn_task_create` is explicitly DENIED there. The prompt asked
for four tool calls — fn_task_done(summary=…), fn_task_document_write, fn_artifact_register,
and creating follow-up tasks. It could make none of them. Every run produced a well-formed
report and persisted NOTHING. And because this milestone replaced `completion-summary`, which
used the working contract, cards quietly lost their agent-authored summary and fell back to
the deterministic backfill.
This is the same failure the reviewer prompt was fixed for — a session instructed to do what
its tool policy forbids — on a node nobody re-checked.
Both durable outputs now travel by PROJECTION, the only channel a writer-less node has.
`summaryTarget: "task"` persists its prose as the card summary. New
`recommendationsTarget: "task"` reads a trailing {"recommendations":[…]} payload, normalizes
it through the SAME rules the store boundary enforces (relocated to
tasks/recommendation-validation.ts so a second producer cannot drift from a copied regex),
and projects it to task.recommendations — the Recommendations tab, where an OPERATOR turns a
proposal into a task. An in-review agent proposes; it never creates board rows. Normalization
drops bad entries rather than throwing: a stray character in a suggestion must not wedge a
card whose code is already approved.
`summaryTarget` also removes this node's verdict requirement, so a reporter can no longer emit
the REVISE that held the merge door and bounced the card with nothing to do.
The guard that should have caught all of this asserted a PROMPT STRING —
`prompt.includes("fn_task_done(summary=")` — as proof a summary gets written. It was green
throughout. It now asserts the projection contract, including inside optional-group templates,
because the executing node of a group is its template child.
pnpm lint 0 errors, test:gate green, core + engine typecheck clean, pipeline-smoke 90/90.