Files
summercms/.planning/phases/07-user-plugin-and-authentication/07-06-PLAN.md
2026-09-22 12:37:08 +02:00

16 KiB

phase, plan, type, wave, depends_on, files_modified, autonomous, requirements, must_haves
phase plan type wave depends_on files_modified autonomous requirements must_haves
07-user-plugin-and-authentication 06 execute 5
07-05
bouncer/mint_test.go
bouncer/refresh_test.go
bouncer/blacklist_test.go
bouncer/password_test.go
bouncer/jwt_test.go
surf/locale_from_principal_test.go
lagoon/validate_test.go
../fonoteka.go/plugins/golem15/user/controllers/api_controller_test.go
../fonoteka.go/plugins/golem15/user/classes/throttle_test.go
../fonoteka.go/plugins/golem15/user/classes/codes_test.go
../fonoteka.go/plugins/golem15/user/classes/events_test.go
../fonoteka.go/plugins/golem15/user/updates/user_session_test.go
../fonoteka.go/plugins/golem15/fonoteka/controllers/api/token_api_controller_test.go
../fonoteka.go/plugins/golem15/fonoteka/controllers/api/me_locale_controller_test.go
../fonoteka.go/plugins/golem15/fonoteka/routes_isolation_test.go
../fonoteka.go/plugins/golem15/fonoteka/classes/auth/api_token_manager_test.go
.planning/phases/07-user-plugin-and-authentication/07-VALIDATION.md
true
AUTH-01
AUTH-02
AUTH-03
AUTH-04
I18N-02
truths artifacts key_links
go vet ./... and go test ./... -race are green in both summercms.go and fonoteka.go (QA-03)
Every row in 07-VALIDATION.md's Per-Task Verification Map has a passing automated command, Task IDs are filled in, and nyquist_compliant is true
The C-01/C-02/C-03/C-04/C-05 carried-forward decisions and every D-01..D-21 decision this phase implemented have at least one passing test asserting the behavior they describe
postcard's memory driver proves mail.activate/mail.restore/mail.reactivate actually send with the right vars, closing the loop RESEARCH.md's Wave 0 gaps opened
path provides
.planning/phases/07-user-plugin-and-authentication/07-VALIDATION.md Task IDs filled in, nyquist_compliant: true, wave_0_complete: true
from to via pattern
07-06 test suite 07-01..07-05 implementation every Wave 0 gap listed in 07-VALIDATION.md now has a corresponding passing test file Wave 0
Close every Wave 0 test gap 07-VALIDATION.md listed, bring `go vet`/`go test -race` green across both repos, and finalize the validation contract so `/gsd:secure-phase 7` has a clean, fully-tested baseline to review. This is the mandatory last plan of the phase per the project's lean-mode workflow rule.

