Node Routing Policy v3
What is live: Property Management at
+1 510-216-0079 is accepting calls on clean candidate SHA
9b4082d. That proves the property flow, not the routing architecture.
The Worker/Durable Object dispatcher, persisted routing policy, automatic fallback,
and routing dashboard are not running.
Current Implementation Status
| Surface | State on 2026-07-09 | Operator meaning |
|---|---|---|
| Property Management line | Live on clean candidate SHA 9b4082d | Create, list, read, and update ticket flow is callable. |
Node mode and /healthz | Implemented and live | Node-local health foundation; not policy routing. |
| Telnyx webhook verification | Implemented at 3450bcab; not deployed | Blocked until TELNYX_PUBLIC_KEY is provisioned and verified in observe mode. |
| L5 authentication config gate | Implemented at 6b531686; not deployed | Blocks enforce mode without a valid public key. |
| Routing controls | Interactive preview only | Selections change the page but do not save policy or route calls. |
| Worker/Durable Object engine | Not built | No edge caller/line assignment or automatic M1/M3 fallback. |
| Policy dashboard and decision viewer | Not built; Phase 3 | No operative routing dashboard exists yet. |
Existing /ops, /voice.html, /monitor.html,
and /admin pages cover service operation, call observation, legacy
monitoring, and business administration. None is the routing-policy dashboard.
Decision
Use a Cloudflare Worker and Durable Object as the deterministic ingress dispatcher, policy authority, and call-ownership serializer. M1 remains stable, M3 remains candidate, and the RIFF business panel remains the policy editor.
Do not implement the earlier app-to-app forwarding design. An ambiguous timeout can make both nodes answer, and best-effort policy copies can split brain.
The Durable Object is a deliberate safety dependency. If it cannot serialize
ownership, ingress returns a retryable 503; it never bypasses the
authority and forwards directly to a phone node.
Fable 5 Review
Fable 5 agreed with the edge-ownership direction but found v2 not ready to implement. V3 resolves its P1 decisions: DO outage behavior, answer fencing, recoverable deduplication, persistent fail-closed node mode, candidate fallback limits, non-destructive drain, media reconnect, line registry, key rotation, and Worker/DO rollback.
Read the complete adversarial review and its disposition.
Findings
| Priority | Finding | Required correction |
|---|---|---|
| P0 | The deployed public call webhook has no Telnyx signature gate. | Repository implementation exists; provision the key, validate observe mode, and deploy enforcement before routing work. |
| P0 | Forward timeout can have an unknown outcome. | Serialize ownership and deduplicate event IDs; never immediately fail over after forwarding starts. |
| P0 | Active media cannot migrate. | Limit failover claims to new calls before ownership is committed. |
| P1 | A registered connector can front a dead origin. | Use signed heartbeats and withdraw unhealthy origins. |
| P1 | Best-effort peer policy sync permits split brain. | Use one versioned Durable Object policy writer. |
| P1 | A dirty checkout can be labelled candidate. | Deploy an exact SHA from a clean supervised runtime worktree. |
Policy
Resolution order is an unexpired caller override, then a stable business
line_id backed by normalized DID, then the default node. Caller
rules expire within 24 hours. Updates use a monotonically increasing revision
and compare-and-set.
{
"schema": "riff_node_routing_policy_v1",
"revision": 42,
"default_node": "M1",
"by_line": {
"property_management_ca": {
"called_number": "+15102160079",
"preferred_node": "M3"
}
}
}
Assignment
- Verify the Telnyx signature and timestamp against the raw body before parsing, assignment, or any other side effect.
- Reuse existing ownership or resolve the current policy revision.
- Select preferred only when its heartbeat, mode, capacity, SHA, cleanliness, tunnel, origin, and DID registry are eligible.
- Persist an ownership assignment with a monotonic epoch before forwarding.
- Worker forwards through a private/assertion-gated origin route. Origin verifies Telnyx again plus the RIFF assertion, then creates or resumes a durable event state machine.
- Origin acquires a DO answer fence for the current epoch immediately before calling Telnyx.
- Origin answers with a media-session token supporting fenced same-call reconnect.
- Duplicates resume incomplete work or return the recorded terminal result.
Failure Semantics
If preferred is known unavailable before forwarding, assign fallback. If forwarding has begun and the result is unknown, keep the original ownership and return a retryable failure. A DO alarm and Telnyx retry reuse the same claim.
Both Telnyx primary and failover URLs terminate at the same Worker/DO. A DO outage
returns 503 rather than bypassing call ownership.
Soft drain blocks new assignments without killing the media connector. Node loss during an active call may drop that call; active migration is not part of v1.
Clean Deployment
deploy-candidate.sh must deploy a full remote SHA into a detached,
clean runtime worktree, update the service supervisor, drain before restart,
verify local health and artifacts, expose the connector, run public L5, and
restore the previous runtime automatically on failure.
A node with dirty=true, stale heartbeat, routing hash mismatch,
shadow/drained mode, or exhausted capacity is ineligible.
Mode is persisted outside /tmp and missing/invalid state boots drained.
Candidate receives only explicit canaries unless it separately passes
fallback_approved.
Panel Contract
Say Property Management prefers M3, not "goes to M3." Show actual next-call target, fallback, node SHA, cleanliness, health, policy revision, caller override expiry, and recent decision evidence. Until the engine exists, the routing guide is labelled as a non-operative preview.
Build Order
- Telnyx verification, recoverable event ledger, media binding, privacy audit, and carrier retry/reconnect measurements.
- Persistent mode, clean deploy/promote/rollback, canonical line registry, node-scoped L5, and signed eligibility heartbeats.
- Worker, Durable Object, fenced ownership epochs, dedicated origins, and versioned edge rollback.
- Versioned policy API, business panel, and decision viewer.
- Replay, chaos, explicit-GO canary, failover, and rollback drills.
Acceptance
The engine is ready only when unsigned webhooks have no side effects, duplicate deliveries cannot answer twice, stale epochs cannot answer, DO loss cannot bypass ownership, dirty nodes cannot be selected, dead preferred origins route new calls to fallback, every decision cites policy revision and clean SHA, deployment survives the watchdog, and public L5 passes before a human is told to call.
Full normative detail is in
docs/superpowers/specs/2026-07-08-node-routing-policy-design.md.