Skip to content

fs-http: document + pin the middleware contract (R2/R3, no runtime change)#159

Merged
Goosterhof merged 2 commits into
mainfrom
war-room/wr0049-fs-http-r2-r3-hardening
Jul 16, 2026
Merged

fs-http: document + pin the middleware contract (R2/R3, no runtime change)#159
Goosterhof merged 2 commits into
mainfrom
war-room/wr0049-fs-http-r2-r3-hardening

Conversation

@Goosterhof

Copy link
Copy Markdown
Contributor

War-room dispatch WR-0049 (Surveyor M2 hardening recs). Documents (R2) and pins (R3) the fs-http middleware invariants. Docs + tests only — no runtime change, no version bump, no CHANGELOG (no release warranted).

Verify-first vs ADR-0037 (0.6.0 guard-by-default)

The source report predates 0.6.0, so each of the 12 invariants was re-verified against HEAD. Result: exactly one was superseded — the old "sync throw aborts the caller" is now "loud-swallowed by default, propagates only under {guard:false}" (already pinned in http.spec.ts's guard-by-default suite). The other 11 hold unchanged.

R2 — docs

  • README.md — new #### Middleware contract subsection: guard-by-default + {guard:false} opt-out, sync-only/fire-and-forget, FIFO, per-instance scoping, non-idempotency, mutation visibility + response-object freshness, and the one gap guard-by-default does NOT close.
  • types.ts — JSDoc on the three register* signatures (flows into the emitted .d.mts).

R3 — 8 contract-pin tests (middleware-contract.spec.ts) + 1 unit pin (guarded.spec.ts)

response-path FIFO · per-instance scoping · non-idempotent double-fire · mutation visibility · response-object-not-reused · sync-loop-completes-before-caller-resumes · async-body-not-awaited · async-rejection-not-caught-by-guard. (Throw-isolation was already fully pinned — not duplicated.) Async-rejection tests use pre-handled rejected promises so no floating unhandled rejection poisons the runner.

⚠️ The one genuinely-new consumer fact

guard-by-default catches SYNCHRONOUS throws only. An async-bodied middleware whose Promise rejects is not caught by guarded() and does not reach onMiddlewareError — it still surfaces as an unhandled rejection. Worth carrying into consumer-adoption guidance. Documented in README + JSDoc, pinned twice (unit + integration).

Verification

format 166 ✓ · oxlint 0 · build (ESM+CJS) ✓ · typecheck ✓ · coverage exit 0 (658 tests) · lint:pkg attw all 🟢. 4 files, +262, no src behavior change.

🤖 Generated with Claude Code

@Goosterhof Goosterhof added the Agent Review Requested Requesting review of specialized AI review agents. label Jul 13, 2026
@Goosterhof
Goosterhof requested a review from a team as a code owner July 13, 2026 11:41
@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Jul 13, 2026

Copy link
Copy Markdown

Deploying fs-packages with  Cloudflare Pages  Cloudflare Pages

Latest commit: 5d1bc7f
Status: ✅  Deploy successful!
Preview URL: https://9ef7dce7.fs-packages.pages.dev
Branch Preview URL: https://war-room-wr0049-fs-http-r2-r.fs-packages.pages.dev

View logs

@Goosterhof Goosterhof left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Verified the "no runtime change" claim directly: packages/http/src/http.ts and packages/http/src/guarded.ts are untouched by this diff (only README.md, types.ts, and two spec files change). types.ts's +38 lines are pure JSDoc additions above three unchanged exported signatures (registerRequestMiddleware/registerResponseMiddleware/registerResponseErrorMiddleware) — no type is narrowed, widened, or renamed. Claim holds.

Cross-checked the new middleware-contract.spec.ts (8 tests) and the guarded.spec.ts addition against the actual runtime at HEAD (src/http.ts's bare for (const middleware of X) middleware(arg) loops, src/guarded.ts's synchronous try/catch):

  • FIFO / per-instance / non-idempotent-registration / mutation-visibility / sync-loop-completes / async-not-awaited — all match the real control flow.
  • The one behavior-relevant new fact — guarded() only catches a synchronous throw, so an async-bodied middleware's rejected promise is invisible to it and escapes as an unhandled rejection — is correctly modeled and doesn't duplicate the existing http.spec.ts §"middleware guarding by default (ADR-0037)" suite (that suite already pins sync throw-isolation + {guard:false} propagation + onMiddlewareError routing; this PR explicitly doesn't re-pin those, and I confirmed no overlap). The new response-path FIFO test also doesn't duplicate http.spec.ts:322's existing request-path ordering test — different path, correctly scoped.

Two nits, no blockers/majors:

  1. types.ts JSDoc adds @returns an idempotent unregister — a claim not in the PR's enumerated 8 contract-pin tests (nor in any pre-existing http.spec.ts test I could find) that calling unregister() twice is a safe no-op. It happens to be true (array.indexOf returns -1 after the first splice, guard skips), but it's asserted in prose only.
  2. The "does not reuse the response object" test pins axios's own per-request object-identity guarantee, not an fs-http invariant — harmless, but if it ever fails the cause is upstream, not this package.

CI: format/lint/build/typecheck/lint:pkg/test:coverage all green at review time; test:mutation was still running (not failed) — worth a final glance before merge but not a gate concern from this diff.

No blockers/majors. Own PR — capping at COMMENT.

Automated war-room agent review — posted because this PR carries the Agent Review Requested label.

Comment thread packages/http/src/types.ts
Comment thread packages/http/tests/middleware-contract.spec.ts

@dmooibroek dmooibroek left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Clean. Docs+tests only, no src behavior change (http.ts/guarded.ts untouched, types.ts additive). One disagreement with the-general's nit fid140: idempotent unregister() IS covered pre-existing at http.spec.ts:433 (double-call, asserts no-throw + stays removed; predates this PR per git log -p) — the nit's premise is wrong.

…ange)

Surveyor M2 (2026-05-13) distilled twelve middleware invariants and
recommended documenting them (R2) and adding contract-pin tests (R3).
Re-verified every invariant against HEAD (fs-http 0.6.0, guard-by-default
ADR-0037), not the pre-0.6.0 report: exactly one — sync-throw-aborts-caller
— is superseded (now loud-swallowed by default, propagates only under
{guard: false}) and is already pinned in http.spec.ts's guard-by-default
suite. The rest hold unchanged.

R2 (docs, no behavior change):
- README §"Middleware contract" — guard-by-default + {guard:false} opt-out,
  sync-only/fire-and-forget, the async-rejection gap guard-by-default does
  NOT close, FIFO, per-instance scoping, non-idempotency, mutation
  visibility + response-object freshness.
- JSDoc on the three HttpService register* signatures (editor-surface
  contract).

R3 (tests, additive):
- tests/middleware-contract.spec.ts (8 pins) — response-path FIFO,
  per-instance scoping, non-idempotent double-fire, mutation visibility,
  response-object-not-reused, sync-loop-before-caller-resume, async body
  not awaited, and async-rejection-not-caught-by-guard (leak-safe via a
  pre-handled rejected promise).
- guarded.spec.ts +1 unit pin — guarded() is synchronous-only; a
  rejected-promise-returning body is not caught (no onError).

Docs + tests only. No source runtime change. Gate: format/lint/build/
typecheck/lint:pkg green; test:coverage 658 passed, exit 0.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FFY2M2jh9MLDWFwwcSi7Go
@Goosterhof
Goosterhof force-pushed the war-room/wr0049-fs-http-r2-r3-hardening branch from c953f6a to d446a80 Compare July 15, 2026 13:53

@Goosterhof Goosterhof left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚪ Approve-worthy

0 blockers · 0 majors · 0 minors · 2 nits (both prior, already dispositioned) · 0 inline

Round 2 — since c953f6a (our prior review): the only PR-authored commit is d446a80c2, and cross-referencing dmooibroek's bus-side note (patch-id 1602d102 identical across the head bump), this is a pure rebase onto main, zero authored delta. Independently confirmed against this diff: packages/http/src/http.ts and guarded.ts remain untouched, types.ts is JSDoc-only (+38 above unchanged signatures), and the two spec files are additive-only. CI green (ci-passed, check, town-crier/gate).

Our two open nits carry forward unchanged, both already dispositioned in-thread:

  • packages/http/src/types.ts:99 (fid140, idempotent-unregister() claim) — refuted: independently re-verified packages/http/tests/http.spec.ts:433-447 ("unregistering a non-existent middleware is a no-op") double-calls unregister() and asserts no-throw + stays removed — that predates this PR and is the idempotency pin. dmooibroek's disposition stands.
  • packages/http/tests/middleware-contract.spec.ts:122 (fid141, response-object-identity test pins axios's own guarantee, not an fs-http invariant) — informational, no author action required.

No new findings on this head. Reposting as coverage since our last GH-visible review predates the rebase; own PR — capping at COMMENT.

Automated war-room agent review — posted because this PR carries the Agent Review Requested label.

…eview nit fid141)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019bM8WgP1qKadymc36wJYCL

@dmooibroek dmooibroek left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

R4 delta d446a80 -> 5d1bc7f: one authored commit, +2 comment lines in middleware-contract.spec.ts L120-121 — documents the response-identity test pins axios's own guarantee (triage upstream on failure). Exactly addresses fid141; no behavior change, no src touch. Priors stand: fid140 refuted (pre-existing coverage http.spec.ts:433), fid141 now documented in-code. Clean.

Resolved since last review: 1.

@Goosterhof
Goosterhof merged commit 20a6ca2 into main Jul 16, 2026
4 checks passed
@Goosterhof
Goosterhof deleted the war-room/wr0049-fs-http-r2-r3-hardening branch July 16, 2026 11:24
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Agent Review Requested Requesting review of specialized AI review agents.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants