[FABLE5] DESIGN BRIEF v2.1 · 2026-07-15 · CODEX ROUNDS 1–2 FOLDED · OWNER-STEERED

whmcs-sentinel — a mission-critical loop for billing, database & VPN-auth

An autonomous monitoring loop for the VilaVPN WHMCS stack, cloned from the battle-proven infra-sentinel skeleton. It watches business invariants — customers can log in, invoices exist, money settles exactly once, VPN auth answers — applies one bounded hot-fix only when provably safe, and turns everything else into /build-loop-ready plan docs. Full plan: ~/ssh/docs/runbooks/whmcs-sentinel-monitoring-loop-plan.md

91/100overall design score
0checks across 5 tiers
0regressable incidents with live catchers
0day-1 findings queued (regressable only)
5hot-fix primitives, frozen allowlist

01What the recon found (10 lanes)

ultracode workflow (7 agents incl. live read-only prod inventory) + /codex SRE catalog + /recall session history. Three discoveries reshaped the design:

🎯 4 hosts, not 2

ocserv VPN auth terminates on a dedicated radius box (172.104.80.179) — a fleet-wide SPOF with no host firewall, pointing at the master DB. Verified live: its freeradius container reads 139.162.106.129/radius. Master down ⇒ billing AND VPN auth down.

💸 Billing bugs are silent

Every expensive past incident was a silent money bug: Payssion FX leaving paid invoices Unpaid, Stripe webhooks silently missing capture events, node traffic double-billing (4.2TB phantom). 3 of 4 panel report endpoints are still non-idempotent today.

📡 Grafana exists — gaps are app-layer

grafana-agent + node/mysqld exporters already run on both main boxes. What's missing: synthetics, php-fpm status (250-worker pool, zero visibility), backup success, cert aggregation, radius canary, and all billing invariants.

02Architecture

Deterministic Python core owns every probe and every mutation. The LLM appears in exactly two caged roles — diagnosis (tools-stripped) and plan-writing (batch) — never in the fix path.

