Documentation
¶
Overview ¶
Command qa-seed copies real (read-only) production Postgres data into a disposable local SQLite file so integration-qa can exercise the frontend against real-shaped data without touching prod and without per-test cleanup — the seeded file is simply discarded afterward. This replaces the prior fallback of "QA against prod + delete test rows".
v1 scope: goals (active), projects (active), tasks (all statuses), decisions, and pending_proposals (all known types) — the tables the frontend integration-qa currently touches. Knowledge/pgvector embedding data is out of scope (semantic search is not exercised by frontend QA).
Source (read-only): reuses storage.BuildServerStores(ctx, storage.BackendPostgres), which reads DATABASE_URL / PGSSLROOTCERT / APP_ENV / WORKSPACE_ID from the environment exactly like cmd/server does. Every read in this tool goes through a domain StoreIface List/Get-style method (ActiveGoals / ListActiveProjects / TasksFiltered / ListAll / All); qa-seed never calls a Create*/Update*/Delete* method on the source stores. Each seedX helper below takes a narrow reader interface (goalReader, projectReader, ...) exposing only the one method it needs, so the read-only contract is visible at every call site, not just documented in prose.
Destination: a fresh SQLite file opened via storage.NewServerStores(..., BackendSQLite), which runs the full migrations/sqlite/ chain at Open() (same as the app does at startup) — schema parity is automatic, see internal/storage/sqlite/db.go Open(). IDs, created_at, and updated_at are preserved via the ImportX methods added to the SQLite stores (ImportGoal/ImportProject/ImportTask/ImportDecision/ImportProposal) instead of Create* (which always mints a new UUID + timestamp) — this is required to keep cross-table references (project.goal_id, task.project_id, decision.task_id, ...) intact.
QA server flow ¶
On success qa-seed also (re)writes a local env file (default ".env.test", see --env-test-out) that points cmd/server at the freshly-seeded SQLite file, so the full flow is:
task qa-seed # seed SQLite + write .env.test go run ./cmd/server -env .env.test # serve the app against real data
.env.test deliberately has no DATABASE_URL (so storage.ResolveFromEnv falls back to SQLite, never prod — see internal/storage/storage.go BackendFromEnv), sets SQLITE_PATH to the exact --dest just seeded, a clearly-fake API_KEY (>=32 chars, required by cmd/server's validateAPIKey — see cmd/server/main.go), and PORT=8080 — a different port from cmd/server's own hardcoded default (8420, see cmd/server/main.go run()) so a QA server and a normally-running dev/prod server can coexist without a bind conflict. It also propagates WORKSPACE_ID from the source env (same value used to open the Postgres source above), so the QA server sees the same workspace scope prod data was captured under, rather than relying on "WORKSPACE_ID unset matches every row" as an implicit side effect.
.env.test is gitignored (matches the repo-wide ".env*" pattern in .gitignore) and is regenerated — overwritten — on every qa-seed run, so it always matches whatever SQLite file was seeded most recently. It is written with 0600 permissions per backend-security-design.md §4.1 (credential-bearing files).