rest: flush-chain integrity — composition test, convention guards, error-path fakes (#174)#187
Merged
Conversation
…nformational-predicate lint, hijack tripwire (#174)
…nnel-test comment fix (#174)
…h lint (#174) The composition test now consumes chain() — the same function New composes its middleware with — so mirror-drift is impossible by construction. The flush convention closes its Unwrap hole: every ResponseWriter wrapper must declare FlushError() error or Unwrap() http.ResponseWriter; a dead-end wrapper (neither, no Flush) now violates, with a meta-case pinning the detection.
…flush-panic pin (#174) Three pins from the #174 reviews: the rollback's captured&& guard (refused flush after WriteHeader(503) keeps the capture — symmetric with cachecontrol's keep-side pin), the commitWriter-presence witness the composition test's premise stands on, and the #165 x #174 composition (103 non-latching, flush captures implied 200, panic meters 200).
) Three confirmed detection evasions closed: a wrapper embedding another wrapper (fixpoint over embeds, feeding the flush AND informational guards, with promotion-aware dead-end escapes), a named-field http.ResponseWriter delegate (the embed-only comment's rationale was false — corrected), and an aliased net/http import (local name resolved per file). Meta-cases pin each; floors keep, with failure messages that name the bump-the-floor path and violations printing before floor aborts.
The Flush/FlushError switch cases credited an escape for EVERY receiver
type under ANY signature, while violations fire only on wrappers — the
promotion fixpoint then laundered a non-wrapper helper's escape into a
clean bill for a wrapper embedding it. Two demonstrated evasions, both
previously zero-violation:
- bare Flush() on a plain helper, embedded: the promoted method IS
matched by http.ResponseController's Flusher branch and silently
swallows errors — now flagged on the embedding wrapper, naming the
helper the method promoted from (provenance via the embed graph);
- malformed FlushError() (int, error) on a plain helper, embedded:
invisible to the controller's method search, so the wrapper is a
runtime dead end — now caught by the dead-end rule.
Non-wrapper receivers now earn escape credit only on the exact shapes
the controller matches (FlushError() error — the same discipline Unwrap
always had); wrappers keep any-signature credit because their shape
problems are flagged individually. Meta-cases added for both evasion
shapes plus the legitimate-promotion counterpart (exact FlushError on a
helper stays clean); all prior meta-cases unchanged and green.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #174.
What
Closes the flush-chain integrity gap: the composed flush property is now pinned against
rest.New's real assembly, the wrapper conventions are mechanically enforced, and the error paths have a reusable fake family.chain()extraction + composition test (rest/rest.go,rest/flushchain_internal_test.go):New's middleware nesting extracted intochain(ds, inner), consumed by bothNewand the composition test — a wrapper inserted intoNewwithout FlushError/Unwrap now fails the suite twice (lint + streaming test). Behavior-preserving; verified against develop's inline assembly.rest/wrapperconventions_test.go): AST sweeps in the types/: mechanical tag-lint test enforcing wire conventions #148/types: mechanical tag-lint test enforcing wire conventions (#148) #162 family — every ResponseWriter wrapper carriesFlushError() errororUnwrap() http.ResponseWriter(never bareFlush(), including promoted from embedded helpers, with origin named); every wrapperWriteHeaderconsultsinformational(); hijack tripwire (no production hijack — remedy message names both ruled options, incl..out-of-scope/gzip-hijack-faithfulness.mdfrom triage: found .out-of-scope/ with the gzip hijack-faithfulness rejection (#181) #185). Detection is transitive (wrapper-embedding-wrapper), field-shape-blind (named delegates), and alias-proof (net/httpimport aliases); each checker meta-tested against its target break; population floors guard scanner rot.rest/metrics.go): closes the flush blind spot — flush-committed implied 200 captured before delegation,ErrNotSupportedrefusal rolls back (errors.Is, the rest: commitWriter.FlushError leaves committed=true when the delegated flush fails #164/rest: cacheControlWriter.FlushError errors.Is discriminator is unpinned #172 discriminator convention), genuine errors keep the capture. Flush-then-panic now meters 200, not 500. Pinned: rollback, keep-side guard, genuine-keep, 103→flush→panic, deadline Unwrap tunnel.rest/fakewriters_test.go): noFlushWriter / flushErrorWriter / failingWriter / flushSnapshotWriter / noFlushUnderlying, consolidated from three files, documented for rest: cacheControlWriter rollback — committed=false reopen and the stamped guard are unwitnessed #178 reuse.gz.Flush→ early return, sticky writer error, close-path log backstop.rest/metrics_internal_test.go): false recorder-masking rationale replaced; new deadline-tunnel witness keepsUnwraptested.Validation
-race ./rest/,make test-integration(MariaDB harness) — green; re-verified post-rebase onto 03dae4b.🤖 Generated with Claude Code