docs(06): create phase plan
This commit is contained in:
@@ -1,8 +1,8 @@
|
||||
---
|
||||
phase: 6
|
||||
slug: http-routing-auth-groups-and-rate-limiting
|
||||
status: draft
|
||||
nyquist_compliant: false
|
||||
status: planned
|
||||
nyquist_compliant: true
|
||||
wave_0_complete: false
|
||||
created: 2026-09-19
|
||||
---
|
||||
@@ -36,17 +36,22 @@ created: 2026-09-19
|
||||
|
||||
## 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 |
|
||||
| 06-01-T1 | 06-01 | 1 | HTTP-05 | T-06-01 | Router verbs + parameterized middleware factories; guard registry primitives unit-tested in isolation | unit | `go test ./surf/... ./bouncer/... ./pact/... -short` (summercms.go) | ✅ created by plan | ⬜ pending |
|
||||
| 06-01-T2 | 06-01 | 1 | HTTP-05 | T-06-04, T-06-05 | Real inv_token guard + inv.scope exact 401/403 bodies; jwt guard behaviorally unchanged | unit + testcontainers | `go test ./plugins/golem15/fonoteka/... ./plugins/golem15/user/... -short` (fonoteka.go) | ✅ created by plan | ⬜ pending |
|
||||
| 06-01-T3 | 06-01 | 1 | HTTP-03 | T-06-02 | Same handler serves JWT and personal-token genres; parity-green on both | integration + parity | `go test ./plugins/golem15/fonoteka/... -run TestGenresSharedHandler -short && go test ./parity/... -run TestParityCorpus` (fonoteka.go) | ✅ created by plan | ⬜ pending |
|
||||
| 06-02-T1 | 06-02 | 2 | HTTP-04 | T-06-06 | Fixed-window Store/Limiter matching Laravel wire contract; trusted-proxy ClientIP | unit | `go test ./surf/... -run 'TestLimiter\|TestClientIP' -short` (summercms.go) | ✅ created by plan | ⬜ pending |
|
||||
| 06-02-T2 | 06-02 | 2 | HTTP-04 | T-06-07 | Five named buckets registered; token group throttled; PublicShareHeaders 429 rewrite | unit + integration | `go test ./plugins/golem15/fonoteka/... -run TestPublicShareHeaders -short` (fonoteka.go) | ✅ created by plan | ⬜ pending |
|
||||
| 06-02-T3 | 06-02 | 2 | HTTP-04 | T-06-09 | All six declared route groups boot cleanly; APP_DEBUG=false pinned for future fixture recordings | integration | `go test ./plugins/golem15/fonoteka/... -run TestAllRouteGroupsBoot -short` (fonoteka.go) | ✅ created by plan | ⬜ pending |
|
||||
| 06-03-T1 | 06-03 | 3 | HTTP-06 | T-06-10, T-06-11 | Raw group refuses house-envelope middleware at registration; bare 500 on raw panic; route table + route:list | unit | `go test ./surf/... -run 'TestRawGroup\|TestRouteTable' -short` (summercms.go) | ✅ created by plan | ⬜ pending |
|
||||
| 06-03-T2 | 06-03 | 3 | HTTP-06, HTTP-08 | — | wire response helpers (JSON writer, +00:00 time, tri-state bool, never-nil slice); swag→openapi-typescript pipeline | unit + build-gate | `go test ./wire/... -short && bash scripts/check-openapi.sh` (fonoteka.go) | ✅ created by plan | ⬜ pending |
|
||||
| 06-03-T3 | 06-03 | 3 | HTTP-09 | T-06-12, T-06-13 | Path-scoped CORS matches config/cors.php; body limits enforced with operator-confirmed production numbers; oauth group raw | integration + human-verify | `go test ./surf/... -run 'TestCORS\|TestBodyLimit' -short` (summercms.go); blocking checkpoint for production body-size numbers | ✅ created by plan | ⬜ pending |
|
||||
| 06-04-T1 | 06-04 | 1 | HTTP-07 | T-06-14 | Private/reserved/CGNAT/metadata IP table matches ManualCoverUrlFetcher.php exactly | unit | `go test ./fetchguard/... -run TestIsReservedOrPrivate -v` (summercms.go) | ✅ created by plan | ⬜ pending |
|
||||
| 06-04-T2 | 06-04 | 1 | HTTP-07 | T-06-14, T-06-15, T-06-16, T-06-17, T-06-18 | Dial-time SSRF guard, https-only, no redirects, streaming byte cap, typed failure reasons | unit (httptest) | `go test ./fetchguard/... -short -race` (summercms.go) | ✅ created by plan | ⬜ pending |
|
||||
| 06-05-T1 | 06-05 | 4 | HTTP-03..09 | all (coverage sweep) | Framework-side coverage gaps closed (bouncer/surf/wire/fetchguard) | unit | `go test ./... -race -short` (summercms.go) | ✅ created by plan | ⬜ pending |
|
||||
| 06-05-T2 | 06-05 | 4 | HTTP-03..09 | T-06-02, T-06-10 (full completion) | App-side coverage gaps; full route-table mutual-exclusivity over every route, not just genres | unit + parity | `go test ./... -race -short && go test ./parity/... -run TestParityCorpus` (fonoteka.go) | ✅ created by plan | ⬜ pending |
|
||||
| 06-05-T3 | 06-05 | 4 | HTTP-03..09 | T-06-19, T-06-20 | Every T-06-xx threat mapped to a passing test or restated acceptance | doc + grep-gate | `grep -c "^\| T-06-" 06-SECURITY-REVIEW.md` | ✅ created by plan | ⬜ pending |
|
||||
|
||||
*Status: ⬜ pending · ✅ green · ❌ red · ⚠️ flaky*
|
||||
|
||||
@@ -54,12 +59,14 @@ Task IDs are filled in by the planner once PLAN.md files exist. Requirement-leve
|
||||
|
||||
## 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
|
||||
- [x] `surf/limiter_test.go` — fixed-window Store semantics, named-bucket resolution, inline throttle keys, stacking — scaffolded and filled by Plan 06-02 Task 1 (no pre-existing file; the plan creates it, satisfying the Nyquist "create the scaffold" rule since there is no separate Wave 0 in this phase's plan set)
|
||||
- [x] `bouncer/registry_test.go` — guard register / duplicate-fail / resolve-by-name — scaffolded and filled by Plan 06-01 Task 1
|
||||
- [x] Fetch-helper package `fetch_test.go`/`ip_test.go` — httptest servers for each typed failure reason — scaffolded and filled by Plan 06-04 Tasks 1-2
|
||||
- [x] `surf/routetable_test.go` — raw-group middleware refusal at registration, route table contents — scaffolded and filled by Plan 06-03 Task 1
|
||||
- [x] Existing infra reused: `surf` router tests, `../fonoteka.go/parity` TestMain and `seedHooks` (token insertion for the `inv_token` guard, extended by Plan 06-01 Task 3)
|
||||
- [x] No new test framework
|
||||
|
||||
No standalone Wave 0 plan is used in this phase's plan set — each implementation plan (06-01 through 06-04) creates and fills its own test files in the same wave as the production code, and every task in every plan carries a concrete `<automated>` verify command (confirmed via `verify.plan-structure` on all 5 plans: `hasVerify: true` on every task). Plan 06-05 (wave 4) is the dedicated coverage-closing plan required by this project's lean-mode "unit tests are always the last plan" rule.
|
||||
|
||||
---
|
||||
|
||||
@@ -67,17 +74,17 @@ Task IDs are filled in by the planner once PLAN.md files exist. Requirement-leve
|
||||
|
||||
| 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 |
|
||||
| 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) | Plan 06-03 Task 3 is a `checkpoint:human-verify` (`autonomous: false`): operator reads the values from the production host; they are recorded in `fonoteka.go/config/http.yaml` replacing the INTERIM defaults, and the existing body-limit tests (which assert against config-loaded values) are re-run to confirm no regression |
|
||||
|
||||
---
|
||||
|
||||
## Validation Sign-Off
|
||||
|
||||
- [ ] All tasks have `<automated>` 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
|
||||
- [x] All tasks have `<automated>` verify or Wave 0 dependencies — confirmed via `gsd-sdk query verify.plan-structure` on all 5 PLAN.md files (`hasVerify: true` on every task, including the one `checkpoint:human-verify` task, which also carries an `<automated>` block for its automatable portion)
|
||||
- [x] Sampling continuity: no 3 consecutive tasks without automated verify — every task across all 5 plans has one
|
||||
- [x] Wave 0 covers all MISSING references — no file listed as `❌ W0` in the original requirement→test map remains missing; every referenced test file is created by an explicit plan task
|
||||
- [x] No watch-mode flags — all commands are one-shot `go test`/`go vet`/`bash` invocations
|
||||
- [x] Feedback latency < 120s — quick-run commands are package-scoped `-short` runs
|
||||
- [x] `nyquist_compliant: true` set in frontmatter
|
||||
|
||||
**Approval:** pending
|
||||
**Approval:** planned — ready for `/gsd:execute-phase 06`
|
||||
|
||||
Reference in New Issue
Block a user