Skip to content

motel: Checks Files for User-Defined Invariants

2026-06-13T15:01:55Z by Showboat 0.6.1

motel check can load user-defined structural thresholds from a separate YAML checks file. This keeps invariants outside the topology, which is useful when the topology is generated by motel import or shared across environments with different limits.

A topology and checks file

The topology has one gateway root. It calls auth.verify and orders.list, and orders.list calls database.query. That gives a static depth of 2, direct fan-out of 2, and 4 spans per trace.

cat > /tmp/checks-file-topology.yaml << 'EOF'
version: 1
services:
  gateway:
    operations:
      request:
        duration: 10ms
        calls:
          - auth.verify
          - orders.list
  auth:
    operations:
      verify:
        duration: 5ms
  orders:
    operations:
      list:
        duration: 8ms
        calls:
          - database.query
  database:
    operations:
      query:
        duration: 4ms
traffic:
  rate: 10/s
EOF
echo 'wrote /tmp/checks-file-topology.yaml'
wrote /tmp/checks-file-topology.yaml
cat > /tmp/checks-pass.yaml << 'EOF'
version: 1
checks:
  max_depth: 2
  max_fan_out: 2
  max_spans: 4
  p99_depth: 2
  p95_spans: 4
EOF
echo 'wrote /tmp/checks-pass.yaml'
wrote /tmp/checks-pass.yaml
build/motel check --checks /tmp/checks-pass.yaml --samples 20 --seed 42 /tmp/checks-file-topology.yaml
PASS  max-depth: 2 (limit: 2)
      path: gateway.request → orders.list → database.query
      p50: 2  p95: 2  p99: 2  max: 2  (20 samples)
PASS  max-fan-out: 2 (limit: 2)
      worst: gateway.request
      p50: 2  p95: 2  p99: 2  max: 2  (20 samples)
PASS  max-spans: 4 static worst-case, 4 observed/20 samples (limit: 4)
      p50: 4  p95: 4  p99: 4  max: 4  (20 samples)
PASS  p99-depth: 2 (limit: 2)
      p50: 2  p95: 2  p99: 2  max: 2  (20 samples)
PASS  p95-spans: 4 (limit: 4)
      p50: 4  p95: 4  p99: 4  max: 4  (20 samples)

The checks file adds result rows for percentile assertions while also replacing the default static limits. The command still reports the built-in max-depth, max-fan-out, and max-spans checks, but their limits come from the checks file.

Failing a user-defined invariant

Tightening max_depth to 1 makes the topology fail, even though the topology itself is valid. The process exits non-zero, so the pattern works in CI.

cat > /tmp/checks-tight.yaml << 'EOF'
version: 1
checks:
  max_depth: 1
  max_fan_out: 2
  max_spans: 4
EOF
echo 'wrote /tmp/checks-tight.yaml'
wrote /tmp/checks-tight.yaml
build/motel check --checks /tmp/checks-tight.yaml --samples 0 /tmp/checks-file-topology.yaml 2>&1; echo "exit code: $?"
FAIL  max-depth: 2 (limit: 1)
      path: gateway.request → orders.list → database.query
PASS  max-fan-out: 2 (limit: 2)
      worst: gateway.request
PASS  max-spans: 4 static worst-case (limit: 4)
Error: one or more checks failed
exit code: 1

Overriding file limits at the command line

Explicit limit flags override matching values from the checks file. This lets CI use a shared checks file while allowing a one-off local experiment or gradual threshold adjustment.

build/motel check --checks /tmp/checks-tight.yaml --max-depth 2 --samples 0 /tmp/checks-file-topology.yaml
PASS  max-depth: 2 (limit: 2)
      path: gateway.request → orders.list → database.query
PASS  max-fan-out: 2 (limit: 2)
      worst: gateway.request
PASS  max-spans: 4 static worst-case (limit: 4)

Percentile thresholds need sampling

Percentile assertions such as p95_spans and p99_depth are evaluated from sampled traces. If --samples is 0, motel check rejects the checks file before running the topology analysis because there is no distribution to compare.

cat > /tmp/checks-percentile.yaml << 'EOF'
version: 1
checks:
  p99_depth: 2
EOF
echo 'wrote /tmp/checks-percentile.yaml'
wrote /tmp/checks-percentile.yaml
build/motel check --checks /tmp/checks-percentile.yaml --samples 0 /tmp/checks-file-topology.yaml 2>&1; echo "exit code: $?"
Error: percentile checks require --samples greater than 0
exit code: 1
build/motel check --checks /tmp/checks-percentile.yaml --samples 20 --seed 42 /tmp/checks-file-topology.yaml
PASS  max-depth: 2 (limit: 10)
      path: gateway.request → orders.list → database.query
      p50: 2  p95: 2  p99: 2  max: 2  (20 samples)
PASS  max-fan-out: 2 (limit: 100)
      worst: gateway.request
      p50: 2  p95: 2  p99: 2  max: 2  (20 samples)
PASS  max-spans: 4 static worst-case, 4 observed/20 samples (limit: 10000)
      p50: 4  p95: 4  p99: 4  max: 4  (20 samples)
PASS  p99-depth: 2 (limit: 2)
      p50: 2  p95: 2  p99: 2  max: 2  (20 samples)