CyberCLI

← All engines · Delivery

RoutingMatrixStore

stable

v1.0.0 · last updated 2026-06-06 · source: cybercli.delivery.routing.RoutingMatrixStore

§1

What it does

Per-CYCON × per-channel grid that decides which channels fire at which posture level — operator-editable on the dashboard, backed by a SQLite override table, with a hardcoded default routing as the floor.

§2

Why you care

The routing matrix is the engine that makes 'wake me up at 3am only if CYCON drops to 1' configurable instead of hardcoded. Each row in the grid is a (CYCON level, channel type) pair, and each cell says 'fire this channel at this posture or don't.' The dispatcher reads the matrix on every PolicyInput to decide which channels get a plan.

Other AI security tools either hardcode the routing (you wake up to whatever they think is important) or expose it as a YAML file (easy to mis-edit). CyberCLI exposes the matrix as a visual grid on the dashboard's /delivery page — green cell = fires, gray cell = silent — and persists every operator edit with an audit row, so 'why did Slack fire at CYCON 4 last Thursday' is answerable two months later.

The matrix is also where a buyer ramps a new channel deliberately. Add ntfy as a destination, leave its row in the matrix gray for a week to watch the dispatcher's would-have-fired log, then enable the cells one CYCON tier at a time. The cell is the unit of trust; you never have to flip a giant on/off switch.

§3

How it works

The DEFAULT_ROUTING table in routing.py maps each DefconState → a set of ChannelTypes that fire at that state. Defaults are conservative: email at CYCON 5 (peacetime), email+slack at CYCON 4 (vigilance), email+slack+pushover at CYCON 3-1 (alert through critical). That table is the floor — if the operator never edits, those channels fire.

Operator edits go to the `delivery_routing_overrides` SQLite table via `RoutingMatrixStore.set_rule(cycon, channel, enabled)`. The table has a composite primary key on (cycon, channel) so a re-edit on the same cell replaces, not duplicates. Each set_rule call also emits a `delivery_routing_set` audit row with the previous value + the new value + the operator id.

On read, `effective_channels_for(cycon)` merges the defaults with the overrides: defaults form the initial set, then each override with `enabled=True` is added and each with `enabled=False` is removed. The result is a deterministic set of ChannelTypes the dispatcher then iterates over. The merge is computed per dispatch — no caching means an operator can hot-swap a row mid-incident and the next signal picks it up.

The matrix is read-only for non-operator code paths. The dispatcher only ever calls `effective_channels_for()`; only the dashboard's /delivery page (and the rare cybercli CLI helper) calls `set_rule()`. That separation is what makes the matrix safe — the dispatcher cannot accidentally rewrite the operator's intent.

§4

Configure & observe

CLI

$ cybercli audit tail -n 10

Filter for outcome=delivery_routing_set to see every matrix edit — who, when, which cell, previous → new state.

Dashboard surfaces

/delivery

The visual matrix. Each cell is a green / gray toggle. Click a cell to flip; the engine writes an audit row + applies the change to the next dispatch.

/audit (filter outcome=delivery_routing_set)

Forensic replay of every matrix edit. Answers 'when did we add Slack at CYCON 4 and who did it.'

Audit row shape

Outcomes emitted
delivery_routing_set
Parameters block

parameters.cycon + parameters.channel + parameters.previous_enabled + parameters.new_enabled + parameters.actor on every row. Combined with the immutable defaults table, the audit log is sufficient to re-derive the live routing matrix state at any point in history.

§5

Status

Stable — the matrix schema has been frozen since 2026-04-20 and the SQLite table has not needed a migration. The dashboard /delivery page is the only operator surface that writes to it.

Shipped 2026-06-07 (storage + API half): RoutingMatrixStore exposes per-channel severity floors. New SQLite table `delivery_channel_severity_floors` stores rank 1..5 (INFO=1, LOW=2, MEDIUM=3, HIGH=4, CRITICAL=5). Methods: set_severity_floor / clear_severity_floor / get_severity_floor / list_severity_floors / channel_satisfies_severity. Operator says 'pushover only at HIGH or above regardless of CYCON' via set_severity_floor(PUSHOVER, 'high'). The dispatcher integration that drops channels failing the floor when computing dispatch plans ships in v1.x.

Shipped 2026-06-07 (storage + API): a new `delivery_recipient_routing` table + RoutingMatrixStore.set_recipient_rule / clear_recipient_rule / list_recipient_overrides / effective_channels_for_recipient. Per-recipient overrides stack on the matrix — recipient.enabled=false removes a channel for that recipient even if the matrix has it on; recipient.enabled=true forces it on even if the matrix has it off. The dispatcher integration that switches the dispatch loop to the per-recipient variant is the v1.x follow-on.

How this engine talks to the rest

Upstream · feeds this engine
Downstream · this engine feeds