whmcs-sentinel daemon deterministic Python · 2-phase cycle observe → correlate → act · fail-closed gates act only at CONFIRMED (3 fails) · fenced single-writer T0 synthetics → T1 host → T2 DB → T3 billing → T4 drift APP 139.162.101.188 nginx · fpm×2(250) · WHMCS crons xserver+redis+freeradius docker backup driver · certs ×3 systems MASTER DB 139.162.106.129 MySQL 8.0 GTID · 19 schemas 272MB free RAM · activitylog 6GB writability · GTID · conns · txns DR REPLICA 45.76.196.131 read-only · lag≤1s · nightly dumps ★ heavy SQL probes run HERE zero load on prod master RADIUS 172.104.80.179 freeradius → master radius DB fleet-wide ocserv auth SPOF no host FW · UDP 1812 wide open HOT-FIX (frozen allowlist) confirm ×2 → evidence → ONE action verify → 15min cooldown → 3/day cap nginx/fpm reload · docker restart-once rerun flocked idempotent job · logrotate PAGE NOW (Prowl P0/P1) money incoherence · master down replica writable · errant GTID fleet auth down · disk ≥95% evidence attached, never auto-fixed PLAN-LATER PIPELINE findings.jsonl → weekly plan-writer clustered → deduped → plan docs whmcs-sentinel-plans/*.md → you review → /build-loop builds it

03Check coverage — 5 tiers, 45 checks

HOT-FIX bounded auto-fix existsPLAN-LATER becomes a plan docPAGE immediate Prowl, human decides
T0 Synthetics (60s)
8 checks
T1 Host / process (120s)
8 checks
T2 DB + replication (2–5m)
8 checks — per-probe authority map
T3 Billing invariants (15–30m)
9 checks — the money tier
T4 Drift & assurance (d/w/m)
9 checks + coverage watchdog
+ Hot-fix allowlist
5 frozen primitives

Highlights of the money tier: duplicate-transaction detector · Paid-but-underfunded / Unpaid-with-money invoices · the exact Payssion Convert-To fingerprint · Stripe webhook event-list drift (catches the silent revert risk) · invoice-generation weekday bands · node traffic double-billing detector · radius accounting freshness · signup/revenue dead-man.

04Monitor only what can come back

A bug fixed once and for all in code can't silently recur — a probe for it is pure noise. Checks are spent only on regressable failures: config a WHMCS/module update can revert, a provider changing under us, or data-shape drift. Permanently-closed incidents (PassWall UA sync-invariant, VilaX2 UTF-8 byte-safety, xrayr rollback guard, no_drop QUIC, ApiPath bypass) are deliberately unmonitored.

Incident (real, from history)Cost thenCatcher now
Payssion Convert-To FX — paid invoices stuck Unpaidrevenue leak, manual fixesB2 coherence + B3 fingerprint PAGE
Payssion secret-key leak (3 years, key still unrotated)forged-pingback exposureB3 replay/source telemetry + rotation-age (partial — reconciliation, not checksums)
Stripe uncaptured payments after WHMCS 8.13.3auto-voided renewalsB4 capture_method + aged requires_capture reconcile PAGE
php-fpm retry-storm (load 248, daily 02:00 recurrence)site down nightlyH1 saturation + D5 backup correlation HOT-FIX
Node traffic double-billing (4.2TB phantom, 1,461 users)customer over-billingB7 duplicate-tuple detector PAGE
Stale redis node cache (5-min stale configs)wrong node configsB7 degraded-mode grep + H3 HOT-FIX
UA-validator too narrow ("no active packages" fleet-wide)all Go clients deadA6 UA regression matrix
Cert drift — vilavpn.net EXPIRED today, .jp at 30dtrust breakageS4 all-domain expiry + A3 dry-run
DR replication break (DROP USER 1396 — live, 2026-07-15)7-min replica stallD2 immediate SQL-error page

05Before / after

Today

  • No synthetic knows if checkout/login actually works
  • php-fpm: 250-worker ceiling, zero visibility, storms found by customers
  • Money bugs found weeks later by complaints / bank statements
  • Backup success invisible (494MB unrotated log, no metric)
  • Radius SPOF unmonitored — auth breaks = ticket flood
  • Issues found ad-hoc in Claude sessions, fixes compete with feature work

With whmcs-sentinel

  • Business invariants probed every cycle, from outside
  • Saturation confirmed twice → ONE bounded reload → verified → cooldown
  • Money incoherence pages you within 30 min, with SQL evidence
  • Dump age/size/integrity + driver exit status as metrics + dead-man
  • radclient canary + radacct freshness + reject-rate baselines
  • Non-urgent issues auto-become reviewed plan docs for /build-loop

06Day-1 backlog — the loop ships with 12 findings already queued

Recon itself surfaced these. They seed findings.jsonl so the plan-writer lane produces its first /build-loop docs immediately:

  • DB host memory pressure — 272MB free, buffer pool 4G/8G, prior swap-thrash evidence at 439/500 conns
  • tblactivitylog 6.0GB + tblmodulelog 1.3GB — WHMCS auto-prune likely off; retention plan needed (never auto-DELETE)
  • backupDatabase.log 494MB unrotated + backup success invisible
  • Cert estate fragmented — 3 renewal systems; vilavpn.net expired, vilavpn.jp 30d
  • xrayr/soga/v2bx report endpoints still non-idempotent — double-billing exposure; needs fleet protocol census
  • Exporter password cleartext in DB-host grafana-agent.yml
  • php-fpm status_path missing on both pools (blocks the saturation check)
  • tgapiRadius SPOF — no host firewall, UDP 1812 open to world, single box for fleet auth
  • Dead container freeradius-fix (exited 14 months ago)
  • App-host freeradius container purpose unclear — real traffic goes to tgapiRadius; clarify or retire
  • DR box hardening leftovers — mysqlx :33060 on *, key-only SSH decision pending

07Decisions — pick A / B / C for each

Revised after codex round 1 (it correctly downgraded several v1 framings). Reply like: DEC1-A DEC2-A DEC3-A DEC4-A DEC5-A or just "all recommended".

DEC-1

Where does the loop run?

RECOMMENDEDA

Dedicated tiny monitoring VPS

A $5–10/mo box outside all four failure domains. Cleanest single-writer/fencing story, no coverage gap when your Mac sleeps or travels. Cost: one more box to keep patched.

score 85/100 — isolation wins for mission-critical
B

Mac, beside infra-sentinel

Proven 24/7 ops model, ssh keys + claude CLI already here. But monitoring pauses when the Mac does — the external dead-man catches loop death, not the coverage gap while asleep.

score 74/100 — fine to start, weaker steady-state
C

DR box (systemd)

24/7 and in-fleet — but the monitor then lives inside a fleet it monitors (a DR-box problem blinds the loop AND the DR) and adds load to the replica the money probes read.

score 70/100 — coupling costs more than it looks
DEC-2

Hot-fix arming posture

RECOMMENDEDA

Per-primitive staged arming

Every primitive ships in shadow mode (journals "would have acted"), gets its own soak review, and is armed by an individual owner go. Order: nginx/logrotate reloads → fpm reload → radius container restart → DR dump rerun. Nothing arms before the M5 soak gate.

score 92/100 — autonomy earned one action at a time
B

Alert-only forever

Never auto-fix; every incident pages you. Zero action risk — and 3AM php-fpm wedges wait for you to wake up, forever.

score 70/100 — safe, permanently manual
C

Bundle-arm after one soak week

The v1 framing: arm reload-class fixes as one batch post-soak. Codex round 1 correctly flagged the bundling — one bad actor in the batch taints the go for all.

score 55/100 — downgraded honestly
DEC-3

Where do SQL probes run?

RECOMMENDEDA

Per-probe authority map

Server-local truth (connections, processlist, binlogs) runs MASTER-only; heavy money invariants run on the replica gated on fresh replication health — if D2 isn't fresh-PASS, the result is UNKNOWN_REPLICA_STALE, never a false certification. Compound checks name a target per sub-probe.

score 92/100 — right layer for every probe
B

Master for everything

Freshest data — bought with load on a box already at 272MB free RAM and history of swap-thrash.

score 45/100 — spends what is scarcest
C

Replica-first, ungated

The v1 wording. Cheap, but a stalled replica could silently certify stale money invariants — exactly the class of silent failure this loop exists to kill.

score 60/100 — the gate is the point
DEC-4

Prod instrumentation rollout

RECOMMENDEDA

Staged

Now: fpm status_path + backup exit-metric + external dead-man. Next: radius canary account once its NAS-source restriction is reviewed. Optional later: non-ASCII auth test account (until then the UTF-8 incident class stays visibly unmonitored, marked ☐).

score 90/100 — small reviewable prod changes
B

All five in one window

Fastest to full coverage, but bundles unrelated prod changes into one change window — harder to attribute if anything regresses.

score 72/100
C

None

No prod changes; H1 and the radius canary degrade to inference (process counts, port checks) — monitoring by inference is the anti-pattern this design bans.

score 40/100
DEC-5

Scheduling model

RECOMMENDEDA

Per-check schedule map

T0 synthetics 60s · host/safety 120s · expensive DB 300s · billing 15–30min · assurance on calendar. Monotonic per-check timers, non-overlap locks, jitter — and the loop pages on its own overdue work.

score 91/100 — honest about mixed cadences
B

Single 60s base cycle

One clock for everything. Fiction in practice — billing sweeps and restore drills can never fit a 60s frame, so the "base cycle" becomes decorative.

score 55/100
C

Single 300s base cycle

Light, but ~15min to CONFIRMED is too slow for checkout-path breakage during the CN evening peak.

score 45/100

08Rollout

M0 — Skeletonclone infra-sentinel patterns: watchdog, journal, kill-epoch, sentinelctl, tests · /build-loop session 1
M1–M2 — All 42 checks, shadow modeT0–T4 wired + billing-SQL fixture replay against historical known-good/bad · Prowl + external dead-man live · /build-loop sessions 1–2
M3 — Instrumentationper DEC-4, staged · each item owner-gated at install
M4 — Plan-writer lanefindings.jsonl → first generated /build-loop plan docs (12 day-1 findings ready)
M5 — One-week observe soakthreshold tuning on real baselines · gate before any arming
M6 — Per-primitive armingeach primitive: own shadow review + individual owner go · per DEC-2
M7 — Assurance drillsmonthly scratch-restore, weekly cert dry-runs, quarterly review of the allowlist itself