diff --git a/Sprint-1-Assessment.md b/Sprint-1-Assessment.md new file mode 100644 index 0000000..8a13a85 --- /dev/null +++ b/Sprint-1-Assessment.md @@ -0,0 +1,233 @@ +# Sprint 1 — Assessment ("Spark") + +**Date:** Pre-Sprint 1 planning +**Module:** Sparkboard (greenfield) +**Author:** Patrick Plate / Lumen (Planner) +**Status:** Entwurf v1 — ready for Patrick GO +**Sprint codename:** Spark +**Sprint goal:** Walking skeleton consuming [plate-auth](https://git.plate-software.de/pplate/plate-auth/wiki) v0.1.0, deployed to `sparkboard.plate-software.de`, four allowlisted users can sign in with Google and post ideas. + +--- + +## 1. Problem analysis + +Sparkboard does not exist yet. There is no codebase, no database, no deployment, no DNS, no CI pipeline. The Sprint 1 problem is therefore not "fix a bug" or "extend a feature" — it is "go from a `git init` to a four-human production system in one sprint." + +The unique pressure on Sprint 1 is that Sparkboard must be **the first real consumer of plate-auth**. Every assumption plate-auth made about how its starter would be used is, by definition, validated or falsified here. If plate-auth's auto-configuration leaves a sharp edge, Sparkboard will hit it. The benefit is that we are doing this hand-in-hand with plate-auth (same developer, same wiki, same week) — defects in plate-auth's contract surface immediately and can be patched in plate-auth, not worked around in Sparkboard. + +The product surface is intentionally tiny: one entity (`Idea`), two endpoints (`GET /api/ideas`, `POST /api/ideas`), two pages (`/login`, `/ideas`). Everything _else_ in Sprint 1 is infrastructure: build, deploy, auth wire-up, PWA, CI/CD. The fact that the product surface is small **is the strategy** — it focuses Sprint 1 entirely on the integration story. + +--- + +## 2. Affected components + +Because Sparkboard is greenfield, "affected" means "created". The full v1 component inventory: + +| Layer | Component | New / Library | Owner | +|------|-----------|--------------|-------| +| Backend | `SparkboardApplication.java` | New | Sparkboard | +| Backend | `Idea` entity + `IdeaRepository` / `IdeaService` / `IdeaController` | New | Sparkboard | +| Backend | `SparkboardOnboardingHook` (`OnboardingHook` SPI impl) | New | Sparkboard | +| Backend | `SparkboardAdminProperties` (`@ConfigurationProperties("sparkboard")`) | New | Sparkboard | +| Backend | Flyway migrations: `V1__init.sql` (Sparkboard tables only) + `V2__seed_family_spark_org.sql` | New | Sparkboard | +| Backend | `de.platesoft:plate-auth-starter:0.1.0` | Library | plate-auth | +| Backend | `application.yml` with `plate.auth.*` configuration | New | Sparkboard | +| Frontend | `auth.ts` (calls `createAuthConfig` from `@platesoft/auth/next-auth`) | New | Sparkboard | +| Frontend | `app/api/auth/[...nextauth]/route.ts` (NextAuth handlers re-export) | New | Sparkboard | +| Frontend | `app/api/backend/[...path]/route.ts` (calls `createProxyHandlers` from `@platesoft/auth/proxy`) | New | Sparkboard | +| Frontend | `app/(app)/ideas/page.tsx` (list, server component) | New | Sparkboard | +| Frontend | `app/(app)/ideas/new/page.tsx` + `` | New | Sparkboard | +| Frontend | `app/(auth)/login/page.tsx` | New | Sparkboard | +| Frontend | `public/manifest.json` + `public/sw.js` + icon set + `lib/sw-register.ts` | New | Sparkboard | +| Frontend | `@platesoft/auth@0.1.0` npm package | Library | plate-auth | +| Infra | `docker-compose.yml` (local dev) + `docker-compose.prod.yml` (TrueNAS) | New | Sparkboard | +| Infra | IONOS Apache vhost for `sparkboard.plate-software.de` | New | Sparkboard (same template as InspectFlow/CannaManage) | +| Infra | `frpc` entry on TrueNAS, port **30011** | New | Sparkboard | +| Infra | DNS: `sparkboard.plate-software.de` CNAME | New | Sparkboard | +| CI | `.gitea/workflows/ci.yml` (build + test) | New | Sparkboard | +| CI | `.gitea/workflows/deploy.yml` (SSH deploy to TrueNAS) | New | Sparkboard | +| Docs | This wiki | New | Sparkboard | + +What is **deliberately not built** (because plate-auth ships it): + +- Any `User` entity / table / repository +- Any JWT-signing or JWT-validating filter +- Any allowlist enforcement +- Any membership / invitation / access-request table or controller +- Any auth-exchange controller +- Any hand-rolled proxy that injects bearer tokens +- Any NextAuth provider config beyond `createAuthConfig({ providers: { google: { ... } } })` + +The size of that **not built** list is the integration thesis. + +--- + +## 3. Current state + +There is no current state. Sprint 1 starts from the empty Gitea repo. The closest analogues are: + +- **plate-auth v0.1.0** (sibling repo, expected to ship before Sprint 1 starts) — provides every auth primitive. +- **InspectFlow Sprint 14** — the origin of every auth pattern that now lives in plate-auth. Sparkboard does **not** copy code from InspectFlow; it consumes plate-auth instead. The patterns are similar by ancestry, not by code-reuse. +- **CannaManage** — sibling project; provides the deployment shape (TrueNAS + frps + IONOS Apache + Caddy/Apache vhost + Gitea Actions). Sparkboard's `docker-compose.prod.yml`, `Caddyfile` (or Apache equivalent), and `deploy.sh` are pattern-mirrored from CannaManage. + +--- + +## 4. Risk assessment + +| # | Risk | Probability | Impact | Mitigation | +|---|------|-------------|--------|-----------| +| R1 | **plate-auth v0.1.0 ships incomplete or with a bug in single-org mode** — Sparkboard is the first consumer, so it finds problems first | Medium | High | Sprint 1 hard-blocks until plate-auth v0.1.0 is green. Sparkboard and plate-auth share a developer (Patrick), so plate-auth bugs become same-day patches, not multi-week tickets. | +| R2 | **Spring Boot 4.1 regression** — Sparkboard is the SB 4.1 guinea pig in plate-software | Low | Medium | Pin to the GA release, not RC/snapshot. If a regression appears, downgrade to SB 4.0 LTS is a one-line `pom.xml` change. plate-auth tests against SB 4.1 in its own CI before publishing. | +| R3 | **Google OAuth misconfiguration** — wrong redirect URI, wrong scopes, wrong consent screen | Medium | Medium | Validate end-to-end in dev with `http://localhost:3000` redirect first, then add the production redirect. Document required Google Cloud Console steps in [Integration Guide §1](Integration-Guide.md). | +| R4 | **NextAuth v5 wire-up confusion** — NextAuth v5 is still relatively new and has notable differences from v4 | Medium | Medium | `@platesoft/auth/next-auth` is the abstraction that hides v5 specifics. As long as that factory is correct, Sparkboard never touches NextAuth v5 internals. | +| R5 | **frps tunnel + IONOS Apache double-proxy** breaks for the new port 30011 | Low | Medium | Pattern is already validated for InspectFlow (30009) and CannaManage (30010). Copy the working frpc.toml and Apache vhost block; only change the port and hostname. | +| R6 | **TrueNAS Docker volume permissions** for the Postgres data dir | Medium | Low | Postgres image runs as UID 999 by default; the TrueNAS dataset must be chowned. Document in `deploy.sh`. Same lesson learned twice already (InspectFlow + CannaManage). | +| R7 | **Gitea Actions SSH deploy step** — SSH key, known_hosts, sudo password | Medium | Medium | Reuse the CannaManage workflow (`appleboy/ssh-action@v1.0.0`). Test the deploy script locally before committing the workflow. | +| R8 | **PWA service-worker caching the login page** — users get stuck on a stale `/login` | Low | High | Service worker is **stub-only** in v1: it registers, claims clients, but caches nothing. Caching strategy is Sprint 4 work, not Sprint 1. | +| R9 | **Allowlist typo locks out one of the four users** | Medium | Low | Allowlist is in `application.yml` env-substituted (`PLATE_AUTH_ALLOWLIST_EMAILS`). Add an admin override mechanism in Sprint 2; for Sprint 1, accept that a redeploy fixes typos. | +| R10 | **Flyway version drift between Sparkboard's history and plate-auth's history** | Low | Medium | Two separate histories (`flyway_schema_history` + `flyway_schema_history_auth`) means they cannot collide. Configured via plate-auth's `plate.auth.flyway.table` property. | +| R11 | **The OnboardingHook fires before plate-auth has finished persisting auth_identities** — race condition on first login | Low | High | plate-auth's contract is that the hook runs **after** `auth_identities` INSERT, in the same transaction. Verified by reading plate-auth's tests. Hypothesis logged in BigMind for resolution during Sprint 1 W1. | + +--- + +## 5. Solution options + +Three options for Sprint 1 scope, scored on how aggressively they prove plate-auth. + +### Option A — Walking skeleton with one read + one write _(RECOMMENDED)_ + +- **Scope:** `Idea` entity only. `GET /api/ideas` + `POST /api/ideas`. List page + create form. PWA installable. Deployed. +- **Plate-auth touch surface:** maximum. Auth, allowlist, membership row, JWT, proxy, single-org config — all exercised. +- **Pro:** smallest possible product surface focuses all energy on integration. +- **Pro:** every Sprint 1 acceptance criterion exercises plate-auth. +- **Pro:** if any Sprint 1 acceptance fails, it's almost certainly an integration issue, not a product issue — easy to localise. +- **Con:** the visible product looks _very_ thin after a sprint. Four users get an idea list and a "+" button and nothing else. +- **Effort:** 2–3 weekends after plate-auth v0.1.0 lands. + +### Option B — Backend + frontend in isolation, no deploy + +- **Scope:** Build the backend and frontend, run them locally, demo locally. Skip the TrueNAS / frps / IONOS / Gitea Actions work. +- **Pro:** less moving parts in one sprint. +- **Con:** doesn't validate the most operationally risky bits (R5, R6, R7). Defers risk. +- **Con:** delivers nothing the four real users can touch — defeats the point of "walking" skeleton. +- **Effort:** 1–2 weekends. + +### Option C — Frontend-only mock with localStorage + +- **Scope:** No backend. Next.js app stores ideas in localStorage. Auth is stubbed. +- **Pro:** trivially deployable as a static site. +- **Con:** doesn't validate plate-auth at all. Sparkboard's entire strategic reason for existing collapses. +- **Effort:** 1 weekend. + +--- + +## 6. Recommendation + +**Option A — Walking skeleton.** + +The visible thinness of Option A is **the cost**, and it is the right cost. Sparkboard's strategic value is in validating plate-auth, not in shipping a four-feature product surface to four humans on weekend two. If Sprint 1 ships a real auth flow, a real membership row, a real JWT, a real proxy, and a real production deploy — _and only one CRUD operation each direction_ — the integration thesis is proven. Sprint 2 (Kindling) adds the product features. + +Option B defers the highest-risk operational work into Sprint 2, where it would compete for attention with reactions/status/comments. Bad trade. + +Option C undermines the whole project. Skip. + +--- + +## 7. Acceptance criteria (A1–A6) + +These are the formal gates Sprint 1 must clear before it is "done". Each is testable; the [Sprint 1 Testplan](Sprint-1-Testplan.md) maps tests onto each. + +### A1 — Allowlisted user can sign in with Google + +Given a Gmail account in `plate.auth.allowlist.emails`, when the user visits `https://sparkboard.plate-software.de/login` and clicks "Sign in with Google", they are redirected to Google, consent, redirected back, and land on `/ideas` as an authenticated session. Tested by **MT-01**. + +### A2 — Non-allowlisted user is rejected with a clean error + +Given a Gmail account NOT in the allowlist, when the user attempts the same flow, they see a generic "not authorised" message and **no Sparkboard user record is created** in any table. Tested by **MT-02**, **IT-03**. + +### A3 — First login auto-creates membership in Family Spark org + +Given a successful first sign-in by an allowlisted user, after the flow completes, the `memberships` table contains exactly one row for that `user_id`: `(user_id, 'SPARK_ORG', FAMILY_SPARK_ID, role)` where `role = ADMIN` if the email is in `sparkboard.admins[]`, otherwise `MEMBER`. Second sign-in does **not** create a duplicate. Tested by **UT-04**, **IT-01**, **MT-03**. + +### A4 — Any authenticated user can create and list ideas + +`POST /api/ideas` with `{ title, description? }` and a valid bearer token creates an `Idea` row with `org_id = FAMILY_SPARK_ID`, `author_id = `, `status = RAW`, and returns the new idea. `GET /api/ideas` returns all ideas in the Family Spark org, newest first. Tested by **UT-02**, **UT-03**, **IT-02**, **E2E-01**. + +### A5 — PWA installable on iOS Safari and Android Chrome + +The deployed app serves a valid `manifest.json` with `name`, `short_name`, `start_url`, `display: standalone`, `theme_color`, and at least a 192×192 and 512×512 icon. The "Add to Home Screen" affordance appears on iOS Safari and Android Chrome. Once installed, the app launches in standalone mode without the browser chrome. Tested by **E2E-03**, **MT-04**, **MT-05**. + +### A6 — Deployed to `https://sparkboard.plate-software.de` via Gitea Actions + +A merge to `main` triggers Gitea Actions, which builds backend and frontend images, pushes to the Gitea container registry, SSH-deploys to TrueNAS, the new containers come up healthy, and the smoke test (`/api/health` returns 200, `/login` returns 200) passes. The whole pipeline is < 10 minutes wall-clock. Tested by **MT-06**, **MT-07**. + +--- + +## 8. plate-auth integration footprint (Sprint 1) + +What Sparkboard touches in plate-auth's surface. If any of these contracts changes in v0.2, Sparkboard knows where to look: + +| plate-auth surface | Sparkboard usage | +|---|---| +| `de.platesoft:plate-auth-starter` Maven artifact | Declared in `backend/pom.xml` | +| `plate.auth.jwt.secret` config | Set from env var `PLATE_AUTH_JWT_SECRET` | +| `plate.auth.exchange.secret` config | Set from env var `PLATE_AUTH_EXCHANGE_SECRET` | +| `plate.auth.allowlist.emails` config | List of 4 emails, env-substituted | +| `plate.auth.providers.google.{client-id, client-secret}` config | Env-substituted | +| `plate.auth.registration.enabled = false` | Explicit | +| `OnboardingHook` SPI | Implemented by `SparkboardOnboardingHook` | +| `MembershipService.upsert(userId, orgType, orgId, role)` API | Called from the hook | +| `Role` enum (`ADMIN`, `MEMBER`) | Used by the hook | +| `@platesoft/auth` npm artifact | Declared in `frontend/package.json` | +| `createAuthConfig(...)` factory | Called from `frontend/auth.ts` | +| `createProxyHandlers(...)` factory | Called from `frontend/app/api/backend/[...path]/route.ts` | +| `flyway_schema_history_auth` table | Owned by plate-auth; Sparkboard's Flyway never touches it | + +Untouched in v1 (deliberately deferred or unneeded): + +- `OrgValidator` SPI (default no-op accepted) +- `OrgDisplayNameResolver` SPI (default `org_id` rendering accepted) +- `InvitationMailer` SPI (no invitations in v1) +- `AccessRequestMailer` SPI (no access requests in v1) +- React hooks from `@platesoft/auth/react` (Sprint 2 candidate if needed for profile page) +- `/api/auth/invite` / `/api/auth/access-requests` HTTP endpoints (not called) + +--- + +## 9. Out of scope (deferred to Sprint 2+) + +| Item | Sprint | +|------|--------| +| Reactions (💡⚡🔥❓👀) | 2 (Kindling) | +| Status transitions in UI | 2 | +| Idea archive / soft-delete UI | 2 | +| Idea edit | 2 | +| Idea detail page | 2 | +| Profile / account-settings page | 2 | +| Comments | 3 (Flame) | +| Search | 3 | +| Notifications (email / web-push) | 4 (Ember) | +| Offline cache (real SW caching) | 4 | +| Native APK (Capacitor) | 5 (Wildfire) | +| Image attachments | 5 | + +--- + +## 10. Open questions + +Cross-referenced to [Open Questions](Open-Questions.md). The four that **must** be answered before Sprint 1 implementation starts: + +- [Q01 — Single-org name and ID strategy](Open-Questions.md#q01-single-org-name-and-id-strategy) — what is the magic UUID? "Family Spark" the display name? +- [Q02 — Allowlist management](Open-Questions.md#q02-allowlist-management) — env var? config file? DB seed? admin endpoint? +- [Q03 — Admin promotion model](Open-Questions.md#q03-admin-promotion-model) — auto on first login? Flyway seed? `sparkboard.admins[]` config? +- [Q07 — PWA assets pipeline](Open-Questions.md#q07-pwa-assets-pipeline) — hand-drawn icons? generator? defer to Sprint 2? + +The other six on the [Open Questions](Open-Questions.md) page affect Sprint 2+ and do not block Sprint 1. + +--- + +## 11. Verdict + +**Recommendation:** APPROVE Option A, A1–A6 acceptance, with [Q01–Q03 and Q07](#10-open-questions) answered before W2 of Sprint 1 starts. + +**Next document:** [Sprint 1 — Plan](Sprint-1-Plan.md) breaks the recommendation into workstreams W0–W6.