plan: add Roadmap.md (v0.1-v0.4 release horizon + versioning policy)
+232
@@ -0,0 +1,232 @@
|
|||||||
|
# Roadmap
|
||||||
|
|
||||||
|
**Status:** Draft v1
|
||||||
|
**Date:** 2026-06-24
|
||||||
|
**Owner:** Patrick (plate-software)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Versioning policy
|
||||||
|
|
||||||
|
plate-auth follows **SemVer**, but with two amendments suited to a closed-source internal library:
|
||||||
|
|
||||||
|
| Version part | Bump trigger | Consumer impact |
|
||||||
|
|---|---|---|
|
||||||
|
| MAJOR (`1.x.x`) | Wire contract change OR removed public API | Coordinated upgrade across all consumers |
|
||||||
|
| MINOR (`x.1.x`) | New feature, new SPI, new endpoint, deprecation | Drop-in upgrade — read CHANGELOG |
|
||||||
|
| PATCH (`x.x.1`) | Bug fix, dependency bump, internal refactor | Drop-in upgrade — no read needed |
|
||||||
|
|
||||||
|
**Amendment 1 — lockstep frontend/backend versions.**
|
||||||
|
`@platesoft/auth@0.x.y` and `de.platesoft:plate-auth-starter:0.x.y` always ship together with identical
|
||||||
|
version numbers. The wire contract is the union of both sides; you cannot upgrade one without the peer.
|
||||||
|
|
||||||
|
**Amendment 2 — `0.x` is internal-experimental.**
|
||||||
|
Until `1.0.0` we may break minor versions if a consumer experience demands it. All breakage is documented
|
||||||
|
in the CHANGELOG with a migration recipe. Once `1.0.0` ships, SemVer is strict.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Release horizon
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
gantt
|
||||||
|
title plate-auth release horizon
|
||||||
|
dateFormat YYYY-MM-DD
|
||||||
|
axisFormat %b %Y
|
||||||
|
|
||||||
|
section v0.1 — Ship
|
||||||
|
Sprint 0 (extraction) :s0, 2026-06-24, 14d
|
||||||
|
v0.1.0 published :milestone, m1, after s0, 0d
|
||||||
|
InspectFlow migrates :ifm, after m1, 7d
|
||||||
|
Sparkboard adopts :sba, after m1, 21d
|
||||||
|
|
||||||
|
section v0.2 — Polish
|
||||||
|
SPI cleanup + Email opt-in :s2, after ifm, 21d
|
||||||
|
v0.2.0 published :milestone, m2, after s2, 0d
|
||||||
|
|
||||||
|
section v0.3 — Operate
|
||||||
|
Multi-replica nonce store :s3, after m2, 14d
|
||||||
|
Refresh rotation hardening :s3b, after s3, 14d
|
||||||
|
v0.3.0 published :milestone, m3, after s3b, 0d
|
||||||
|
|
||||||
|
section v0.4 — Open
|
||||||
|
Document SPI, write integration tests :s4, after m3, 30d
|
||||||
|
1.0.0-RC candidate :milestone, m4, after s4, 0d
|
||||||
|
```
|
||||||
|
|
||||||
|
Dates are indicative — Sprint 0 has a hard target (Sparkboard cannot start until v0.1 ships).
|
||||||
|
Everything after Sprint 0 is "as priorities allow."
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## v0.1.0 — Ship (Sprint 0 deliverable)
|
||||||
|
|
||||||
|
**Theme:** *Extract InspectFlow 14.1-14.6 verbatim. No new features. No clever refactors.*
|
||||||
|
|
||||||
|
### Backend (`plate-auth-starter`)
|
||||||
|
|
||||||
|
- ✅ Move `de.platesoft.inspectflow.*` auth code under `de.platesoft.auth.*`
|
||||||
|
- ✅ `PlateAuthAutoConfiguration` with `@ConditionalOnMissingBean` overrides
|
||||||
|
- ✅ `PlateAuthProperties` (`plate.auth.*` config namespace)
|
||||||
|
- ✅ Flyway migrations under `classpath:db/migration/auth/V1..V6`
|
||||||
|
- ✅ SPI: `OrgValidator`, `OrgDisplayNameResolver`, `InvitationMailer`,
|
||||||
|
`AccessRequestMailer`, `OnboardingHook`
|
||||||
|
- ✅ Endpoints: `/api/auth/**`, `/api/invitations/**`, `/api/access-requests/**`, `/api/admin/**`
|
||||||
|
- ✅ Spring Boot 4.1.0 + Java 25 LTS baseline
|
||||||
|
- ✅ Published to Gitea Package Registry
|
||||||
|
|
||||||
|
### Frontend (`@platesoft/auth`)
|
||||||
|
|
||||||
|
- ✅ `createAuthConfig({ providers, exchange, secret })` → NextAuth v5 `NextAuthConfig`
|
||||||
|
- ✅ Google + Microsoft Entra ID + Email magic-link providers (opt-in via options)
|
||||||
|
- ✅ `exchange/envelope.ts` (Web Crypto HMAC-SHA256, Edge-runtime compatible)
|
||||||
|
- ✅ `createProxyHandlers({ backendUrl })` with hop-by-hop header stripping + `duplex:"half"`
|
||||||
|
- ✅ `useAccessToken()`, `useMemberships()` client hooks
|
||||||
|
- ✅ Published to Gitea Package Registry npm
|
||||||
|
|
||||||
|
### Validation
|
||||||
|
|
||||||
|
- ✅ InspectFlow's current auth replaced by the library — full E2E test pass
|
||||||
|
- ✅ Sparkboard greenfield setup completes in <30min using only `Integration-Guide.md`
|
||||||
|
- ✅ Plan Reviewer (architect mode) approves Sprint-0-Plan.md before code starts
|
||||||
|
|
||||||
|
**Out of scope for v0.1:**
|
||||||
|
- Multi-replica nonce store (in-memory is fine for InspectFlow + early Sparkboard)
|
||||||
|
- WebAuthn / passkeys
|
||||||
|
- Per-tenant JWT secret rotation
|
||||||
|
- Externalized audit-log sink (Loki / OpenSearch)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## v0.2.0 — Polish
|
||||||
|
|
||||||
|
**Theme:** *Make the consumer experience pleasant. Smooth the SPI corners InspectFlow + Sparkboard found.*
|
||||||
|
|
||||||
|
Triggered by feedback from the two consumers during their first month using v0.1.
|
||||||
|
|
||||||
|
### Likely contents
|
||||||
|
|
||||||
|
- `InvitationMailer` default implementation that *actually* sends mail via `JavaMailSender`
|
||||||
|
(currently no-op logger)
|
||||||
|
- `AccessRequestMailer` admin-notification template
|
||||||
|
- Configurable invitation expiration window (currently hardcoded 7d in `InvitationService`)
|
||||||
|
- `useAccessToken()` Edge-runtime safety pass — current impl uses `getSession()` which is App-Router-only
|
||||||
|
- Email magic-link default UX page (consumers currently have to build their own callback page)
|
||||||
|
- Better error responses (RFC 7807 Problem Details) for `/api/auth/*` endpoints
|
||||||
|
- TypeScript improvements: stricter `ExchangeEnvelope` shape, exported types for `Membership`, `Invitation`
|
||||||
|
- ADR-style docs auto-published to wiki
|
||||||
|
|
||||||
|
### Possibly v0.2 (depends on consumer demand)
|
||||||
|
|
||||||
|
- WebAuthn / passkey provider (NextAuth supports it; we add the backend plumbing)
|
||||||
|
- Per-app branding on the exchange endpoint (custom user-agent string for audit)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## v0.3.0 — Operate
|
||||||
|
|
||||||
|
**Theme:** *Run plate-auth across multiple replicas + harden refresh tokens.*
|
||||||
|
|
||||||
|
Triggered when the first consumer hits horizontal scaling.
|
||||||
|
|
||||||
|
### Backend
|
||||||
|
|
||||||
|
- **Nonce store via Redis / Postgres** instead of in-memory `ConcurrentHashMap` — current impl breaks
|
||||||
|
replay protection across replicas (an envelope can be replayed up to N-1 times where N = replica count).
|
||||||
|
- **Refresh token rotation table** — currently refresh tokens are stateless JWTs which means revocation
|
||||||
|
requires a denylist. Move to opaque refresh tokens with a `refresh_tokens` table for revocation +
|
||||||
|
family-tracking + reuse detection.
|
||||||
|
- **Configurable token signing key rotation** — support a JWK Set with `kid` header so we can rotate the
|
||||||
|
HMAC secret without all-user logout.
|
||||||
|
- **Pluggable login-event sink** — emit `LoginEvent` to a `LoginEventSink` SPI so consumers can ship to
|
||||||
|
Loki / OpenSearch / Sentry instead of (or in addition to) Postgres.
|
||||||
|
|
||||||
|
### Frontend
|
||||||
|
|
||||||
|
- Session sliding-expiration vs absolute-expiration toggle
|
||||||
|
- Optional `requireRoles(['ADMIN'])` middleware helper
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## v0.4.0 — Open
|
||||||
|
|
||||||
|
**Theme:** *Document the SPI thoroughly. Write the integration tests we wish we had at v0.1.*
|
||||||
|
|
||||||
|
This is the "ready to be used by a developer who doesn't know me" version.
|
||||||
|
|
||||||
|
- Complete SPI Javadoc + TSDoc
|
||||||
|
- Integration test suite that any consumer can run against their override of `OrgValidator`
|
||||||
|
- Example consumer apps (minimal Sparkboard-like + minimal InspectFlow-like) checked into the plate-auth
|
||||||
|
repo under `examples/`
|
||||||
|
- `Architecture.md` upgraded with a "how to add a new auth provider" recipe
|
||||||
|
- Public-facing wiki polish — diagrams, terminology glossary, FAQ
|
||||||
|
|
||||||
|
If at this point both consumers are happy + we want to start product #3, we cut **`1.0.0-RC1`**.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1.0.0 criteria
|
||||||
|
|
||||||
|
We do not ship `1.0.0` until:
|
||||||
|
|
||||||
|
1. ✅ At least two consumers have been running v0.x in production for ≥3 months
|
||||||
|
2. ✅ At least one minor upgrade (v0.x → v0.y) has been performed without breakage
|
||||||
|
3. ✅ The SPI has been used to override at least three different extension points across consumers
|
||||||
|
(proves the extension surface is real)
|
||||||
|
4. ✅ A security review (external or internal) has signed off on the threat model
|
||||||
|
5. ✅ No "must rename / must refactor" backlog items are open
|
||||||
|
|
||||||
|
If we hit `1.0.0` we promise **strict SemVer** thereafter — breaking changes only on major bumps,
|
||||||
|
deprecation notices ≥1 minor cycle in advance.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Deprecation policy
|
||||||
|
|
||||||
|
- **Deprecation lives in code as `@Deprecated(since="0.x", forRemoval=true)` + CHANGELOG entry.**
|
||||||
|
- **One full minor cycle of warning before removal.** Something deprecated in `0.3` is removed no earlier
|
||||||
|
than `0.5`.
|
||||||
|
- **Migration recipe is mandatory.** Every deprecation includes a `// MIGRATION:` Javadoc/TSDoc comment
|
||||||
|
pointing at the replacement.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## What is explicitly *not* on the roadmap
|
||||||
|
|
||||||
|
Items that have been considered and rejected (for now):
|
||||||
|
|
||||||
|
| Idea | Why not |
|
||||||
|
|---|---|
|
||||||
|
| SAML support | No customer has asked. Adds significant code + audit surface. |
|
||||||
|
| SCIM provisioning | Same — no demand. Revisit if a B2B customer shows up. |
|
||||||
|
| Multi-org-type-per-user (e.g. user has memberships to a Company *and* a Club) | Schema supports it but no consumer wants it. Don't build until asked. |
|
||||||
|
| Public OIDC server (plate-auth as IdP) | We are consumers, not providers. Different threat model. Don't bend the architecture. |
|
||||||
|
| Audit log UI components | UI is per-product. Library emits structured events; consumers visualize. |
|
||||||
|
| iOS / Android SDKs | Native apps can call `/api/auth/exchange` directly. SDKs add maintenance load with no obvious win. |
|
||||||
|
|
||||||
|
If consumer demand justifies any of these, they get added to the roadmap with their own discovery doc.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Hypothesis-driven scoping
|
||||||
|
|
||||||
|
Each release answers (or refutes) a hypothesis. If the hypothesis is refuted, the next release adapts.
|
||||||
|
|
||||||
|
| Version | Hypothesis | How we validate |
|
||||||
|
|---|---|---|
|
||||||
|
| v0.1 | Extraction is non-lossy — InspectFlow keeps 100% of features after migration | Run full InspectFlow E2E suite post-migration |
|
||||||
|
| v0.1 | Sparkboard can start day 1 with the library | Time-to-first-signin <30min from a clean repo |
|
||||||
|
| v0.2 | The SPI is enough — no consumer needs to fork | Track GitHub-style "forks" / shadow-overrides across consumers |
|
||||||
|
| v0.3 | We hit the multi-replica nonce limitation in practice | First time a consumer scales beyond 1 replica + reports replay anomaly |
|
||||||
|
| v0.4 | Library is documented enough to onboard a stranger | Bring in a fresh contributor, time them on adding a feature |
|
||||||
|
|
||||||
|
When a hypothesis is refuted (e.g. consumers *do* fork the library at v0.2), the next minor turns into a
|
||||||
|
"why do they fork" investigation before adding more features.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Cross-references
|
||||||
|
|
||||||
|
- Sprint 0 detail: [`Sprint-0-Plan.md`](Sprint-0-Plan.md)
|
||||||
|
- Architectural rationale: [`Architecture.md`](Architecture.md)
|
||||||
|
- Open design questions: [`Open-Questions.md`](Open-Questions.md)
|
||||||
Reference in New Issue
Block a user