From 3ce29d5f4decdcc664036cebab8003907a8bac3e Mon Sep 17 00:00:00 2001 From: S'Bussiso Dube <80188685+Sbussiso@users.noreply.github.com> Date: Wed, 9 Sep 2026 13:53:42 -0700 Subject: [PATCH] =?UTF-8?q?Scale=20to=20zero=20=E2=80=94=20this=20service?= =?UTF-8?q?=20is=20idle=20by=20default?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Called by self-hosted Command Center installs on a ~15-minute background tick, so it is idle by design. Today, with no self-hosted installs, it serves literally nothing: the only entries in 24 hours of logs are SSH sessions. The comment justifying always-warm claimed a cold start "would routinely leak into the client's grace-period math". That had the scale wrong by four orders of magnitude — SENTINEL_LICENSE_GRACE_HOURS is 72 HOURS, and this service binds its port in ~4s (measured). Three things make this safe, all verified rather than assumed: 1. Boot fits Fly's window. The proxy waits ~8s for an auto-started machine to bind; 4s clears it. This is not theoretical — the Sentinel AI agent needs 10s, missed that window, and had every wakeup fail with RemoteDisconnected until it was kept warm. 2. The caller waits longer than Fly gives up: Command Center's check-in timeout is 10s. 3. A miss is free. license_client.py treats network/5xx as "unreachable" and applies the 72-hour grace, so a failed check-in is a designed path. The next tick 15 minutes later catches it. Flips if a license check ever moves onto a user-blocking path — then a 4s cold start is a 4s page stall. Noted in fly.toml. Co-Authored-By: Claude Opus 5 --- fly.toml | 34 +++++++++++++++++++++++++++++----- 1 file changed, 29 insertions(+), 5 deletions(-) diff --git a/fly.toml b/fly.toml index e530237..72ca422 100644 --- a/fly.toml +++ b/fly.toml @@ -36,12 +36,36 @@ primary_region = "sjc" [http_service] internal_port = 8000 force_https = true - auto_stop_machines = "off" + auto_stop_machines = "stop" auto_start_machines = true - # Always-warm rather than scale-to-zero: the ~15-minute check-in - # interval means a cold start would routinely leak into the client's - # grace-period math (see Command Center's SENTINEL_LICENSE_GRACE_HOURS). - min_machines_running = 1 + # Scale to zero. This service is called by self-hosted Command Center + # installs on a ~15-minute background tick, so it is idle by default: + # today, with no self-hosted installs, it serves literally nothing. + # + # The previous comment here justified always-warm by claiming a cold + # start "would routinely leak into the client's grace-period math". + # That had the scale wrong by four orders of magnitude — + # SENTINEL_LICENSE_GRACE_HOURS is 72 HOURS, and this service binds its + # port in ~4s (measured 2026-09-09). A cold start cannot meaningfully + # consume a three-day window. + # + # Three things make scale-to-zero safe here, and all three are worth + # re-checking before copying this to another service: + # + # 1. Boot fits the window. Fly's proxy waits ~8s for an auto-started + # machine to bind. 4s clears it. (The Sentinel AI agent needs 10s + # and had to be kept warm for exactly this reason.) + # 2. The caller waits longer than Fly gives up: Command Center's + # check-in timeout is 10s. + # 3. A miss is free. license_client.py treats network/5xx as + # "unreachable" and applies the 72-hour grace window, so a failed + # check-in is a designed path, not an error path — the next tick + # 15 minutes later catches it and no customer notices. + # + # This flips if a license check ever moves onto a user-blocking path + # (a synchronous validation on dashboard load). Then a 4s cold start + # becomes a 4s page stall and this needs to go back to always-warm. + min_machines_running = 0 processes = ["app"] [[http_service.checks]]