plan: add Sprint-0-Assessment.md (inventory + classification + risks)

Patrick Plate
2026-06-24 14:15:05 +02:00
parent 306565f1bf
commit 8c94f29dcf
+197
@@ -0,0 +1,197 @@
# Sprint 0 — Assessment
**Status:** Draft v1
**Date:** 2026-06-24
**Owner:** Patrick (plate-software)
**Purpose:** Inventory InspectFlow's current auth implementation, classify each piece as
*lift-and-shift / needs-reshaping / leave-behind*, and surface the risks of the extraction.
---
## 1. Source-of-truth inventory
InspectFlow's auth surface was built over six sub-sprints (14.1 14.6). The artifacts below were read
directly from `inspectflow/backend` and `inspectflow/frontend` at the time of this assessment.
### 1.1 Backend — Java / Spring Boot 4.0.7
| Layer | Class / file | Lines | Domain coupling | Status |
|---|---|---|---|---|
| Config | [`SecurityConfig`](Architecture.md:62) | 67 | Hardcoded URL list for `permitAll` includes InspectFlow-specific routes (`/api/companies/*/public-info`) | **Reshape** — make permit-list config-driven |
| Filter | [`JwtAuthenticationFilter`](Architecture.md:62) | 60 | None — pure JWT extraction + SecurityContext | **Lift-and-shift** |
| Filter | `OrgContextResolver` | ~80 | Reads `X-Org-Id` header, validates via MembershipService — no concrete-org coupling | **Lift-and-shift** |
| Service | `JwtService` | 97 | None — pure JWT issuance using HMAC SHA-256 | **Lift-and-shift** |
| Service | `OAuthService` | ~200 | Calls `TenantAutoMapService` for MS tenant→company mapping (InspectFlow-specific) | **Reshape** — hoist auto-map to T3 via `OnboardingHook` SPI |
| Service | `ExchangeService` | ~150 | None — HMAC verify + nonce dedup (in-memory `ConcurrentHashMap`) | **Lift-and-shift** (note: in-memory replay store breaks under multi-replica — flagged for v0.3) |
| Service | `MembershipService` | ~250 | Joins to `companies` for display name → use `OrgDisplayNameResolver` SPI | **Reshape** |
| Service | `InvitationService` | ~300 | Mails via `InvitationMailer` (already abstracted in Sprint 14.3) | **Lift-and-shift** with minor SPI surface tweak |
| Service | `AccessRequestService` | ~250 | Same pattern as Invitation | **Lift-and-shift** |
| Service | `LoginEventService` | ~120 | None — async event recorder | **Lift-and-shift** |
| Service | `AuthService` | ~180 | Password login / register / refresh — uses User + Role only | **Lift-and-shift** |
| Service | `OnboardingService` | ~200 | **Heavy** — manages InspectFlow's "first sign-in" business-state machine | **Leave behind** (T3) — expose `OnboardingHook` SPI for plate-auth to call |
| Service | `TenantAutoMapService` | ~150 | **Heavy** — MS Entra tenant → InspectFlow Company auto-mapping | **Leave behind** (T3) |
| Controller | `OAuthController` | ~80 | None | **Lift-and-shift** |
| Controller | `AuthController` | ~150 | None | **Lift-and-shift** |
| Controller | `InvitationController` | ~120 | None | **Lift-and-shift** |
| Controller | `AccessRequestController` | ~100 | None | **Lift-and-shift** |
| Controller | `AdminAuditController` | ~80 | None | **Lift-and-shift** |
| Entity | `User` | ~80 | None — `default_org_id` is polymorphic UUID | **Lift-and-shift** |
| Entity | `UserIdentity` | ~70 | None — provider-agnostic | **Lift-and-shift** |
| Entity | `Membership` | ~90 | Polymorphic `(org_type, org_id)` already | **Lift-and-shift** |
| Entity | `Invitation` | ~80 | Polymorphic | **Lift-and-shift** |
| Entity | `AccessRequest` | ~80 | Polymorphic | **Lift-and-shift** |
| Entity | `LoginEvent` | ~60 | None | **Lift-and-shift** |
| Entity | `Company` | ~120 | **Pure InspectFlow** — addresses, embedding vector, industry | **Leave behind** (T3) |
| Enums | `OrgType`, `MembershipRole`, `MembershipStatus`, `InvitationStatus`, `AccessRequestStatus`, `LoginProvider`, `Role` | ~70 total | None | **Lift-and-shift** |
| Migration | V26 — `user_identities`, password_hash nullable | — | None | **Renumber to V1** |
| Migration | V27 — `memberships` + org-FK trigger | — | Trigger references `companies` | **Renumber to V2 + leave trigger as T3 consumer responsibility** |
| Migration | V28 — `invitations` + audit | — | None | **Renumber to V3** |
| Migration | V29 — `access_requests` | — | None | **Renumber to V4** |
| Migration | V30 — `companies.microsoft_tenant_id` | — | **Pure InspectFlow** | **Leave in InspectFlow's migration set** |
| Migration | V31 — `login_events` + revinfo actor | — | None | **Renumber to V5** (V30 stays in app, so plate-auth's tail is V5 not V6) |
### 1.2 Frontend — TypeScript / Next.js 15 / NextAuth v5
| Layer | File | Lines | Domain coupling | Status |
|---|---|---|---|---|
| Config | `auth-config.ts` | ~150 | InspectFlow-branded provider opts, invite-token extraction from URL | **Reshape** — extract `createAuthConfig(opts)` factory |
| Lib | `exchange.ts` | ~80 | None — HMAC envelope sign + POST | **Lift-and-shift** |
| Lib | `auth.ts` | ~40 | Hardcoded localStorage keys (`inspectflow_access_token`) | **Reshape** — make key prefix configurable |
| Route | `app/api/auth/[...nextauth]/route.ts` | ~10 | None | **Lift-and-shift** as default boilerplate |
| Route | `app/api/[...path]/route.ts` (proxy) | ~120 | None — calls `auth()` + `BACKEND_URL` env var | **Reshape** — extract `createProxyHandlers(opts)` factory |
| Middleware | `middleware.ts` | ~5 | Trivial passthrough | **Lift-and-shift** |
| Context | `auth-context.tsx` | ~100 | Syncs NextAuth session → localStorage with InspectFlow keys | **Reshape** — provide hook, let consumer choose persistence |
| Component | `<SignInButton />`, `<UserMenu />`, etc. | — | InspectFlow UI styling | **Leave behind** — components stay per-product |
### 1.3 What is *not* in InspectFlow but should be
| Item | Justification |
|---|---|
| `PlateAuthAutoConfiguration` | Spring Boot starter auto-config doesn't exist yet — InspectFlow wires beans manually |
| `PlateAuthProperties` with `@ConfigurationProperties("plate.auth")` | InspectFlow has scattered `@Value("${jwt.secret}")` etc. — needs consolidation |
| `META-INF/spring/...AutoConfiguration.imports` | Spring Boot 3+ auto-config registration file |
| `OrgValidator` SPI | Currently `MembershipService.grantMembership(…)` checks `companies` directly via repository |
| Default `OrgDisplayNameResolver` | Currently joined inline in `MembershipService` |
| `OnboardingHook` SPI | Currently `OAuthService` calls `OnboardingService` directly |
| `createAuthConfig(opts)` factory | InspectFlow's `auth-config.ts` is mostly procedural |
| `createProxyHandlers(opts)` factory | Same |
| Lockstep version contract documentation | InspectFlow doesn't version its in-tree code as a public artifact |
---
## 2. Reusability classification — summary
```mermaid
pie title "Auth surface — extraction classification"
"Lift-and-shift" : 65
"Reshape (small)" : 25
"Leave behind (T3)" : 10
```
- **~65% lift-and-shift**: entities, filters, JwtService, ExchangeService, controllers, password-auth,
login events, invitation flow, access request flow.
- **~25% reshape**: SecurityConfig permit-list, MembershipService FK lookup, frontend factories, config namespace.
- **~10% leave behind (T3)**: Company entity, OnboardingService, TenantAutoMapService, V30 migration.
The extraction is *fundamentally* a rename + repackage operation with **5 well-defined SPI seams**.
---
## 3. Risk register
| # | Risk | Likelihood | Impact | Mitigation |
|---|---|---|---|---|
| R1 | Flyway migration renumbering corrupts InspectFlow's `flyway_schema_history` | Medium | **High** — could break a production deploy | Use Flyway `baseline` strategy: introduce `flyway_schema_history_auth` separate from app's table OR use `installed_rank` re-mapping (see Sprint-0-Plan § Migration strategy) |
| R2 | InspectFlow ends up shadow-overriding services because SPI is too narrow | Medium | Medium — forks defeat purpose of library | Plan v0.2 SPI follow-up; track shadow-overrides during InspectFlow migration |
| R3 | Spring Boot 4 + Java 25 not yet GA when we ship | Low (Sparkboard already committed) | Medium | Pin to specific stable milestone; coordinate with Sparkboard team |
| R4 | NextAuth v5 final API differs from current beta | Low | Medium | Pin `next-auth@5.0.0-beta.X` and lockstep with our v0.1 release |
| R5 | In-memory nonce store breaks under multi-replica (already known) | Certain (when scaling) | Medium | Documented in `Roadmap.md` as v0.3 work; v0.1 ships with this known limitation |
| R6 | Gitea Package Registry npm publishing not yet configured | Medium | Medium | Sprint 0 includes a "publish pipeline" task; validate by publishing a `0.0.0-snapshot` first |
| R7 | `de.platesoft.auth.*` package rename breaks downstream consumers that imported from `de.platesoft.inspectflow.security.*` | Certain (InspectFlow consumes) | Low — mechanical refactor | Migration-InspectFlow.md provides exact sed/find-replace recipes |
| R8 | Microsoft Entra ID coupling assumed (InspectFlow uses it, Sparkboard might not) | Low — feature is config-gated | Low | Provider enabled via `plate.auth.providers.microsoft.enabled=false` default |
| R9 | `OrgType` enum is shared across products — what if Sparkboard adds `WORKSPACE` and InspectFlow doesn't know about it? | Medium | Low — enum is in lib, value space is open | Make `OrgType` a `String`-backed enum or keep as enum but document the "consumer registers their own types" pattern. Decision deferred to Open-Questions Q01. |
| R10 | The published JAR contains entity classes — consumers must not extend or modify them | Low | Medium | Mark entities `final` where Hibernate allows; document the contract in Architecture.md |
| R11 | HMAC secret regeneration causes total session invalidation across all users | Low (deliberate ops action) | Low | Document operational runbook; v0.3 adds `kid`-based rotation |
| R12 | Java 25 LTS adoption — Sparkboard pioneer, InspectFlow currently on Java 25 already | Low | Low | Verified InspectFlow `pom.xml` shows Java 25 — no risk |
---
## 4. Dependencies we are accepting
Version snapshot at time of extraction (read from `inspectflow/backend/pom.xml`):
| Dep | Version | Notes |
|---|---|---|
| Spring Boot | 4.1.0 (target — InspectFlow on 4.0.7 today) | Bump as part of extraction |
| Java | 25 LTS | Aligned with InspectFlow + Sparkboard |
| jjwt | 0.12.6 | HMAC SHA-256 token signing |
| Hibernate | (Spring Boot managed) | + Envers for audit |
| Postgres driver | (Spring Boot managed) | |
| Flyway | (Spring Boot managed) | |
| Lombok | (Spring Boot managed) | |
| MapStruct | (Spring Boot managed) | DTO mapping |
| NextAuth | 5.x (beta tracking) | Frontend session layer |
| Next.js | 15+ App Router | Required for `auth()` + `duplex:"half"` |
| React | 19 | Required by NextAuth v5 |
We accept tight coupling to these. We do **not** abstract over framework boundaries.
---
## 5. Effort estimate
Single-engineer days, including planning, code, tests, plan reviews:
| Phase | Effort | Notes |
|---|---|---|
| Wiki documentation (this batch) | 1d | Done in this session |
| Plan Review iteration loop | 0.51d | Plan Reviewer + Ask Phase + Patrick GO |
| Repo scaffolding (Maven + npm + CI) | 0.5d | Gitea Actions, publish pipeline |
| Backend extraction (rename + repackage) | 1.5d | Mechanical; lots of imports to update |
| Backend SPI work (5 SPIs + auto-config) | 1d | Net new code |
| Flyway migration consolidation | 0.5d | + integration tests |
| Frontend extraction (factories + types) | 1d | |
| Internal tests | 1d | Integration tests covering exchange + auth flow |
| InspectFlow migration | 1d | Per `Migration-InspectFlow.md` |
| Sparkboard adoption dry-run | 0.5d | Validates Integration-Guide.md |
| Buffer | 1d | Always |
| **Total** | **~910 engineer-days** | Sprint 0 fits in 2 calendar weeks |
---
## 6. Recommendation
**GO** — proceed to Sprint-0-Plan v1 with the following constraints:
1. **Strict T1+T2 scope.** Anything T3-flavored stays in InspectFlow until at least v0.2.
2. **5 SPIs are mandatory in v0.1.** Without them InspectFlow cannot migrate cleanly.
3. **Lockstep versioning is mandatory.** Frontend and backend ship together with identical version numbers.
4. **Migration renumbering uses a separate `flyway_schema_history_auth` table** (or equivalent) — must NOT
touch InspectFlow's existing `flyway_schema_history`. Final decision in Open-Questions Q03.
5. **In-memory nonce store is acceptable for v0.1.** Documented limitation. Multi-replica fix in v0.3.
6. **No Spring Boot 4 GA gate.** Use the milestone Sparkboard team has already adopted, even if it's a
release candidate.
If the Plan Reviewer disagrees with any of these, we iterate. But these are the assumptions Sprint-0-Plan
v1 will be built on.
---
## 7. Open questions raised during assessment
The following items surfaced during analysis but should not block Sprint 0 planning. They are tracked in
[`Open-Questions.md`](Open-Questions.md) for Patrick to resolve before code starts:
- **Q01** — Concrete org table abstraction (Company stays vs. generic Organization)
- **Q02** — Microsoft Entra ID in v0.1 or defer to v0.2 (depends on Sparkboard plans)
- **Q03** — Flyway migration strategy (separate schema history table vs. baseline reset)
- **Q04** — Email magic-link provider in v0.1 or v0.2
- **Q05** — npm package name (`@platesoft/auth` vs alternatives)
- **Q06** — SemVer policy details (especially around peer version lockstep)
- **Q07** — Gitea publishing pipeline (Gitea Actions vs manual deploy)
- **Q08** — Spring Boot version pin (4.0.7 vs 4.1.0 vs latest milestone)
---
*Assessment ready for Plan Reviewer. Sprint-0-Plan v1 will be drafted assuming all Section 6 recommendations
hold; any Plan Reviewer pushback re-opens the recommendation list.*