docs(P00): create phase plans

PLAN.md: M1 decomposed into 5 execution phases (Waves A-E) + final review:
- Phase 1 / Wave A: Foundations (REQ-038, 039, 040) - monorepo, Postgres+RLS,
  audit hash-chain, SecretProvider, Trigger.dev bootstrap
- Phase 2 / Wave B: Identity & RBAC (REQ-001..005) - WorkOS SSO, tenant
  provisioning, RBAC at gateway, invitations, role assignment
- Phase 3 / Wave C: BYOM (REQ-006..009) - endpoint registry, validate-on-save,
  OpenAI-compatible routing shim, REQ-009 reject path
- Phase 4 / Wave D: Relay Agent (REQ-010..013, REQ-026 hook) - modular install
  script, Go binary, WebSocket registration, heartbeat, SSH whitelist hook
- Phase 5 / Wave E: Dashboard surfacing (REQ-014) - status fan-out, green/
  yellow/red, last 100 logs, RLS-scoped target views
- Phase 6 / Final: review + audit + milestone ship (v0.1.0 release)

Includes the 3 mandatory MVP/UX sections: User-Facing Surface (8 dashboard
surfaces), Happy Path (8 BDD steps = M1 demo), UX Acceptance Criteria (9).
Wave parallelism: A first, then B+C+D parallel, then E, then Final.

Tags run on v0.0.x patch line; final phase patch v0.0.7 IS the v0.1 milestone
release. Merge to main at final phase.

