Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/verify-env-preflight.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@dawn-ai/cli": patch
---

`dawn verify` now runs an environment preflight. A new `runtime` check asserts the running Node version meets Dawn's `22.13.0` floor (a stale Node fails verify) and, when `dawn.config.ts` configures a sandbox provider, runs the provider's Docker daemon preflight. The `deps` env-var check is now provider-aware: it derives the required API-key env var from the providers your routes actually use (e.g. `ANTHROPIC_API_KEY` for an Anthropic-only app) instead of always nagging about `OPENAI_API_KEY`.
10 changes: 8 additions & 2 deletions apps/web/content/docs/cli.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ When a route, tool, or config module fails to load with the opaque ESM error `do

## `dawn verify`

Runs four checks in one call (`app`, `routes`, `typegen`, `deps`) — the canonical pre-deploy integrity gate.
Runs five checks in one call (`app`, `routes`, `typegen`, `deps`, `runtime`) — the canonical preflight before `dawn dev`, `dawn start`, or a deploy. A green `dawn verify` means "this app will boot in this environment."

```
dawn verify
Expand All @@ -54,7 +54,13 @@ Flags:
- `--json` — emit a structured report (`{ status, appRoot, checks, counts }`) instead of human-readable text.
- `--env-file <path>` — path to a `.env` file (overrides `dawn.config.ts` `env` and the default `./.env`).

The `deps` check covers missing packages and missing env vars (advisory) — uniquely useful before a deploy. See [Deployment](/docs/deployment) for the recommended workflow.
The `deps` check covers missing packages and missing env vars (advisory). It is **provider-aware**: it derives the API-key env var from the providers your routes actually use — an Anthropic-only app is checked for `ANTHROPIC_API_KEY`, an OpenAI app for `OPENAI_API_KEY`, a multi-provider app for the union, and a local Ollama app for none. A missing key is a warning, not a failure (the key may come from the runtime environment).

