Phase 6: HTTP routing, auth groups and rate limiting - Discussion Log
Audit trail only. Do not use as input to planning, research, or execution agents.
Decisions are captured in CONTEXT.md — this log preserves the alternatives considered.
Date: 2026-09-19
Phase: 6-HTTP routing, auth groups and rate limiting
Areas discussed: Bucket scope & limiter, Token guard depth, Outbound fetch guard, Route surface & OAuth exempt
Bucket scope & limiter
Which buckets does Phase 6 define and test?
| Option |
Description |
Selected |
| Fonoteka 5 + mechanism |
Five fonoteka named buckets plus inline throttle form; other plugins' buckets land with those plugins; roadmap "seven" corrected |
✓ |
| All 11 now |
Every named bucket of every ported plugin declared in Phase 6 |
|
| Fonoteka 5 + user 3 |
Add the user stub's three buckets now |
|
Wire behavior
| Option |
Description |
Selected |
| Exact Laravel parity |
Fixed window, X-RateLimit-* headers, Retry-After/Reset on 429, same 429 body |
✓ |
| Same limits, Go-native algorithm |
Token bucket/sliding window, same headers, edge bursts may differ |
|
| You decide |
Researcher picks cheapest faithful option |
|
Counter store
| Option |
Description |
Selected |
| In-process, stdlib |
Mutex map + sweep behind surf.Limiter, Store interface for later |
✓ |
| In-process via otter (cooler) |
Adds otter and the cooler package now |
|
| Postgres-backed |
Survives restarts, DB write per limited request |
|
Client IP
| Option |
Description |
Selected |
| Trusted-proxy config |
XFF honored only from trusted CIDRs, rightmost untrusted hop |
✓ |
| RemoteAddr only |
Ignore forwarding headers |
|
| Always trust X-Forwarded-For |
Spoofable if directly reachable |
|
Notes: Scouting found five named buckets in fonoteka/routes.php, not seven; eight more live in user, feedback, websockets and journal plugins.
Token guard depth
How real is the personal-token guard?
| Option |
Description |
Selected |
| Real verifier, test-only minting |
ApiToken lookup with PHP rules; tokens inserted by tests/seed hooks; Phase 7 adds CRUD |
✓ |
| Interface + fake guard |
Real lookup waits for AUTH-03 |
|
Where does inv.scope land?
| Option |
Description |
Selected |
| Phase 6, in golem15.fonoteka |
It is the token group's auth step; exact 401/403 bodies; parameterized names in surf |
✓ |
| Phase 7 with AUTH-03 |
Guard-only auth in Phase 6 |
|
Guard registry shape
| Option |
Description |
Selected |
| Named guards in bouncer |
Registry by name, Guard interface, single user accessor plus credential accessor, fail boot on duplicates |
✓ |
| Just named middleware |
Convention only |
|
| You decide |
Planner picks |
|
OAuth bearer guard slot
| Option |
Description |
Selected |
| Name reserved, no implementation |
Registry proven with two real guards |
✓ |
| Stub that always 401s |
Mount /oauth/mcp behind a rejecting guard |
|
Outbound fetch guard
Host policy
| Option |
Description |
Selected |
| Per-caller policy, both modes |
AllowHosts and/or PublicOnly; private-IP block always on |
✓ |
| Allow-list only |
Manual cover URL would need an allow-list (behavior change) |
|
DNS rebinding
| Option |
Description |
Selected |
| Dial-time IP check |
net.Dialer Control validates the connected IP |
✓ |
| Match PHP: pre-resolve only |
Keep the gap, note the risk |
|
Scope
| Option |
Description |
Selected |
| Framework helper + tests only |
Fetcher ports are Phase 12/14 |
✓ |
| Helper + both fonoteka fetchers |
Port ManualCoverUrlFetcher and CoverImporter now |
|
Limits source
| Option |
Description |
Selected |
| Caller passes them; no framework default-open |
Zero value fails construction |
|
| Framework defaults in config |
http.fetch.* defaults, overridable per call |
✓ |
Notes: User chose framework config defaults over the recommended caller-required values. Recorded with the constraint that an effective limit can never be unlimited.
Route surface & OAuth exempt
Route surface
| Option |
Description |
Selected |
| Groups + proving routes only |
All groups as builders; only real handlers mounted; genres under both prefixes; no 501 shells |
✓ |
| All 154 as 501 shells |
Whole surface registered now |
|
| Shells for auth-sensitive routes only |
Public, token and OAuth groups fully registered |
|
OAuth exemption
| Option |
Description |
Selected |
| Group flag + boot-time inspection |
Raw group refuses house middleware, bare 500 on recover, route table + route:list |
✓ |
| Separate mux mounted beside the pipeline |
Own ServeMux and minimal chain |
|
| You decide |
Planner picks |
|
Response conventions
| Option |
Description |
Selected |
| Framework helpers + types, no envelope middleware |
JSON writer, Carbon time, nullable bool, never-nil slice |
✓ |
| Helpers live in fonoteka.go |
Framework stays neutral |
|
CORS and body limits
| Option |
Description |
Selected |
| Path-scoped CORS from config, exact parity |
Reproduce cors.php, no CORS on /_fonoteka/api; body limits from real deployment values |
✓ |
| CORS on everything |
Header difference on the JWT group |
|
Claude's Discretion
Package homes and names, sweep interval and Store shape, inline throttle key composition (must match Laravel), swag/openapi-typescript workflow and drift check, locale stage depth, how group mutual exclusivity is asserted, plan count and split.
Deferred Ideas
User/feedback/websockets buckets with their plugins; shared limiter store after v1; cooler/otter cache when needed; cover fetcher ports in Phase 12/14; OAuth guard and handlers in Phase 8; token CRUD in Phase 7.