Skip to content

Contracts Guide

Handover contracts validate step output before dependent steps begin. They catch malformed artifacts early, preventing wasted work downstream.

What is a Contract?

A contract validates that a step produced correct output. Contracts can check:

  • Structure - JSON schema compliance
  • Types - TypeScript compilation
  • Behavior - Test suite results

Configuration

Contracts are defined in a step's handover section:

yaml
steps:
  - id: navigate
    handover:
      contract:
        type: json_schema
        schema_path: .wave/contracts/navigation.schema.json
        source: .wave/output/analysis.json
        on_failure: retry
        max_retries: 2
FieldDefaultDescription
type-json_schema, typescript_interface, test_suite, markdown_spec, format, non_empty_file, llm_judge, source_diff, agent_review, event_contains, or spec_derived_test
schema_path-Schema file path (for json_schema)
source-File to validate
command-Test command (for test_suite)
dirworkspaceWorking directory for test_suite: project_root, absolute, or relative
must_passtrueWhether failure blocks progression
on_failureretryAction: retry or halt
max_retries2Maximum retry attempts

Contract Types

JSON Schema

Validates output structure:

yaml
handover:
  contract:
    type: json_schema
    schema_path: .wave/contracts/navigation.schema.json
    source: .wave/output/analysis.json

Example schema:

json
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "type": "object",
  "required": ["files", "summary"],
  "properties": {
    "files": {
      "type": "array",
      "items": {
        "type": "object",
        "required": ["path", "purpose"],
        "properties": {
          "path": { "type": "string" },
          "purpose": { "type": "string" }
        }
      }
    },
    "summary": { "type": "string" }
  }
}

TypeScript Interface

Validates generated TypeScript compiles:

yaml
handover:
  contract:
    type: typescript_interface
    source: .wave/output/types.ts
    validate: true

If tsc is unavailable, degrades to syntax-only checking.

Test Suite

Validates by running tests:

yaml
handover:
  contract:
    type: test_suite
    command: "go test ./..."
    dir: project_root
    must_pass: true
    max_retries: 3

Use dir: project_root when the command needs project files (like go.mod). Without it, the command runs in the ephemeral workspace directory.

Non-Empty File

Validates that the artifact file exists and is not empty. Useful as a lightweight check that a step produced output.

yaml
handover:
  contract:
    type: non_empty_file
    source: .wave/output/result.md

No additional configuration fields are required beyond type and source.

LLM Judge

Uses an LLM to evaluate the artifact against criteria specified in a prompt. The LLM reads the artifact content and returns a pass/fail judgment.

yaml
handover:
  contract:
    type: llm_judge
    source: .wave/output/plan.md
    prompt: "Does this plan address all acceptance criteria from the issue?"
    model: balanced
FieldDefaultDescription
prompt-Evaluation criteria for the LLM judge
modelcheapestModel tier (cheapest, balanced, strongest) or a specific model identifier

Source Diff

Validates that the step produced meaningful source code changes by checking the git diff. Catches the failure mode where a step claims success but made no actual changes.

yaml
handover:
  contract:
    type: source_diff
    glob: "*.go"
    min_files: 1
FieldDefaultDescription
glob(all files)Glob pattern to filter which changed files count
min_files1Minimum number of qualifying changed files

Agent Review

Delegates validation to another agent session using an adapter runner. Unlike other contract types, agent_review does not use NewValidator — the executor calls ValidateWithRunner() instead.

yaml
handover:
  contract:
    type: agent_review
    source: .wave/output/implementation.md
    prompt: "Review this implementation for security issues"

Event Contains

Validates that specific pipeline events occurred during step execution. Matches events by state and optional message substrings. Used in Wave's own default ontology pipelines.

yaml
handover:
  contract:
    type: event_contains
    events:
      - state: ontology_warn
        contains: "delivery"
FieldDefaultDescription
events-List of event patterns to match
events[].state-Required event state to match (e.g., ontology_warn)
events[].contains(any)Optional substring the event message must include

Spec-Derived Test

Generates and runs tests derived from a specification document. Like agent_review, this type requires an adapter runner — NewValidator returns nil and the executor calls ValidateSpecDerived() instead.

yaml
handover:
  contract:
    type: spec_derived_test
    source: .wave/output/spec.md
    test_dir: .wave/output/tests/

Failure Handling

Retry Behavior

When on_failure: retry:

  1. Step transitions to retrying
  2. Re-executes with fresh context
  3. Validates again
  4. After max_retries failures, transitions to failed

Halt Behavior

When on_failure: halt:

  1. Step immediately fails
  2. Pipeline stops
  3. Error includes validation details

Optional Contracts

Use must_pass: false for advisory checks:

yaml
handover:
  contract:
    type: test_suite
    command: "npm run lint"
    must_pass: false    # Log but don't block

Common Patterns

yaml
- id: navigate
  output_artifacts:
    - name: analysis
      path: .wave/output/analysis.json
  handover:
    contract:
      type: json_schema
      schema_path: .wave/contracts/navigation.schema.json
      source: .wave/output/analysis.json

Implementation Contract

yaml
- id: implement
  handover:
    contract:
      type: test_suite
      command: "go build ./... && go test ./..."
      dir: project_root
      max_retries: 3

Chained Validation

Use a script for multiple checks:

yaml
handover:
  contract:
    type: test_suite
    command: ".wave/scripts/validate.sh"
bash
#!/bin/bash
set -e
npx ajv validate -s schema.json -d output.json
npm test

Schema Organization

.wave/
├── contracts/
│   ├── navigation.schema.json
│   ├── specification.schema.json
│   └── implementation.schema.json
└── pipelines/
    └── feature-flow.yaml

Debugging Failures

Check audit logs:

bash
cat .wave/traces/<pipeline-id>.jsonl | jq 'select(.type == "contract_failure")'

See Also

Released under the MIT License.