---ci---
phase: 0
milestone: v0.1
status: plan
---/ci---
This commit is contained in:
CIAgent
2026-08-24 22:36:50 +00:00
parent 551d4d64af
commit f18f507cb3
2 changed files with 248 additions and 2 deletions
+2 -2
View File
@@ -1,8 +1,8 @@
{
"phase": 0,
"stage": "research",
"stage": "plan",
"milestone": "v0.1",
"phase_role": "pre_execution",
"attempts": 1,
"updated_at": "2026-08-24T22:55:00Z"
"updated_at": "2026-08-24T23:05:00Z"
}
+246
View File
@@ -0,0 +1,246 @@
# Plan — Milestone 1 (v0.1)
Source spec: `.ciagent/steer-v0.1-spec.md` (v1.1, locked 2026-08-24).
M1 scope: REQ-001..014, REQ-038, REQ-039, REQ-040 (17 REQs). M1 acceptance gate: spec §2.3.
Architecture: `.ciagent/ARCHITECTURE.md`. Decisions: `.ciagent/CLARIFY.md`. Research: `.ciagent/RESEARCH.md`. Personas: `.ciagent/PERSONAS.md`.
M1 is decomposed into **5 execution phases** (Waves AE). Each wave is a vertical slice: scaffolds + implements + tests + ships a patch on the v0.0.x line. Each wave maps to explicit REQ IDs and has must-have verification items. Waves are ordered by dependency; A → B → C may overlap (B starts once A's `withTenant` + `audit` are green); D is independent of B/C and can run in parallel; E depends on B + D.
## Phase mapping (CIAgent phase model)
| CIAgent phase | Wave | Branch | Patch tag | REQs |
|---------------|------|--------|-----------|------|
| Phase 0 | pre-execution | `phase/00-pre-execution` | v0.0.1 | (this plan) |
| Phase 1 | Wave A — Foundations | `phase/01-foundations` | v0.0.2 | REQ-038, REQ-039, REQ-040 |
| Phase 2 | Wave B — Identity & RBAC | `phase/02-identity-rbac` | v0.0.3 | REQ-001, REQ-002, REQ-003, REQ-004, REQ-005 |
| Phase 3 | Wave C — BYOM | `phase/03-byom` | v0.0.4 | REQ-006, REQ-007, REQ-008, REQ-009 |
| Phase 4 | Wave D — Relay Agent | `phase/04-relay-agent` | v0.0.5 | REQ-010, REQ-011, REQ-012, REQ-013, REQ-026 (whitelist hook) |
| Phase 5 | Wave E — Dashboard surfacing | `phase/05-dashboard` | v0.0.6 | REQ-014 |
| Phase 6 | Final — Review + Ship | `phase/06-final-review-ship` | v0.0.7 ← milestone release | all M1 |
Tags run on the v0.0.x patch line (no prior minor). The final phase's patch (v0.0.7) IS the v0.1 milestone release. The milestone merge to `main` happens at the final phase.
---
## User-Facing Surface (MVP/UX §1)
M1 ships ONE user-facing surface: the **Platform Lead admin dashboard** (browser, Next.js, at `/dashboard`). The chat UI is M3 — explicitly not in M1.
M1 dashboard surfaces (each is a pass/fail QA surface):
1. **SSO entry** (`/login`): "Sign in with SSO" button → WorkOS redirect → session → `/dashboard` redirect. (REQ-001)
2. **Onboarding checklist** (`/dashboard`): first-tenant view shows the 5 onboarding steps with status badges (grey/green): Configure BYOM, Install Relay Agent, Register Target, Verify Green Status, Invite Team. (REQ-002)
3. **BYOM config form** (`/dashboard/byom`): URL input + API key input + "Validate & Save" button. On save: green "Validated" badge with the test inference result, or red error panel with details. (REQ-006, REQ-007)
4. **Relay Agent install instructions** (`/dashboard/relay`): per-tenant `curl|bash` install command with the tenant registration token embedded; supported OS list shown; "unsupported OS" callout. (REQ-010)
5. **Targets list** (`/dashboard/targets`): one row per registered Relay Agent — hostname, OS, IP, agent version, health (green/yellow/red), last-seen, "View logs" link. (REQ-012, REQ-014)
6. **Target detail** (`/dashboard/targets/<id>`): health badge, last 100 log lines (streamed via WebSocket fan-out), registration metadata. (REQ-014)
7. **Team / RBAC** (`/dashboard/team`): list members + role; "Invite" form (email → single-use link); role change dropdown (Admin/Operator/Viewer). (REQ-003, REQ-004)
8. **Audit export** (`/dashboard/audit`): admin-only "Download audit log (CSV)" button — no query UI in MVP (spec §2.2). (REQ-038)
Non-UI surfaces: the Relay Agent install script (`curl|bash`), the Go binary, the systemd unit. These are operator-facing artifacts, documented in the install guide.
## Happy Path (MVP/UX §2)
End-to-end M1 scenario (maps to spec Journey 2 steps 14 + 7 + 9), written before EXECUTE:
> **Given** a fresh CoreCI Chat deployment with Postgres + WorkOS + AWS Secrets Manager configured,
> **when** a Platform Lead completes the M1 onboarding,
> **then** the M1 acceptance gate (spec §2.3) passes: SSO signup → BYOM green validation → Relay Agent install on a target Linux host → target registered → green status in the admin dashboard. Audit logging, RLS, and secret manager are operational.
BDD steps:
1. **Given** an unauthenticated visitor, **when** they hit `/login` and complete WorkOS SSO, **then** a tenant is provisioned (first signup), they are assigned Admin, and `/dashboard` loads with the 5-step onboarding checklist (all grey). (REQ-001, REQ-002)
2. **Given** the Admin on `/dashboard/byom`, **when** they submit a valid BYOM URL + API key and click "Validate & Save", **then** the API key is stored in the secret manager (not the DB), a test inference call to `/v1/chat/completions` returns 200, the endpoint row is inserted under RLS, the checklist step turns green, and an audit entry is appended. (REQ-006, REQ-007, REQ-038, REQ-039, REQ-040)
3. **Given** the Admin on `/dashboard/relay`, **when** they copy the `curl|bash` command and run it on an Ubuntu 24.04 host, **then** the install script detects Ubuntu, downloads the Go binary, verifies the checksum, writes the systemd unit, writes the tenant token to `/etc/coreci/relay.env`, enables + starts the service, and exits 0. (REQ-010)
4. **Given** the systemd service is running, **when** the Relay Agent starts, **then** it opens an outbound WebSocket to the SaaS within 60s, registers (tenant/target/hostname/Ubuntu 24.04/IP/version), the control plane inserts a `targets` row under RLS + appends an audit entry, and the dashboard `/targets` list shows the new row. (REQ-011, REQ-012, REQ-038, REQ-039)
5. **Given** the Relay Agent is registered, **when** it sends a heartbeat every 30s, **then** `last_seen` updates and the dashboard health badge is **green**. (REQ-013, REQ-014)
6. **Given** the Admin on `/dashboard/team`, **when** they invite `ops@example.com` as Operator, **then** WorkOS sends a single-use acceptance link; when the invitee accepts and hits the API, RBAC enforces Operator permissions (e.g., cannot edit BYOM). (REQ-003, REQ-004, REQ-005)
7. **Given** a second tenant T2 exists, **when** T1's user issues any DB query, **then** RLS returns zero T2 rows (cross-tenant pen test passes). (REQ-039)
8. **Given** the unsupported-OS case, **when** the install script runs on Fedora, **then** it exits non-zero with a message listing Ubuntu 24.04 LTS and Debian 12+. (REQ-010, Edge 16)
This Happy Path is the M1 demo recording required by the M1 review.
## UX Acceptance Criteria (MVP/UX §3)
1. **SSO works in <3 clicks** from `/login` to `/dashboard` for a returning user (REQ-001).
2. **BYOM validation feedback is synchronous** — the "Validate & Save" button shows a spinner and resolves in <10s with a green/red result (REQ-007).
3. **Dashboard health badge turns green within 90s** of the Relay Agent's first heartbeat (REQ-013, REQ-014).
4. **Install command is copy-pasteable** — the `/dashboard/relay` page shows one `curl -fsSL <url> | sh` line with the tenant token already embedded; no manual editing required (REQ-010).
5. **RBAC is enforced on the very next API call** after a role change — the dashboard re-fetches and the new permissions apply immediately (REQ-004, REQ-005).
6. **Cross-tenant isolation is verifiable** — the M1 pen-test script demonstrates zero leakage across two tenants (REQ-039).
7. **Audit log is append-only** — a test that attempts `UPDATE`/`DELETE` on `audit_log` as the app role fails with a permission error (REQ-038).
8. **Secrets are never in the DB** — a test that scans `byom_endpoints` and all tenant-scoped tables for plaintext credentials passes (returns zero matches); secrets are resolvable only via `SecretProvider.get` (REQ-040).
9. **Unsupported OS aborts cleanly** — running the install script on an unsupported OS exits non-zero with an actionable message (REQ-010, Edge 16).
---
## Wave A — Foundations (Phase 1)
**Goal:** Monorepo scaffold + Postgres schema with RLS + append-only hash-chain audit + SecretProvider interface + Trigger.dev bootstrap. No HTTP routes yet.
**Depends on:** Phase 0.
**REQs covered:** REQ-038, REQ-039, REQ-040.
**Personas:** backend-engineer, data-engineer, security-engineer (sign-off).
### Tasks
1. **Scaffold monorepo** (backend-engineer): pnpm workspace; `apps/control-plane` (Next.js App Router + TS), `apps/relay-agent` (Go module, empty for now), `apps/dashboard` (part of control-plane for M1), `packages/{db,auth,audit,secrets,config,runtime}`. `tsconfig.json` base + per-package extends. `package.json` scripts: `lint`, `typecheck`, `test` (vitest), `migrate`. `turbo.json` or pnpm `--filter` orchestration. `.gitignore` additions (`.secrets/`, `node_modules`, `dist`, `.next`).
2. **Postgres schema + migrations** (data-engineer): `packages/db/migrations/0001_init.sql``tenants`, `users`, `tenant_memberships (user_id, tenant_id, role)`, `targets`, `byom_endpoints (tenant_id, url, secret_ref, validated)`, `invitations`, `audit_log (id BIGSERIAL, tenant_id, prev_hash, curr_hash, payload JSONB, created_at, user_id, target_id, correlation_id, event_type)`. All tenant-scoped tables carry `tenant_id UUID NOT NULL`.
3. **RLS policies** (data-engineer + security-engineer): per-table policy `USING (tenant_id = current_setting('app.tenant_id')::uuid)`. `packages/db/rls.sql` run by the migrator. App role `coreci_app` with INSERT/SELECT only; `REVOKE UPDATE, DELETE ON audit_log FROM coreci_app`. `migrator` role with BYPASSRLS for migrations only.
4. **`withTenant` helper** (data-engineer): `packages/db/withTenant.ts` — opens a transaction, `SET LOCAL app.tenant_id = $1`, runs the callback, commits. Throws if called outside a transaction. Unit test: a query outside `withTenant` returns zero tenant-scoped rows.
5. **Audit writer** (backend-engineer + security-engineer): `packages/audit/writer.ts``append(event)` computes `curr_hash = sha256(prev_hash || canonical(payload))`, INSERTs inside the caller's transaction. Constraint trigger rejects a forged `prev_hash`. `AuditWriteHaltError` thrown on failure → caller's transaction rolls back. Unit test: append 3 entries, verify the chain; attempt UPDATE/DELETE → permission denied.
6. **`SecretProvider` interface + impls** (backend-engineer + security-engineer): `packages/secrets/provider.ts` (interface), `packages/secrets/aws-sm.ts` (`@aws-sdk/client-secrets-manager`), `packages/secrets/local-encrypted.ts` (AES-256-GCM, master key from `SECRET_MASTER_KEY_DEV`). `SecretValue` type with `[REDACTED]` toString. Unit tests for both impls.
7. **Trigger.dev bootstrap** (backend-engineer): `packages/runtime/index.ts` — initializes the Trigger.dev client from config; registers a `runtimeHealthCheck` task that runs every 5 min and appends an audit entry. No chat tasks.
8. **Cross-tenant pen-test scaffold** (security-engineer): `tests/pen/cross-tenant.test.ts` — two tenants, attempt to read T2 as T1, assert zero rows. (Full pen test runs at M1 review.)
### Must-haves (verify before ship)
- [ ] `pnpm typecheck` + `pnpm lint` + `pnpm test` green.
- [ ] Migrations run clean against a fresh Postgres 16.
- [ ] `withTenant` test: query outside wrapper returns zero tenant rows.
- [ ] Audit chain test: 3 appends verify; UPDATE/DELETE rejected.
- [ ] `SecretProvider` test: put/get round-trip for both impls; `toString()` returns `[REDACTED]`.
- [ ] Cross-tenant pen-test scaffold compiles + runs (T1 sees zero T2 rows).
- [ ] Trigger.dev health task appends an audit entry on a 5-min tick.
- [ ] Code coverage ≥ 80% on `packages/db`, `packages/audit`, `packages/secrets`.
## Wave B — Identity & RBAC (Phase 2)
**Goal:** WorkOS SSO + session + tenant resolution + RBAC at the API gateway from the first endpoint. First HTTP routes.
**Depends on:** Wave A (withTenant, audit).
**REQs covered:** REQ-001, REQ-002, REQ-003, REQ-004, REQ-005.
**Personas:** backend-engineer, frontend-engineer, security-engineer (sign-off).
### Tasks
1. **WorkOS SSO route** (backend-engineer): `/api/auth/login` → WorkOS hosted SSO redirect; `/api/auth/callback` → code exchange → session. Session stored httpOnly cookie + `sessions` table row (tenant_id, user_id, role).
2. **Tenant provisioning** (backend-engineer): on first signup, if the WorkOS `organizationId` has no tenant, INSERT tenant + tenant_membership(role=Admin) inside `withTenant`. Audit append (provision event).
3. **Tenant resolution middleware** (backend-engineer): `packages/auth/middleware.ts` — reads cookie, loads session, `SET app.tenant_id` via `withTenant`, attaches `req.user = {id, tenantId, role}`.
4. **RBAC enforcement** (backend-engineer + security-engineer): `packages/auth/rbac.ts` — role → route permission map (Admin: all; Operator: read + chat; Viewer: read). Applied at the API gateway. First protected endpoint: `GET /api/me` (returns user + tenant). Test: Viewer calling `POST /api/byom` → 403.
5. **Invitations** (backend-engineer): `POST /api/invitations` (Admin only) → WorkOS invitation API → single-use link emailed. `POST /api/invitations/accept` → creates tenant_membership. Edge 15: bounce webhook → mark invalid.
6. **Role assignment** (backend-engineer): `PATCH /api/team/<userId>` (Admin only) → updates `tenant_memberships.role`. Enforced on next API call.
7. **Login + dashboard shell** (frontend-engineer): `/login` page (SSO button), `/dashboard` shell with the 5-step onboarding checklist (all grey — steps light up as later waves ship). `/dashboard/team` page (invite + role UI).
### Must-haves
- [ ] SSO round-trip works (test with WorkOS sandbox).
- [ ] First signup provisions tenant + Admin role; audit entry written.
- [ ] RBAC: Viewer → 403 on Admin-only route; Operator → 200 on read.
- [ ] Role change enforced on the next API call (test).
- [ ] Invitation email sent (WorkOS); acceptance creates membership; bounce marks invalid.
- [ ] Audit entry appended for every auth event (login, provision, invite, role change).
- [ ] Coverage ≥ 80% on `packages/auth`.
## Wave C — BYOM (Phase 3)
**Goal:** BYOM endpoint registry + validate-on-save + routing shim (OpenAI-compatible). No chat/orchestration in M1 — the routing shim is a proxy contract + REQ-009 reject path.
**Depends on:** Wave A (secrets, audit, withTenant), Wave B (RBAC).
**REQs covered:** REQ-006, REQ-007, REQ-008, REQ-009.
**Personas:** backend-engineer, frontend-engineer, security-engineer (sign-off).
### Tasks
1. **BYOM endpoints table + routes** (backend-engineer): `POST /api/byom` (Admin only) — takes `{url, apiKey}`; `secrets.put(tenantId, "byom", apiKey)``secret_ref`; INSERT `byom_endpoints (url, secret_ref, validated=false)` under `withTenant`; audit append (config event).
2. **Validate-on-save** (backend-engineer): after INSERT, call the BYOM validator: `POST <url>/v1/chat/completions` with a trivial test prompt; on 200 → `UPDATE ... validated=true`, audit append (validation ok), return green; on failure → DELETE the row (or mark invalid), audit append (validation fail), return error details (Edge 11). All inside one transaction per the audit-halt rule.
3. **Routing shim** (backend-engineer): `packages/byom/router.ts``routeInference(tenantId, payload)` resolves the tenant's validated endpoint via `withTenant`, fetches the key via `secrets.get`, POSTs to `/v1/chat/completions`. M1 exposes a test endpoint `POST /api/byom/test-inference` (Admin only) that calls the shim and returns the raw response — proves REQ-008 routing without needing chat.
4. **REQ-009 reject path** (backend-engineer + security-engineer): if no validated BYOM endpoint exists or the endpoint is unreachable, `routeInference` throws `ByomUnconfiguredError` / `ByomUnreachableError` → API returns a clear actionable error. Test: with no endpoint configured, calling `/api/byom/test-inference` → 400 with actionable message; with an unreachable URL → 503 with actionable message.
5. **BYOM dashboard page** (frontend-engineer): `/dashboard/byom` — URL + API key form, "Validate & Save" button, green/red result panel, current endpoint status. `/dashboard` checklist step 1 turns green on validated save.
### Must-haves
- [ ] Save stores key in secret manager; DB holds only `secret_ref` (test: scan tables for plaintext keys → zero).
- [ ] Validation POST hits the configured endpoint; green/red result accurate.
- [ ] Routing shim sends 100% of test-inference calls to the configured endpoint (outbound traffic log test).
- [ ] REQ-009: unconfigured → 400 actionable; unreachable → 503 actionable. No inference attempted.
- [ ] Audit entries for config + validation events.
- [ ] Coverage ≥ 80% on `packages/byom` + BYOM routes.
## Wave D — Relay Agent (Phase 4)
**Goal:** Modular install script + Go binary + WebSocket registration + heartbeat + auto-reconnect + SSH whitelist hook (no SSH adapter yet).
**Depends on:** Wave A (audit, withTenant), Wave B (auth token issuance).
**REQs covered:** REQ-010, REQ-011, REQ-012, REQ-013, REQ-026 (whitelist hook only).
**Personas:** go-engineer (phase-specific), backend-engineer (control-plane WS server), security-engineer (sign-off on whitelist hook).
### Tasks
1. **Tenant registration token** (backend-engineer): `POST /api/relay/issue-token` (Admin only) — issues a per-tenant registration token, stored as a secret-manager ref. The dashboard `/dashboard/relay` page shows the `curl|bash` command with the token embedded.
2. **WS server** (backend-engineer): `apps/control-plane/api/relay/ws` — accepts outbound WebSocket, authenticates the tenant token, on `register` message INSERTs `targets (tenant_id, hostname, os, os_version, ip, agent_version, last_seen)` under `withTenant`, audit append. Responds `{registered, targetId}`. Handles `ping` → updates `last_seen``pong`.
3. **Go binary — WebSocket client** (go-engineer): `apps/relay-agent/main.go` — reads `CORECI_TENANT_TOKEN` + `CORECI_SAAS_URL` from `/etc/coreci/relay.env`, connects `wss://<saas>/api/relay/ws`, sends `register`, then `ping` every 30s. On disconnect: exponential backoff (1/2/4/8/16s), max 5 attempts → alert + keep trying every 60s. systemd `Restart=on-failure` for hard crashes.
4. **Install script (modular)** (go-engineer): `scripts/install.sh` with separate functions `detect_os`, `install_binary`, `write_systemd_unit`, `register_target`, `main`. `detect_os` parses `/etc/os-release` (Ubuntu ≥ 24.04, Debian ≥ 12); else exit non-zero with the supported-OS list (Edge 16). `install_binary` downloads the Go binary for the detected arch, verifies SHA256, installs to `/usr/local/bin/coreci-relay-agent`. `write_systemd_unit` writes the unit + `daemon-reload` + `enable --now`. `register_target` writes `/etc/coreci/relay.env` with the tenant token. Idempotent (re-run upgrades).
5. **apt fallback** (go-engineer): documented apt package path (same script, `--method=apt` flag). The apt package ships the same binary + unit. (Documented; primary path is curl|bash.)
6. **SSH whitelist file + enforcement hook** (go-engineer + security-engineer): `/etc/coreci/ssh-whitelist.json` (fixed list: cat, ls, systemctl status, journalctl, df, du, ps, top, ss, netstat, ip, uptime, uname, free, who, w, last, dmesg, lscpu, lspci, lsblk, mount, findmnt, hostname, "ip addr", "ip route", "ss -tlnp"; argument deny list: -exec, -execdir, --exec, |, >, >>, &, ;, &&, ||). `apps/relay-agent/whitelist/check.go``CheckCommand(cmd) error` parses the command, checks base + args, returns error if rejected. Unit tests: every whitelist command passes; `rm -rf`, `find -exec`, `cat /etc/shadow | nc` all rejected. **No SSH execution path in M1** — M2 plugs the adapter into `CheckCommand`.
### Must-haves
- [ ] Install script on Ubuntu 24.04 succeeds (exit 0, systemd service running, agent connected within 60s).
- [ ] Install script on Debian 12+ succeeds (same).
- [ ] Install script on an unsupported OS (e.g., Fedora) aborts cleanly with the supported-OS list (Edge 16).
- [ ] Re-running the script upgrades, does not fail.
- [ ] Relay Agent registers with full metadata (tenant/target/hostname/OS/IP/version); audit entry written.
- [ ] Heartbeat updates `last_seen`; dashboard can read it (Wave E surfaces this).
- [ ] Auto-reconnect: kill the WS server, agent retries with backoff, max 5 → alert; restart server → agent reconnects.
- [ ] `CheckCommand`: every whitelist command passes; every deny-list case rejected. Coverage 100% on the whitelist module.
- [ ] Whitelist file format is versioned (JSON `{"version": 1, "commands": [...], "arguments": {"deny": [...]}}`).
## Wave E — Dashboard surfacing (Phase 5)
**Goal:** Dashboard shows Relay Agent health (green/yellow/red), target hostname, last 100 log lines, per-tenant view under RLS. M1 gate demo path complete.
**Depends on:** Wave B (auth, dashboard shell), Wave D (Relay Agent + WS server).
**REQs covered:** REQ-014.
**Personas:** frontend-engineer, backend-engineer.
### Tasks
1. **Status fan-out WebSocket** (backend-engineer): `apps/control-plane/api/relay/status` — Admin-authenticated WS that pushes target status changes (health, last_seen, log lines) to connected dashboard clients. Status computed from `targets.last_seen` (green < 60s, yellow < 5min, red > 5min).
2. **Targets list page** (frontend-engineer): `/dashboard/targets` — server component fetches targets via the API (under RLS), renders rows (hostname, OS, IP, version, health badge, last-seen, "View logs"). Client component subscribes to the status WS for live updates.
3. **Target detail page** (frontend-engineer): `/dashboard/targets/<id>` — health badge, registration metadata, last 100 log lines (streamed). Under RLS (T1 admin cannot view T2's target — pen-test asserts).
4. **Onboarding checklist completion** (frontend-engineer): `/dashboard` checklist step "Verify Green Status" turns green when at least one target is green. The M1 demo path (SSO → BYOM green → install → register → green dashboard) is end-to-end walkable.
5. **Audit export** (backend-engineer + frontend-engineer): `/dashboard/audit` — admin-only "Download CSV" button. No query UI (spec §2.2). CSV scoped to the tenant under RLS.
### Must-haves
- [ ] Dashboard shows a registered target as green within 90s of first heartbeat.
- [ ] Killing the agent → badge turns yellow then red as `last_seen` ages.
- [ ] Restarting the agent → badge turns green again.
- [ ] T1 admin cannot view T2's target (RLS pen-test asserts).
- [ ] Last 100 log lines render on the target detail page.
- [ ] Audit CSV export is tenant-scoped (no cross-tenant rows).
- [ ] The full M1 Happy Path (UX §2) is walkable end-to-end.
---
## Final Phase — Review + Ship (Phase 6)
**Goal:** Multi-persona code review + project health audit + milestone ship (v0.1.0 release, merge to main).
**REQs covered:** all M1 (sign-off).
### Tasks
1. **Code review** (lead-developer + all personas): review all changes in `milestone/v0.1-bootstrap` since `main`. Auto-apply P0 fixes; flag P1+ for post-hoc.
2. **Audit** (lead-developer): reconstruction test (git log matches `.ciagent/` files); file/branch/commit discipline; cross-tenant pen test runs green; install script runs on the 3 OS cases (Ubuntu 24.04 pass, Debian 12+ pass, unsupported abort).
3. **M1 acceptance gate verification** (lead-developer): spec §2.3 — Platform Lead can SSO → BYOM green → install → register → green dashboard. Audit/RLS/secrets operational.
4. **Milestone ship**: tag `v0.0.7` (final phase patch = v0.1 milestone release); merge `phase/06``milestone/v0.1-bootstrap``main`; create Gitea release with full milestone summary; build + upload Relay Agent binaries (linux amd64/arm64) + install script + apt package.
5. **Complete**: mark all M1 REQs complete in REQUIREMENTS.md; mark milestone complete in ROADMAP.md; clear checkpoint.
### M1 review deliverables (per Sarah's kickoff)
1. Per-REQ pass/fail test report with evidence (17 REQs).
2. Demo recording: fresh tenant → SSO → BYOM green → install → register → green dashboard.
3. Cross-tenant isolation pen test result (zero leakage).
4. Install script logs: Ubuntu 24.04 (pass), Debian 12+ (pass), unsupported OS (clean abort).
---
## Wave ordering & parallelism
```
Phase 0 (this plan) ──▶ Wave A (foundations)
├──▶ Wave B (identity/RBAC) ──▶ Wave E (dashboard) ──▶ Final
│ ▲
└──▶ Wave C (BYOM) ─────────────────┤
Wave D (relay agent) ─────────────┘
```
- A must complete first (B, C, D all depend on `withTenant` + `audit` + `secrets`).
- B and C can run in parallel after A (different territories; both depend on A only).
- D can run in parallel after A (independent of B/C; the WS server in D needs B's auth token-issuance endpoint — coordinate the contract in the plan, then D's WS server + B's token endpoint can land in the same wave window).
- E depends on B (dashboard shell + auth) and D (agent + WS server).
- Final depends on all.
## Test strategy
- Unit: vitest in `packages/*` + `apps/control-plane`; Go `testing` in `apps/relay-agent`.
- Integration: a Postgres 16 container in CI; `withTenant` + audit + RLS + secrets tests against it.
- Pen test: `tests/pen/cross-tenant.test.ts` runs at Wave A (scaffold) and Final (full).
- Install test: CI matrix runs `scripts/install.sh` on Ubuntu 24.04, Debian 12, Fedora (expects abort) containers.
- Coverage gate: ≥ 80% on new modules (spec §6).
- Lint + typecheck: `pnpm lint` + `pnpm typecheck` must be green before any wave ships.