feat(preflight): deploy-time secret-liveness probes + bin (#188 Phase 2)#197
Merged
Conversation
CI cannot hold production secrets, so a dead secret is invisible until the first live request fails. On 2026-07-15 four secrets were simultaneously dead in one production day — a dead SANDBOX_API_KEY, a stale SANDBOX_API_URL, and a dead LiteLLM router key + URL — each present in `wrangler secret list` yet invalid against its live endpoint, with nothing checking liveness anywhere. Bind the check at DEPLOY time instead: a product declares probes built from its real env, and `agent-app-preflight` runs them as a step before deploy, failing with a message that names exactly which secret to rotate. - `/preflight`: `runPreflight(probes)` → per-probe verdict + latency + overall pass/fail (any critical probe fails → the run fails; probes default critical). - Standard builders (explicit config, read nothing global): `routerChatProbe` (cheap POST /chat/completions, max_tokens 1 — 200 live, 401/403 dead key, 503 upstream-down), `sandboxAuthProbe` (GET /v1/sandboxes?limit=1), `httpHeadProbe`. Failures classify dead-key vs stale-url vs upstream-down. - `bin/preflight.mjs` reads `preflight.config.mjs` from cwd, prints a table, exits 1 on failure / 2 on config error. - Example config wires legal-agent's four incident probes. Server-only: not added to the browser-safe manifest.
tangletools
approved these changes
Jul 15, 2026
tangletools
left a comment
There was a problem hiding this comment.
✅ Auto-approved drewstone PR — 3e4571ae
This PR was opened by the trusted drewstone account.
The full PR reviewer audit still runs separately and will publish findings if it detects issues.
tangletools · auto-approval · reason: drewstone_author · 2026-07-15T19:40:22Z
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Why
CI cannot hold production secrets, so a dead secret is invisible until the first live request fails. On 2026-07-15 four secrets were simultaneously dead in one production day — a dead
SANDBOX_API_KEY, a staleSANDBOX_API_URL, and a dead LiteLLM router key + URL. Each one was present inwrangler secret list(so nothing looked wrong) yet invalid against its live endpoint, and nothing anywhere checked liveness.This is the one incident class from #188 Phase 2 that cannot be a CI gate. It binds at deploy time instead: the deploy already has the real secrets in its environment, which is the one place they can be probed.
What
New server-only
/preflightsubpath + anagent-app-preflightbin.runPreflight(probes)→ per-probe verdict + measured latency + overall pass/fail. APreflightProbeis{ name, run, critical? }; the run fails iff a critical probe fails, and probes are critical by default. Probes run concurrently.routerChatProbe({ baseUrl, apiKey, model })— one cheapPOST /chat/completions(max_tokens: 1).200→ live;401/403→ dead key (names the secret to rotate);503→ upstream down (key still valid, explicitly says don't rotate); timeout / unreachable → check the URL secret.sandboxAuthProbe({ baseUrl, apiKey })—GET /v1/sandboxes?limit=1; same classification.httpHeadProbe({ name, url, expectStatus? })— plain reachability (the stale-platform-URL class).formatPreflightReportrenders an aligned table + a verdict line that names the dead probes and tells the operator to rotate the secret named in eachFAILrow.bin/preflight.mjs(agent-app-preflight) readspreflight.config.mjsfrom cwd (override withPREFLIGHT_CONFIG), prints the table, and exits0live /1a critical secret is dead /2config error. One config file + one deploy-workflow step per product.Every failure message names the exact secret to rotate (e.g.
DEAD KEY — https://router…/chat/completions returned 401; rotate LITELLM_API_KEY).Example
examples/preflight.config.mjswires legal-agent's four incident probes, each commented with the secret it guards.Tests
src/preflight/index.test.tscovers every builder against a fakefetch(200 / 401 / 503 / timeout / connection-refused), request-shape assertions, report aggregation, and critical-vs-non-critical folding. No live network. End-to-end smoke of the shipped bin confirmed exit0(pass, non-critical warn),1(critical dead), and2(missing config).Server-only: not added to the browser-safe manifest;
tests/browser-safe-subpaths.test.tsstays green untouched.pnpm typecheck && pnpm test(2472 tests) andpnpm buildall green.Closes part of #188 (Phase 2 —
/preflightsecret-liveness bin).