Every T-06-01 through T-06-18 and T-06-SC threat ID from plans 01-04 is mapped in 06-SECURITY-REVIEW.md to a named passing test or a restated accept/transfer rationale -- none left unmapped
The full route-table mutual-exclusivity assertion (zero jwt.auth on /api/v1/fonoteka, zero inv_token/inv.scope on /_fonoteka/api/v1, across the WHOLE assembled route table, not just the genres pair) has a dedicated passing test
go vet ./... and go test ./... -race are green in both repos, including every -short-gated and testcontainers-gated test
The parity harness re-run confirms both genres routes (jwt and personal_token) are status: ported and passing, with no regression in the recorded-passing count from prior phases
test iterates Router.Routes() asserting group mutual exclusivity
.Routes()
Close Phase 6 with full unit coverage of everything Plans 06-01 through 06-04 built (earlier plans carried behavior/smoke tests but were not blocked on full coverage, per this project's lean-mode workflow rule), a dedicated full-route-table mutual-exclusivity test, and `06-SECURITY-REVIEW.md` mapping every threat ID named across the phase to a passing test or a restated, deliberate risk acceptance.
Purpose: prove -- not just assert -- that the guard registry, limiter, raw-group enforcement, CORS/body-limit scoping, and the SSRF fetch helper hold under coverage broader than the happy-path tests each earlier plan shipped inline, and that the phase's security-load-bearing status (auth guard registry, rate limiting, SSRF fetch helper) is closed out with an explicit, reviewable trail.
Output: coverage-gap tests across both repos; a full route-table isolation test; 06-SECURITY-REVIEW.md; a green go vet/go test -race in both repos; a clean parity harness re-run.
@.planning/PROJECT.md
@.planning/phases/06-http-routing-auth-groups-and-rate-limiting/06-CONTEXT.md
@.planning/phases/06-http-routing-auth-groups-and-rate-limiting/06-RESEARCH.md
@.planning/phases/06-http-routing-auth-groups-and-rate-limiting/06-01-SUMMARY.md
@.planning/phases/06-http-routing-auth-groups-and-rate-limiting/06-02-SUMMARY.md
@.planning/phases/06-http-routing-auth-groups-and-rate-limiting/06-03-SUMMARY.md
@.planning/phases/06-http-routing-auth-groups-and-rate-limiting/06-04-SUMMARY.md
Task 1 (summercms.go): Coverage gaps in bouncer, surf (limiter/routetable/cors), wire, and fetchguard
summercms.go/bouncer/registry_coverage_test.go, summercms.go/surf/limiter_coverage_test.go, summercms.go/surf/routetable_coverage_test.go, summercms.go/surf/cors_coverage_test.go, summercms.go/wire/response_coverage_test.go, summercms.go/fetchguard/fetch_coverage_test.go
summercms.go/bouncer/registry.go, guard.go, context.go (Plan 06-01)
summercms.go/surf/limiter.go, limiter_store.go, clientip.go, cors.go, bodylimit.go, routetable.go (Plans 06-02, 06-03)
summercms.go/wire/response.go (Plan 06-03)
summercms.go/fetchguard/policy.go, fetch.go, ip.go (Plan 06-04)
.planning/phases/06-http-routing-auth-groups-and-rate-limiting/06-01-PLAN.md through 06-04-PLAN.md's blocks (every T-06-xx entry, for cross-reference against Task 3's security review)
Run `go test ./bouncer/... ./surf/... ./wire/... ./fetchguard/... -coverprofile` locally to find lines/branches not exercised by the tests each plan already shipped inline, then add targeted tests closing the real gaps found (do not pad with trivial assertions). Expected gap categories to check specifically, since they are easy to under-test inline while building the primary feature: (a) Registry.Register with a guard implementing NEITHER Guard nor CredentialGuard (the type-switch default-fail branch); (b) MemoryStore's sweep goroutine actually removing an expired entry (not just TooManyAttempts' lazy-expiry path) -- construct with a short sweep interval and assert the internal entry count drops after the sweep fires; (c) RegisterMiddlewareFactory/RegisterHouseMiddlewareFactory duplicate-name failure (only the non-house variant may have been tested inline); (d) pathScopedCORS with a path matching NONE of the configured globs (already covered for the two named fonoteka groups, but add a framework-level fixture-route test independent of fonoteka.go); (e) wire.Time.UnmarshalJSON round-tripping both a +00:00 and a Z-suffixed input; (f) fetchguard.Fetch's PublicOnlyMode explicitly (Task 2 of 06-04 tests AllowHostsMode's private-IP rejection; add the PublicOnlyMode private-IP and PublicOnlyMode-any-host-accepted-when-public cases if not already present); (g) surf.Router.Routes() called before any route is registered (empty-router case) and after a raw group with zero routes (proving Raw:true is still inspectable via the Group's own state, not just via a RouteInfo entry -- if Task 1 of 06-03 could only assert this through a routed fixture, add the missing empty-raw-group introspection path here, or explicitly document in this task's test file why it is structurally untestable and rely on the routed-fixture proof instead).
cd /media/nvme/dev/golem15/summercms.io/summercms/summercms.go && go vet ./... && go test ./... -race -short
- go test ./bouncer/... ./surf/... ./wire/... ./fetchguard/... -cover reports coverage on every new file from Plans 06-01/06-02/06-03/06-04 with no 0%-covered exported function remaining.
- Every gap category (a)-(g) above has either a passing test or an explicit one-line comment in the coverage test file explaining why it is not applicable/testable, so a reviewer never has to wonder whether a gap was missed vs. deliberately skipped.
Framework-side Phase 6 code (bouncer, surf, wire, fetchguard) has coverage beyond each plan's inline happy-path tests, with every identified gap either closed or explicitly justified.
Task 2 (fonoteka.go, parity): App-side coverage gaps, full route-table isolation test, parity re-run
fonoteka.go/plugins/golem15/fonoteka/classes/auth/token_guard_coverage_test.go, fonoteka.go/plugins/golem15/fonoteka/middleware/token_scope_coverage_test.go, fonoteka.go/plugins/golem15/fonoteka/routes_isolation_test.go, fonoteka.go/parity/parity_test.go
fonoteka.go/plugins/golem15/fonoteka/classes/auth/token_guard.go, token_guard_test.go (Plan 06-01)
fonoteka.go/plugins/golem15/fonoteka/middleware/token_scope.go, token_scope_test.go, public_share_headers.go (Plans 06-01, 06-02)
fonoteka.go/plugins/golem15/fonoteka/routes.go, plugin.go (post-06-03, full seven-group state)
fonoteka.go/parity/parity_test.go, manifest.yaml (current state)
Add coverage for: (a) TokenGuard.AuthenticateCredential with a token row that has a non-nil ExpiresAt in the FUTURE (usable) versus in the PAST (not usable) as two explicit cases, and a RevokedAt-set case, all against real Postgres (not just the "unknown hash" case Plan 06-01 likely covered first); (b) InvScope with a credential type-assertion failure (bouncer.Credential(ctx) set to something that is NOT *models.ApiToken -- proves the middleware fails closed rather than panicking); (c) PublicShareHeaders wrapping a handler that writes headers AFTER a partial body write (edge case for the buffer-then-flush design) if not already covered.
Create routes_isolation_test.go: assemble the real app (`app.Handler`-equivalent or a direct `party.Activate` + `surf.BuildRouter` call against `app.PluginIDs`) and call `.Routes()`; assert, over the FULL route table (not just the genres pair): zero entries with `Pattern` prefixed `/api/v1/fonoteka` have `"jwt.auth"` in `Middleware`; zero entries with `Pattern` prefixed `/_fonoteka/api/v1` have `"inv_token"` or any entry with a `strings.HasPrefix(m, "inv.scope:")` in `Middleware`; the one entry for `/.well-known/oauth-authorization-server`-or-`/oauth/mcp/*`-prefixed pattern (if any routes exist there yet) has `Raw == true`. This is the completion of T-06-02/T-06-10's partial coverage from Plans 06-01/06-03.
Re-run `go test ./parity/... -run TestParityCorpus` and confirm the summary line's `passing` count includes both genres routes and has not regressed from Plan 06-01's Task 3 result; if `check-phase2.sh --fresh-php` is available in this environment, run it once and note the result in the plan's SUMMARY (do not block completion on live-PHP availability, matching Phase 3's precedent that `--fresh-php` is a sign-off convenience, not a gate).
cd /media/nvme/dev/golem15/summercms.io/summercms/fonoteka.go && go vet ./... && go test ./... -race -short && go test ./parity/... -run TestParityCorpus
- routes_isolation_test.go passes and its assertions run against the FULL route table returned by a real Router.Routes() call, not a hand-built fixture list.
- go test ./parity/... -run TestParityCorpus reports both genres routes as passing.
- go vet ./... and go test ./... -race are green.
App-side guard/middleware edge cases are covered against real Postgres; group mutual-exclusivity is proven over the entire assembled route table, not a hand-picked pair; the parity harness confirms no regression.
Task 3 (.planning): Security review mapping every T-06-xx threat to a passing test
.planning/phases/06-http-routing-auth-groups-and-rate-limiting/06-SECURITY-REVIEW.md
.planning/phases/06-http-routing-auth-groups-and-rate-limiting/06-01-PLAN.md through 06-04-PLAN.md's blocks (every T-06-01 through T-06-18 and T-06-SC entry)
All coverage test files created in Task 1 and Task 2 of this plan
Write 06-SECURITY-REVIEW.md with one table row per threat ID from T-06-01 through T-06-18 plus T-06-SC (19 entries total across Plans 06-01-06-04's threat_model blocks): Threat ID, Category, Plan of origin, Disposition (copy verbatim from the originating plan), and either the exact test function name (file:TestName) that proves the mitigation holds, or a restated accept/transfer rationale copied verbatim from the originating plan's threat_model table (for the three `accept` dispositions: T-06-03, T-06-08, T-06-SC). Explicitly grep both repos for any place `bouncer.Credential` or `TokenGuard`'s resolved token is logged, and confirm no call site logs the raw bearer token or the token hash outside the guard's own DB update (grep -rn "raw\|bearer\|LastUsedIP" across the auth package and confirm no fmt.Print*/log.* call is adjacent). Explicitly grep for any second `RegisterMiddleware`(non-house) call registering `"inv.must-change-password"` to confirm Plan 06-03's switch to `RegisterHouseMiddleware` is the only registration site (no stale duplicate).
cd /media/nvme/dev/golem15/summercms.io/summercms/summercms.go && grep -c "^| T-06-" .planning/phases/06-http-routing-auth-groups-and-rate-limiting/06-SECURITY-REVIEW.md
- The security review table contains exactly 19 T-06-xx rows (T-06-01 through T-06-18 plus T-06-SC), none missing.
- Every `mitigate` disposition names a real, currently-passing test function (verified by cross-referencing the test file); every `accept` disposition restates the originating plan's rationale rather than inventing a new one.
- `grep -rn "RegisterMiddleware(p.ID(), \"inv.must-change-password\"" fonoteka.go/plugins/golem15/fonoteka/plugin.go` returns zero matches (confirms the house-tagged variant is the only registration).
Every threat identified across Phase 6's four implementation plans is mapped to a passing test or a deliberate, restated risk acceptance -- no threat ID is silently dropped between planning and phase close.
<threat_model>
Trust Boundaries
Boundary
Description
this plan's tests -> every earlier plan's security-load-bearing code
this is the phase's closing verification pass, not new production code -- its own risk surface is limited to test-code correctness and review completeness
STRIDE Threat Register
Threat ID
Category
Component
Disposition
Mitigation Plan
T-06-19
Repudiation
Security review completeness
mitigate
06-SECURITY-REVIEW.md is required to map every T-06-xx ID from all 4 prior plans by name -- an unmapped ID is a review gap, not an accepted risk, and must be caught before phase close (Task 3's exact-count acceptance criterion)
T-06-20
Tampering
Coverage gaps could hide a real Phase 6 defect behind an inline happy-path test
mitigate
Task 1/2 run coverage tooling against every new package and require either a closing test or an explicit documented reason per gap category, rather than trusting each plan's own inline tests were exhaustive
</threat_model>
Full suite in both repos: `cd summercms.go && go vet ./... && go test ./... -race` and `cd ../fonoteka.go && go vet ./... && go test ./... -race` (testcontainers Postgres included). `06-SECURITY-REVIEW.md` committed and cross-checked against all four prior plans' `` blocks. Parity harness re-run confirms both genres routes pass with no regression.
<success_criteria>
Coverage gaps identified across bouncer/surf/wire/fetchguard and the fonoteka auth/middleware packages are closed or explicitly justified.
A dedicated test proves group mutual-exclusivity over the FULL assembled route table.
06-SECURITY-REVIEW.md maps every T-06-01 through T-06-18 plus T-06-SC to a passing test or a restated acceptance rationale.
go vet ./... and go test ./... -race are green in both repos.
The parity harness confirms both genres routes pass with no regression from Plan 06-01.
</success_criteria>
Create `.planning/phases/06-http-routing-auth-groups-and-rate-limiting/06-05-SUMMARY.md` when done