Skip to content

rest: pass informational 1xx WriteHeaders through the writer wrappers (#165)#177

Merged
SyniRon merged 3 commits into
developfrom
agent/issue-165
Jun 7, 2026
Merged

rest: pass informational 1xx WriteHeaders through the writer wrappers (#165)#177
SyniRon merged 3 commits into
developfrom
agent/issue-165

Conversation

@SyniRon

@SyniRon SyniRon commented Jun 7, 2026

Copy link
Copy Markdown
Collaborator

Closes #165.

net/http deliberately does not treat informational responses as a commit; all three writer wrappers disagreed, latching on the FIRST WriteHeader regardless of status class — a 103 would meter as the final status, burn the panic-recovery latch (connection abort instead of the contract 500), and eat the Cache-Control stamp. All three now pass non-latching 1xx through via a shared informational() predicate: forward to the delegate, latch nothing; the final WriteHeader behaves as a first call.

The carve-out that review caught: the stdlib commits on 101 Switching Protocols (code != StatusSwitchingProtocols on its informational path) — three review agents independently verified the original [100,199] predicate diverged in the dangerous direction (a 101-then-panic would become a clean 101 to the client, the recovery 500 silently dropped). The predicate now mirrors the stdlib exactly; the old latching path was correct for 101 and stays.

  • One shared table test drives {100, 103} through each wrapper's owning middleware over a real server (httptrace.Got1xxResponse — the recorder has no 1xx machinery); a fourth wrapper joins by adding a row
  • Wire-level 101 witness: 101-then-panic asserts the honest connection abort per wrapper — the inlined-drift mutant that survived the whole suite without it is confirmed killed
  • 11 mutants run across two review rounds, all killed; per-wrapper failure isolation verified
  • rest: commitWriter.FlushError leaves committed=true when the delegated flush fails #164-era latch docs rescoped to "latching WriteHeader (final status, or the 101 carve-out)" — the latch≡wire equation no longer holds once interim responses exist

Two review rounds; pre-existing adjacent findings routed to #174 (statusWriter flush capture gap, predicate lint), #175 (1xx carries Content-Encoding), #176 (gateway statusRecorder 1xx latch).

🤖 Generated with Claude Code

SyniRon and others added 3 commits June 7, 2026 13:46
…appers (#165)

net/http deliberately leaves its wroteHeader latch false for 1xx — the
chain's three writer wrappers all disagreed, latching on the FIRST
WriteHeader regardless of status class:

- statusWriter metered a 103 as the final status
- commitWriter sent a 103-then-panic down the re-panic path (connection
  abort) instead of writing the contract 500
- cacheControlWriter burned the stamp decision on the 103, so the real
  200 committed with no Cache-Control

All three now treat 100..199 as pass-through via a shared informational()
predicate: forward to the delegate, latch nothing — the subsequent final
WriteHeader behaves exactly as a first call. One shared table test
(informational_internal_test.go) drives the 103 through each wrapper's
owning middleware on a real server (wire-faithful 1xx machinery, the
forwarded 103 observed via httptrace) — a fourth wrapper joins the table.
Latch-era docs (#164 rollback phrasing, the deferred metrics relabel)
made precise for the forwarded-1xx case.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Post-review fixup. net/http routes 1xx through its non-latching
informational path EXCEPT 101 Switching Protocols, where it sets
wroteHeader and commits; the predicate now mirrors that carve-out so a
post-101 panic re-panics instead of "writing" a contract 500 the stdlib
silently drops. 101 row added to the boundary pin (red on the old
predicate; dropping the carve-out goes red again). The shared wire
table now writes both 100 and 103 so inlined-range drift in any one
wrapper fails at wire level, not only in the unit pin.

Doc sweep: corrected every "stdlib never latches on 1xx" claim
(informational.go, informational_internal_test.go, cachecontrol.go,
sentry.go, metrics.go), scoped the latch≡wire phrasing leftovers to the
FINAL response (sentry FlushError + tests, cachecontrol rollback
premise, commitSnapshot, metrics status()/Help/never-wrote family,
gzip Content-Length note), tightened the informational.go preface
(Expect:100-continue bypasses the wrappers; legacy statusRecorder
latches on 1xx, #176), and noted two pre-existing adjacent holes:
statusWriter's flush-blind implied 200 (#174) and gzip's
Content-Encoding on interim responses (#175).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Round-2 fixups. TestWriterWrappers_101Latches pins the 101 carve-out at
wire level per wrapper (own request each — nothing may follow a 101):
commitWriter re-panics a 101-then-panic into an honest connection abort
(one sentry event) instead of a clean 101; statusWriter meters the
committed status="101", not the unwritten-case 500 relabel;
cacheControlWriter's burned commit refuses a post-101 stamp (live-map
peek — the stdlib's own Header() snapshot masks that stamp at the wire).
Each row kills its wrapper's inlined carve-out-less-range mutant; the
commitWriter mutant survived the entire prior suite.

Doc sweep: the latching set is "non-1xx, plus 101" — replaced the stale
"FINAL (non-1xx)" equation and the two now-false 1xx generalisations
(sentry.go rollback note, metrics.go relabel note) with the
non-latching-1xx vocabulary and predicate pointers; precision fixes in
the satellites.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@SyniRon
SyniRon merged commit 268491a into develop Jun 7, 2026
3 checks passed
@SyniRon
SyniRon deleted the agent/issue-165 branch June 7, 2026 18:48
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

rest: 1xx WriteHeader burns the commit latch in all three writer wrappers

1 participant