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:
@@ -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*
|
||||
Reference in New Issue
Block a user