Files
solopm-server/plans/16-phases.md
T
2026-08-14 11:29:11 -06:00

177 lines
9.6 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 16 — Phased Roadmap
Eleven phases, each independently buildable and verifiable. A build agent starting a phase should read: `00`, `01`, `02`, the files listed for the phase, and nothing else. **Check off tasks and acceptance criteria here (and the phase box in `00-overview.md`) as they are completed and verified.**
Dependency graph: `0 → 1 → 2 → 3 → 4 → 5 → 6 → {7, 8} → 9 → 10` (7 and 8 are parallel-safe after 6).
## Phase 0 — Foundation
Spec files: [01-architecture.md](01-architecture.md), [14-design-system.md](14-design-system.md), [15-deployment.md](15-deployment.md)
- [x] Repo skeleton per architecture layout; Go module `solopm.com/solopm-server`
- [x] chi server + middleware chain (minus auth resolver), slog, request logging, graceful shutdown
- [x] Config loader with full env var set + `.env.example`
- [x] golang-migrate wired (embedded migrations) + migration 0001 (extensions + enums); sqlc configured and generating
- [x] Vue app scaffolded (Vite, TS, Pinia, Router, Tailwind) with design system copied to `web/design-system/` and wired; AppShell (sidebar/navbar) renders
- [x] `go:embed` SPA serving with deep-link fallback; Vite dev proxy
- [x] docker-compose (app + postgres + meilisearch + mailhog dev profile); Makefile targets
- [x] `/healthz`
Acceptance:
- [ ] `docker compose up` serves the SPA shell at `:8080`; `/healthz` reports db ok — not verified: no `docker` CLI available in this environment. Functionally equivalent path verified instead: `go run ./cmd/solopm serve` against the real dockerized postgres/meilisearch returned SPA 200 and `/healthz` `{"database":"ok","meilisearch":"ok","smtp":"absent"}`. Someone with docker access should run the literal command once to close this out.
- [x] `make migrate-up && make migrate-down` cycles cleanly
- [x] Shell passes the design-system conformity checklist
## Phase 1 — Auth & users
Spec files: [05-auth.md](05-auth.md) · Depends: 0
- [ ] Migrations: users (+seeds), oauth_accounts, sessions, api_tokens
- [ ] Register/login/logout with scs sessions; first-user-becomes-admin; rate limiting
- [ ] GitHub + Gitea OAuth (linking rules incl. verified-email guard)
- [ ] PAT create/list/revoke + bearer middleware
- [ ] `/me` endpoints, `/users` picker, admin user endpoints
- [ ] Frontend: login/register/oauth-callback pages, auth store + guard, settings→profile/security/tokens tabs, admin users page
Acceptance (scriptable with curl):
- [ ] Register → is_admin true; second register → false
- [ ] Cookie-authed and PAT-authed `GET /me` both succeed; revoked PAT → 401
- [ ] system/zombie users absent from `/users`; login as ids 12 impossible
- [ ] OAuth flow against a real or mocked provider produces a linked account (verified-email path unit-tested)
## Phase 2 — Event backbone & audit
Spec files: [03-domain-events.md](03-domain-events.md) · Depends: 1
- [ ] Migrations: audit_log, events, jobs
- [ ] `events.Emit` (audit + outbox in-tx), pg_notify, dispatcher goroutine, job worker pool with backoff/dead states
- [ ] `auth.*` and `user.*` events emitting through the pipeline
- [ ] Admin jobs inspection endpoint
Acceptance:
- [ ] Login writes an audit row and a processed event row
- [ ] `kill -9` during a pending job → job retried after restart (integration test)
- [ ] A job failing 8 times lands in `dead` and is visible via admin endpoint
## Phase 3 — Projects core
Spec files: [06-projects.md](06-projects.md) · Depends: 2
- [ ] Migrations: projects, project_members, project_dependencies, project_favorites, labels
- [ ] Project CRUD + status/completed_at automation + owner transfer + membership + favorites + label CRUD/seeding + dependencies with cycle detection
- [ ] `RequireProjectMember`/`RequireProjectOwner` middleware; non-member 404s
- [ ] Activity feed endpoints (project + dashboard)
- [ ] All mutations emit events per taxonomy
Acceptance:
- [ ] Non-member GET on another's project → 404
- [ ] Dependency cycle attempt → 409 `cycle_detected`
- [ ] Every mutation appears in `/projects/{id}/activity` with correct actor
- [ ] Sole-owner cannot be removed/deleted without transfer (409)
## Phase 4 — Issues & epics
Spec files: [07-issues-epics.md](07-issues-epics.md) · Depends: 3
- [ ] Migrations: epics, issues, issue_blockers, issue_labels, issue_watchers
- [ ] Issue CRUD, numbering, sub-issues, blockers + cycles, labels, points, completed_at automation
- [ ] board_rank helpers (`Between/Before/After`) with unit tests; `?view=board`; list filters/sort
- [ ] Epic CRUD, attach/detach/reorder issues, computed progress (counts + points)
Acceptance:
- [ ] Two projects both mint `#1`; 20 parallel creates in one project yield 20 unique consecutive numbers
- [ ] Status→done sets completed_at; back to in_progress clears it
- [ ] Blocker cycle → 409; epic progress matches a hand-computed fixture
- [ ] Board view returns rank-ordered groups; a move lands between its neighbors
## Phase 5 — Views (frontend core)
Spec files: [13-frontend.md](13-frontend.md) (+ [14-design-system.md](14-design-system.md)) · Depends: 4
- [ ] Dashboard, project list/overview, project settings (general/members/labels tabs)
- [ ] Issue table with FilterBar + URL-synced state
- [ ] Kanban board with optimistic drag-and-drop
- [ ] Issue detail page (all sidebar fields editable, sub-issues, blockers)
- [ ] Epic list/detail pages
- [ ] Stores: projects, issues, board, epics; badges/pickers/dialog components
Acceptance:
- [ ] Drag card between columns → persists after reload (status + position)
- [ ] Filters/sort survive refresh via URL; shareable link reproduces the view
- [ ] Failed PATCH rolls back the optimistic move with a toast
- [ ] New pages pass the conformity checklist
## Phase 6 — Collaboration
Spec files: [08-collaboration.md](08-collaboration.md) · Depends: 5
- [ ] Migrations: comments, links, attachments, wiki_pages
- [ ] Comments CRUD + resolve; polymorphic links/attachments + `/files/` auth-checked serving; wiki CRUD with slug rules + conflict warning
- [ ] Server-side mention/#ref extraction into event payloads; watcher auto-watch/mute semantics
- [ ] Frontend: MarkdownEditor/MarkdownView (mention + #ref autocomplete/rendering, paste-to-attach), comments UI with resolve, attachments/links UI, wiki pages, WatchButton + watcher list
Acceptance:
- [ ] `@user` in a comment produces a mention entry in the event payload; `#3` renders as a link to issue 3
- [ ] Resolving collapses the thread; unresolve restores it
- [ ] Upload lands at the specified disk path; non-member GET on `/files/…` → 404; oversized upload → 413
- [ ] Unwatch (mute) then comment again → no re-auto-watch
## Phase 7 — Notifications & real-time
Spec files: [09-notifications-realtime.md](09-notifications-realtime.md) · Depends: 6
- [ ] Migration: notifications
- [ ] Fan-out consumer (rules + precedence + dedupe), inbox/bell endpoints
- [ ] SSE hub + `/stream` + heartbeats; frontend sse.ts + realtime store + in-place updates
- [ ] SMTP sender, instant emails, daily digest job, email_mode setting UI
Acceptance:
- [ ] Two-browser test: assign in A → B's bell increments and B's board card moves within 2s, no reload
- [ ] Actor never notified of own action; `mentioned` wins precedence over `commented`
- [ ] MailHog shows instant email; digest job groups a day of notifications into one email
- [ ] Muted watcher gets neither bell nor email
## Phase 8 — Search (parallel-safe with 7)
Spec files: [10-search.md](10-search.md) · Depends: 6
- [ ] Meilisearch client, index bootstrap, event→sync mapping, `search_sync` job handler
- [ ] `/search` endpoint with membership filtering; reindex CLI + admin endpoint
- [ ] CommandPalette (Cmd+K) + `/search` page
Acceptance:
- [ ] Created issue findable in < 5s; soft-deleted issue disappears from results
- [ ] Non-member's content never appears (integration test with two users)
- [ ] `solopm reindex` rebuilds equal counts from Postgres truth
- [ ] Palette opens with Cmd+K, arrows navigate, Enter routes
## Phase 9 — Integrations
Spec files: [11-integrations.md](11-integrations.md) · Depends: 7
- [ ] Migrations: incoming_webhook_receipts, outgoing_webhooks, outgoing_webhook_deliveries
- [ ] Incoming endpoint: HMAC verify, receipts, repo routing, closes-#N automation (system user, via_webhook), replay
- [ ] Outgoing: CRUD, signed delivery jobs, delivery log, test endpoint, SSRF guard
- [ ] Export JSON + CSV streaming
- [ ] Frontend: settings→webhooks tab (secret, receipts, outgoing CRUD + deliveries), export tab
Acceptance:
- [ ] Replayed GitHub PR-merged fixture with valid signature closes `#N`; audit shows system user with via_webhook=true; bad signature → 401 with receipt logged invalid
- [ ] Duplicate delivery id → 200 without reprocessing
- [ ] Outgoing delivery retries against a flaky test receiver, then records success with correct `X-SoloPM-Signature`
- [ ] JSON export contains every entity family; CSV opens with correct columns
## Phase 10 — Retention & hardening
Spec files: [12-retention.md](12-retention.md) · Depends: 9
- [ ] Cull job + CLI + admin endpoint with dry-run; cascade scopes incl. disk files; zombie reassignment; auxiliary retention (notifications/events/jobs/receipts/deliveries)
- [ ] Backup/restore docs + drill; systemd unit example; reverse-proxy doc
- [ ] Final pass: rate limits verified, error envelope consistency, `/healthz` complete, README quickstart
Acceptance:
- [ ] Dry-run reports the exact would-delete set with zero deletions
- [ ] Culling a project removes all its rows **and** files (orphan-check test passes)
- [ ] Culled user's comments render as "Deleted User"; audit keeps original id
- [ ] Fresh-clone `docker compose up` walkthrough from the README works end-to-end