Wanderland

Wanderland Core: Verify Route

Per-request shape probe. A request carrying the verify option runs the route's actual response through ShapeMatcher against one or more scenarios declared in config. No synthetic input — the actual is always whatever the real request produced. The probe is for monitoring and debugging, not for batch testing; batch testing lives in --type test.

Shape of a scenario

Scenarios attach to a route by name. The tree is scenarios.<route-name>.<scenario-name>.{expected, description, ...}. Only expected: is read by the probe; extra fields like description surface in failure payloads.

Inline

service: hello-world
port: 9294
boundary_path: lib/hello_world/boundaries

routes:
  /hello:
    method: get
    boundary: echo
    name: hello
  /greet/:name:
    method: get
    boundary: greet
    name: greet

scenarios:
  hello:
    smoke:
      description: "echoes the message param"
      expected:
        echoed: "world"
  greet:
    alice:
      description: "canonical greeting format"
      expected:
        greeting: "Hello, alice!"
    polite_tone:
      description: "greeting always starts with Hello"
      expected:
        greeting:
          matches: "^Hello, "

!Fixture-loaded

External files keep long scenarios out of config.yml and let multiple routes share expectations. Configure the Fixture resolver with search dirs:

resolvers:
  Fixture:
    fixture_dirs:
      - scenarios
      - fixtures

scenarios:
  greet:
    alice: !Fixture greet/alice
    polite_tone: !Fixture greet/polite_tone
    regression_ticket_742: !Fixture bugs/ticket-742-greeting-regression
  hello:
    smoke: !Fixture hello/smoke

Each !Fixture <name> looks up <dir>/<name>.yml (and <dir>/mocks/<name>.yml) across the configured fixture_dirs; first hit wins. The loaded YAML replaces the tag at parse time, so after resolution scenarios.greet.alice is a plain hash with expected: and whatever else the fixture file carries.

A fixture file (scenarios/greet/alice.yml):

description: "canonical greeting format"
expected:
  greeting: "Hello, alice!"

A bug-regression fixture (scenarios/bugs/ticket-742-greeting-regression.yml):

description: |
  Ticket #742 — greeting lost its comma in release 0.4.2.
  Guards against "Hello alice" sneaking past in a future refactor.
expected:
  greeting:
    matches: "^Hello, [a-zA-Z]+!$"

Fixtures and inline entries can mix inside the same scenarios: block.

Headers and options

Two equivalent forms map to the same verify option:

The option value is always a string. Two conventions:

Missing or empty option = verify boundary skips. Zero cost when you're not probing.

Behaviour

Match

The probe returns a passthrough signal carrying the original payload plus _verify — one entry per scenario run. The prior crossing's type_addr is preserved, so format and seal handle it as any other success.

GET /greet/alice   HTTP/1.1
X-Wanderland-Verify: alice

200 OK
{
  "greeting": "Hello, alice!",
  "_verify": [
    {
      "name": "alice",
      "description": "canonical greeting format",
      "passed": true,
      "failures": []
    }
  ]
}

Mismatch

The probe halts with 422, naming the failing scenarios and the ShapeMatcher failures each one produced. The request fails visibly — a probe that silently passed a wrong response would defeat the point.

GET /greet/bob   HTTP/1.1
X-Wanderland-Verify: alice

422 Unprocessable Entity
{
  "error": "scenario mismatch",
  "route": "greet",
  "verify": "alice",
  "scenarios": [
    {
      "name": "alice",
      "description": "canonical greeting format",
      "passed": false,
      "failures": ["greeting: expected \"Hello, alice!\", got \"Hello, bob!\""]
    }
  ]
}

All-scenarios mode

X-Wanderland-Verify: route runs every scenario attached to the current route. The passthrough payload's _verify carries them all; a halt lists only the failing ones.

GET /greet/bob   HTTP/1.1
X-Wanderland-Verify: route

422 Unprocessable Entity
{
  "error": "scenario mismatch",
  "route": "greet",
  "verify": "route",
  "scenarios": [
    {
      "name": "alice",
      "description": "canonical greeting format",
      "passed": false,
      "failures": ["greeting: expected \"Hello, alice!\", got \"Hello, bob!\""]
    }
  ]
}

No scenarios found

Asking to verify a route that has no scenarios — or naming a scenario that isn't registered — halts with 422 and the list of what's available:

GET /greet/alice   HTTP/1.1
X-Wanderland-Verify: typo_name

422 Unprocessable Entity
{
  "error": "no scenarios found",
  "route": "greet",
  "verify": "typo_name",
  "available": ["alice", "polite_tone", "regression_ticket_742"]
}

Chain placement

Declared in core_injections at position: last, between enforce_denials (interleave) and trace_emit (last). Because position: last appends at fold time, the earlier declaration wins the earlier spot in the tail:

user slots
    ↓
enforce_denials   (interleaved between user slots)
    ↓
verify_route      ← runs the shape probe
    ↓
trace_emit        ← attaches _trace if requested
    ↓
format            ← MIME-matched renderer
    ↓
seal              ← root commitment over the chain

Sitting before trace_emit means the verify crossing is part of the trace when --wanderland-trace is also set — the trace shows both what the route produced and how it measured against the scenario.

Combining with --wanderland-trace

curl -H 'X-Wanderland-Verify: route' \
     -H 'X-Wanderland-Trace: 1' \
     http://localhost:9294/greet/alice

Response payload includes _verify (the scenario outcomes) and _trace (the event stack, which itself includes the verify_route crossing and its result). Useful for debugging exactly where a response shape drifted.

Relationship to other tools

Source

Site Audit

wanderland.dev

oculus-view: fence: fence execute HTTP 404