Wanderland

Wanderland Core: Archetype System

The engine is a VM. The archetype is the combustion sequence. The user config is the fuel. Crossings are the exhaust.

The Core Loop

The engine runs two slot sequences.

The boot sequence is fixed. runtime.yml lists the slots: load boundaries, configure resolvers, mount storage, mount adapters, capture env, register injections, mount routes. Sites do not pick this sequence — it runs the same way for every boot.

The per-request sequence is the route's chain. A route declares it in one of two ways. Inline: a chain: field listing boundaries. Or via an archetype: a named YAML template registered by a pack, with !UserConfig knobs the user fills with their own implementation choices.

Each slot binds a boundary. Each boundary produces a crossing. Same engine, two different fuel sources.

Archetypes

A work archetype is a YAML file shipped by a pack. It defines the structural chain for a route: the sequence of boundaries that always run plus !UserConfig knobs where the user names a specific implementation.

A pack registers its archetypes at gem-load:

Wanderland::Archetypes.register(
  "infrastructure_deployment",
  File.expand_path("archetypes/infrastructure_deployment.yml", __dir__)
)

The user picks one in config:

archetype: infrastructure_deployment   # default for every route
provisioner: cfn_provisioner            # fills the provisioner knob

Or per route:

routes:
  /deploy:
    archetype: infrastructure_deployment
    provisioner: cdk_provisioner

Route-level overrides top-level. A route without an archetype uses its chain: field directly. An unknown archetype name fails at boot with the registered list.

Scenario Archetype

An archetype that ships from a deployment pack defines the chain shape and exposes the knobs the user has to fill:

# wanderland-pipelines-pack/lib/.../archetypes/infrastructure_deployment.yml
name: infrastructure_deployment

slots:
  - name: start_run
    boundary: start_run
    args:
      system: !UserConfig system
      environment: !UserConfig environment

  - name: provision
    boundary: !UserConfig provisioner
    args:
      template: !UserConfig template
      parameters: !UserConfig parameters

  - name: publish
    boundary: publish_stack

  - name: complete
    boundary: complete_run

Three of the slots name fixed boundaries (start_run, publish_stack, complete_run). One — provision — names its boundary as a !UserConfig thunk. The user's config decides which provisioner runs:

archetype: infrastructure_deployment
provisioner: cfn_provisioner
template: stacks/web.yml
parameters: { instance_type: t3.medium }

!UserConfig thunks resolve at mount time. UserConfig.data is scoped to the route — config domain merged with the route's own spec — so the same archetype produces different chains per route when the route fills knobs differently.

The pack ships the boundary classes (cfn_provisioner, cdk_provisioner, start_run, etc.). The pack's entry file globs its boundary directory and requires each file; the existing boundary :name, ... DSL self-registers.

Engine Archetype

The boot init chain lives at lib/wanderland/runtime.yml. It is the same for every site:

name: runtime

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: adapters
    boundary: boot_mount_adapters
    args:
      config:
        adapters: !UserConfig adapters

  - name: triggers
    boundary: boot_mount_triggers
    args:
      config:
        triggers: !UserConfig triggers

  - name: env
    boundary: env_snapshot
    args:
      env_allowlist: !UserConfig env_allowlist

  - name: denials
    boundary: context_from_shape
    args:
      under: denials
      shape: !UserConfig denials
      capabilities: [denials]

  - name: core_injections
    boundary: register_injections
    args:
      injections:
        - { boundary: enforce_denials, position: interleave }
        - { boundary: verify_route,    position: last }
        - { boundary: trace_emit,      position: last }
        - { boundary: format,          position: last }
        - { boundary: seal,            position: last }

  - name: user_injections
    boundary: register_injections
    args:
      injections: !UserConfig injections

  - name: archetype
    boundary: boot_mount_archetype
    args: {}

The terminal slot is boot_mount_archetype. It mounts core routes, then walks the user's routes. For each route it picks an archetype:

When an archetype is named, the registered YAML loads, slots resolve through TemplateEngine with UserConfig.data scoped to (config domain ⊕ route spec), and the resolved slot list becomes the route's chain.

An unknown archetype name fails at boot. The error lists the archetypes registered through Wanderland::Archetypes so the operator can see which packs contributed what.

The Combustion Model

Every level of the system is the same loop:

Create potential → burn through boundaries → produce crossings. Same engine. Different fuel.

Tracing

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.

What Dissolves

Three artifacts carry the model.

runtime.yml lives in wanderland-core, next to runtime.rb. One file, one boot sequence. Sites do not pick from a menu of boot archetypes.

Wanderland::Archetypes is a registry: register(name, path), lookup(name), load(name), registered. Packs register their archetype YAMLs from their gem entry file. Site-local archetypes can register the same way from config.ru or a Rakefile.

boot_mount_archetype is the terminal boundary in the boot chain. It owns the per-route resolution: route override → top-level default → chain field → fail loud.

A pack that contributes archetypes follows two conventions. Boundaries: glob lib/<pack>/boundaries/**/*.rb and require each — the boundary :name, ... DSL self-registers when the file loads. Archetypes: glob lib/<pack>/archetypes/*.yml and call Wanderland::Archetypes.register(name, path) for each. No new DSL. The pack's entry file is the single integration point.

Site Audit

wanderland.dev

oculus-view: fence: fence execute HTTP 404

Input Adapters (Modes)

The 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:

The CLI Entry Point

wanderland [--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

Command Registration

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

The shim is a symlink to wanderland — the binary name is passed as the command. deploy -> wanderland deploy -> look up :commands:deploy -> run.

Mode Inference

If no mode flag is given:

Cross-Mode Operation

wanderland --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.

Commands as Crossings

The storage is the command registry:

The CLI is the engine with argv as fuel. Everything is a crossing.