The `runtime` check gates **environment readiness**:
- **Node** — asserts the running Node version is at least `22.13.0` (Dawn's floor; below it `node:sqlite` breaks). Below the floor **fails** `verify` with a non-zero exit.
- **Docker** — present only when `dawn.config.ts` configures a sandbox provider; it runs the provider's daemon preflight and **fails** if the daemon is unreachable. Apps with no sandbox skip this sub-check entirely.

See [Deployment](/docs/deployment) for the recommended workflow.

## `dawn routes`

Expand Down
2 changes: 2 additions & 0 deletions apps/web/content/docs/configuration.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

`dawn.config.ts` is a TypeScript file at your app root that exports a single default object conforming to the `DawnConfig` interface. Dawn loads it (via `tsx`) before every CLI command and at runtime startup. If the file is absent, all keys take their documented defaults — no file is required for a working app.

Run [`dawn verify`](/docs/cli) as your preflight before `dawn dev` or `dawn start`: it validates this config and app integrity plus environment readiness (Node version, the provider API key your routes need, and — when `sandbox` is configured — the Docker daemon).

```ts
// dawn.config.ts
export default {
Expand Down
2 changes: 2 additions & 0 deletions apps/web/content/docs/getting-started.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,8 @@ npm run eval

Runs the quality eval in `src/app/research/evals/research-quality.eval.ts`. The agent run replays fixtures, then two dataset cases are scored across four scorers (`toolCalled`, `contains`, `cites-source`, `llmJudge`). The generated fixtures also cover the judge requests from `llmJudge`, so the scaffolded eval runs without an API key.

Before running the app live, run `dawn verify` as your preflight — it checks app integrity plus environment readiness (Node version, the provider API key your routes need, and the Docker daemon when a sandbox is configured) so `dawn dev` and `dawn start` boot cleanly. See [`dawn verify`](/docs/cli).

## 4. Run it live

Set your API key and start the dev server:
Expand Down
99 changes: 99 additions & 0 deletions docs/superpowers/plans/2026-07-12-verify-env-preflight.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
# `dawn verify` environment preflight — implementation plan

> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development. Steps use checkbox (`- [ ]`) syntax. Follow TDD.

**Goal:** Extend `dawn verify` (no new command) with a `runtime` check (Node ≥ 22.13.0 + Docker daemon when a sandbox is configured) and fix the hardcoded `OPENAI_API_KEY` deps check to be provider-derived.

**Architecture:** New checks slot into `verify.ts`'s existing `verifyApp → checks[] → counts` pipeline, sharing its `--json`/exit semantics.

**Spec:** `docs/superpowers/specs/2026-07-12-verify-env-preflight-design.md`

**Conventions:** `src`→`.js` / `test`→`.ts`; `exactOptionalPropertyTypes` → conditional-spread; `pnpm --filter @dawn-ai/cli lint`; changeset **patch**.

---

## Task 1: `runtime` check — Node version + Docker daemon

**Files:**
- Create: `packages/cli/src/lib/verify/check-runtime.ts`
- Test: `packages/cli/test/check-runtime.test.ts`

- [ ] **Step 1: Failing test** — `checkRuntime({ nodeVersion, sandboxProvider })`:
- `nodeVersion: "22.12.5"` → `node.ok === false`, `status: "failed"`, `floor: "22.13.0"`.
- `nodeVersion: "22.14.0"` → `node.ok === true`.
- `sandboxProvider` with a stub `preflight()` → `{ ok: false, detail: "..." }` → `docker.ok === false`, `status: "failed"`; `{ ok: true }` → `docker.ok === true`.
- No `sandboxProvider` → `docker` field absent, status driven by Node only.
- [ ] **Step 2: Run → fail.**
- [ ] **Step 3: Implement** `check-runtime.ts`:

```ts
import type { SandboxProvider } from "@dawn-ai/workspace"
const NODE_FLOOR = "22.13.0"
export interface RuntimeCheckResult {
readonly name: "runtime"
readonly node: { readonly version: string; readonly ok: boolean; readonly floor: string }
readonly docker?: { readonly ok: boolean; readonly detail: string }
readonly status: "passed" | "warning" | "failed"
}
function gte(a: string, b: string): boolean { /* pure numeric semver compare of MAJOR.MINOR.PATCH */ }
export async function checkRuntime(input: {
readonly nodeVersion?: string
readonly sandboxProvider?: Pick<SandboxProvider, "preflight" | "name">
}): Promise<RuntimeCheckResult> {
const version = input.nodeVersion ?? process.versions.node
const nodeOk = gte(version, NODE_FLOOR)
const node = { version, ok: nodeOk, floor: NODE_FLOOR }
let docker: RuntimeCheckResult["docker"]
if (input.sandboxProvider?.preflight) {
const r = await input.sandboxProvider.preflight()
docker = { ok: r.ok, detail: r.detail ?? (r.ok ? "reachable" : "unreachable") }
}
const failed = !nodeOk || docker?.ok === false
return { name: "runtime", node, ...(docker ? { docker } : {}), status: failed ? "failed" : "passed" }
}
```
VERIFY the `SandboxProvider.preflight` return shape (`{ ok, detail?, warnings? }`) in `packages/workspace/src/sandbox-types.ts`. Write `gte` as a pure MAJOR.MINOR.PATCH compare (no deps); test it on `22.9.0`/`22.13.0`/`22.13.1`/`23.0.0`.
- [ ] **Step 4: Run → pass**; `pnpm --filter @dawn-ai/cli typecheck && lint`.
- [ ] **Step 5: Commit** `feat(cli): checkRuntime — Node floor + Docker daemon probe for verify`.

---

## Task 2: Provider-derived API-key check (fix the hardcoded OPENAI_API_KEY)

**Files:**
- Modify: `packages/cli/src/lib/verify/check-dependencies.ts`
- Possibly modify: `packages/langchain/src/chat-model-factory.ts` (export `providerSpecs`/a `providerEnvVar` map) — or add the map in cli
- Test: `packages/cli/test/check-dependencies.test.ts` (extend)

- [ ] **Step 1: Failing test** — `checkDependencies` on a fixture whose route model is an Anthropic id → `missingEnvVars` includes `ANTHROPIC_API_KEY` (when unset) and does NOT include `OPENAI_API_KEY`; an OpenAI app → `OPENAI_API_KEY`; a multi-provider app → the union; an ollama-only app → none. Env-file resolution still honored (set the key in a temp `.env` → not missing).
- [ ] **Step 2: Run → fail** (current code hardcodes `["OPENAI_API_KEY"]`).
- [ ] **Step 3: Implement:**
- Define `PROVIDER_ENV_VAR: Record<string,string>` (`openai→OPENAI_API_KEY`, `anthropic→ANTHROPIC_API_KEY`, `google→GOOGLE_API_KEY`, `mistral→MISTRAL_API_KEY`, `groq→GROQ_API_KEY`, `xai→XAI_API_KEY`, `openrouter→OPENROUTER_API_KEY`; omit `ollama`). Source the provider list from `chat-model-factory.ts:providerSpecs` — export it if not already, else mirror with a comment pointing at the source of truth.
- Replace `RECOMMENDED_ENV_VARS` with: infer each route's provider from its `model` id (reuse the SDK's `inferProvider` — grep for it; `dawn check`/the model-id validation already infer provider), union the providers, map to env vars, dedupe. Requires the route manifest — `checkDependencies` already receives app context; thread the manifest/providers in (mirror how `verify.ts` passes data to the deps check).
- Keep it a `warning` (not failed) when a key is missing (matches current behavior); keep the env-file resolution.
- [ ] **Step 4: Run → pass**; existing `check-dependencies`/`verify` tests updated for the new behavior.
- [ ] **Step 5: Commit** `fix(cli): verify checks the API key the app actually needs (provider-derived, not hardcoded OPENAI_API_KEY)`.

---

## Task 3: Wire `runtime` into `verifyApp` + output

**Files:**
- Modify: `packages/cli/src/commands/verify.ts`
- Test: `packages/cli/test/verify-command.test.ts` (extend)

- [ ] **Step 1: Failing test** — `dawn verify --json` on a fixture includes a `checks[]` entry with `name: "runtime"`; a mocked stale `process.versions.node` makes verify exit non-zero; the human summary (non-json) prints a `Runtime: Node <v>` line.
- [ ] **Step 2: Run → fail.**
- [ ] **Step 3: Implement** — in `verifyApp`, call `checkRuntime({ sandboxProvider: <resolved from dawn.config sandbox, if any> })`, push its result into `checks[]`, and fold its status into `counts`/overall result (a `failed` runtime check fails verify). Add `RuntimeCheckResult` to the `VerifyCheckResult` union. In `runVerifyCommand`'s human path, print a runtime summary line. Resolve the configured sandbox provider the same way `collect-sandbox-errors.ts` does (reuse, don't duplicate).
- [ ] **Step 4: Run → pass**; full `@dawn-ai/cli` suite green (update the verify tests' expected check counts).
- [ ] **Step 5: Commit** `feat(cli): dawn verify runs the runtime/env preflight`.

---

## Task 4: Docs + changeset + PR

- [ ] **Step 1:** Update `apps/web/content/docs/cli.mdx` `verify` section (now checks env/runtime: Node floor, Docker, provider-derived keys); a one-liner in `getting-started.mdx`/`configuration.mdx` that `dawn verify` is the preflight before `dawn dev`/`dawn start`. No banned phrases; gpt-5 ids only. `node scripts/check-docs.mjs` → PASS.
- [ ] **Step 2:** `.changeset/verify-env-preflight.md` — **patch** for `@dawn-ai/cli` (+ `@dawn-ai/langchain` if `providerSpecs` was exported from it). Confirm the set via `git log … --name-only`.
- [ ] **Step 3:** Full local verify (`pnpm build && typecheck && lint && test && check-docs`); rebase, push, PR, watch `validate` + review.

**Notes:** Branch e.g. `feat/verify-env-preflight`; pin before subagent dispatch. Build after the error-code registry so runtime failures can carry `DAWN_E` codes (add them if the registry has landed; otherwise leave a follow-up note).
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
# `dawn verify` environment preflight — design

**Date:** 2026-07-12
**Status:** approved (brainstorm)
**Topic:** Extend the existing `dawn verify` command with environment/runtime preflight checks (Node version, Docker daemon, provider-derived API keys) so "why won't it start" is answered in one place — **without adding a new CLI command**.

## Problem

`dawn check` validates config/descriptors; `dawn verify` validates *app integrity* (app root, routes, typegen, deps). Neither verifies the **environment** the app will actually run in:
- No **Node-version** assertion exists anywhere at runtime, yet the real floor is 22.13.0 (below it `node:sqlite` — used by `@dawn-ai/sqlite-storage`/`@dawn-ai/memory` — needs an experimental flag and breaks).
- **Docker daemon** reachability is only probed by `dawn check`, and only when a sandbox provider is configured — not part of `verify`.
- `dawn verify`'s deps check *does* flag missing env vars, but via a **hardcoded** `RECOMMENDED_ENV_VARS = ["OPENAI_API_KEY"]` (`check-dependencies.ts`) — it always nags about `OPENAI_API_KEY` regardless of which provider the app uses, and never checks the key the app *does* need (e.g. `ANTHROPIC_API_KEY`).

**Decision (locked):** do NOT add a `dawn doctor` command — keep the CLI surface minimal. Fold these checks into `dawn verify`.

## Goal

`dawn verify` gains environment readiness as part of its existing `checks[]` model: a new **`runtime`** check (Node + Docker) and a fixed, provider-aware **`deps` env-var** check. Same `--json` output, same counts, same exit semantics. A green `dawn verify` should mean "this app will boot in this environment."

## Non-goals

- No new command; no interactive UI; no separate colored output system (verify has its own line-based output + `--json`).
- Not re-validating config/descriptors (that's `dawn check`, which `verify` already shares its advisory model-id pass with).
- Not installing anything or mutating the environment.

## Architecture

Everything slots into `packages/cli/src/commands/verify.ts`'s existing check pipeline (`verifyApp` → `checks[]` → `counts` → success/failure result). New checks follow the established result-object shape (`{ name, status: "passed"|"warning"|"failed", … }`).

### 1. New `runtime` check — Node + Docker

New module `packages/cli/src/lib/verify/check-runtime.ts` returning:
```ts
interface RuntimeCheck {
readonly name: "runtime"
readonly node: { readonly version: string; readonly ok: boolean; readonly floor: "22.13.0" }
readonly docker?: { readonly ok: boolean; readonly detail: string } // present only if a sandbox provider is configured
readonly status: "passed" | "warning" | "failed"
}
```
- **Node:** read `process.versions.node`, semver-compare to floor `22.13.0`. Below floor → `failed` (hard: `node:sqlite` genuinely breaks). This is new logic (no runtime version assert exists today).
- **Docker:** if `dawn.config.ts` configures a sandbox provider, call the provider's `preflight()` (the same `{ ok, detail, warnings? }` contract `dawn check` uses via `collect-sandbox-errors.ts`). Unreachable daemon → `failed` (the app can't run sandboxed). If no sandbox is configured, omit the docker sub-check entirely (don't nag about Docker an app doesn't use). Reuse the provider preflight; do not reimplement the `docker version` probe.

Distinction from `dawn check`: check runs the sandbox preflight only as config validation; verify frames Docker as an **environment-readiness** gate and reports it in the integrity result.

### 2. Fix the `deps` env-var check to be provider-aware

`packages/cli/src/lib/verify/check-dependencies.ts` currently hardcodes `RECOMMENDED_ENV_VARS = ["OPENAI_API_KEY"]`. Replace with a **provider → key-env-var table** and derive the required set from the providers the app's routes actually use:
- Add a `providerEnvVar(provider)` map (`openai→OPENAI_API_KEY`, `anthropic→ANTHROPIC_API_KEY`, `google→GOOGLE_API_KEY`/`GEMINI_API_KEY`, `mistral→MISTRAL_API_KEY`, `groq→GROQ_API_KEY`, `xai→XAI_API_KEY`, `openrouter→OPENROUTER_API_KEY`; `ollama` → none). Base it on `providerSpecs` in `chat-model-factory.ts:12-22` (single source; export or mirror it).
- Determine which providers the app uses by inferring the provider from each route's `model` id (the same `inferProvider`/discovery `dawn check` already does — `verify` already builds the route manifest). Union → required key env vars.
- Missing a required key → same `warning` the deps check already emits (keep it a warning, not a hard fail — a key may legitimately come from the runtime environment; matches current behavior). Keep the existing env-file resolution (`--env-file` > config env > `./.env`).
- Result: verify stops nagging about `OPENAI_API_KEY` for an Anthropic-only app, and correctly flags the key the app actually needs.

### 3. Wire into `verifyApp`

Add the `runtime` check to the `checks[]` produced by `verifyApp`, and update `VerifyCheckResult` union + the human-readable summary in `runVerifyCommand` (a `Runtime: Node <v> OK` / `Docker: reachable` line; warnings for missing keys as today). `--json` gains the new check objects automatically (they're in `checks[]`). Exit: a `failed` runtime check fails verify (non-zero), consistent with existing failed checks.

## Interaction with the error-code registry

Once the error-code registry (separate spec) lands, the runtime check's failure messages adopt codes (e.g. `DAWN_E5101 Node too old`, `DAWN_E2002 Docker unreachable`). This spec does not depend on the registry — if built first, add codes as a fast-follow; if the registry is built first, use codes from the start.

## Error handling / edge cases

- Node floor check must not itself require Node ≥22.13 to run (it runs on whatever Node invoked the CLI) — pure `process.versions.node` string compare, no `node:sqlite` import in the check.
- Docker preflight already handles "daemon not running" → `{ ok:false, detail }`; surface `detail` verbatim.
- An app with no routes / no models → no required keys (skip the provider-key derivation gracefully).
- `--json` consumers: new fields are additive; existing `checks[]` entries unchanged.

## Testing

- Unit (`check-runtime`): Node below/at/above floor → failed/passed; docker sub-check present only when a sandbox provider is configured (inject a fake provider with a stub `preflight()` returning ok/not-ok).
- Unit (`check-dependencies`): an Anthropic-only fixture app → requires `ANTHROPIC_API_KEY`, does NOT flag `OPENAI_API_KEY`; an OpenAI app → requires `OPENAI_API_KEY`; multi-provider → union; ollama-only → no key required. Env-file resolution still honored.
- Integration (`verify-command` test): `dawn verify --json` on a fixture includes the `runtime` check; a stale Node (mock `process.versions.node`) fails verify; the human summary prints the runtime line.
- Full `@dawn-ai/cli` suite stays green (additive checks; existing verify tests updated for the new check count).

## Rollout

One PR. Changeset: **patch** for `@dawn-ai/cli` (and `@dawn-ai/langchain` if `providerSpecs`/`providerEnvVar` is exported from there). Docs: update `cli.mdx`'s `verify` section + `configuration.mdx`/`getting-started` to mention `dawn verify` as the preflight. Build after the error-code registry (to emit codes) but independently shippable.
Loading
Loading