docs(06-09): complete transactional panic recovery plan

Tasks completed: 1/1
- Buffer route responses so panic recovery can discard partial output

SUMMARY: .planning/phases/06-http-routing-auth-groups-and-rate-limiting/06-09-SUMMARY.md
This commit is contained in:
Jakub Zych
2026-09-20 21:06:51 +02:00
parent 93d63c351b
commit 158df899f6

View File

@@ -0,0 +1,119 @@
---
phase: 06-http-routing-auth-groups-and-rate-limiting
plan: 09
subsystem: http
tags: [surf, panic-recovery, response-buffering, raw-routes, security]
requires:
- phase: 06-http-routing-auth-groups-and-rate-limiting
provides: raw/house route separation, bare and opaque panic recovery contracts, and wire.WriteOpaque500
provides:
- Transactional response buffering shared by house and raw panic recovery
- Partial-status, header, and body disclosure regressions for both route kinds
- Successful explicit-status and implicit-200 buffered response coverage
affects: [phase-08-oauth, phase-12-api-routes, phase-14-api-routes]
tech-stack:
added: []
patterns:
- Route output is buffered privately and committed only after successful handler return
- Panic recovery discards route-owned status, headers, and body before writing the contract fallback
key-files:
created: []
modified:
- surf/router.go
- surf/router_test.go
key-decisions:
- "Use one unexported bufferedResponse for house and raw recovery so panic-before-write and panic-after-write have identical fallback behavior"
- "Implement http.Flusher as a no-op without exposing the destination writer, preserving the discard-on-panic guarantee"
- "Replace only buffered header keys during success commit so outer response headers such as path-scoped CORS remain intact"
patterns-established:
- "Transactional HTTP recovery: handler output crosses the client boundary only after a normal return"
- "Buffered writers preserve net/http first-WriteHeader and implicit-200 semantics"
requirements-completed: [HTTP-06]
duration: 4 min
completed: 2026-09-20
---
# Phase 6 Plan 09: Transactional Panic Recovery Summary
**Shared transactional response buffering now prevents house and raw routes from leaking partial status, headers, or body bytes when a handler panics.**
## Performance
- **Duration:** 4 min
- **Started:** 2026-09-20T19:02:14Z
- **Completed:** 2026-09-20T19:06:12Z
- **Tasks:** 1 TDD task
- **Files modified:** 2
## Accomplishments
- Added a private `bufferedResponse` that records headers, the first status, and body bytes without touching the destination writer.
- Changed `recoverJSON` and `recoverBare` to discard all buffered route output on panic and emit only their locked opaque JSON or bare 500 contracts.
- Added adversarial real-route tests that write `202`, a secret header, and secret body bytes before panicking, plus success coverage for explicit status, repeated `WriteHeader`, and implicit 200 behavior.
## Task Commits
TDD commits were created atomically:
1. **RED: Add failing partial-write panic and successful-buffer regressions** - `f6ba67a` (test)
2. **GREEN: Buffer route responses before panic recovery** - `93d63c3` (fix)
**Plan metadata:** this summary commit
## Files Created/Modified
- `surf/router.go` - Shared buffered response writer and transactional house/raw recovery wrappers.
- `surf/router_test.go` - Partial-write disclosure regressions and successful buffered response semantics.
## Decisions Made
- Both recovery wrappers use the same buffer implementation so raw and house routes differ only in their panic fallback body contract.
- `Flush` is intentionally a no-op; exposing or flushing the destination before handler completion would invalidate the security boundary.
- Success commits defensively copy buffered header slices and replace only the keys written by the route, retaining unrelated outer-wrapper headers.
## Deviations from Plan
None - plan executed exactly as written.
## Issues Encountered
The sandbox's default Go build cache was read-only, so verification used the task-local `GOCACHE=/tmp/summercms-06-09-gocache`. This did not affect source or test behavior.
## Verification
- `go test ./surf -run 'Test.*(Recover|Panic|BufferedResponse|RawGroup)' -count=1 -race -short` - pass
- `go vet ./surf` - pass
- `go test ./surf -count=1 -race -short` - pass
- Source inspection confirms both wrappers pass only `bufferedResponse` to `next`; the destination is written only by success commit or the panic fallback.
- House panic response is exactly `{"error":true,"message":"Internal server error"}` with no secret header/body; raw panic response is status 500 with zero body and empty Content-Type.
## User Setup Required
None - no external service configuration required.
## Next Phase Readiness
- HTTP-06's opaque/bare panic boundary is now safe even after handlers attempt partial writes.
- Raw OAuth routes in Phase 8 and remaining house routes in Phases 12-14 can rely on the same transactional recovery contract.
- No blockers.
## Known Stubs
None.
## Self-Check: PASSED
- FOUND: `surf/router.go`, `surf/router_test.go`
- FOUND: commits `f6ba67a`, `93d63c3`
- PASS: targeted race regressions, `go vet ./surf`, and full `surf` race suite
---
*Phase: 06-http-routing-auth-groups-and-rate-limiting*
*Completed: 2026-09-20*