From 4a4f89e157a39056c4369921e9c52e8ee4501d23 Mon Sep 17 00:00:00 2001 From: Rainier Potgieter Date: Wed, 27 May 2026 00:51:09 +0200 Subject: [PATCH] fix(core): forward pre_output gate on_fail into compiled output The Gate type carries on_fail?: OnFailAction and quality_gates.pre_output is Gate[], but the pre_output gate call sites compiled via compileGateValidator(check, message) and never forwarded on_fail, so a runtime executing the compiled spec could not recover what action a failing pre_output gate should trigger. Same class as #64, different authoring site. Mirror #64: carry the recovery action as a first-class typed field on the compiled gate representation. For pre_output gates that representation is the gate validator's return shape, so add onFail?: OnFailAction to QualityGateResult, add an onFail parameter to compileGateValidator, and forward gate.on_fail at both pre_output call sites (per-step qualityGates and globalQualityGates). The action is surfaced on the failing result and omitted when the author did not specify on_fail (mirrors the optional source field, no invented default). The step.verification call site is unchanged; its recovery lives on CompiledStep.verification per #64. Adds a parametrised canary across all five OnFailAction values plus the absent case, asserting the compiled pre_output gate carries the authored on_fail through compilation. Co-Authored-By: Claude Opus 4.7 (1M context) --- packages/core/compiler.test.ts | 42 ++++++++++++++++++++++++++++++++++ packages/core/compiler.ts | 22 ++++++++++++++---- packages/core/types.ts | 2 ++ 3 files changed, 62 insertions(+), 4 deletions(-) diff --git a/packages/core/compiler.test.ts b/packages/core/compiler.test.ts index edbb145..0862e5c 100644 --- a/packages/core/compiler.test.ts +++ b/packages/core/compiler.test.ts @@ -553,6 +553,48 @@ describe("CompiledStep.executionPlan carries parallel execution through compilat }); }); +// ============================================================================= +// pre_output gate on_fail survives compilation (issue #72) +// ============================================================================= + +describe("pre_output gate carries on_fail through compilation", () => { + const actions: OnFailAction[] = ["retry", "escalate", "skip", "abort", "revise"]; + + // A step with no verification, so qualityGates[0] is the pre_output gate. + function specWithGate(onFail?: OnFailAction): LogicSpec { + return { + ...makeSpec({ scored: { description: "Scored step" } }), + quality_gates: { + pre_output: [ + { + name: "confidence-check", + check: "{{ output.score >= 0.8 }}", + message: "Score too low", + ...(onFail !== undefined ? { on_fail: onFail } : {}), + }, + ], + }, + }; + } + + it.each(actions)("forwards on_fail: %s onto the failing pre_output gate result", (onFail) => { + const result = compileStep(specWithGate(onFail), "scored", makeCtx()); + expect(result.qualityGates).toHaveLength(1); + expect(result.qualityGates[0]!({ score: 0.5 })).toEqual({ + passed: false, + message: "Score too low", + onFail, + }); + }); + + it("omits onFail when the pre_output gate has no authored on_fail", () => { + const result = compileStep(specWithGate(), "scored", makeCtx()); + const failed = result.qualityGates[0]!({ score: 0.5 }); + expect(failed).toEqual({ passed: false, message: "Score too low" }); + expect(failed).not.toHaveProperty("onFail"); + }); +}); + // ============================================================================= // Output Format in systemPromptSegment // ============================================================================= diff --git a/packages/core/compiler.ts b/packages/core/compiler.ts index c2a84d2..84a19b1 100644 --- a/packages/core/compiler.ts +++ b/packages/core/compiler.ts @@ -15,6 +15,7 @@ import type { ExecutionPlan, JsonSchemaObject, LogicSpec, + OnFailAction, QualityGateValidator, Reasoning, RetryConfig, @@ -255,14 +256,27 @@ function compileSelfReflection( * Compile a gate check expression into a QualityGateValidator function. * Uses the expression engine's evaluate() to evaluate the check expression * with the step output injected as `{ output }` in the expression context. + * + * When the gate carries an authored on_fail recovery action, it is forwarded + * onto the failing result so a runtime can recover what action a failed gate + * should trigger without reaching back into the un-compiled spec. Omitted when + * the author did not specify one (the source field is optional). */ -function compileGateValidator(checkExpression: string, message?: string): QualityGateValidator { +function compileGateValidator( + checkExpression: string, + message?: string, + onFail?: OnFailAction, +): QualityGateValidator { return (output: unknown) => { const result = evaluate(checkExpression, { output }); if (result) { return { passed: true }; } - return { passed: false, ...(message ? { message } : {}) }; + return { + passed: false, + ...(message ? { message } : {}), + ...(onFail !== undefined ? { onFail } : {}), + }; }; } @@ -398,7 +412,7 @@ export function compileStep( } if (spec.quality_gates?.pre_output) { for (const gate of spec.quality_gates.pre_output) { - gates.push(compileGateValidator(gate.check, gate.message)); + gates.push(compileGateValidator(gate.check, gate.message, gate.on_fail)); } } return gates; @@ -463,7 +477,7 @@ export function compileWorkflow(spec: LogicSpec, context: WorkflowContext): Comp const globalGates: QualityGateValidator[] = []; if (spec.quality_gates?.pre_output) { for (const gate of spec.quality_gates.pre_output) { - globalGates.push(compileGateValidator(gate.check, gate.message)); + globalGates.push(compileGateValidator(gate.check, gate.message, gate.on_fail)); } } diff --git a/packages/core/types.ts b/packages/core/types.ts index 35c8bfc..6728707 100644 --- a/packages/core/types.ts +++ b/packages/core/types.ts @@ -640,6 +640,8 @@ export interface QualityGateResult { passed: boolean; /** Human-readable reason surfaced when the check fails */ message?: string; + /** Recovery action authored on the gate, surfaced when the gate fails */ + onFail?: OnFailAction; } /** Runtime quality gate validator function (v1.1 Compiler) */