Purpose: earlier plans wrote focused, task-scoped tests (TDD behavior blocks); this plan is the systematic pass that fills any remaining coverage hole — cross-cutting flows (full login→refresh→logout sequences, the D-20 exemption under concurrent access, TestMustChangePasswordLock's full route-table proof) that don't naturally belong inside a single earlier task. Output: green go vet/go test -race in both modules, and a 07-VALIDATION.md with nyquist_compliant: true.

<execution_context> @$HOME/.claude/get-shit-done/workflows/execute-plan.md @$HOME/.claude/get-shit-done/templates/summary.md </execution_context>

@.planning/PROJECT.md @.planning/ROADMAP.md @.planning/STATE.md @.planning/phases/07-user-plugin-and-authentication/07-CONTEXT.md @.planning/phases/07-user-plugin-and-authentication/07-RESEARCH.md @.planning/phases/07-user-plugin-and-authentication/07-VALIDATION.md @.planning/phases/07-user-plugin-and-authentication/07-01-SUMMARY.md @.planning/phases/07-user-plugin-and-authentication/07-02-SUMMARY.md @.planning/phases/07-user-plugin-and-authentication/07-03-SUMMARY.md @.planning/phases/07-user-plugin-and-authentication/07-04-SUMMARY.md @.planning/phases/07-user-plugin-and-authentication/07-05-SUMMARY.md Task 1: Complete summercms.go coverage — bouncer, surf, lagoon bouncer/mint_test.go, bouncer/refresh_test.go, bouncer/blacklist_test.go, bouncer/password_test.go, bouncer/jwt_test.go, surf/locale_from_principal_test.go, lagoon/validate_test.go bouncer/mint.go, bouncer/refresh.go, bouncer/blacklist.go, bouncer/password.go, bouncer/jwt.go, bouncer/context.go, surf/locale_from_principal.go, lagoon/validate.go (all from 07-01, read the CURRENT state — not the plan text — since Task 3 of 07-01 may have adjusted details during execution), .planning/phases/07-user-plugin-and-authentication/07-VALIDATION.md (Wave 0 Requirements: `bouncer/{mint,refresh,blacklist}_test.go`, `surf/locale_from_principal_test.go`) - Any behavior from 07-01's Task 2/3 `` blocks not already covered by a passing test gets one now (cross-check by running `go test ./bouncer/... ./surf/... ./lagoon/... -cover` and reading the coverage report for any of `mint.go/refresh.go/blacklist.go/password.go/locale_from_principal.go`'s exported functions below a reasonable bar — every exported function needs at least one direct test, not just indirect coverage through a handler test in another repo). - A concurrency test: two goroutines calling `MemoryBlacklist.Add`/`IsBlacklisted` on the same jti simultaneously never panics or races (`-race` clean); same for `PostgresBlacklist` against a real (testcontainers) Postgres. - A round-trip test: `Mint` → `Verify` → `Refresh` → `Verify` (on the new token) → logout-equivalent `Add` with `validUntil=now` → the new token's `IsBlacklisted` is immediately `true`. Run `go test ./bouncer/... ./surf/... ./lagoon/... -cover -short` and `-race`, read the coverage report, and add the missing direct-unit tests for any exported symbol from 07-01 that has no dedicated test today. Add the concurrency and round-trip tests described above. Do not modify production code in this task unless a test uncovers an actual bug (if so, fix it, note it in the SUMMARY's Deviations section per the standard executor protocol, and keep the fix minimal). go vet ./... && go test ./bouncer/... ./surf/... ./lagoon/... -race -cover - `go test ./bouncer/... ./surf/... ./lagoon/... -race` exits 0 with no data-race report - A coverage report shows no Phase 7 exported function in `mint.go/refresh.go/blacklist.go/password.go/locale_from_principal.go` with zero direct test references - The concurrent `MemoryBlacklist`/`PostgresBlacklist` Add/IsBlacklisted test passes under `-race` go vet and go test -race are green across bouncer/surf/lagoon; every exported Phase 7 symbol in these packages has a direct test. Task 2: Complete fonoteka.go coverage — user plugin, fonoteka plugin, mail ../fonoteka.go/plugins/golem15/user/controllers/api_controller_test.go, ../fonoteka.go/plugins/golem15/user/classes/throttle_test.go, ../fonoteka.go/plugins/golem15/user/classes/codes_test.go, ../fonoteka.go/plugins/golem15/user/classes/events_test.go, ../fonoteka.go/plugins/golem15/user/updates/user_session_test.go, ../fonoteka.go/plugins/golem15/fonoteka/controllers/api/token_api_controller_test.go, ../fonoteka.go/plugins/golem15/fonoteka/controllers/api/me_locale_controller_test.go, ../fonoteka.go/plugins/golem15/fonoteka/routes_isolation_test.go, ../fonoteka.go/plugins/golem15/fonoteka/classes/auth/api_token_manager_test.go ../fonoteka.go/plugins/golem15/user/controllers/api_controller.go (current, all handlers from 07-02/07-03), ../fonoteka.go/plugins/golem15/fonoteka/controllers/api/token_api_controller.go, me_locale_controller.go (07-04), .planning/phases/07-user-plugin-and-authentication/07-VALIDATION.md (Per-Task Verification Map — `TestApiController`, `TestGetApiArray`, `TestTokenApi`, `TestMustChangePasswordLock`, the mail memory-driver assertion) - A full sequence test (real Postgres): register → fetch → update → change-password → refresh → logout → the logged-out token gets 401 on a subsequent fetch — the Go-side mirror of the `nuxt-auth` fixture, run as a fast in-process `httptest` sequence independent of the PHP-recorded parity fixtures (this test must be able to run in CI without the isolated PHP instance). - `TestMustChangePasswordLock`: a user with `must_change_password=true` gets 423 on `/_fonoteka/api/v1/genres` and `/_fonoteka/api/v1/tokens`, succeeds on `GET/PUT /_fonoteka/api/v1/me/locale`, succeeds on `/_user/api/v1/change-password`, and after that call the lock is cleared and `/_fonoteka/api/v1/genres` succeeds. - `TestGetApiArray`: the full payload shape (base fields + organisation_id/role + must_change_password + preferred_locale) for a user with all of those set, run against a real boot of both plugins together (not a unit-level mock) — the definitive AUTH-02 acceptance test. - Mail: `postcard`'s `memory` driver captures `mail.activate` (from `Register`'s user-mode branch), `mail.restore` (from `ForgotPassword`), and `mail.reactivate` (from `Login`'s soft-delete-restore branch, D-17) each with the expected `Vars` keys (`link`/`code`/`name` as applicable), AND re-confirms the pl/en locale-suffix routing 07-03 Task 3 already tests (`mailTemplate`/`resolveMailLocale`) still holds after any coverage-driven changes in this task -- do not let a coverage fix silently regress the locale-suffix wiring. - `TestTokenApi`: mint with each of the three individual scopes plus a two-scope combination, then a scope-ceiling violation attempt (`"admin"`) is rejected before any row is persisted; `InvScope` middleware (Phase 6, unchanged) 403s a `write`-scoped route call made with a `read`-only token, proving the ceiling is enforced end-to-end through the ALREADY-SHIPPED Phase 6 gate, not just at mint time. - `TestBlacklistSweepStarts`: booting the `golem15.user` plugin with a short test-only `golem15.user.jwt.blacklist_sweep_interval` and a pre-inserted expired `jwt_blacklist` row observes the row removed after waiting past the interval -- the test hook confirming 07-02 Task 2's sweep goroutine actually runs during a real plugin Boot, not just that `BlacklistStore.Sweep` works in isolation (07-01). - `TestNoDeferredRoutesResolve` (D-05): booting both plugins and asserting the real route table has no entry whose path matches any of the deferred PIN-login, device-auth, 2FA, `GET /api/user/batch`, or `GET /_user/activate/{id}` paths -- and that `POST /_user/api/v1/login`'s success body never contains a `two_factor_required` key. Run `go test ./plugins/golem15/... -cover -short` and `-race` in `fonoteka.go`, fill every coverage gap the report shows for Phase 7 files, and add the six cross-cutting tests described above. Where a gap traces to a real bug (not just missing coverage), fix it minimally and record the deviation. Confirm `go list -deps ./plugins/golem15/user/...` contains no `plugins/golem15/fonoteka` import (the AUTH-02 import-direction invariant) as an explicit, named test — not just an incidental compile-time fact. go vet ./... && go test ./... -race -cover - `go test ./... -race` exits 0 in fonoteka.go - `TestMustChangePasswordLock` proves 423 on `/_fonoteka/api/v1/genres` and `/tokens`, 200 on `me/locale` and `/_user/api/v1/change-password`, and 200 on `/_fonoteka/api/v1/genres` again after the lock clears - `TestGetApiArray` asserts the full payload shape against a real dual-plugin boot, not a mock - The `postcard` memory driver captures all three mail sends (activate/restore/reactivate) with non-empty vars, and the pl/en locale-suffix template names for all three - `TestBlacklistSweepStarts` observes an expired `jwt_blacklist` row removed after a real plugin Boot with a short sweep interval - `TestNoDeferredRoutesResolve` finds zero route-table matches for PIN/device/2FA/batch/activate-link paths, and login's success body never contains `two_factor_required` go vet and go test -race are green across all of fonoteka.go; TestMustChangePasswordLock and TestGetApiArray both pass against a real dual-plugin boot; mail sends (with locale suffixes) are asserted through the memory driver; the blacklist sweep goroutine and the D-05 deferred-route absence are both proven by a named test. Task 3: Finalize 07-VALIDATION.md and run the full phase gate .planning/phases/07-user-plugin-and-authentication/07-VALIDATION.md .planning/phases/07-user-plugin-and-authentication/07-VALIDATION.md (current draft — Per-Task Verification Map with `TBD` Task IDs, Wave 0 Requirements checklist, Validation Sign-Off checklist) Fill in every `TBD` Task ID in the Per-Task Verification Map with the real `{plan}-{task}` id it landed in (e.g. `07-02-2`, `07-04-1`), set every `Status` cell to `✅ green` (confirmed by the full run below), check off every Wave 0 Requirements box, check off every Validation Sign-Off box, and set the frontmatter `wave_0_complete: true` and `nyquist_compliant: true`. Do not mark a row green without having actually re-run its `Automated Command` in this task.
Run the full phase gate: `go vet ./... && go test ./... -race` in `summercms.go`; `go vet ./... && go test ./... -race` in `fonoteka.go`; `summer parity:replay --manifest fonoteka.go/parity/manifest.yaml`. Record the final corpus coverage numbers (recorded/ported/pending) in this plan's SUMMARY.
go vet ./... && go test ./... -race - `07-VALIDATION.md`'s Per-Task Verification Map has zero `TBD` Task IDs and zero `⬜ pending` Status cells - `07-VALIDATION.md` frontmatter reads `nyquist_compliant: true` and `wave_0_complete: true` - `go vet ./... && go test ./... -race` exits 0 in both summercms.go and fonoteka.go - `summer parity:replay --manifest fonoteka.go/parity/manifest.yaml` reports zero failing cases 07-VALIDATION.md has zero TBD/pending rows, nyquist_compliant: true; the full phase gate (both repos + parity replay) is green.

<threat_model>

Trust Boundaries

No new trust boundaries — this plan tests behavior already gated in 07-01..07-05; its own threat surface is "a test asserts the wrong thing and gives false confidence," mitigated by tying every new test to a specific D-XX/C-XX/T-07-NN citation in its name or comment rather than writing generic smoke tests.

STRIDE Threat Register

Threat ID Category Component Disposition Mitigation Plan
T-07-01 Spoofing / Elevation of Privilege Full-sequence test mitigate (verification) The Go-side login→refresh→logout→401 sequence test proves T-07-01's mitigation holds end-to-end, not just in the isolated 07-01/07-02 unit tests
T-07-08 Elevation of Privilege TestMustChangePasswordLock mitigate (verification) Proves the 423 lock's exempt set against a real dual-plugin boot, the definitive AUTH-04 acceptance test

Note: /gsd:secure-phase 7 runs after this plan and produces 07-SECURITY-REVIEW.md; this plan's job is to hand it a fully green, fully tested baseline, not to perform the security review itself.

</threat_model>

`go vet ./... && go test ./... -race` green in both `summercms.go` and `fonoteka.go`. `summer parity:replay` green across the full manifest (154-route fonoteka surface + 15-route user surface + tokens/me-locale). `07-VALIDATION.md` fully signed off.

<success_criteria> Phase 7 is fully implemented, fully tested, and ready for the security-review agent — no open Wave 0 gaps, no red tests, no TBD rows in the validation contract. </success_criteria>

Create `.planning/phases/07-user-plugin-and-authentication/07-06-SUMMARY.md` when done