Wanderland

The Forking Walk

At each level, walk up AND fork into the capability child. Common config on the main hierarchy, capability-specific handlers at the fork.


The Problem

A capability request needs two kinds of data from the type hierarchy: the common config (bridge transport, host, port) that lives on the type itself, and the capability-specific handlers (what actions to run for peek vs poke) that need to be scoped to the capability name.

The original design concatenated type_addr:capability into a single address and walked that. This worked but created a fake address — the walk started from a position that only existed if you happened to put a record there. It also meant only the type walk could find capabilities. Sessions and targets couldn't contribute capability-specific behaviour.


The Mechanism

The forking walk adds an optional capability parameter. At each level of the walk, after collecting crossings at the current address, the walk also queries at {current}:{capability}. Both sets of results go into the same stack.

Walk :bridges:json-rpc:remark:streams:mdast (capability: peek)

:bridges:json-rpc:remark:streams:mdast       ← collect (mdast config)
:bridges:json-rpc:remark:streams:mdast:peek  ← fork (peek handlers for mdast)
:bridges:json-rpc:remark:streams             ← collect (stream defaults)
:bridges:json-rpc:remark:streams:peek        ← fork (peek for streams)
:bridges:json-rpc:remark                     ← collect (remark bridge config)
:bridges:json-rpc:remark:peek                ← fork (peek for remark)
:bridges:json-rpc                            ← collect (json-rpc transport)
:bridges:json-rpc:peek                       ← fork
:bridges                                     ← collect (bridge defaults)
:bridges:peek                                ← fork
:                                            ← collect (root)
:peek                                        ← fork (universal peek)

Fork addresses that don't exist return empty — no cost beyond the query. The fork is one extra store query per level.


Applied to All Three Walks

The capability fork applies uniformly to all three walks in the compiler:

Walk Starting Address What the Fork Finds
TO Target address (e.g. :streams:my-doc) Capability-specific overrides on the target or its ancestors. A :streams:peek could add default peek behaviour for all streams.
FROM Session address (e.g. :sessions:users:gfawcett) Capability-specific session behaviour. A :sessions:admin:peek could add audit logging for admin peeks. A :sessions:peek could add rate limiting for all sessions.
TYPE Type address (from target's type_addr) The primary source of capability handlers. Bridge config on the main hierarchy, handlers at the fork. :bridges:json-rpc:remark:streams:mdast:peek has the peek handlers.

All three contribute, collapse merges them with the standard precedence (type lowest, from highest). This means:


Universal Capabilities

A capability defined at root (:peek, :repl, :introspect) is found by every walk because every walk reaches :. No special registration needed. Put a record at :repl with type_addr: ":capability" and every type in the system can now repl.

Type-specific capabilities override or extend the root default via normal collapse precedence. :bridges:json-rpc:remark:streams:mdast:peek overrides :peek because it's more specific (closer to the leaf).


Implementation

# query/walk.rb
def initialize(address, types: nil, capability: nil, store: nil)
  @capability = capability
end

def perform
  while current && !visited.include?(current)
    collect_at(current, layers)

    if @capability
      fork_addr = current == ':' ? ":#{@capability}" : "#{current}:#{@capability}"
      collect_at(fork_addr, layers) unless visited.include?(fork_addr)
      visited << fork_addr
    end

    current = parent_of(current)
  end
end
# engine/compiler.rb — all three walks get the capability fork
def resolve_ast
  to_stack   = Query::Walk.new(@address,    capability: @capability).perform
  from_stack = Query::Walk.new(@session_id, capability: @capability).perform
  type_stack = Query::Walk.new(@type_addr,  capability: @capability).perform

  collapsed = Query::Collapse.new(to_stack, from_stack, type_stack).perform
end

Relationship to Anti-Crossings

Anti-crossings work naturally with forked addresses. An :anti:poke at :bridges:json-rpc:remark:streams:mdast:peek cancels a :poke at that same fork address. The anti-crossing filtering happens in the store query, before the walk sees the results. The fork just generates one more address to query — all existing store-level mechanisms apply.