Every wanderland-core service exposes the same introspection surface — every route, every boundary, every framework schema, every halt diagnostic, every registered scenario, every boundary's reference doc. The same JSON answers come back regardless of how you ask: HTTP request, CLI subcommand, MCP tool call (when that adapter lands). One catalogue, three lenses.
This guide walks the surface from broad to narrow: routes → a single route → boundaries → a single boundary → schemas → scenarios → diagnostics → generated docs.
Every example below appears twice — HTTP and CLI — because the data is the same. The CLI subcommand path mirrors the URL path: /inspect/route/:name ↔ inspect route --name <value>. Slashes become spaces; :capture becomes --capture VALUE. The CLI reaches the same compiled route through the same Dispatch.invoke seam.
Two output lenses:
| Form | How to ask for it (HTTP) | How to ask for it (CLI) |
|---|---|---|
| JSON | default, or Accept: application/json |
--accept application/json |
| YAML (CLI default) | Accept: text/plain (or none) |
default |
| Markdown | Accept: application/x.wanderland.template |
--accept application/x.wanderland.template |
Examples in this guide use CLI YAML for sample output — the most readable for humans skimming the surface. Substitute --accept application/json to get JSON when piping into tooling.
Three endpoints answer in increasing depth.
# Cheapest: load-balancer ping. Always answers.
curl http://localhost:9295/health
bundle exec bin/wanderland --type cli config.yml health
status: ok
service: sprout-engine
timestamp: '2026-04-30T11:41:46-04:00'
# Identity + registered surface count.
curl http://localhost:9295/status
bundle exec bin/wanderland --type cli config.yml status
status: ok
service: sprout-engine
boundaries:
- adapter_cli
- adapter_http
- echo
- enforce_denials
- format
- ...
routes: 18
timestamp: '2026-04-30T11:41:46-04:00'
# Full compliance snapshot — manifest + routes + diagnostics.
curl http://localhost:9295/healthcheck
bundle exec bin/wanderland --type cli config.yml healthcheck
/healthcheck returns the full boundary manifest (one entry per registered boundary), the compiled routes list, and the diagnostics summary nested under diagnostics:. status: ok on a clean boot, degraded if any boot-time error landed.
/inspect/routes is the user-declared route names — the surface the site actually advertises. Framework routes (the inspect family, the liveness triptych) aren't in here; they're documented elsewhere as core machinery.
curl http://localhost:9295/inspect/routes
bundle exec bin/wanderland --type cli config.yml inspect routes
routes:
- captures
- chain
- hello
- secret
Each name is what :name is set to on the route. Pick one and ask for its full shape:
curl http://localhost:9295/inspect/route/hello
bundle exec bin/wanderland --type cli config.yml inspect route --name hello
name: hello
method: get
path: /hello
dispatcher: chain
user_chain:
- echo
compiled_chain:
- enforce_denials
- echo
- verify_route
- trace_emit
- format
- seal
grid:
scenarios: []
registered_injections:
- boundary: enforce_denials
position: interleave
- boundary: verify_route
position: last
- boundary: trace_emit
position: last
- boundary: format
position: last
- boundary: seal
position: last
Two chain views matter:
user_chain — what the site config declared. For a single-boundary route that's one entry; for a chain: route it's the slot list verbatim.compiled_chain — what the dispatcher actually walks, after the framework's default injections wrap around the user chain. enforce_denials interleaves between user slots; verify_route, trace_emit, format, and seal append in order. The same shape every site gets, regardless of what's declared.grid is set when the route uses the grid dispatcher (declared via grid: shorthand); null for chain routes. scenarios lists scenario names attached to this route.
/inspect/boundaries is every registered boundary — framework-shipped and site-declared together, sorted alphabetically.
curl http://localhost:9295/inspect/boundaries
bundle exec bin/wanderland --type cli config.yml inspect boundaries
boundaries:
- adapter_cli
- adapter_http
- archetype_inspect
- boot_load_boundaries
- boot_mount_routes
- collect_boundary_reference
- echo
- enforce_denials
- format
- health
- ...
- verify_route
Pick one and inspect the registered manifest:
curl http://localhost:9295/inspect/boundary/echo
bundle exec bin/wanderland --type cli config.yml inspect boundary --name echo
name: echo
identity: boundary:echo
requirements: []
capabilities:
- echo
description: Echo input params back as result
when_shape:
input_shape:
params: true
output_shape:
echoed: true
source: Wanderland::Boundaries::Echo
The fields are the registered manifest verbatim:
identity — the from_addr this boundary signs as when its crossings reach the audit log.requirements / capabilities — what must be in context before this boundary runs, and what tags this boundary stamps on its crossing.when_shape — the guard that decides whether the boundary runs, or null for "always".input_shape / output_shape — the contracts the boundary declares. true means "any value"; nested hashes describe nested shape; absence on input_shape means "no constraint declared." Strict-mode runtimes reject undeclared writes against output_shape.source — the Ruby class the registry records as the boundary's implementation.A 404 includes the registered list under available: so you know what you can ask for next.
Two registries document what the framework itself reserves. The framework-schema endpoint covers the input envelope (what every boundary receives); the shape-schema endpoint covers the output side (what every boundary may write without per-boundary declaration).
curl http://localhost:9295/inspect/framework-schema
bundle exec bin/wanderland --type cli config.yml inspect framework-schema
stages:
request:
- key: params
type: hash
description: Callable arguments (path captures + query + body, merged)
- key: headers
type: hash
description: Canonical header map
- key: route
type: hash
description: The compiled route record
- key: runtime
type: object
description: The booted runtime instance
- key: context
type: object
description: The append-only Context
- ...
slot_input:
- ...
Each stage names the framework keys present at that point in the chain. request is what Dispatch.invoke builds; slot_input is what each chain slot receives (request keys + args from chain config).
Filter to one stage:
curl http://localhost:9295/inspect/framework-schema/request
bundle exec bin/wanderland --type cli config.yml inspect framework-schema --stage request
curl http://localhost:9295/inspect/shape-schema
bundle exec bin/wanderland --type cli config.yml inspect shape-schema
framework_keys:
- status
- error
- cause
- missing
modules:
- name: signals
framework_keys:
- status
- error
- cause
- missing
schemas:
- type_addr: ':signals:stop:halt'
payload:
status: true
error: true
- type_addr: ':signals:stop:error'
payload:
status: true
error: true
cause: true
- type_addr: ':signals:stop:denied'
payload:
status: true
error: true
missing: true
The top-level framework_keys is the union of every registered shape module's keys. Boundaries may write these without declaring them in their per-boundary output_shape — strict-output enforcement skips framework keys.
Each module documents the type_addr → payload pairings it owns. signals is the built-in module covering halt forms; sites can register additional modules at boot.
Filter to one module:
curl http://localhost:9295/inspect/shape-schema/signals
bundle exec bin/wanderland --type cli config.yml inspect shape-schema --module signals
Sites that declare a scenarios: block in config.yml expose them through introspection too. The site without scenarios returns an empty hash.
curl http://localhost:9295/inspect/scenarios
bundle exec bin/wanderland --type cli config.yml inspect scenarios
scenarios:
hello:
default:
description: Canonical example
has_input: true
has_expected: true
input:
params:
message: Hello world
expected:
echoed: Hello world
greet:
alice:
description: greeting alice with the canonical format
has_input: true
has_expected: true
input:
params:
name: alice
expected:
greeting: Hello, alice!
The endpoint reports every scenario the runtime registered at boot, grouped by route name. has_input / has_expected are convenience flags; the full input and expected blocks ride alongside.
Filter to one route:
curl http://localhost:9295/inspect/scenarios/hello
bundle exec bin/wanderland --type cli config.yml inspect scenarios --route hello
wanderland --type test config.yml walks this same registry, dispatches each through verify_route, and reports pass/fail. The introspection endpoint is the read-only view of what the runner will exercise.
Some boundaries record warnings or errors during boot — a missing boundary_path, a config field that referenced a fixture that didn't load, an adapter mode unrecognized. These don't halt boot but are surfaced for operators to act on.
curl http://localhost:9295/inspect/diagnostics
bundle exec bin/wanderland --type cli config.yml inspect diagnostics
summary:
errors: 0
warnings: 0
total: 0
severity:
entries: []
A clean boot returns zero entries. If boundary_path had pointed at a missing directory, a warning entry would land here without halting the process. summary.errors > 0 is what flips /healthcheck to degraded.
Filter to one severity level:
curl http://localhost:9295/inspect/diagnostics/warning
bundle exec bin/wanderland --type cli config.yml inspect diagnostics --severity warning
The doc-generator endpoint folds the boundary registry, the framework schema, and the shape schema into one tree. Default JSON returns the raw tree; the template MIME renders it through the gem-bundled ERB template into AWS-SDK-style markdown.
# JSON tree (raw data — feed into your own renderer if you want)
curl http://localhost:9295/inspect/docs/boundaries
bundle exec bin/wanderland --type cli config.yml inspect docs boundaries
# Markdown (gem-bundled template)
curl -H "Accept: application/x.wanderland.template" \
http://localhost:9295/inspect/docs/boundaries
bundle exec bin/wanderland --type cli config.yml \
--accept application/x.wanderland.template inspect docs boundaries
Markdown looks like:
# Boundary Reference
80 boundaries registered.
## Framework signal shapes
| key | declared by |
| --- | --- |
| `status` | signals |
| `error` | signals |
| ...
### `signals` schemas
- **`:signals:stop:halt`** — payload keys: `status`, `error`
- **`:signals:stop:error`** — payload keys: `status`, `error`, `cause`
- **`:signals:stop:denied`** — payload keys: `status`, `error`, `missing`
## Boundaries
### `echo`
Echo input params back as result
- **identity**: `boundary:echo`
- **source**: `Wanderland::Boundaries::Echo`
- **capabilities**: `echo`
**Input shape**
\`\`\`yaml
params: true
\`\`\`
**Output shape**
\`\`\`yaml
echoed: true
\`\`\`
---
### `format`
...
For one boundary instead of all:
curl http://localhost:9295/inspect/docs/boundary/echo
bundle exec bin/wanderland --type cli config.yml inspect docs boundary --name echo
# Markdown for one boundary
curl -H "Accept: application/x.wanderland.template" \
http://localhost:9295/inspect/docs/boundary/echo
bundle exec bin/wanderland --type cli config.yml \
--accept application/x.wanderland.template inspect docs boundary --name echo
Operators who want a custom template ship one alongside their config and pass it as a query param:
curl -H "Accept: application/x.wanderland.template" \
"http://localhost:9295/inspect/docs/boundaries?template=docs/templates/refguide.md.erb"
The template path is resolved under the runtime's config_dir; .. segments are rejected. Gem-bundled templates use the wanderland: prefix (wanderland:templates/<file>).
The CLI runner derives a subcommand name from each route's path. The mapping is mechanical:
/inspect/route/:name → inspect route.:name) become CLI options: --name VALUE.key=value argv.A full URL → CLI translation:
| URL | CLI form |
|---|---|
GET /health |
health |
GET /inspect/routes |
inspect routes |
GET /inspect/route/:name |
inspect route --name <value> |
GET /inspect/scenarios/:route |
inspect scenarios --route <value> |
GET /inspect/diagnostics/:severity |
inspect diagnostics --severity <value> |
GET /inspect/framework-schema/:stage |
inspect framework-schema --stage <value> |
GET /inspect/shape-schema/:module |
inspect shape-schema --module <value> |
GET /inspect/docs/boundaries |
inspect docs boundaries |
GET /inspect/docs/boundary/:name |
inspect docs boundary --name <value> |
Run bundle exec bin/wanderland --type cli config.yml help to see the full list a particular site exposes — the CLI table includes user-declared routes alongside the inspect family.
/inspect/routes are user-declared only. The framework's own routes (the inspect family, liveness triptych) are CORE_ROUTES — every wanderland-core service exposes them, but they don't show up under user-route listings.--accept is the CLI's HTTP-Accept equivalent. Same negotiation seam in both adapters; the value sets effective_mime for the format slot to dispatch on.effective_mime falls back to text/plain; the registered text-rendering formatter emits YAML for human skimming. Pass --accept application/json for JSON./inspect/diagnostics is a runtime snapshot. Boot-time diagnostics are captured at boot and don't update; runtime errors don't show up here./inspect/docs/* is the doc generator — it folds /inspect/boundary/* + /inspect/framework-schema + /inspect/shape-schema into one tree and optionally renders through ERB templates. Use it when you want the data; use the others when you want a single field.wanderland.dev