The engine is a VM. The archetype is the combustion sequence. The user config is the fuel. Crossings are the exhaust.
The engine does one thing:
!UserConfig thunks into the archetype slotsEvery slot is a boundary. Every boundary creates potential, transforms it, and produces an output crossing. The slot sequence is the combustion — potential in, crossings out.
An archetype is a YAML file shipped inside the gem. It defines a slot sequence — named positions, each binding a boundary to user config via !UserConfig thunks.
The terminal slot determines the runtime shape:
| Archetype | Terminal behavior | Runtime |
|---|---|---|
engine |
rack_server — blocks on HTTP |
Long-running service |
scenario |
shape_validate — checks expected shape |
One-shot, exits |
cli |
Returns result | One-shot, exits |
Everything before the terminal slot is identical: load boundaries, configure resolvers, mount storage, seed data. The archetype IS the program.
A scenario is not a special class. It is a config that boots through the scenario archetype:
# lib/wanderland/archetypes/scenario.yml
name: scenario
description: One-shot boundary execution with validation
slots:
- name: boundaries
boundary: boot_load_boundaries
args:
target: !UserConfig boundary_path
- name: resolvers
boundary: boot_configure_resolvers
args:
config:
resolvers: !UserConfig resolvers
- name: storage
boundary: boot_mount_storage
args:
config:
storage: !UserConfig storage
- name: seed
boundary: seed_storage
args:
records: !UserConfig records
- name: execute
boundary: !UserConfig operation
args: !UserConfig input
- name: validate
boundary: shape_validate
args:
expected: !UserConfig expected
The user YAML provides the fuel:
archetype: scenario
boundary_path: ../lib/wanderland/boundaries
resolvers:
Fixture:
fixture_dirs: [fixtures]
storage:
mounts:
":test:":
driver: sqlite
path: ":memory:"
records:
- to_addr: ":test:overlay:01"
from_addr: ":boundaries:spring"
type_addr: ":types:tick"
payload: { shape: circle, color: red }
at: "2026-04-09T10:00:00Z"
operation: overlay_ops
input:
action: compose
expected:
payload:
shape: star
color: red
No Scenario class. No load_all. No hydrate_context!. No run. No verify. Just YAML through the engine.
The rspec spec becomes:
Dir.glob("scenarios/**/*.yml").each do |path|
it File.basename(path) do
result = Wanderland.boot(path)
expect(result).to be_passing
end
end
The HTTP service is the same pattern:
# lib/wanderland/archetypes/engine.yml
name: engine
slots:
- name: boundaries
boundary: boot_load_boundaries
args:
target: !UserConfig boundary_path
- name: resolvers
boundary: boot_configure_resolvers
args:
config:
resolvers: !UserConfig resolvers
- name: storage
boundary: boot_mount_storage
args:
config:
storage: !UserConfig storage
- name: triggers
boundary: boot_mount_triggers
args:
config:
triggers: !UserConfig triggers
- name: routes
boundary: boot_mount_routes
args: {}
Same boot, different terminal behavior. The boot_mount_routes slot creates the Rack app. config.ru calls run Wanderland.boot("config.yml") and blocks on traffic.
Every level of the system is the same loop:
Create potential → burn through boundaries → produce crossings. Same engine. Different fuel.
Every boundary execution opens a trace span. The span tree IS the combustion record:
boot (root span)
├── boot_load_boundaries (span)
├── boot_configure_resolvers (span)
├── boot_mount_storage (span)
├── seed_storage (span)
├── overlay_ops (span)
│ ├── Storage.append × 3
│ └── Storage.overlay
└── shape_validate (span)
No separate trace mechanism. The span tree is built by Boundary.execute opening spans. Storage operations record on the current span. The trace writes to storage at the engine's boot address.
Scenario class — replaced by the scenario archetypeScenario.load_all — replaced by globbing YAML and booting each oneScenario#run — replaced by the execute slotScenario#verify — replaced by the validate slotScenario#hydrate_context! — replaced by the seed slotShapeMatcher integration in Scenario — replaced by shape_validate boundaryWanderland.boot — simplified to: load archetype, resolve thunks, run slotsThe engine has three input adapters. The adapter determines how fuel arrives. The archetype determines what happens with it. The mode can be explicit (--http, --test) or inferred from the archetype.
| Mode | Adapter | Fuel source | Terminal behavior |
|---|---|---|---|
--http |
Rack | path + verb + params from HTTP request | Long-running, blocks on traffic |
--test |
YAML | fixed input shape from scenario file | One-shot, validate shape and exit |
| (default) | CLI | command + args from argv | One-shot, execute and exit |
All three run the same engine. The adapter is just how !UserConfig gets populated:
!UserConfig per route!UserConfig at boot!UserConfig at invocationwanderland [--mode] [source] [args]
-> resolve source (file path, command name, or stdin)
-> determine mode (from flag, archetype declaration, or inference)
-> load archetype
-> hydrate !UserConfig from source + args
-> run slot sequence
-> terminal slot determines runtime behavior
CLI commands are crossings in storage. Registering a command writes a crossing:
wanderland register --command deploy --for deploy-spec.yml
to_addr: ":commands:deploy"
from_addr: ":sessions:graeme"
type_addr: ":types:command"
payload:
archetype: cli
spec: deploy-spec.yml
Running a command reads the crossing, loads the spec, hydrates the archetype:
wanderland deploy --target prod
# or via shim:
deploy --target prod
:commands:deploy from storage!UserConfigThe shim is a symlink to wanderland — the binary name is passed as the command. deploy -> wanderland deploy -> look up :commands:deploy -> run.
If no mode flag is given:
mode: http -> Rack adaptermode: test -> YAML adapter, validate on exitmode: cli or no mode -> CLI adapter.yml with expected: key -> infer test modewanderland --http scenario.yml boots a scenario as an HTTP service. The operation becomes a POST endpoint. The expected shape becomes the response validator. Same YAML, different adapter.
wanderland --test config.yml boots a service config but runs it as a one-shot health check. Hit /health, validate the shape, exit.
The mode is orthogonal to the archetype. Any config can run in any mode. The adapter just changes how fuel arrives and how the terminal slot behaves.
The storage is the command registry:
:commands:name:commands:name:commands:The CLI is the engine with argv as fuel. Everything is a crossing.