Skip to content

feat(net): AuthSource + Auth/SetHeaders layers + Guarded body (Slice 0 PR 3)#66

Merged
NotAProfDev merged 12 commits into
mainfrom
feat/net-http-auth-guarded
Jul 4, 2026
Merged

feat(net): AuthSource + Auth/SetHeaders layers + Guarded body (Slice 0 PR 3)#66
NotAProfDev merged 12 commits into
mainfrom
feat/net-http-auth-guarded

Conversation

@NotAProfDev

@NotAProfDev NotAProfDev commented Jul 3, 2026

Copy link
Copy Markdown
Owner

Closes #64

Slice 0 PR 3 of the net-http construction surface (spec: docs/superpowers/specs/2026-06-30-net-http-construction-surface-design.md; plan: docs/superpowers/plans/2026-07-02-net-http-auth-guarded.md).

What's here

  • ADR-0034 — records the construction-surface decisions and amends ADR-0030/0031 append-only (runtime-free charter wording, HttpError::Auth, Guarded replacing 0031 §3's Permit enum, boot-time total pacing coverage, §4 HTTP-status passthrough).
  • AuthSource (RPITIT, Send-bounded) + NoAuth + the Auth layer — credentials stamped on the final request innermost, so a future Retry (Slice 1) re-signs per attempt; authorize failures classify as ErrorKind::Auth and short-circuit before the inner service.
  • SetHeaders — static default headers just outside Auth; dynamic credentials win on collision (caller-set < static defaults < Auth).
  • Guarded<B> — the concurrency permit rides the response body (async-lock, runtime-neutral), released at the earlier of stream-end or drop; forwards is_end_stream/size_hint (a defaulted size_hint would make a max-size guard fail open).

Notes

  • net-http-api stays free of any async runtime — the only added lib dep is async-lock (runtime-agnostic). No tokio/hyper/reqwest/serde.
  • Tests are executor-free (manual poll + real async_lock::Semaphore), proving runtime-neutrality; both permit-release paths (eager terminal-frame + early drop) are pinned.
  • just ci green. Delivered subagent-driven TDD with per-task + whole-branch review.

Next: PR 4 (RateKey + RateLimitConfig<K> + boot-time coverage validation) closes Slice 0.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features
    • Added HTTP request authentication layers with an AuthSource credential-stamping seam, per-attempt freshness, and header precedence via SetHeaders.
    • Added a Guarded streaming response body that holds a concurrency permit until terminal frame read or early drop.
  • Documentation
    • Updated HTTP construction and resilience ADRs to reflect the finalized auth and guarded-body contracts.
  • Chores / Tests
    • Expanded unit tests for auth short-circuiting, request/header transparency, and permit lifetime; added shared async-lock workspace dependency.

NotAProfDev and others added 10 commits July 3, 2026 06:44
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add Guarded<B>, the permit-carrying http_body::Body wrapper that
releases an optional async_lock::SemaphoreGuardArc concurrency permit
at the earlier of stream-end (poll_frame's terminal None) or drop.
Forwards is_end_stream/size_hint to the inner body per the
wrapper-transparency rule. Adds the async-lock workspace dependency.
Replace all four #[allow(clippy::significant_drop_tightening)] with
#[expect] and concise justifications. Verify via clippy -D warnings
that each suppression is load-bearing — all four lint-fire cases were
confirmed and retained.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Jul 3, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 67108513-61ad-460d-a4ba-81b4b0cb29b5

📥 Commits

Reviewing files that changed from the base of the PR and between d374704 and 0d6ac38.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock, !**/*.lock
📒 Files selected for processing (2)
  • CHANGELOG.md
  • Cargo.toml
✅ Files skipped from review due to trivial changes (2)
  • CHANGELOG.md
  • Cargo.toml

📝 Walkthrough

Walkthrough

Adds the HTTP auth seam and header layers, a Guarded response-body wrapper with async-lock permit handling, public API re-exports, and related ADR, spec, plan, and changelog updates.

Changes

Auth/SetHeaders/Guarded construction surface

Layer / File(s) Summary
Workspace and crate dependency setup
Cargo.toml, crates/adapter/net/http/api/Cargo.toml
Adds async-lock to the workspace dependency set and references it from the net-http-api crate manifest.
AuthSource seam and Auth/SetHeaders layers
crates/adapter/net/http/api/src/auth.rs
Defines AuthSource, NoAuth, Auth, and SetHeaders with tests covering request stamping, freshness, auth failure short-circuiting, Send futures, static headers, and collision precedence.
Guarded response body with permit lifecycle
crates/adapter/net/http/api/src/body.rs
Adds Guarded<B> with optional semaphore permit handling, forwards body transparency methods, and tests permit release at stream end, drop, and error.
Crate public API wiring
crates/adapter/net/http/api/src/lib.rs
Adds the auth module and re-exports AuthSource, NoAuth, Auth, SetHeaders, and Guarded.
ADR, plan, spec, and changelog updates
docs/adr/0034-...md, docs/adr/0030-...md, docs/adr/0031-...md, docs/superpowers/plans/2026-07-02-net-http-auth-guarded.md, docs/superpowers/specs/2026-06-30-net-http-construction-surface-design.md, CHANGELOG.md
Adds ADR-0034, amends the prior ADRs, updates the implementation plan, renumbers the spec reference, and adds a changelog entry for the new construction surface.
Estimated code review effort: 3 (Moderate) ~25 minutes

Suggested labels: enhancement

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title follows Conventional Commits and clearly summarizes the Auth, SetHeaders, and Guarded changes.
Linked Issues check ✅ Passed The PR implements AuthSource, Auth/SetHeaders layering, Guarded body behavior, async-lock usage, and ADR-0034 updates as requested.
Out of Scope Changes check ✅ Passed No unrelated code changes are evident; the touched docs, manifests, and source files align with the linked construction-surface scope.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/net-http-auth-guarded

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot 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.

Actionable comments posted: 1

🧹 Nitpick comments (2)
docs/superpowers/specs/2026-06-30-net-http-construction-surface-design.md (1)

465-468: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Drop the branch name from the ADR-numbering note.

The docs/adr-ws-transport-stack reference is a local workflow detail and will age out quickly. Keep the 0034 reservation, but generalize the WebSocket note so the spec stays stable after the branch disappears.

♻️ Suggested edit
-   ADR-0034** that records them and the construction-surface decisions? (The repo has
-   landed 0029–0031 append-only; leaning a standalone ADR. **Note:** 0032/0033 are taken by
-   the WebSocket transport pair on branch `docs/adr-ws-transport-stack`, so this workstream's
-   ADR is **0034**, not 0032.)
+   ADR-0034** that records them and the construction-surface decisions? (The repo has
+   landed 0029–0031 append-only; leaning a standalone ADR. 0032/0033 are reserved by the
+   WebSocket transport pair, so this workstream's ADR is **0034**, not 0032.)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/superpowers/specs/2026-06-30-net-http-construction-surface-design.md`
around lines 465 - 468, The ADR-numbering note in the spec includes a transient
branch name that will age out and should be removed. Update the wording around
the ADR reservation so it still states that the workstream is ADR-0034 and that
0032/0033 are already taken by the WebSocket transport pair, but generalize the
WebSocket reference without mentioning docs/adr-ws-transport-stack. Locate the
note in the spec section discussing ADR-0034 and revise that sentence only.
crates/adapter/net/http/api/src/body.rs (1)

141-150: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

Error terminal frame doesn't release the permit eagerly.

poll_frame only clears permit when frame.is_none(). A Some(Err(_)) frame also practically ends the stream but leaves the permit held until Drop. Not a leak, just a delayed release for error paths — worth aligning with the "earliest of stream-end or drop" intent.

♻️ Proposed fix
         let this = self.project();
         let frame = ready!(this.inner.poll_frame(cx));
-        if frame.is_none() {
+        if !matches!(frame, Some(Ok(_))) {
             // Eager release at stream-end: a fully-read but still-held body
             // must not keep hold of one of the venue's concurrency slots.
             // Dropping the guard is synchronous and runtime-free.
             *this.permit = None;
         }
         Poll::Ready(frame)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/adapter/net/http/api/src/body.rs` around lines 141 - 150, The
eager-release logic in `Body::poll_frame` only drops `permit` on `None`, so an
`Some(Err(_))` terminal frame keeps the slot until `Drop`. Update the
`poll_frame` path to also clear `this.permit` when `this.inner.poll_frame(cx)`
returns an error frame, while preserving the existing behavior for `None`, so
`Body` releases the permit at the earliest stream termination.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@crates/adapter/net/http/api/src/body.rs`:
- Around line 105-111: Guarded currently only releases its semaphore permit from
poll_frame, so if the wrapped body is already ended at construction time the
permit can remain held until Drop. Update Guarded::new and/or the Guarded body
flow in body.rs to eagerly release the permit when inner.is_end_stream() is
already true, using the existing Guarded and SemaphoreGuardArc handling. Add a
test covering the already-ended body case to verify the permit is released
without requiring any polling.

---

Nitpick comments:
In `@crates/adapter/net/http/api/src/body.rs`:
- Around line 141-150: The eager-release logic in `Body::poll_frame` only drops
`permit` on `None`, so an `Some(Err(_))` terminal frame keeps the slot until
`Drop`. Update the `poll_frame` path to also clear `this.permit` when
`this.inner.poll_frame(cx)` returns an error frame, while preserving the
existing behavior for `None`, so `Body` releases the permit at the earliest
stream termination.

In `@docs/superpowers/specs/2026-06-30-net-http-construction-surface-design.md`:
- Around line 465-468: The ADR-numbering note in the spec includes a transient
branch name that will age out and should be removed. Update the wording around
the ADR reservation so it still states that the workstream is ADR-0034 and that
0032/0033 are already taken by the WebSocket transport pair, but generalize the
WebSocket reference without mentioning docs/adr-ws-transport-stack. Locate the
note in the spec section discussing ADR-0034 and revise that sentence only.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 9a1514dd-0ff3-4195-b8e1-03aeb61c9c10

📥 Commits

Reviewing files that changed from the base of the PR and between 19d4c74 and e8f867f.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock, !**/*.lock
📒 Files selected for processing (11)
  • CHANGELOG.md
  • Cargo.toml
  • crates/adapter/net/http/api/Cargo.toml
  • crates/adapter/net/http/api/src/auth.rs
  • crates/adapter/net/http/api/src/body.rs
  • crates/adapter/net/http/api/src/lib.rs
  • docs/adr/0030-http-transport-contract-wire-bytes-streaming-composition.md
  • docs/adr/0031-http-resilience-venue-pacing.md
  • docs/adr/0034-http-construction-surface-auth-guarded-boot-coverage.md
  • docs/superpowers/plans/2026-07-02-net-http-auth-guarded.md
  • docs/superpowers/specs/2026-06-30-net-http-construction-surface-design.md

Comment thread crates/adapter/net/http/api/src/body.rs
Addresses CodeRabbit review on #66:
- release the concurrency permit at construction when the wrapped body is
  already ended (a consumer may observe is_end_stream() and never poll);
- release on an error terminal frame too, not only on None;
- drop the transient WS branch name from the spec's ADR-numbering note.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@NotAProfDev

Copy link
Copy Markdown
Owner Author

@coderabbitai all three findings validated and addressed in d374704 (just ci green):

  1. [Major] Release the permit when the body is already ended. Valid — a consumer may observe is_end_stream() and never poll, so the poll_frame eager-release can't cover an already-ended body. Guarded::new now releases the permit at construction when inner.is_end_stream() is true (added permit_releases_when_body_is_already_ended_at_construction). This made new non-const + where B: Body, which is fine — the crate is unreleased and RateLimit (the eventual constructor) isn't built yet.

  2. [Nit] Error terminal frame doesn't release eagerly. Valid — an error frame practically ends the stream. poll_frame now releases on !matches!(frame, Some(Ok(_))), i.e. on both None and Some(Err(_)) (added permit_releases_on_error_frame_before_drop).

  3. [Nit] Drop the transient branch name from the ADR-numbering note. Valid — generalized to "0032/0033 are reserved by the WebSocket transport pair" (that pair has since merged via docs(adr): WebSocket transport contract + resilience (ADR-0032, ADR-0033) #62).

Both new tests were RED-verified (they fail without the fix).

@coderabbitai

coderabbitai Bot commented Jul 4, 2026

Copy link
Copy Markdown

Tip

For best results, initiate chat on the files or code changes.

@NotAProfDev Thanks for the thorough follow-through — the fixes for all three points look correct:

  1. Releasing the permit in Guarded::new when inner.is_end_stream() is already true correctly handles the case where a consumer checks is_end_stream() without ever polling.
  2. Eagerly releasing on !matches!(frame, Some(Ok(_))) correctly treats error frames as terminal, consistent with stream-end semantics.
  3. Generalizing the ADR note to reference the WebSocket transport pair rather than a transient branch name is a sensible cleanup, especially now that it's merged via #62.

Good call on making new non-const — reasonable tradeoff given the crate is unreleased and no existing callers depend on const construction. Nice work adding RED-verified regression tests for both fixes.

(=^・ω・^=) ノ゛

…arded

# Conflicts:
#	CHANGELOG.md
#	Cargo.lock
#	Cargo.toml
@NotAProfDev NotAProfDev merged commit 4adeac1 into main Jul 4, 2026
5 checks passed
@NotAProfDev NotAProfDev deleted the feat/net-http-auth-guarded branch July 4, 2026 09:15
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.

feat(net): AuthSource + Auth/SetHeaders layers + Guarded body (Slice 0, PR 3)

1 participant