Wanderland

Wanderland Core: Storage IOU Drivers

The Pattern

Every write to storage produces a crossing in the central SQLite index. The crossing always lives at its to_addr. The payload is either inline (small data) or an IOU marker pointing at where the content actually lives.

SQLite (always)
  to_addr:    :pki:keys:my-key
  from_addr:  :system:pki
  type_addr:  :types:pki:key
  sig:        "abc123..."
  at:         "2026-04-10T12:00:00Z"
  payload:    { _iou: "uuid", _driver: "file_store", _args: { path: "/keys/my-key.pem" }, _size: 8192 }

The SQLite index is the source of truth for what happened, when, and signed by whom. Content storage is just where the bytes live. You can swap content backends without losing provenance. You can query across all writes without hitting every backend.

IOU Marker Shape

{
  _iou:    "uuid-here",       # unique handle
  _driver: "file_store",      # which plugin
  _args:   { ... },           # driver-specific args
  _size:   8192               # bytes (optional, for indexing/quotas)
}

The _args hash is opaque to the index. Each driver defines its own arg schema:

Driver Args
file_store { path: "/abs/path" }
yaml_store { path: "/identities.yml", at: "alice.private_key" }
db_store { connect: "postgres://...", table: "blobs", id: "uuid" }
http_store { url: "https://...", method: "GET" }
ssh_store { host: "server", path: "/data/file" }
s3_store { bucket: "...", key: "...", region: "..." }

Driver Interface

A driver is a plugin that implements two methods:

module Wanderland
  module Storage
    module Drivers
      class FileStore
        # Write content, return an IOU marker.
        # The driver picks the args; caller doesn't dictate the path.
        def write(content, hint: nil)
          path = generate_path(hint)
          File.write(path, content)
          {
            "_iou" => SecureRandom.uuid,
            "_driver" => "file_store",
            "_args" => { "path" => path },
            "_size" => content.bytesize
          }
        end

        # Read content given the args from a marker.
        def read(args)
          File.read(args["path"])
        end
      end
    end
  end
end

Drivers register at process load time, same as boundaries:

Wanderland::Storage::Drivers.register("file_store", FileStore.new(root: "/data/files"))
Wanderland::Storage::Drivers.register("yaml_store", YamlStore.new)
Wanderland::Storage::Drivers.register("http_store", HttpStore.new)

Write Path

Storage::Registry.append(record)
  → SQLite driver receives the record
  → Check payload size against threshold
  → If small: store inline
  → If large: extract via configured driver
      → driver.write(content, hint: to_addr) → marker
      → replace payload leaf with marker
  → INSERT lean record into SQLite

Multiple IOU extractions per record are supported — IOU.extract walks the payload tree and replaces every large leaf with a marker. Each marker can use a different driver based on configuration (size, type, content pattern).

Read Path

Storage::Registry.at(addr)
  → SQLite returns lean record(s)
  → Caller receives records with IOU markers in payload
  → Optional: hydrate the record
      → Walk payload, find IOU markers
      → For each marker: lookup driver by name, call read(args)
      → Replace marker with content

Hydration is opt-in. Most queries don't need the full content — they just need to know what exists. Hydrate when you need the bytes.

Existing Drivers Become Plugins

The current FileDriver and YamlStore get refactored into IOU driver plugins:

The SQLiteDriver becomes the universal index. Other backends become content stores accessed via IOU markers.

Configuration via Mount Spec

The storage mount spec declares which drivers extract for which prefixes:

storage:
  mounts:
    ":pki:":
      driver: sqlite
      path: ":memory:"
      content_drivers:
        - condition: { size: { gt: 4096 } }
          driver: file_store
          args: { root: "/var/wanderland/keys" }

    ":streams:":
      driver: sqlite
      path: "streams.db"
      content_drivers:
        - condition: { size: { gt: 1024 } }
          driver: file_store
          args: { root: "./content/streams" }

The SQLite driver consults the content_drivers list when appending. First matching condition wins. No match = inline.

Provenance Survives Driver Migration

Because the crossing record (with sig and trace) always lives in SQLite, you can:

The content is the leaf. The crossing is the root. The marker is the bridge.

Signed I/O

Every driver write is part of a crossing, which is signed by the writing identity. The marker preserves the link — you can follow the marker, read the content, and verify the original crossing's signature still matches the (canonical) record. Tampering with content in the backend doesn't break the index; it breaks the round-trip verification.

For stricter integrity: the marker can include a content hash (_sha256) so a rehydrate-and-verify round trip catches backend tampering immediately. The index sig covers the marker (which includes the hash). The hash covers the content. Two-layer integrity.

Implementation Path