Skip to content

feat(preflight): deploy-time secret-liveness probes + bin (#188 Phase 2)#197

Merged
drewstone merged 1 commit into
mainfrom
feat/preflight
Jul 15, 2026
Merged

feat(preflight): deploy-time secret-liveness probes + bin (#188 Phase 2)#197
drewstone merged 1 commit into
mainfrom
feat/preflight

Conversation

@drewstone

Copy link
Copy Markdown
Contributor

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 stale SANDBOX_API_URL, and a dead LiteLLM router key + URL. Each one was present in wrangler 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 /preflight subpath + an agent-app-preflight bin.

  • runPreflight(probes) → per-probe verdict + measured latency + overall pass/fail. A PreflightProbe is { name, run, critical? }; the run fails iff a critical probe fails, and probes are critical by default. Probes run concurrently.
  • Standard probe builders (explicit config, read nothing global — the same probe runs in a deploy step, a test, or a local check):
    • routerChatProbe({ baseUrl, apiKey, model }) — one cheap POST /chat/completions (max_tokens: 1). 200 → live; 401/403dead key (names the secret to rotate); 503upstream 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).
  • formatPreflightReport renders an aligned table + a verdict line that names the dead probes and tells the operator to rotate the secret named in each FAIL row.
  • bin/preflight.mjs (agent-app-preflight) reads preflight.config.mjs from cwd (override with PREFLIGHT_CONFIG), prints the table, and exits 0 live / 1 a critical secret is dead / 2 config 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.mjs wires legal-agent's four incident probes, each commented with the secret it guards.

Tests

src/preflight/index.test.ts covers every builder against a fake fetch (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 exit 0 (pass, non-critical warn), 1 (critical dead), and 2 (missing config).

Server-only: not added to the browser-safe manifest; tests/browser-safe-subpaths.test.ts stays green untouched. pnpm typecheck && pnpm test (2472 tests) and pnpm build all green.

Closes part of #188 (Phase 2 — /preflight secret-liveness bin).

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 tangletools left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ 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

@drewstone
drewstone merged commit 4de097f into main Jul 15, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants