- Operator-confirmed 128MiB body limits (134217728); D-18/T-06-13 closed - HTTP-06, HTTP-08, HTTP-09 marked complete
12 KiB
Phase 6 Plan 03: Raw groups, wire, CORS, and body-limit Summary
Registration-time raw-group house-middleware refusal, route table + route:list, wire JSON helpers, path-scoped CORS, and operator-confirmed 128MiB body limits (134217728) with a committed OpenAPI document from ListGenres
Performance
- Duration: 20 min active execution (human-verify pause between Task 3 code and close-out not counted)
- Started: 2026-09-19T17:55:32Z
- Completed: 2026-09-19T18:57:39Z
- Tasks: 3 (Task 3 includes the production body-limit checkpoint)
- Files modified: 24
Accomplishments
GroupRawplus sticky raw inheritance: a raw group cannot spawn a non-raw child. House-envelope names are declared only throughpact.HasHouseMiddleware;BuildRouterregisters them andwrap()fails boot withsurf: raw group cannot use house-envelope middleware %q (plugin %q). Raw panics write a bare 500; non-raw keep the house JSON body.Router.Routes()returns a defensive[]RouteInfo(method, pattern, plugin, middleware, raw).surf.RouteListCommandrenders it; generatedmainregistersroute:listnext toserve. Mutual-exclusivity of jwt.auth vs inv_token/inv.scope is asserted over the assembled table (closes T-06-02 / T-06-10).wire.WriteJSONmatches genre_controller byte-for-byte (SetEscapeHTML(false), trailing-newline trim).wire.Timemarshals Carbon+00:00neverZ;TriBoolnulls when invalid;Slicenever returns nil.ListGenresdelegates and carries swag annotations;docs/openapi.jsonis committed and validated by openapi-typescript 7.13.0.- Path-scoped CORS copies
config/cors.php(api/*,_user/api/*,_journal/api/*,_feedback/api/*,oauth/mcp/*). JWT/_fonoteka/api/v1/genresemits noAccess-Control-Allow-Origin; personal-token/api/v1/fonoteka/genresemits*. - Body limits: non-raw routes wrap
http.MaxBytesReaderfromhttp.body_limits.default_bytes;body.limit:Noverrides innermost; raw routes are uncapped at this layer. Production values are operator-confirmed 2026-09-19: nginxclient_max_body_size=128M, php.inipost_max_size=128Mandupload_max_filesize=128M→ 134217728 / 134217728 (128 × 1024 × 1024). D-18 / T-06-13 closed. NoINTERIMremains in production yaml. golem15.fonotekaimplementsHouseMiddlewares()withinv.must-change-password. The oauth group isGroupRaw("/", Use(), empty)with zero handlers; Phase 8 attachesthrottle:fonoteka-oauth-token/throttle:fonoteka-oauth-registerper-route.
Task Commits
Each task was committed atomically:
- Task 1: Raw group enforcement, house-middleware capability, route table, route:list -
fa7e6d1(feat, summercms.go) - Task 2: wire helpers (framework) -
9ecf2d1(feat, summercms.go) - Task 2: OpenAPI from ListGenres and wire delegation (app) -
db51df6(feat, fonoteka.go) - Task 3 code: path-scoped CORS and per-route body limits (framework) -
30539b9(feat, summercms.go) - Task 3 code: raw oauth group, house-tagged password middleware, INTERIM yaml -
cdc324a(feat, fonoteka.go) - Task 3 checkpoint: production 128MiB body limits -
73671a8(feat, fonoteka.go)
Plan metadata: (this commit)
Files Created/Modified
pact/capabilities.go—GroupRawon Router;HasHouseMiddlewaresurf/router.go— raw flag, house-tagged registration, BuildRouter, per-route recoverBare/recoverJSON, body-limit wrap, path-scoped CORSsurf/routetable.go—RouteInfoandRoutes()defensive copysurf/routelist_command.go—route:listtable commandsurf/cors.go—CORSConfig, Laravel glob compile, path-scoped middlewaresurf/bodylimit.go—http.MaxBytesReaderdefault +body.limit:Noverridewire/response.go— WriteJSON, WriteOpaque500, Time, TriBool, Sliceinternal/build/build.go— generated main appendssurf.RouteListCommandplugins/golem15/fonoteka/plugin.go—HouseMiddlewares();inv.must-change-passwordmoved out ofMiddlewares()plugins/golem15/fonoteka/routes.go— emptyGroupRawoauth landing spotplugins/golem15/fonoteka/controllers/genre_controller.go— wire delegation + swag commentsplugins/golem15/fonoteka/routes_cors_test.go— CORS, house-capability, raw-refusal, route-table exclusivity on real pluginsconfig/http.yaml— cors.php paths; body_limits 134217728/134217728 operator-confirmed 2026-09-19scripts/check-openapi.sh/scripts/swagger2openapi.go/docs/openapi.json— swag → OpenAPI 3 → openapi-typescript
Decisions Made
- House-tagged middleware is a plugin capability (
HasHouseMiddleware), never aRegister*call from plugin code (D-16, T-06-11). AssembleisBuildRouterthencompile(), soroute:listinspects the table without serving.- CORS
*globs match nested path segments the way Laravelapi/*does; Gopath.Matchwould leave/api/v1/fonoteka/genreswithout CORS and fail Pitfall 10. - Body-size numbers are not a guess: operator read production nginx + php.ini on 2026-09-19; both keys are 134217728 (D-18, T-06-13).
- swag v1.16.6 stays (STACK.md rejects swag v2 RC); a local converter bridges Swagger 2 → OpenAPI 3 for openapi-typescript 7.13.0. Phase 10 owns wiring types into the admin SPA.
Deviations from Plan
Auto-fixed Issues
1. [Rule 3 - Blocking] Laravel glob instead of Go path.Match for CORS paths
- Found during: Task 3 (path-scoped CORS)
- Issue: The plan specified
path.Match("api/*", trimmedPath). Go'spath.Matchdoes not let*cross/, soapi/*would not matchapi/v1/fonoteka/genresand the personal-token group would emit no CORS headers — failing Pitfall 10 and HTTP-09. - Fix:
compileLaravelGlobtreats*as.*(Laravelfnmatch/api/*semantics) against the leading-slash-stripped path. - Files modified:
surf/cors.go,surf/cors_test.go - Verification:
go test ./surf -run TestCORS; fonotekaTestCORSPathScopedOnAssembledRouter(JWT empty ACAO, token*) - Committed in:
30539b9(Task 3)
2. [Rule 3 - Blocking] Local swagger2openapi converter so openapi-typescript 7 can validate swag v1 output
- Found during: Task 2 (check-openapi.sh)
- Issue: swag v1.16.6 emits Swagger 2.0 (
docs/swagger.json). openapi-typescript 7.13.0 requires OpenAPI 3.x. STACK.md forbids swag v2 (RC only). - Fix:
scripts/swagger2openapi.goconverts the swag JSON to OpenAPI 3; the script writesdocs/openapi.jsonand deletes the intermediate swagger.json. No new dependency. - Files modified:
scripts/swagger2openapi.go,scripts/check-openapi.sh - Verification:
bash scripts/check-openapi.shexits 0;docs/openapi.jsoncontains/_fonoteka/api/v1/genres - Committed in:
db51df6(Task 2)
Total deviations: 2 auto-fixed (2 Rule 3 blocking) Impact on plan: Both required for HTTP-08/HTTP-09 to be true. No scope creep; no extra libraries.
Issues Encountered
None beyond the two auto-fixes. The human-verify checkpoint is planned flow, not an issue: operator reported all three production values as 128 MB on 2026-09-19.
Checkpoint Resolution (D-18 / T-06-13)
Operator-confirmed 2026-09-19 from the production host:
| Source | Value |
|---|---|
nginx client_max_body_size |
128M |
php.ini post_max_size |
128M |
php.ini upload_max_filesize |
128M |
Converted with binary megabytes (nginx m / PHP M = 1024×1024): 128 * 1024 * 1024 = 134217728.
fonoteka.go/config/http.yaml:
http.body_limits.default_bytes: 134217728http.body_limits.upload_bytes: 134217728
INTERIM comments replaced with the confirmation comment. Body-limit tests assert against config-loaded / fixture YAML values and were not changed.
Re-run:
go test ./surf -count=1 -run BodyLimit(summercms.go) — passgo test ./plugins/golem15/fonoteka/... -count=1 -short(fonoteka.go) — pass
Auth Gates
Task 3 checkpoint:human-verify for production body-size numbers. Operator confirmed; numbers applied; plan closed. Not a deviation.
Known Stubs
None that block this plan. The oauth GroupRaw has zero handlers by design (Phase 8 owns the two POST routes). Empty jwt_locale / onboarding / public_invitation / public_share builders were already declared in 06-02.
User Setup Required
None remaining. The production-host body-size read is done (2026-09-19).
Next Phase Readiness
- Phase 8 can mount
/oauth/mcp/tokenand register on the existing raw group without house envelope/error middleware. - Phase 10 can consume
docs/openapi.json(already valid TypeScript via openapi-typescript). - 06-05 owns full coverage, the remaining route-table assertions, and
06-SECURITY-REVIEW.md. - No blockers.
Self-Check: PASSED
- FOUND: surf/routetable.go, surf/routelist_command.go, surf/cors.go, surf/bodylimit.go, wire/response.go, pact/capabilities.go
- FOUND: fonoteka.go/config/http.yaml default_bytes=134217728 upload_bytes=134217728, zero INTERIM
- FOUND:
fa7e6d1,9ecf2d1,30539b9(summercms.go); db51df6, cdc324a, 73671a8 (fonoteka.go) go test ./surf -count=1 -run BodyLimitpass;go test ./plugins/golem15/fonoteka/... -shortpass
Phase: 06-http-routing-auth-groups-and-rate-limiting Completed: 2026-09-19