diff --git a/packages/opencode/src/dag/blocks.ts b/packages/opencode/src/dag/blocks.ts index c63cd33f7..42be90b10 100644 --- a/packages/opencode/src/dag/blocks.ts +++ b/packages/opencode/src/dag/blocks.ts @@ -327,6 +327,14 @@ function node(input: { outputSchema?: Record }): NodeConfig { const instruction = input.instruction?.trim() ? "Block-specific instruction:\n{{instruction}}" : "" + // issue #323: a reporting checkpoint adjudicates a direction, so its + // prompt must demand adversarial independent verification. The production + // incident: a gate confirmed parent-supplied "defect evidence" that was a + // production no-op because it re-read the parent's narrative instead of + // the source. Upstream claims are hypotheses, not facts. + const adversarial = input.reportToParent + ? "As a reporting checkpoint you adjudicate this direction: treat upstream and parent-supplied claims as hypotheses to verify, never facts to confirm. Independently read the relevant source before endorsing, spot-check at least three of the most load-bearing claims (name the count you actually inspected), cite what you actually inspected, and replan or reject the direction when a load-bearing claim does not survive inspection." + : "" return { id: input.id, name: input.name, @@ -339,6 +347,7 @@ function node(input: { "Workflow objective:\n{{objective}}", instruction, input.contract, + adversarial, "Use dependency outputs as evidence and return a concise artifact that downstream blocks can consume. Do not ask the user questions from this child session.", ] .filter(Boolean) diff --git a/packages/opencode/test/dag/blocks.test.ts b/packages/opencode/test/dag/blocks.test.ts index ba51154e5..a36184d27 100644 --- a/packages/opencode/test/dag/blocks.test.ts +++ b/packages/opencode/test/dag/blocks.test.ts @@ -44,6 +44,56 @@ describe("workflow blocks", () => { ]) }) + // issue #323: a reporting checkpoint adjudicates a direction — its prompt + // must demand adversarial independent verification, not self-confirmation + // of upstream claims. The production incident: cp-after-exploration + // confirmed a parent-supplied "defect" that was a production no-op because + // the gate re-read the parent's narrative instead of reading the source. + it("compiles reporting checkpoints with an adversarial verification clause", () => { + const nodes = DagBlocks.compileWorkflowBlocks({ + objective: "Ship the feature", + blocks: [ + { id: "cp-after-exploration", kind: "explore", report_to_parent: true }, + { id: "stage", kind: "coding", depends_on: ["cp-after-exploration"] }, + ], + }) + const checkpoint = nodes.find((node) => node.id === "cp-after-exploration") + expect(checkpoint?.report_to_parent).toBe(true) + // Upstream claims are hypotheses to verify, not facts to confirm. + expect(checkpoint?.prompt_template.inline).toContain("hypotheses to verify") + // Independent source inspection is mandatory for load-bearing claims. + expect(checkpoint?.prompt_template.inline).toContain("Independently read the relevant source") + // A quantified spot-check floor on the most load-bearing claims. + expect(checkpoint?.prompt_template.inline).toContain("at least three") + // A claim that fails inspection must fail the direction, not pass it. + expect(checkpoint?.prompt_template.inline).toContain("replan or reject") + }) + + it("keeps the adversarial clause off non-reporting nodes", () => { + const nodes = DagBlocks.compileWorkflowBlocks({ + objective: "Ship the feature", + blocks: [ + { id: "map", kind: "explore" }, + { id: "stage", kind: "coding", depends_on: ["map"] }, + ], + }) + // Only adjudicating checkpoints pay the adversarial cost; evidence + // producers (a plain explore) and executors (coding) do not. + for (const node of nodes) { + expect(node.report_to_parent).toBe(false) + expect(node.prompt_template.inline).not.toContain("hypotheses to verify") + } + }) + + it("gives default-reporting synthesize nodes the adversarial clause", () => { + const nodes = DagBlocks.compileWorkflowBlocks({ + objective: "Ship the feature", + blocks: [{ id: "closing", kind: "synthesize" }], + }) + expect(nodes[0]?.report_to_parent).toBe(true) + expect(nodes[0]?.prompt_template.inline).toContain("hypotheses to verify") + }) + it("composes configured design delivery capabilities without new lifecycle kinds", () => { const nodes = DagBlocks.compileWorkflowBlocks({ objective: "Design, implement, and review project-owned memory",