Skip to content

feat: add declarative tool review gates for workflow run outputs (#369)#370

Merged
marccampbell merged 6 commits into
mainfrom
feat/369-tool-review-gates
Jun 6, 2026
Merged

feat: add declarative tool review gates for workflow run outputs (#369)#370
marccampbell merged 6 commits into
mainfrom
feat/369-tool-review-gates

Conversation

@elasticclaw-factory

Copy link
Copy Markdown
Contributor

Implements #369.

Adds deterministic, non-LLM validation gates that inspect structured pipeline outputs (e.g. CodeBuild, E2E tests, security scanners) and drive stage transitions without involving an LLM judge.

Pipeline YAML additions

  • gate on a stage: inspects a named output from a prior run action
    • pass / fail conditions: JSON path + expected values
    • required: blocks PR creation if gate fails
    • treat_skipped_as_pass: missing output treated as pass
  • gate_result trigger: auto-transition on a specific gate verdict
  • output_matches trigger: general primitive for JSON path matching

Example pipeline

stages:
  - id: validate
    label: "Validate"
    on_enter:
      run:
        command: python3 scripts/run_android_codebuild.py --source-dir next_mobile
        output: android_validation
    gate:
      output: android_validation
      pass:
        path: status
        values: [passed, skipped]
      fail:
        path: status
        values: [failed, error]
      required: true
      treat_skipped_as_pass: true

  - id: create_pr
    label: "Create PR"
    triggers:
      - gate_result:
          stage: validate
          verdict: pass
    on_enter:
      inject: "Gate passed. Create a PR."

  - id: fix_issues
    label: "Fix Issues"
    triggers:
      - gate_result:
          stage: validate
          verdict: fail
    on_enter:
      inject: "Gate failed. Fix the issues and retry."

Implements #369 — deterministic, non-LLM validation gates that inspect
structured pipeline outputs and drive stage transitions.

Changes:
- pkg/hub/pipeline/pipeline.go: Add Gate, GateResultTrigger, OutputMatchesTrigger
  types and stage lookup helpers. Gate evaluates pass/fail conditions against
  named JSON outputs from prior run actions.
- pkg/hub/pipeline_runner.go: evaluateGate(), persistGateResult(), loadGateResult(),
  hasFailedRequiredGate(), autoTransitionAfterGate() — gate evaluation logic
  integrated into runOnEnter after run actions complete.
- pkg/hub/linear.go: Block [DONE] signal when any required gate has failed.
- pkg/hub/db.go: Add pipeline_gate_results table (v10 migration) for persisting
  gate verdicts.
- Tests: gate parsing, evaluation (pass/fail/skipped/error), transitions,
  required-gate PR blocking, JSON path walking.
@greptile-apps

greptile-apps Bot commented Jun 6, 2026

Copy link
Copy Markdown
Contributor

Reviews (1): Last reviewed commit: "feat: add declarative tool review gates ..." | Re-trigger Greptile

Comment thread pkg/hub/pipeline_runner.go Outdated
Comment thread pkg/hub/pipeline_runner.go Outdated
Comment thread pkg/hub/pipeline_runner.go
Comment thread pkg/hub/pipeline_runner.go
- Block both 'fail' and 'error' verdicts for required gates in runOnEnter
- hasFailedRequiredGate now checks verdict IN ('fail','error')
- Distinct chat message for 'error' vs 'fail' verdicts
- Add test for error-verdict gate blocking [DONE]
- Add test for error-verdict in hasFailedRequiredGate
- Comment on loadGateResult noting future UI use
@greptile-apps

greptile-apps Bot commented Jun 6, 2026

Copy link
Copy Markdown
Contributor

Comments Outside Diff (1)

  1. pkg/hub/pipeline_runner.go, line 1249-1265 (link)

    P1 output_matches trigger never evaluated at runtime

    StageForOutputMatches is defined in pipeline.go, has full test coverage, and the PR description lists it as a supported general primitive — but it is never called from any production code path. checkPipelineMessageTriggers (the function that drives all runtime trigger evaluation) only calls StageForMessageContains; no equivalent call to StageForOutputMatches exists anywhere in pipeline_runner.go. A pipeline stage with an output_matches trigger will parse without error but the trigger will silently never fire, causing stages to remain stuck rather than transition.

Reviews (2): Last reviewed commit: "fix: address greptile review comments on..." | Re-trigger Greptile

StageForOutputMatches was defined and tested but never called from
production code. checkPipelineMessageTriggers now falls back to
output_matches evaluation when message_contains doesn't match, so
stages with output_matches triggers can actually transition at runtime.

Adds TestCheckPipelineMessageTriggersOutputMatches to verify the
end-to-end trigger evaluation.
@greptile-apps

greptile-apps Bot commented Jun 6, 2026

Copy link
Copy Markdown
Contributor

Reviews (3): Last reviewed commit: "fix: wire output_matches trigger into ch..." | Re-trigger Greptile

Comment thread pkg/hub/pipeline_runner.go Outdated
- persistPipelineOutput now parses JSON stdout regardless of exit code,
  so validation tools that emit structured output on nonzero exit can
  still have their output inspected by gates.
- runOnEnter continues to gate evaluation when a stage has a gate and
  the run produced a named output, even if the command exited nonzero.
  This avoids requiring continue_on_error=true on every gate stage.
- Distinction: nonzero exit + valid JSON -> gate evaluates normally;
  nonzero exit + no/invalid JSON -> gate verdict 'error'.
- Tests: nonzero exit with valid JSON (gate evaluates to fail),
  nonzero exit with no JSON (gate verdict error).
@greptile-apps

greptile-apps Bot commented Jun 6, 2026

Copy link
Copy Markdown
Contributor

Comments Outside Diff (1)

  1. pkg/hub/pipeline_runner.go, line 1265-1274 (link)

    P1 output_matches trigger can fire on every message, causing backwards stage regressions

    StageForOutputMatches queries persistent DB state rather than message text. Once an output matches the trigger, it stays in the DB indefinitely — so every subsequent call to checkPipelineMessageTriggers (regardless of message content) re-evaluates it and finds a match. claimPipelineStageTransition only skips the transition when the claw is already in the target stage; once the pipeline advances past that stage, any new message will retroactively re-transition the claw back to it, re-executing on_enter. Unlike gate_result triggers (fired once via goroutine) or message_contains (text-dependent), this trigger has no natural one-shot mechanism.

Reviews (4): Last reviewed commit: "fix: evaluate gate even when run exits n..." | Re-trigger Greptile

Claw added 2 commits June 6, 2026 22:55
…SkippedAsPass is set

When a gate's output is missing and TreatSkippedAsPass is true, the gate
verdict is 'skipped'. Auto-transition now normalizes this to 'pass' so
that gate_result: { verdict: pass } triggers correctly fire, honouring
the contract stated in the PR description.

Also fixes persistPipelineOutput to parse JSON stdout regardless of exit
code, so validation tools that emit structured output on nonzero exit can
still have their output inspected by gates.

Tests: skipped-as-pass auto-transition, nonzero exit with valid JSON
(gate evaluates to fail), nonzero exit with no JSON (gate verdict error).
output_matches triggers query persistent DB state (pipeline outputs).
Without a one-shot guard, every subsequent call to
 checkPipelineMessageTriggers would re-evaluate and re-transition the
claw back to the same stage, causing backwards regressions and re-executing
on_enter actions.

Changes:
- Add pipeline_stage_history table (v11 migration) to track visited stages
- Add recordPipelineStageVisit / hasVisitedPipelineStage helpers in
  pipeline_state.go
- transitionPipelineStageWithContext records each stage visit on successful
  transition
- checkPipelineMessageTriggers skips output_matches triggers for stages
  the claw has already visited

This gives output_matches a natural one-shot mechanism: it fires once
when the output first matches, and never again.
@greptile-apps

greptile-apps Bot commented Jun 6, 2026

Copy link
Copy Markdown
Contributor

Reviews (5): Last reviewed commit: "fix: prevent output_matches trigger from..." | Re-trigger Greptile

@marccampbell marccampbell merged commit d9f5488 into main Jun 6, 2026
11 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant