Three-tier routing cache (ESL global -> file -> HTTP) eliminates HTTP
from call path for 2500-5000 concurrent calls. Lua reads cached route
in ~0.1us instead of blocking on api:execute('curl', ...).
Engine fixes:
- recalcBestLocked now skips stale nodes (was 5 min bug -> now ~10-15s)
- PickNodeForCall action_type='node' checks staleness for consistency
- onMetric callback pushes cache on every metric (no ticker delay)
Config:
- stale_threshold_sec default 20 -> 10 (industry standard)
- contrib/ included in Makefile deploy target
- route.lua paths updated for /opt/pulse-lets-go
Routing rules:
- RouteRule model: 3 match fields (caller_id, dest, ingress_trunk regex),
3 action types (node/pool/auto), time-based (days of week, time range)
- Engine router.go: CompiledRule with pre-compiled regex,
PickNodeForCall with cascade (dead node → next rule), pickNodeFromPool
- Separate sync.RWMutex for Router — zero contention admin vs traffic
- Validate-before-save pattern: engine validates before JSON save
- Graceful degradation: corrupted rules skipped, no rules → PickNode()
Persistence:
- data/routing.json with atomic save (tmp→rename), config.Manager
- Auto-created empty on first start
API (admin only):
- GET/POST/PUT/DELETE /api/routing/rules
- PUT /api/routing/rules/:id/toggle
- PUT /api/routing/rules/reorder
Integration:
- /api/route now uses PickNodeForCall with caller_id/dest/ingress_trunk
query params (already sent by route.lua)
- RouteResponse includes matched_rule field for debugging
- Main.go loads routing rules on startup with graceful fallback
Web UI:
- /routing page: table with priority arrows, inline edit, create form,
regex test tool, toggle, delete, action badges, hits counter
Tests:
- 28 tests: matchRule, time matching, PickNodeForCall cascade,
pool scoring, validation, hit counting, compilation