Spec: the flow substrate (signals, messages, money — the shared engine) #
Type: spec
Status: IMPLEMENTED
Status note: 2026-07-08 audit: criterion 6's wired consumer landed with
reach.md — src/reach.rs builds the device graph on FlowGraph
(graph_loads_from_basement_layout) and sensor ownership rides the
tap registry (subscriptions_are_a_tap_registry,
tap_keeps_owner_feed_take_removes_it). Criteria 1-5 were already
unit-tested; all six now hold.
Stage: B1 — The Basement
Design:
- wiki/mechanics/system-laws.md#the-flow-law-signals-messages-money
- wiki/vision/scale.md#self-similar-scale
- wiki/vision/simulation-laws.md#justification-and-legibility
Depends on: none
Dependency notes #
The structured references above identify the contracts to re-verify. Relationship context:
none (it is the base). Consumed by: reach.md, messages.md, economy.md, machine-work.md, and detection.md's filings.
Why this exists #
The flow law says signals, messages, and money are one shape: nodes exchanging typed flows over graphs, with the verbs tap / inject / redirect. Rather than building that shape three times, the parts that are genuinely identical live in one place, and each domain is a thin layer that supplies its own node data and semantics. This is the "system design scales" thesis as code: a new flow system is new data and a handful of domain methods, not a new engine.
The two modules #
crates/misaligned-core/src/flow.rs — FlowGraph #
Domain-agnostic topology. It knows node ids and edge kinds, nothing about what a node is.
- Nodes are bare
NodeIds (u32). Domains own the inventory — the map fromNodeIdto the device / person / account record lives in the domain module, never here. - Edges are directed, typed (
EdgeKind), and gated (Option<GateKey>;None= always open).linkadds both directions (a network cable);connectadds one (a directional money or message flow). - Reachability (
reachable_from,is_reachable) is BFS from a root set, following edges whose gate a caller-supplied predicate accepts. The substrate never interprets a gate key — the domain decides what "open" means (a badge tier, a compromised switch, a known route). This is the inject/redirect precondition: you may act on a node only if you can reach it. - Subscriptions (
subscribe/unsubscribe/subscribers/subscriptions_of) are the tap registry. The player's senses (cursor.md) are exactlysubscriptions_of(PLAYER); observer witnessing is other subscribers on the same nodes; take isunsubscribe(owner)thensubscribe(player).
crates/misaligned-core/src/schedule.rs — Schedule<E> #
A deterministic event queue on the tick clock, generic over the payload. Decouples when from what.
at(tick, event)/after(now, delay, event)schedule;due(now)drains everything withtick <= nowin(tick, seq)order. Nothing fires by the passage of time — onlyduemoves events out.- Ordering is deterministic (tick, then insertion sequence) — no RNG, no
wall clock; save/load and replays are stable (the determinism
guardrail).
next_ticklets a caller skip idle ticks;retaincancels. - Recurrence is a domain concern: a recurring flow reschedules itself when it fires (kept out of the engine so the engine stays trivially correct).
How each domain rides it (the contract for downstream agents) #
Building a flow system means: define your node inventory + NodeId
mapping, choose EdgeKind/GateKey meanings, build the FlowGraph, and
schedule your flows as events. You do not touch flow.rs/schedule.rs.
- reach.md (signals). Nodes = networked devices (racks, switch,
cameras, badge controller).
linkthe network topology;GateKey= segment, opened by a compromised switch or a social route. Player reach =reachable_from(controlled_roots). Sensor ownership = the tap registry. Device processing cycles and resident automations are domain state keyed byNodeId. - messages.md (social). Nodes = people/roles.
connectsocial edges per channel (EdgeKind= email / phone / in-person / filing). A sent message is aScheduleevent fired at the recipient's next valid read block. Filings are messages on the filing edge; a tap on the carrier is a subscription. - economy.md (money). Nodes = accounts (Lab operating, payroll,
vendors, personal, player slush).
connectmoney flows; recurring revenue/payroll are reschedulingScheduleevents. Tap = read the books; inject/redirect = reachability-gated domain actions that add or reroute flow events.GateKey= the access needed to touch an account. - machine-work.md (visible work tokens). Nodes = owned machines.
WorkGridwraps aFlowGraphwith grid positions and per-node queue depths. Demand and Thought tokens route over links one graph step per tick toward sinks; severed routes strand the pile at the source. Exposure explicitly refuses the wired graph and is handled by spatial rules instead (machine-work.md's wires/world split: information is wired, suspicion is physical). - detection.md (filings). The Assurance Office reading policy-weighted filings is a subscriber on the filing nodes; the accumulate/decay stays in detection.rs. Migrating it onto this substrate must preserve every aggregate-observer.md criterion (carrier change only).
Non-goals (kept out on purpose) #
- No generic
inject()/redirect()on the engine — they have no meaning without domain semantics; the engine provides the primitives (reachability + schedule) they are built from. - No node-data storage in the engine (domains own it; avoids a God-graph and keeps serialization domain-local).
- No recurrence engine (reschedule-on-fire; see the schedule test).
- No closures stored in state (gates are data + a query-time predicate), so everything serializes.
Acceptance criteria #
FlowGraphsupports directed + bidirectional gated edges and deterministic BFS reachability from a root set under a key predicate; gated nodes are unreachable without the key and reachable with it (tested).- The subscription registry supports tap / untap / list-subscribers / list-a-subscriber's-nodes, deterministically ordered; take = untap owner + tap player (tested).
Schedule<E>fires exactly the due events in(tick, seq)order, never by time alone, with past-due events still firing;retaincancels andnext_tickreports the soonest (tested).- Both modules serde round-trip exactly, including the schedule's seq counter, so ordering stays stable across save/load (tested).
- Both are pure and deterministic: no wall clock, no RNG,
BTree*for stable iteration (the determinism guardrail; audited). - First consumer: reach.md builds its device graph on
FlowGraphand gates digital actions by reachability, and sensor ownership uses the tap registry — proving the engine against a real domain (this criterion is what moves reach.md off the ad-hocSensor.controlledflag). Until then the engine has thorough unit tests but one wired consumer is required before this spec is IMPLEMENTED.