feat: Tailscale graceful degradation — chain never fails

RouteCost.lean:
- latencyClass 4 = 'offline' (Tailscale down/unreachable)
- networkLatencyCost returns qOne for offline (maximum cost)
- Computation continues with local-only fallback

scale_space_solver.py:
- detect_tailscale(): returns available=False if not installed/running
- get_latency_class(): returns 4 (offline) when Tailscale unavailable
- latency_to_voltage/sigma(): map any class to FPGA parameters
- Chain never raises — offline is just another latency class

Verified:
- Tailscale up: 4 peers detected, latency classes assigned
- Tailscale down: returns class 4 (offline), computation continues
- Unknown IP: returns class 4 (offline), no crash
This commit is contained in:
Brandon Schneider 2026-05-28 19:19:14 -05:00
parent f5bc4ab941
commit 159ba50059
2 changed files with 103 additions and 0 deletions

View file

@ -210,12 +210,18 @@ def fammMemoryBonus (a b : RouteNode) : Nat :=
- near (1): same cluster, 1-10ms → qEighth - near (1): same cluster, 1-10ms → qEighth
- far (2): cross-network, 10-100ms → qQuarter - far (2): cross-network, 10-100ms → qQuarter
- derp (3): DERP relay, >100ms → qHalf - derp (3): DERP relay, >100ms → qHalf
- offline (4): Tailscale down/unreachable → qOne (maximum cost)
When Tailscale doesn't exist or is down, the chain must not fail.
Offline nodes get latencyClass=4, which routes computation to local-only.
The cost is still computable — offline is just another latency class.
The latency class determines the FPGA voltage mode: The latency class determines the FPGA voltage mode:
- local → σ₀ (1.2V, exact) - local → σ₀ (1.2V, exact)
- near → σ₁ (1.0V, normal) - near → σ₁ (1.0V, normal)
- far → σ₂ (0.8V, approximate) - far → σ₂ (0.8V, approximate)
- derp → σ₃ (0.6V, coarse) - derp → σ₃ (0.6V, coarse)
- offline → σ₃ (0.6V, coarse, local-only fallback)
This is the "coursing agent" — latency shapes the computation. -/ This is the "coursing agent" — latency shapes the computation. -/
def networkLatencyCost (a b : RouteNode) : Nat := def networkLatencyCost (a b : RouteNode) : Nat :=
@ -227,6 +233,7 @@ def networkLatencyCost (a b : RouteNode) : Nat :=
| 1 => qEighth -- near: 1-10ms | 1 => qEighth -- near: 1-10ms
| 2 => qQuarter -- far: 10-100ms | 2 => qQuarter -- far: 10-100ms
| 3 => qHalf -- derp: >100ms (DERP relay) | 3 => qHalf -- derp: >100ms (DERP relay)
| 4 => qOne -- offline: Tailscale down, maximum cost
| _ => qOne -- unknown: maximum cost | _ => qOne -- unknown: maximum cost
def weighted (weight component : Nat) : Nat := def weighted (weight component : Nat) : Nat :=

View file

@ -15,9 +15,105 @@ smoothing. At each scale σ, nodes whose pairwise cost is below σ·max_cost
are clustered together. The reduced problem is solved, then expanded back. are clustered together. The reduced problem is solved, then expanded back.
""" """
import json
import math import math
import subprocess
from typing import Optional from typing import Optional
# ── Tailscale Detection (graceful degradation) ──────────────────────────
_LATENCY_CLASSES = {
0: {'name': 'local', 'ms_max': 1, 'voltage': 1200, 'sigma': 0.0},
1: {'name': 'near', 'ms_max': 10, 'voltage': 1000, 'sigma': 0.25},
2: {'name': 'far', 'ms_max': 100, 'voltage': 800, 'sigma': 0.50},
3: {'name': 'derp', 'ms_max': 1000, 'voltage': 600, 'sigma': 1.0},
4: {'name': 'offline', 'ms_max': None, 'voltage': 600, 'sigma': 1.0},
}
def detect_tailscale() -> dict:
"""Detect Tailscale status. Returns dict with 'available', 'peers', 'latency_map'.
If Tailscale is not installed or not running, returns available=False
with empty peers and latency_map. The chain never fails.
"""
result = {
'available': False,
'peers': {},
'latency_map': {},
'derp_region': None,
}
# Check if tailscale binary exists
try:
proc = subprocess.run(
['tailscale', 'status', '--json'],
capture_output=True, text=True, timeout=5
)
if proc.returncode != 0:
return result # tailscale not running
except (FileNotFoundError, subprocess.TimeoutExpired):
return result # tailscale not installed
try:
status = json.loads(proc.stdout)
result['available'] = True
result['derp_region'] = status.get('CurrentTailnet', {}).get('Name')
for peer_id, peer in status.get('Peer', {}).items():
hostname = peer.get('HostName', peer_id)
tailscale_ip = peer.get('TailscaleIPs', [None])[0]
relay = peer.get('Relay', '')
latency = peer.get('CurAddr', '')
# Classify latency
if not peer.get('Online', False):
latency_class = 4 # offline
elif relay: # DERP relay
latency_class = 3 # derp
elif tailscale_ip:
latency_class = 1 # near (same tailnet)
else:
latency_class = 2 # far
result['peers'][hostname] = {
'ip': tailscale_ip,
'latency_class': latency_class,
'relay': relay,
'online': peer.get('Online', False),
}
if tailscale_ip:
result['latency_map'][tailscale_ip] = latency_class
except (json.JSONDecodeError, KeyError, TypeError):
pass # malformed status, return what we have
return result
def get_latency_class(node_ip: str, ts_status: Optional[dict] = None) -> int:
"""Get latency class for a node. Returns 4 (offline) if Tailscale unavailable.
The chain never fails offline is just another latency class.
"""
if ts_status is None:
ts_status = detect_tailscale()
if not ts_status['available']:
return 4 # offline — Tailscale not running
return ts_status['latency_map'].get(node_ip, 4) # default to offline
def latency_to_voltage(latency_class: int) -> int:
"""Map latency class to FPGA voltage in millivolts."""
return _LATENCY_CLASSES.get(latency_class, _LATENCY_CLASSES[4])['voltage']
def latency_to_sigma(latency_class: int) -> float:
"""Map latency class to scale space sigma."""
return _LATENCY_CLASSES.get(latency_class, _LATENCY_CLASSES[4])['sigma']
try: try:
import numpy as np import numpy as np
HAS_NUMPY = True HAS_NUMPY = True