# Containr — Project Roadmap Last verified: **2026-09-17** against `main` (`a1e4352`). Goal: bring Containr from "working core + large headless backend" to a finished, installable, documented v1.0 self-hosted platform. --- ## 1. Verified Current State Everything below was run, not assumed. | Check | Result | |---|---| | `go build ./...` | clean | | `go vet ./...` | clean | | `go test ./...` | all packages pass (api, build, cli, config, database, ha, middleware, networking, security) | | `tsc -b` (frontend) | clean | | `eslint .` | clean | | `vite build` | clean | | `vitest run` | 3/3 pass — one smoke test file only | | `go run ./cmd/migrate up` vs empty Postgres | clean — 52 app tables + `goose_db_version`, version `20260917000000` | | `go run ./cmd/migrate up` vs legacy schema | clean — APwhy parallel tables + `migrations` bookkeeping dropped, dead `users` columns removed | | Backend boot vs migrated Postgres+Redis | starts, `/live` → 200, `/health` → `{"database":"ok","redis":"ok"}`, protected routes → 401 | | Frontend demo mode (`?demo=1`) | Projects, workspace canvas, Templates render correctly in the Vertice theme | ### Feature inventory — backend vs frontend The backend exposes **~95 route registrations**; the frontend consumes **52** of them via `api-client.ts`. The gap below is the real roadmap. | Feature | Backend | Frontend | Docs | |---|---|---|---| | Auth (Better Auth: email, GitHub, Google; bootstrap gate; internal manual user creation) | done | done (`/auth/*`, People page) | done | | Projects CRUD | done | done (`/projects`) | done | | Project canvas (nodes, groups, auto-connections, drag layout) | via services API | done (React Flow, `ProjectCanvas`) | done | | Services CRUD | done | done (canvas + `/services/:id` detail) | done | | Deployments: create, history, rollback, logs | done | done (service detail) | done | | Service env variables | done | done (service detail config section) | done | | Service metrics (Docker stats) | done | done (metrics section + `/metrics-demo`) | done | | Host monitoring (`/system/host`) | done | done (Usage + Projects telemetry cards) | done | | Builds + live status over `/ws` | done | done (`/builds`, `useBuildUpdates`) | done | | Templates — list/detail/deploy | done | done (catalog page) | done | | Templates — **Compose catalog** (GitHub import, paste YAML, `x-containr` metadata, community source) | **missing** | **missing** | spec written, not built | | Git providers (PAT add/remove, repo list, branches) | done | done (Settings → Git Providers) | done | | Git repo connect + webhook auto-deploy | done (`/git/repositories/connect`, `/git/webhooks`, `/api/git/webhooks/:id` receiver) | done — provider→repo→branch in Add Service, connect+webhook on create | guides claim it works | | GitHub App install flow | done (`install_url` + connect endpoint) | done — Install button + `installation_id` return handling | done (real-provider e2e pending credentials) | | Cron jobs (CRUD, trigger, executions) | done — real scheduler + `docker exec` capture | done — Cron tab on service detail | OPERATIONS.md | | Managed databases (CRUD, actions, backup/restore) | done | done — `/databases` page (conn info, actions, backups) | OPERATIONS.md | | Preview environments (CRUD, promote, cleanup) | done (bookkeeping only — no real container deploy; URL is a placeholder) | done — Previews section on service detail | OPERATIONS.md | | Security scans, vulnerabilities, compliance/GDPR reports | done (heuristic scanner — image tags, insecure URLs, build-command smells; no Trivy/Grype binary scanning) | done — `/security` page (scan trigger, findings, metrics, history) | OPERATIONS.md | | Audit logs | done | done — `/settings/audit-logs` filterable page | done | | Autoscaling policies + manual scale | done | done (beta) — per-service Scaling section; policies in-memory, lost on restart | AUTOSCALING.md | | HA / failover policies | done | done (beta) | OPERATIONS.md | | Node agents (register additional VPS/LXC/VM hosts, heartbeats) | done | done — token issue/revoke + install command on Usage page | done | | API gateway (merged APwhy): upstream services, API keys, rate limits, ops/traffic analytics | management API done — native handlers under `/api/v1/gateway/*`, schema-corrected; **traffic proxy path still not mounted** (Phase 2) | **missing** | not documented | | Proxmox provider (cluster/nodes/VM/LXC CRUD) | written, **routes never registered** — unreachable | none | not documented | | Notifications (build/audit feed) | via existing APIs | done (shell bell menu) | — | | Self-upgrade (`/system/upgrade/*`) | done | done (Upgrade button) | — | | Light theme | CSS vars done | done (toggle in top bar) | — | | CLI (`cmd/cli`) | exists | — | thin | | Traefik + Cloudflare Tunnel infra | `infra/` done | — | done | | Demo mode (`?demo=1`) | n/a | done | — | ### Known issues and debt - ~~**React Flow edge warnings**~~ — **fixed**: `ServiceNode` now renders invisible `Handle` components (target left, source right), so inferred auto-connections attach without `#008` warnings. - ~~**OpenAPI drift**~~ — **resolved**: spec now covers all 153 mounted operations (114 path items, 154 operations incl. root probes). Schemas were corrected against the real Go structs (Service, Git*, NodeAgent, HostMonitoring, UpgradeStatus, metrics); stale `source`/`build_config` contract removed. `api-types.ts` regenerated; `api-client.ts` raw types now alias generated schemas — camelCase normalizers stay by design. - **Two DB access patterns remain**: GORM is gone (agents handler ported to sqlc), but most handlers still hand-write `database/sql` — ~140 call sites across ~14 files. Porting them to `sqlc/queries/` is mechanical but large; scheduled as its own pass. - ~~**Two migration systems**~~ — **resolved**: `migrations/` and its loader are deleted. `migrations_goose/` is the only path; `20260401163000_baseline.sql` carries the consolidated schema (idempotent — safe over partially-migrated legacy installs), and `20260917000000_drop_apwhy_parallel_auth.sql` removes the dead APwhy objects. Fresh install and legacy upgrade both verified via `cmd/migrate`. - ~~**APwhy is triple-implemented**~~ — **resolved**: vendored server, `cmd/apwhy`, embedded SolidJS bundle, and the unwired copy under `internal/api/server.go` + cloned packages are all deleted (~7.5k lines). Management API lives at `/api/v1/gateway/*` on Containr auth; traffic proxy mount remains Phase 2 work. Note: `umami_sync_cache` and `database_connections` were dropped with the dead tables — re-add via a new migration when Phase 2 Umami sync lands. - **Proxmox written but never wired**: `internal/proxmox` + `internal/api/proxmox.go` define 17 endpoints; `RegisterProxmoxRoutes` is never called. Kept deliberately — see §2.2. - ~~**Dead weight**~~ — **removed**: `reactflow` dep, `cmd/test-*` harnesses, `migrations/0001_init.sql` + `.bak`. Correction to the earlier audit: `feature_response.go` is *live* (`respondDependencyUnavailable` is used by `logs.go`) and stays. - **Test coverage**: frontend has focused tests (`variable-utils`) but no canvas/flow coverage; backend coverage concentrated in `internal/api` and infra packages; deployment engine, docker client, metrics, scaling, agents have no tests. --- ## 2. Decisions 1. **APwhy — merged, consolidated.** DECIDED: APwhy becomes a first-class Containr feature ("API Gateway"): deploy a service, publish it behind a managed route with keys, rate limits, and analytics. Canonical implementation is the Containr-native Gin handlers (`internal/api/apwhy_handlers.go`) — they already use Containr auth and DB. The vendored standalone server (`internal/apwhy/*`, `cmd/apwhy`, embedded SolidJS bundle) is deleted after the useful parts are ported (`gateway/proxy.go`, analytics queries). The parallel auth/RBAC schema from migration 008 is dropped — Better Auth owns identity. APwhy's upstream repo at `ref/APwhy` stays as the standalone edition. 2. **Proxmox — kept, and generalized into infrastructure providers over Terraform/OpenTofu.** DECIDED: node provisioning becomes an infrastructure-as-code pipeline. `internal/proxmox` stays for read/inventory (cluster status, node stats, existing VMs/LXCs); all *provisioning* goes through Terraform — one engine, providers as HCL modules. Proxmox (bpg/proxmox) is provider #1, AWS EC2 is provider #2; adding Hetzner/GCP/later providers is then a module, not a feature. Provisioning a node = render module → `terraform apply` → cloud-init installs Docker + `containr-agent` → node auto-enrolls. VM CRUD alone is not the feature; capacity-on-click is. - Engine: `terraform-exec` (or OpenTofu binary — same HCL, OSS license; pick at implementation time). Provider binaries + tfstate live under a `data/iac/` dir; provisioning requires the binary, core platform does not. - Secrets: provider credentials encrypted at rest in an `infra_connections` table, instance-scoped. 3. **Multi-node / autoscaling / HA in v1.0?** — **Ship agents + host monitoring; mark autoscaling/HA as beta.** They are backend-complete but unproven end-to-end. 4. **Environments**: `environment_id` exists in the schema and the seeder creates production/preview/development, but the UI exposes a single `env:production` badge. — **Production-only in v1.0**; preview environments land in Phase 3. 5. **DB access**: — **sqlc only.** Remove the GORM dependency (used by ~1 handler) and stop hand-writing SQL in handlers. --- ## 3. Phases ### Phase 0 — Hygiene and contract (1 pass, blocking) Everything else builds on a truthful API contract and a clean tree. - [x] Remove dead code: `migrations/0001_init.sql`, `migrations/*.bak`, `cmd/test-build-manager`, `cmd/test-railpack`, `reactflow` npm dep. (`feature_response.go` kept — it is live.) - [x] **APwhy consolidation** (per §2.1): Containr's native `internal/gateway` was already the real proxy — nothing needed porting; the vendored server, `cmd/apwhy`, the embedded SolidJS bundle, the unwired `internal/api/server.go` copy, and the cloned packages are deleted. Management routes moved to `/api/v1/gateway/*` (services CRUD + validate, keys, ops/traffic analytics); handlers fixed against the real schema (UUID ids, integer `enabled`, plan validation). Parallel auth/RBAC tables dropped in the migration consolidation. - [x] Consolidate migrations: `migrations/` collapsed into the goose baseline `20260401163000_baseline.sql` (idempotent); `migrations_goose/` is the only path; legacy loader + Dockerfile copy removed. Fresh-boot (53 tables) and upgrade-from-legacy both verified through `cmd/migrate`. - [x] Converge DB access on sqlc: agents handler ported off GORM, `gorm` dependency dropped, agent queries in `sqlc/queries/agents.sql`. Remaining hand-written `database/sql` in other handlers (~140 sites) is tracked as a follow-up pass — sqlc is the target for all new code. - [x] Bring `docs/api/openapi.yaml` to parity with `routes.go`: 114 paths / 154 operations cover every mounted route (incl. `/live`, `/health`, `/ready`, `/api/agents/*` token routes with server overrides; Proxmox handlers are registered nowhere, intentionally undocumented). `api-types.ts` regenerated; `api-client.ts` contract types deduped to generated schemas — normalized view models kept. - [x] Fix auto-connection edges — `ServiceNode` lacked `Handle` components; invisible target/source handles added in `nodes.tsx`. - [x] Rerun desloppify; record score. 2026-09-17 scan (`ref/` excluded): objective **66.1**, strict **16.5** — note the tool's scoring model changed: strict now drags the 75%-weighted subjective pool (20 human review items, all unscored). Mechanical dimensions: code quality 90.7, security 97.0, file health 81.4, test health 35.0. The old "≥89 strict" target is not comparable; treat objective ≥ 70 as the next milestone. - [x] `go vet`, `eslint`, `tsc`, `go test`, `vitest`, `vite build` all green; boot smoke verified (`/live` → 200, `/health` → db/redis ok, protected routes → 401) and documented in `CONTRIBUTING.md`. **Gate**: `docker compose up -d` from a clean clone produces a working stack; OpenAPI spec matches the router 1:1; zero dead packages. ### Phase 1 — Finish the core deploy loop (the Railway promise) The product's spine: *project → service → deploy → logs → rollback*. Most pieces exist; this phase closes the gaps that block real usage. - [x] **Git connect flow, end to end**: Settings → GitProvidersSection manages providers; service dialog picks provider → repo → branch; on create the repo is connected (`POST /git/repositories/connect`, 409-tolerant) and a push webhook is registered (`POST /git/webhooks`). Live provider push verification pending real credentials. - [x] **Webhook auto-deploy**: public receiver `POST /api/git/webhooks/:id` verifies HMAC (GitHub `X-Hub-Signature-256`, Gitea `X-Gitea-Signature`) or GitLab `X-Gitlab-Token`, maps repo+branch → services, inserts deployment rows and enqueues `runDeploymentAndSync` (trigger=webhook), which flips canvas node status to building. Unit-tested signature verification. Not yet e2e-verified against a real provider. - [ ] **Compose template catalog** per `docs/superpowers/specs/2026-04-14-compose-template-catalog-design.md`: - `service_templates.compose_yaml`, `screenshots`, `source_type`, parsed `config` summary (migration + sqlc). - `POST /templates/import/github`, `POST /templates/import/compose`. - Variables discovered from `${VAR}`/`${VAR:-default}` placeholders. - Catalog UI: screenshots, badges, import dialogs (GitHub + paste), install wizard → creates one service per Compose service. - [x] **Service creation flows on canvas**: Add Service covers Image, Git repo (provider → repo → branch → connect+webhook), Database (engine + plan → real `POST /databases` provisioning, not a hollow service row), worker/cron types, and a Template entry that jumps to the catalog with the project preselected (`/templates?project=`). - [x] **Variables UX**: Variables section on the service detail page — add / edit / remove rows, secret masking (masked `********` secrets preserved on save; editing replaces), bulk `.env` paste (`KEY=VAL`, comments, `export` prefix, quoted values), inline validation (required / invalid / duplicate keys). Per-environment values are already covered by the schema: each environment owns its service instances, and variables are per-service. - [x] **Deploy lifecycle polish**: backend already writes deploy progress to `services.status`; the canvas now polls faster while any service is in a transitional state so build → deploy propagates to nodes, deployment history rows link straight into that deployment's log tail, and rollback asks for confirmation before firing. - [x] **Node/port collision handling**: verified structurally impossible in the current model — services never claim host ports (`PortMappings` is never populated; traffic routes via container IPs + Traefik discovery), and managed databases bind `127.0.0.1` with ephemeral host ports. If a claim ever conflicts, Docker's bind error lands in `deployments.error` and shows in the failed-deploy log tail — not silent. Revisit if a service-level host-port config is ever added. - [x] Auth hardening: e2e-verified against a live stack (scratch Postgres + Redis + real Better Auth sidecar). Fixed a real gate hole: sign-ups write `auth_users` but the gate counted `users`, which only mirrors on the first authenticated call — a second registration slipped through the pre-mirror window. `countLocalUsers` now unions both tables (verified: `users=0, auth_users=1` already returns `mode: login` and 403s on both public register paths; internal-token manual creation works, wrong token refused). Session expiry redirect confirmed in `App.tsx` (401 → null → `/auth/sign-in?redirect=…`). OAuth happy paths remain unverifiable without real GitHub/Google credentials — providers are env-gated and `disableImplicitSignUp` means OAuth cannot bypass the gate by creating accounts. **Gate**: a fresh instance can register → create project → connect GitHub repo → push → see build → see deploy → view logs → roll back, with zero manual API calls. Template install from pasted Compose works the same way. ### Phase 2 — Surface the headless backend All of these have working APIs and no UI. Cheapest feature wins in the repo. - [x] **Databases** (`/databases/*` + backup/restore): `/databases` page (nav item) — expandable cards with connection URL + copy, start/stop/ restart actions, metrics summary, backup list + New backup + Restore (with confirm). Verified e2e against a real `postgres:16` image: provision → `psql` connect → stop/start → seed → backup (6.4MB archive) → drop table → restore → data recovered. - [x] **Cron jobs** (`/cron-jobs/*`): real engine + UI. Backend was a stub (`calculateNextRun` returned now+1h, `executeCronJob` slept 2s and wrote "success" without running anything). Now: `robfig/cron` parses the schedule (validated on create → 400), a 60s scheduler goroutine executes due enabled jobs, and commands run via `docker exec sh -c` inside the service's `containr--*` container with stdout+stderr captured into `cron_executions` — verified e2e (trigger + scheduler tick, real alpine output). UI: "Cron" tab on the service detail page — list/create/ edit/enable-toggle, manual Run, execution history with logs. (Not added to Add Service: jobs exec into an existing service's container, so they live on the service, not as a standalone type.) - [x] **Preview environments UI**: service-detail "Previews" section — list per service, create (branch/PR/TTL), promote-to-production with confirm, delete with confirm. Verified e2e: create→list→promote→delete. Fixed two latent backend bugs: create scanned NULL `services.type` (every real service failed) and three deployment inserts omitted the NOT NULL `version` column (create + rollback + promote all broke on fresh installs). - [ ] **Preview environments runtime**: preview creation writes bookkeeping rows and a fake `*.preview.containr.local` URL — nothing is actually deployed. Real preview = build the branch + deploy container + route a real subdomain + TTL cleanup that tears down containers. Sizeable; schedule with env support (§2.4). - [x] **Audit logs**: real page at `/settings/audit-logs` (linked from the Settings page) — filterable table by resource, action, actor (email), and time range, with pagination. Backend widened accordingly: the list endpoint was self-scoped and dropped `user_email`; it now joins `users` and accepts `user_id`/`actor`/`since` filters. Widening is consistent with the platform's no-roles trust model (every authed user can already create users). - [x] **Agents/nodes**: onboarding flow on the Usage page — issue per-node tokens (raw `cagt_…` shown once, only sha256 hash stored in `agent_auth_tokens`), one-time copy + prefilled `CONTAINR_API_URL`/`CONTAINR_AGENT_AUTH_TOKEN` install command, issued tokens list with label/created/last-used and revoke (revoked tokens rejected at register/heartbeat — verified e2e). Env-configured shared tokens (`CONTAINR_AGENT_AUTH_TOKEN(S)`) still accepted. Node cards with heartbeat status + telemetry were already on the Usage page. - [ ] **API Gateway (merged APwhy, full revival)** — new runtime work, not just UI. Target: every upstream service published through Containr is reachable, key-gated, rate-limited, and metered: - [ ] Mount the traffic path: catch-all route (e.g. `/g/:serviceSlug/*path` or per-service subdomain) → resolve `api_services` → API-key check → rate limit (`rpm_limit`, `monthly_quota` via `usage_counters`) → proxy through the ported `proxy.go` → record `metrics_timeseries` + `incident_events`. - [ ] Port `POST /services/:id/validate` (upstream reachability check) from the vendored server. - [ ] Full management surface under `/api/v1/gateway/*`: services CRUD + validate, keys issue/revoke/quotas, `analytics/ops`, `analytics/traffic`, incidents list. - [ ] UI: new "Gateway" page — register upstream (auto-suggest running Containr services), assign route prefix, issue/revoke keys with quotas, per-service traffic + ops charts (feeds Usage page too), incident timeline. - [ ] Optional: Umami sync (`umami_sync_cache`) if a Umami instance is configured — otherwise drop the table. - [ ] Note: APwhy's own auth/RBAC/deploy/database endpoints are *not* ported — Better Auth owns identity, Containr owns deployments and databases natively. - [x] **Notifications depth**: `notifications` table + `GET /notifications`, `POST /notifications/:id/read`, `POST /notifications/read-all`. Bell feed now reads real persisted rows with unread badge + mark-read. Producer wired: deployment success/failure writes a notification. Verified e2e: list, unread count, mark-one, mark-all. Further producers (backup completion, agent offline, upgrade available) can reuse `insertUserNotification`. - [x] **GitHub App install surfacing**: Settings → Git Providers shows an "Install GitHub App" action that opens `install_url`, and handles the `?installation_id=` return by calling `POST /git/github-app/connect`. (Verified at typecheck/lint level; real GitHub App redirect e2e tracked under the OAuth/provider-credentials item in Phase 4.) - [x] **Database → service binding**: `/databases` cards have a Bind action — pick project → service → variable key (default `DATABASE_URL`) and the `connection_url` is injected as a secret env var. Verified e2e: merged PUT lands the URL and preserves masked secrets verbatim. - [x] **Backup export**: `GET /databases/:id/backups/:bid/download` streams the archive out of the `containr-db-backups` volume via a stopped utility container + `CopyFromContainer` (daemon logs mangle non-UTF-8, so `cat` through logs was not viable). Download action in the `/databases` backups list. Verified e2e: byte-identical archive, 404 missing/cross-user, 401 unauthenticated. - [x] **Scheduled database backups**: `database_services.backup_schedule` (cron expr) + `next_backup_at`; `PUT /databases/:id` accepts `backup_schedule` (validated, empty string disables). The cron tick folds due databases into `createBackupProcess`. Schedule row in the `/databases` backups section. Verified e2e: `* * * * *` produced three consecutive completed archives with real volume data. **Gate**: every registered route is reachable from the UI or has an explicit "API-only" note in the spec. A request to `/g/{service}` with a valid key reaches the upstream container and appears in the analytics tables. ### Phase 3 — Advanced operations - [x] **Autoscaling UI** (`/scaling/*`): per-service policy editor (min/max/target CPU/mem + enable toggle) and manual scale control on service detail Scaling section. Verified live: policy CRUD, state auto-registration, bounds rejection, scheduler dispatch (fails "no ready nodes" without agents — error surfaces in UI). Scaling history timeline not surfaced. Beta: policies are in-memory — they do not survive a backend restart. - [x] **HA / failover UI** (`/ha/*`): `/ha` page with manager enable/disable, status cards (nodes/health checks/alerts), manual failover with confirmation, per-service failover policy CRUD (project→service picker), active alerts with resolve, health check results. Verified live. Beta: policies/alerts are in-memory, lost on restart. Health-check and notifier CRUD endpoints remain unexposed. - [x] **Security center** (`/security/*`): `/security` page with project picker, scan trigger (type + optional service), security score and vuln metrics cards, findings list with resolve/ignore, scan history. Fixed latent bug: `security/metrics` queried a non-existent `score` column (score lives in `summary` jsonb) — was 500 on every project. Verified live: comprehensive scan produced 4 findings (3 high, 1 medium), resolve flow, metrics aggregation. Real mechanism documented: heuristic config analysis, NOT Trivy/Grype. Remaining: compliance assessment/report viewer UI, security audit correlation view. - [ ] **Multi-node scheduling**: service → node placement rules using agent telemetry; health-based rescheduling. Depends on Phase 2 agents. - [ ] **Infrastructure providers over Terraform** (per §2.2): - [ ] `internal/iac` package: wraps terraform-exec/OpenTofu — render provider module + vars, `init`/`apply`/`destroy` in per-node workspaces under `data/iac/`, capture outputs (IP, name, node id). - [ ] `infra_connections` table: provider type (`proxmox`, `aws`), encrypted credentials, instance-scoped; managed in Settings. - [ ] `proxmox` module: LXC/VM on chosen node/storage, cloud-init installs Docker + `containr-agent` with enrollment token → auto-registers. - [ ] `aws` module: EC2 instance (AMI + instance type + SG) with the same cloud-init enrollment → same node pipeline, different provider. - [ ] Register `RegisterProxmoxRoutes` behind auth at `/api/v1/proxmox/*` for read/inventory (cluster, nodes, stats, existing VMs/LXCs) — write/provision path is Terraform only. - [ ] Node lifecycle: destroy = `terraform destroy` + agent deregistration; drift detection via periodic `plan` (mark node drifted in UI). - [ ] UI — Nodes/Infrastructure page: provider connections, provision wizard (pick provider → region/node → size → enroll), node cards with provider badge, VM/LXC lifecycle actions. - [ ] **Environments** (per §2.4): env switcher, per-env variables/domains, promote flow — ships with preview environments. - [x] **Service exec console**: `POST /services/:id/exec` + Console section on service detail — one-off `sh -c` commands via `ExecRun` (30s cap, 64KB output), ownership-checked, captured output + exit code per run. Verified live: real container exec, stderr capture, 404 foreign service, 401 unauthenticated. Interactive PTY remains out of scope. **Gate**: scaling and failover demonstrably work against a two-node lab; security scans produce real reports, not empty tables. ### Phase 4 — Productization and release - [ ] **First-run experience**: setup wizard (create admin → optional OAuth → optional GitHub App → done), seeded demo project optional, empty states everywhere. - [ ] **Install story**: single `curl | sh`-style installer that writes `.env`, pulls images, runs compose; verify on a clean VPS. - [ ] **Self-upgrade loop**: verify `/system/upgrade/pull` actually swaps the running image; document rollback. - [x] **Docs pass (partial)**: `AUTOSCALING.md` rewritten to document the real `/scaling` API + Scaling section (was generic Cloudflare/Swarm advice); new `docs/guides/OPERATIONS.md` covers cron, exec console, managed databases, preview environments, security scans, HA, and node-agent onboarding — all synced to verified behavior. Remaining: rewrite getting-started to the canvas flow; publish OpenAPI at `/docs` inside the app (Docs page exists — render the spec there). - [ ] **Tests**: raise frontend beyond smoke (canvas interactions, template install wizard, git connect); backend table-driven tests for deployments, templates import, webhooks; Playwright e2e suite for the Phase-1 golden path. - [ ] **OAuth e2e with real credentials**: GitHub/Google providers are env-gated and config-verified only — run the happy path once real provider apps exist (bootstrap gate already proven: OAuth cannot bypass it via `disableImplicitSignUp`). - [ ] **sqlc port — remaining raw SQL**: ~140 hand-written `database/sql` call sites across ~14 handler files remain (agents + tokens are sqlc). Mechanical port to `sqlc/queries/`; the consolidated baseline schema is now the codegen source of truth. - [ ] **Autoscaling persistence**: policies, service states, and scale events live in process memory — a backend restart wipes them and reverts replicas to policy minimums on next registration. Persist to Postgres before leaving beta. - [x] **Demo-data parity for new pages**: `demoDatabases` fixture wired into `/databases?demo=1`; `demoCronJobsByService` fixtures wired into the service Cron tab (worker + api demo services). Scaling, Previews, HA and Security sections still show empty in demo mode — acceptable: those are interaction surfaces, not dashboards. - [x] **CI**: existing workflow already covered Go tests, frontend lint/typecheck/build, compose config validation, and startup preflight. Added: compose **boot** smoke (postgres + redis + backend + frontend + traefik up, `/health` poll, frontend check, teardown) and a tag-gated GHCR publish job (backend + frontend images on `v*` tags). Not yet observed green on GitHub — first run pending. - [ ] **Release**: `v1.0.0` tag, changelog, release notes, screenshots refresh in README. - [ ] Post-build workflow per repo rules: codebase-to-course, desloppify, security-reviewer, audit; append README scorecard. **Gate**: clean VPS → installer → first service deployed in under 10 minutes; CI green; docs match the product. --- ## 4. Cross-Cutting (every phase) - **Spec parity**: OpenAPI is the contract — regenerate types on every API change (`npm run generate:api`); CI check for drift. - **No manual API type duplication** in `api-client.ts` — consume `generated/api-types.ts`. - **Vertice design system**: all new UI follows `index.css` tokens, mono labels, lime accent; no new palettes. - **Demo mode parity**: every new page ships demo data in `demo-data.ts`. - **Accessibility**: keyboard nav, focus rings, aria labels — the shell already sets the pattern. - **Commits**: conventional messages, `Authored By: TDvorak ` trailer; squash-merge bodies kept short. - **Pre-push gate**: build + test + lint + boot smoke, every time. ## 5. Explicitly Out of Scope for v1.0 - Billing / metering (self-hosted; Usage page is monitoring-only). - Kubernetes backend — Docker/Compose is the runtime. - Magic-link, GitLab/Bitbucket/Gitea **auth** (removed by auth spec). Git *providers* beyond GitHub remain PAT-based. - Mobile app, TUI, desktop app.