The framework's reflective surface: runtime endpoints that report what's registered, what's configured, and what each request actually walked through. Everything the engine knows about its own structure is exposed as HTTP routes, reachable with nothing but curl.
Every wanderland engine ships with a fixed set of introspection endpoints mounted in Engine::CORE_ROUTES. Sites can override their handler implementations but cannot remove the routes — operators always have a known URL to reach for "what am I looking at?"
Three categories:
/health, /status, /healthcheck. Load balancer ping through to full service manifest./inspect/route/:name. Report user-declared vs. post-injection chain for any named route./inspect/boundary/:name. Report manifest entry for any registered boundary.Collectively these are the reflective substrate on which JSON Schema synthesis, capability audits, and operational tooling are built.
/inspect/route/:nameReports the chain composition of a single named route. The :name capture is matched against Engine#named_routes, which only indexes user-declared routes (framework routes like the health triptych and /inspect/* are not listed as targets).
{
"name": "hello",
"method": "get",
"path": "/hello",
"user_chain": ["echo"],
"compiled_chain": ["enforce_denials", "echo", "trace_emit"],
"registered_injections": []
}
name, method, path — straight from the route registration.user_chain — the ordered boundary names the site declared in config.yml. These are the slots an operator wrote intentionally.compiled_chain — the ordered boundary names the walker actually steps through. The delta between user_chain and compiled_chain is the injection layer's contribution (denial enforcement, trace emission, formatting, plus any site-custom injections).registered_injections — the ordered list of declared injections (name + position) that produced the compiled chain. Placeholder empty array today; populated once the injection layer lands.{
"error": "unknown route: \"nope\"",
"available": ["hello", "report"]
}
Unknown name returns 404 with the sorted list of available route names — safe to expose, because named routes are already the site's declared public surface.
/inspect/boundary/:nameReports the manifest entry for any boundary registered in Wanderland::Boundary. Includes framework-shipped boundaries (echo, enforce_denials, health, etc.) and any site-provided ones loaded from the boundaries directory.
{
"name": "echo",
"identity": null,
"requirements": [],
"capabilities": ["echo"],
"description": "Echo input params back as result",
"when_shape": null,
"source": "Wanderland::Boundaries::Echo"
}
name — the registration key.identity — the PKI identity this boundary signs as, if one was declared. Null for boundaries without a signing identity.requirements — scopes the caller must hold.capabilities — what this boundary declares about itself.description — human-readable line from the boundary DSL.when_shape — the declared when: guard for this boundary, if any. Shown as its raw ShapeMatcher shape (nil means "use BASE_DEFAULT_WHEN at runtime").source — the class name that registered the boundary.Unknown name returns 404 with the full sorted list of registered boundaries:
{
"error": "unknown boundary: \"nope\"",
"available": ["adapter_cli", "adapter_http", "archetype_inspect", ...]
}
The current response covers what the Boundary::Registration struct carries today. Additions will come as the boundary DSL grows:
input_shape / output_shape — ShapeMatcher shapes describing the boundary's contract. Once declared, these become the seed for JSON Schema synthesis.serves — the MIME type a formatter boundary renders (landing with the format stage).Each new declaration on the boundary DSL propagates through this response without further plumbing — the endpoint dumps whatever the registration carries.
Neither inspection endpoint carries authentication or authorization by default. Their payloads describe chain shape and boundary contracts — operational information, not sensitive runtime state. Sites that need to gate introspection add an auth injection (see wanderland-core-injections for the pattern) with position: { before: inspect_route } and position: { before: inspect_boundary }, or target the whole /inspect/* surface with a matched interleave condition.
The boundary manifest already carries enough to synthesize partial schema: requirements become scope predicates, capabilities become declared claims, when_shape becomes activation conditions. The two missing pieces — input_shape and output_shape — bring the surface up to full contract:
input_shape is that boundary's input JSON Schema.args: resolved through !UserConfig thunks, composes into a JSON Schema for config.yml.output_shape of slot N must satisfy the when: of slot N+1 — a static check the engine can run at boot to flag dead slots.Introspection is the read path; the DSL additions are the write path. As the DSL grows the endpoints reflect the expanded manifest automatically, and the JSON Schema synthesizer reads through the same surface any operator does over HTTP.
runtime_inspectruntime_inspect is a scenario-only boundary used in spec/scenarios/*. It boots a fresh runtime from a site path, optionally dispatches a request through it, and returns the boot context, request context, and dispatch crossing as a shape for ShapeMatcher assertions. It is not mounted as a route and does not overlap with the live /inspect/* endpoints.
runtime_inspect when a scenario needs to spin up an arbitrary site configuration in-process and assert on its chain or dispatch behavior./inspect/route/:name or /inspect/boundary/:name when an operator — or an integration test — wants the same information from a live engine.