--- phase: 6 slug: http-routing-auth-groups-and-rate-limiting status: draft nyquist_compliant: false wave_0_complete: false created: 2026-09-19 --- # Phase 6 — Validation Strategy > Per-phase validation contract for feedback sampling during execution. --- ## Test Infrastructure | Property | Value | |----------|-------| | **Framework** | Go stdlib `testing` + `testify` (assert/require); `net/http/httptest` for router, limiter, CORS and fetch-helper tests; `testcontainers-go` Postgres only where the `inv_token` guard and parity harness need real rows | | **Config file** | none — plain `func TestX(t *testing.T)`; parity `TestMain` in `../fonoteka.go/parity` is reused | | **Quick run command** | `go vet ./... && go test ./... -short` (run in the repo the task writes to) | | **Full suite command** | `go test ./... -race` in `summercms.go` and in `../fonoteka.go` | | **Estimated runtime** | ~20 s quick, ~120-180 s full | --- ## Sampling Rate - **After every task commit:** Run `go vet ./... && go test ./... -short` - **After every plan wave:** Run `go test ./...` in both repos - **Before `/gsd:verify-work`:** Full suite green in both repos with `-race`, parity harness green on genres under both auth groups, swag + `openapi-typescript` gate green - **Max feedback latency:** 120 seconds --- ## Per-Task Verification Map Task IDs are filled in by the planner once PLAN.md files exist. Requirement-level map from 06-RESEARCH.md §Validation Architecture: | Task ID | Plan | Wave | Requirement | Threat Ref | Secure Behavior | Test Type | Automated Command | File Exists | Status | |---------|------|------|-------------|------------|-----------------|-----------|-------------------|-------------|--------| | TBD | TBD | TBD | HTTP-03 | TBD | Same handler serves JWT `/_fonoteka/api/v1/genres` and personal-token `/api/v1/fonoteka/genres`; unknown and malformed ids both 404; groups mutually exclusive in the route table | integration | `go test ./... -run 'TestGenresSharedHandler|TestGroupsMutuallyExclusive'` (fonoteka.go) | ❌ W0 | ⬜ pending | | TBD | TBD | TBD | HTTP-04 | TBD | Five named buckets, inline `throttle:N,M`, stacked limiters; fixed-window Laravel semantics and headers; client IP only via trusted-proxy rule | unit + integration | `go test ./surf/... -run 'TestLimiter|TestClientIP'` | ❌ W0 | ⬜ pending | | TBD | TBD | TBD | HTTP-05 | TBD | Guard registry: `jwt` and `inv_token` resolve to one `bouncer.User(ctx)`; duplicate/unknown guard name fails boot; `inv.scope` 401/403 bodies exact | unit + integration | `go test ./bouncer/... -run TestGuardRegistry` | ❌ W0 | ⬜ pending | | TBD | TBD | TBD | HTTP-06 | TBD | `[]`, `+00:00`, tri-state `null`, omitted keys; raw OAuth group refuses house envelope/error middleware at registration, verified over the route table | unit + route-table | `go test ./surf/... -run 'TestResponseTypes|TestRawGroupExemption'` | ❌ W0 | ⬜ pending | | TBD | TBD | TBD | HTTP-07 | TBD | Fetch helper rejects non-allow-listed host and private/loopback IPs at dial time, https only, no redirects, byte cap while streaming, timeout | unit (httptest) | `go test ./... -run TestFetch` | ❌ W0 | ⬜ pending | | TBD | TBD | TBD | HTTP-08 | — | swag generates OpenAPI from handler annotations; `openapi-typescript` yields valid TS; drift check | build gate | phase gate script (exact command set at plan time) | ❌ W0 | ⬜ pending | | TBD | TBD | TBD | HTTP-09 | TBD | Path-scoped CORS equals `config/cors.php` (JWT group gets no CORS headers); `http.MaxBytesReader` body limits per group | integration | `go test ./surf/... -run 'TestCORS|TestBodyLimit'` | ❌ W0 | ⬜ pending | *Status: ⬜ pending · ✅ green · ❌ red · ⚠️ flaky* --- ## Wave 0 Requirements - [ ] `surf/limiter_test.go` — fixed-window Store semantics, named-bucket resolution, inline throttle keys, stacking - [ ] `bouncer/registry_test.go` — guard register / duplicate-fail / resolve-by-name - [ ] Fetch-helper package `fetch_test.go` — httptest servers for each typed failure reason - [ ] `surf/routetable_test.go` — raw-group middleware refusal at registration, route table contents - [ ] Existing infra reused: `surf` router tests, `../fonoteka.go/parity` TestMain and `seedHooks` (token insertion for the `inv_token` guard) - [ ] No new test framework --- ## Manual-Only Verifications | Behavior | Requirement | Why Manual | Test Instructions | |----------|-------------|------------|-------------------| | Production `client_max_body_size` / `post_max_size` / `upload_max_filesize` values | HTTP-09 | The production nginx vhost and php.ini are operator-managed and not in any repo (06-RESEARCH.md A2) | Operator reads the values from the production host; they are recorded in config defaults and the test asserts the recorded numbers | --- ## Validation Sign-Off - [ ] All tasks have `` verify or Wave 0 dependencies - [ ] Sampling continuity: no 3 consecutive tasks without automated verify - [ ] Wave 0 covers all MISSING references - [ ] No watch-mode flags - [ ] Feedback latency < 120s - [ ] `nyquist_compliant: true` set in frontmatter **Approval:** pending