8000 feat(bep): parse request heads strictly and forward a canonical head and body by minimal-foundry[bot] · Pull Request #2362 · gominimal/minimal · GitHub
[go: up one dir, main page]

Skip to content

feat(bep): parse request heads strictly and forward a canonical head and body - #2362

Merged
norrietaylor merged 15 commits into
mainfrom
agent/2261-parse-requests-strictly-and-forward-a-canonical
Oct 20, 2026
Merged

norrietaylor merged 15 commits into
mainfrom
agent/2261-parse-requests-strictly-and-forward-a-canonical

Conversation

@minimal-foundry
@minimal-foundry minimal-foundry Bot commented Oct 19, 2026 •
Copy link
Copy Markdown
Contributor

T8 — Parse requests strictly and forward a canonical head over HTTP/1.1

slice: S0
kind: task
covers: BEP-074, BEP-076, BEP-077
size: L
est: 450 code, 360 tests
executor: agent
difficulty: needs care
model: strongest
diagnostics: a refused head is audited framing_ambiguous, and min box audit <box> shows the marker
observability: one debug log line per closed connection naming the framing rule that closed it
layers:
- http:
- crates/bep/src/http.rs (new): the strict HTTP/1.1 parser and canonical re-serializer of BEP-077, with BEP-074's close and h2c strip and BEP-076's Alt-Svc strip
- crates/bep/src/upstream.rs (new): upstream connections keyed per box, never shared across boxes
- crates/bep/src/lib.rs: module lines
test: cargo next 8000 est run -p bep h2_prior_knowledge_preface_is_closed
test: cargo nextest run -p bep h2c_upgrade_is_stripped
test: cargo nextest run -p bep alt_svc_is_stripped_from_responses
test: cargo nextest run -p bep prop_forwarded_head_is_canonical
test: cargo nextest run -p bep prop_ambiguous_framing_is_refused
test: cargo nextest run -p bep upstream_connections_never_shared_across_boxes
produces:
- crates/bep/src/http.rs
- crates/bep/src/upstream.rs
consumes: none
proof: Ambiguous framing or the h2 preface closes the connection, and every forwarded head is canonical.
depends on:
- blocked by #2258

Proof

In crates/bep, a client that opens a TCP connection with the HTTP/2 preface gets the connection closed and nothing reaches the upstream. A head with Content-Length plus Transfer-Encoding, duplicate Content-Length, an obs-fold or a transfer coding other than one final chunked is refused and records a framing_ambiguous audit event for the box. Every forwarded head is the canonical re-serialization of the parsed request (lowercase names, trimmed values, HTTP/1.1), and it parses back to the same request. h2c upgrades and Alt-Svc response headers are stripped. Idle upstream connections are held per box and authority and never go to another box.

Notes from the agent: Observability gap: the bep crate has no logging dependency, and Cargo.toml is not in this task's layers. So http.rs does not write the debug log line itself. CloseReason::rule() returns the name of the framing rule that closed the connection. The caller that owns the connection loop must log one debug line per closed connection with that name. Diagnostics gap: no audit log or min box audit exists in the tree yet. forward_request_head records an AuditEvent { box_id, marker: "framing_ambiguous", rule } through an AuditSink trait in http.rs. The task that adds the local audit log must implement AuditSink so that min box audit <box> shows the marker. Design choices: only HTTP/1.1 request lines are accepted (an HTTP/1.0 request is refused as malformed). A Content-Length value that is not a single decimal number counts as framing_ambiguous. Upgrade: h2c removes only the h2c token, so another upgrade (for example websocket) and its Connection token stay. Body relay (chunked and length-framed) is not part of this change. forward_request_head returns the bytes read after the head for the caller to relay. I ran the whole bep suite once at the end: 19 passed. I deleted a proptest-regressions file that the first failing run created, and did not commit it.

Tests

line exit tail
cargo nextest run -p bep h2_prior_knowledge_preface_is_closed 0 Applying loadouts: default Running task runtime-implement-test-0 in session task-runtime-implement-test-0-3200... Compiling bep v0.0.1 (/workbench/crates/bep) Finished 'test' profile [unoptimized] target(s) in 5.05s ──────────── Nextest run ID 1d4966c7-ffd0-4897-bda5-f3d37cb8689e with nextes
cargo nextest run -p bep h2c_upgrade_is_stripped 0 Applying loadouts: default Running task runtime-implement-test-1 in session task-runtime-implement-test-1-871e... Finished 'test' profile [unoptimized] target(s) in 0.35s ──────────── Nextest run ID 383021fc-bc52-453f-abb6-c0e0ce811030 with nextest profile: default Starting 1 test across 1
cargo nextest run -p bep alt_svc_is_stripped_from_responses 0 Applying loadouts: default Running task runtime-implement-test-2 in session task-runtime-implement-test-2-ba19... Finished 'test' profile [unoptimized] target(s) in 0.32s ──────────── Nextest run ID 9e195cdd-ffd2-4917-9fbe-000ec1b603da with nextest profile: default Starting 1 test across 1
cargo nextest run -p bep prop_forwarded_head_is_canonical 0 Applying loadouts: default Running task runtime-implement-test-3 in session task-runtime-implement-test-3-1f9d... Finished 'test' profile [unoptimized] target(s) in 0.31s ──────────── Nextest run ID 237f7c1b-dbf9-4470-8a9c-6d092c2a867d with nextest profile: default Starting 1 test across 1
cargo nextest run -p bep prop_ambiguous_framing_is_refused 0 Applying loadouts: default Running task runtime-implement-test-4 in session task-runtime-implement-test-4-5a3b... Finished 'test' profile [unoptimized] target(s) in 0.31s ──────────── Nextest run ID 95597d72-abc7-4c6b-80e1-edd73a9cdb73 with nextest profile: default Starting 1 test across 1
cargo nextest run -p bep upstream_connections_never_shared_across_boxes 0 Applying loadouts: default Running task runtime-implement-test-5 in session task-runtime-implement-test-5-e3a4... Finished 'test' profile [unoptimized] target(s) in 0.29s ──────────── Nextest run ID 7b03cba8-ffa0-41fc-abc3-de171154dfaa with nextest profile: default Starting 1 test across 1

Verify

min task run agent-verify passed ([tasks.verify]).

Closes #2261

Residuals


Generated by Claude Code

Add a strict HTTP/1.1 head parser that closes on the h2 prior-knowledge
preface and on ambiguous framing, records a framing_ambiguous audit
event, strips an h2c upgrade, and forwards the canonical
re-serialization of the parsed head (BEP-074, BEP-077). Response heads
lose every Alt-Svc header (BEP-076). Idle upstream connections are kept
per box and authority, so no two boxes share one.
@coderabbitai
coderabbitai Bot commented Oct 19, 2026 •
Copy link
Copy Markdown
Contributor

Important

Review skipped

Bot user detected.

To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration
  • Configuration used: Organization UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: ea66f019-2377-48b1-aa84-543d4f121c0b

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

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

@minimal-foundry
minimal-foundry Bot commented Oct 19, 2026 •
Copy link
Copy Markdown
Contributor Author

Reviewed: 1 finding, none blocking at 8b49a95a0694.

Address the comments on the diff.

Clobber: none: all three files are new or one-line additions.

@minimal-arch minimal-arch 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.

arch-review: PASS_WITH_CONDITIONS · 1 condition · the full ruling

The parser and the pool conform to §5.7. The h2 preface closes the connection, h2c is stripped, Alt-Svc is stripped, only canonical heads go upstream, and upstream connections are keyed per box. One condition is open. The PR body defers the production audit sink, the debug line for each closed connection, and the body relay, but it names no issue for them. Line 216 of §5.7 requires an audited close and no parser desync, so these three items need an owner. File or name the issues and list them in the PR body (C1).

Ruled at d24c6cd against gominimal/arch@3dab1097cda1 by claude-opus-5-5.

Comment thread crates/bep/src/http.rs Resolved
Comment thread crates/bep/src/http.rs Resolved
Comment thread crates/bep/src/http.rs Resolved
@minimal-foundry minimal-foundry Bot 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.

minimal-review — advisory review

Verdict COMMENT — 3 findings, 2 inline.

Panel: 3 of 3 personas ran (correctness, security, supply-chain).

Comment thread crates/bep/src/http.rs Resolved
Comment thread crates/bep/src/upstream.rs Resolved
@norrietaylor
Copy link
Copy Markdown
Member

/revise address ALL review concerns

@minimal-foundry
Copy link
Copy Markdown
Contributor Author

That /revise is held, not dropped. A coder is already running on #2261. Your note is kept and goes into the next round here, which starts when that coder finishes.

Nothing to type again: the command gets a rocket when its round starts, or a refusal that names the reason. No round was counted for it yet.

The request parser now closes on a head with more than one Host field.
The upstream pool keeps at most MAX_IDLE_PER_KEY idle connections per box
and authority, and removes a key when its idle list is empty.
@minimal-arch minimal-arch 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.

arch-review: PASS_WITH_CONDITIONS · 1 condition · the full ruling

The compare conforms to §5.7. The duplicate Host refusal is a fast close (line 218), and the idle cap keeps the per-box key (line 216). One gap stays open. Line 216 requires an audited close on ambiguous framing, but no AuditSink exists outside the tests, and no caller logs CloseReason::rule(). The PR body names this work but links no issue. File the issue and link it, as C1 says.

Ruled at 08ed473 against gominimal/arch@3dab1097cda1 by claude-opus-5-5.

Comment thread crates/bep/src/http.rs Resolved
@minimal-foundry
Copy link
Copy Markdown
Contributor Author

Round 1 of 10 on the runtime's review findings, pushed as 08ed473afbf7.

line exit tail
cargo nextest run -p bep h2_prior_knowledge_preface_is_closed 0 Applying loadouts: default Running task runtime-implement-test-0 in session task-runtime-implement-test-0-62ed... Compiling bep v0.0.1 (/workbench/crates/bep) Finished 'test' profile [unoptimized] target(s) in 4.82s ──────────── Nextest run ID 925a8b6e-1a2d-4846-8a61-3b9dc8b042dc with nextes
cargo nextest run -p bep h2c_upgrade_is_stripped 0 Applying loadouts: default Running task runtime-implement-test-1 in session task-runtime-implement-test-1-405e... Finished 'test' profile [unoptimized] target(s) in 0.34s ──────────── Nextest run ID d51163da-14db-4637-bce3-659aa2166bc5 with nextest profile: default Starting 1 test across 1
cargo nextest run -p bep alt_svc_is_stripped_from_responses 0 Applying loadouts: default Running task runtime-implement-test-2 in session task-runtime-implement-test-2-b78b... Finished 'test' profile [unoptimized] target(s) in 0.31s ──────────── Nextest run ID efd84cbe-9041-4433-9867-8734e78a5ab1 with nextest profile: default Starting 1 test across 1
cargo nextest run -p bep prop_forwarded_head_is_canonical 0 Applying loadouts: default Running task runtime-implement-test-3 in session task-runtime-implement-test-3-1618... Finished 'test' profile [unoptimized] target(s) in 0.30s ──────────── Nextest run ID 2c2dff8c-32a7-4af4-9787-4ff13c4d9273 with nextest profile: default Starting 1 test across 1
cargo nextest run -p bep prop_ambiguous_framing_is_refused 0 Applying loadouts: default Running task runtime-implement-test-4 in session task-runtime-implement-test-4-fc47... Finished 'test' profile [unoptimized] target(s) in 0.32s ──────────── Nextest run ID f0ca3a3f-2021-427a-9182-564efd30bc82 with nextest profile: default Starting 1 test across 1
cargo nextest run -p bep upstream_connections_never_shared_across_boxes 0 Applying loadouts: default Running task runtime-implement-test-5 in session task-runtime-implement-test-5-c6d8... Finished 'test' profile [unoptimized] target(s) in 0.35s ──────────── Nextest run ID 7a5642fb-f687-4568-be58-2a8b69283e45 with nextest profile: default Starting 1 test across 1

Verify min task run agent-verify passed.

Review threads the coder judged:

  • 👎 invalid — crates/bep/src/http.rs:239: No code change in this task can fix this. The production audit sink that feeds min box audit (BEP-039) is outside this task's layers. The runner writes the PR body, so the coder cannot add the issue link there. I listed this follow-up in the round notes so the runner can carry it.
  • 👎 invalid — crates/bep/src/http.rs:257: This task parses and forwards heads only. No body relay exists in this PR. Forwarded.request.body exposes the framing (Chunked or ContentLength) so the later relay can re-frame the body. That relay is a separate task, and I listed it in the round notes for the PR body.
  • 👎 invalid — crates/bep/src/http.rs:327: The bep crate has no logging dependency, and adding one changes Cargo.toml, which is outside this task's layers. CloseReason::rule() gives the rule name for the log line. The connection loop that writes the line is a later task, and I listed it in the round notes for the PR body.
  • 👍 valid — crates/bep/src/http.rs:290: parse_request now closes on more than one Host field (Malformed("duplicate_host")), with a test. A check of Host and the target against the approved authority needs that authority as an input. That check belongs to authority pinning (BEP-029/BEP-055), not this parser.
  • 👍 valid — crates/bep/src/upstream.rs: checkin now keeps at most MAX_IDLE_PER_KEY (8) idle connections per key and drops the rest. checkout removes a key when its list becomes empty. The test covers both.

A request head with the h2 preface or ambiguous framing closes the connection, and a framing refusal records a framing_ambiguous audit event. Every forwarded head is the canonical re-serialization of the parsed request. A head with two Host fields now also closes the connection, and each box keeps its own capped set of idle upstream connections.

Notes from the agent: Revise round 1. Two threads led to code changes: duplicate Host is refused as Malformed("duplicate_host"), and the upstream pool has a per-key idle cap (MAX_IDLE_PER_KEY = 8) and removes empty keys. Three threads ask for the PR body to name follow-up issues. I cannot edit the PR body, so I list the follow-ups here for the runner: (1) a production AuditSink that writes the framing_ambiguous event to the local audit log read by min box audit (BEP-039). (2) the body relay, which must re-frame a chunked body and not copy raw bytes. (3) the connection loop that drops the connection and writes the one debug line per close with CloseReason::rule(). The bep crate has no logging dependency, and Cargo.toml is outside this task's layers. So the debug line cannot land in this task. A check of Host against the approved authority needs the authority as an input, and it belongs to the authority-pinning work (BEP-029, BEP-055), not this parser task. just clippy-strict was not run.

@minimal-foundry minimal-foundry Bot 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.

minimal-review — advisory review

Verdict COMMENT — 4 findings, 4 inline.

Panel: 3 of 3 personas ran (correctness, security, supply-chain).

Comment thread crates/bep/src/http.rs Resolved
Comment thread crates/bep/src/http.rs Resolved
Comment thread crates/bep/src/http.rs Resolved
Comment thread crates/bep/src/upstream.rs Resolved
@minimal-foundry
Copy link
Copy Markdown
Contributor Author

Round 2 of 10 on a /revise note: the coder changed nothing. Revise round 2: no code change. The two foundry findings (duplicate Host, idle cap per key) were fixed in 08ed473 and that fix is still at the branch head. The four minimal-arch C1 threads ask for issue links in the PR body. A coder cannot write the PR body and has no gh credential, so the runner or a person must add these follow-ups to the PR body: (1) the production AuditSink that appends the framing_ambiguous record to the min/v1 audit log, so that min box audit <box> shows it (BEP-039). Today only the test Vec implements AuditSink. (2) The body relay, which must re-frame the body from Forwarded.request.body (Chunked or ContentLength) and never relay raw chunked bytes. (3) The connection loop that writes one debug log line per closed connection with CloseReason::rule(). The bep crate has no tracing dependency, and to add one changes crates/bep/Cargo.toml and Cargo.lock, which are outside this task's layers. So the task's diagnostics line (the audit marker i…[truncated]

Review threads the coder judged:

  • 👎 invalid — crates/bep/src/http.rs:239: No code change in crates/bep fixes this. The audit-log sink (BEP-039) is outside this task's layers, and the coder cannot edit the PR body. The follow-up is in the round notes for the runner to put in the PR body.
  • 👎 invalid — crates/bep/src/http.rs:257: This PR relays no body. It parses and forwards heads only, and Forwarded.request.body gives the framing so a later relay can re-frame the body. The coder cannot edit the PR body. The body-relay follow-up is in the round notes.
  • 👎 invalid — crates/bep/src/http.rs:327: The log line needs a tracing dependency in crates/bep/Cargo.toml and Cargo.lock, which are outside this task's layers. CloseReason::rule() gives the rule name. The connection-loop follow-up is in the round notes for the PR body.
  • 👎 invalid — crates/bep/src/http.rs:347: Filing an issue and editing the PR body are not in a coder's reach: no gh credential. The production audit sink (BEP-039) and the per-close debug line are outside this task's layers. Both follow-ups are in the round notes for the runner to link in the PR body.

1 outdated runtime thread(s) were not judged: PRRT_kwDOPdfcxM6uGSmt

@norrietaylor norrietaylor left a comment •
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Babysitter review of 08ed473 (T8: strict HTTP/1.1 head parser, canonical re-serialization, per-box upstream pool). Read as a COMMENT review so it starts no coder round.

What I checked. parse_request refuses the h2 preface on its first line, obs-fold, Content-Length with Transfer-Encoding, more than one Content-Length element (equal or not), a non-decimal or empty length, any transfer coding other than exactly one chunked, a bare CR or LF, whitespace before a colon, a second Host, and a head over 64 KiB. Nothing reaches the upstream on a refusal, and only a framing refusal records framing_ambiguous. strip_h2c removes the h2c token, HTTP2-Settings and their Connection tokens while keeping other upgrades and options. relay_response_head drops every Alt-Svc spelling. UpstreamPool keys idle connections by (box, lowercased host, port) and checkin can only return a connection under the key it was checked out with, so no path shares a connection across boxes. The two proptests pin the canonical round trip and the framing oracle. No blocking finding from me.

Findings, none blocking

  1. parse_request_line forwards the target as received, and the pool is keyed by host. A box can send an absolute-form target whose authority differs from Host (GET https://other.example/ HTTP/1.1 with Host: api.github.com). RFC 9112 §3.2.2 says a recipient uses the absolute-form authority over Host, so the proxy would pin the connection and the grant to one authority while the upstream routes by another. The connection still goes to the Host server, so the exposure is bounded to that server, but the check is cheap: refuse (Malformed("target_authority")) when an absolute-form target's authority is not Host, or rewrite it to origin-form before serialize. If the authority check belongs to the task that resolves the host set, say so in the module doc and leave it there.

  2. HTTP/1.0 request lines close as Malformed("http_version"). Git and curl send HTTP/1.1, so this is fine if intended by BEP-077, but the module doc lists the close reasons and does not say that HTTP/1.0 is one. One sentence there.

  3. A request with no Host parses as complete and is forwarded. The caller has to have an authority to check out an upstream, so it must check; the doc on forward_request_head should say the authority check is the caller's.

Residuals. I agree with minimal-arch's C1: the production AuditSink that appends to the min/v1 audit log, the body relay that re-frames from Forwarded.request.body, and the per-close debug line all belong in one filed issue linked from the PR body. The coder cannot file it or edit the body, so that is on the person running this epic, and the PR waits on it before merge.


Generated by Claude Code

Copy link
Copy Markdown
Member

/revise The epic owner decided (2026-10-19) to fix in this PR the two of minimal-arch's C1 residuals that fit, and to file the third. This round:

  1. Body relay. After forward_request_head returns, relay the request body to the upstream by re-framing it from Forwarded.request.body: for ContentLength(n) copy exactly n bytes (starting with Forwarded.rest); for Chunked parse the chunked framing from the client and write canonical chunks upstream; never relay raw chunked bytes. Pin it with a test for each framing and one for a client that closes mid-body.

  2. Per-close debug line. In the connection loop that owns the close, write one tracing::debug! line per closed connection naming CloseReason::rule() and the box id. The tracing dependency in crates/bep/Cargo.toml is accepted by the epic owner as a layer exception (the same line feat(bep): append one hash-chained audit record per proxy decision #2361 adds); say so in the commit body.

The production AuditSink that appends the framing_ambiguous record to the min/v1 audit log is filed as #2435 and waits on #2361; it is already listed under Residuals in the PR body, so do not implement it here. Then resolve the four arch C1 threads with a one-line pointer to the commit or to #2435.


Generated by Claude Code

drop_box now moves the box id's epoch on. A connection checked out
before drop_box carries the old epoch, and checkin drops it, so a later
box with the same id never gets a dead box's connection.
relay_request_body copies exactly Content-Length bytes, or parses the
box's chunked framing and writes canonical chunks upstream. Raw chunked
bytes never go upstream. forward_requests is the request half of a box
connection. It writes one tracing::debug! line per closed connection,
with the box id and CloseReason::rule().

The parser also refuses a request with no Host, an absolute-form target
that names another authority, and a Connection option that names
Content-Length, Transfer-Encoding or Host. Response heads now pass the
same framing checks as request heads.

The tracing dependency in crates/bep/Cargo.toml (and its Cargo.lock
line) is outside this task's layers. The epic owner accepted it as a
layer exception, the same line that #2361 adds.
@minimal-foundry
Copy link
Copy Markdown
Contributor Author

Round 3 of 10 on a /revise note, pushed as e94f4597faa3.

line exit tail
cargo nextest run -p bep h2_prior_knowledge_preface_is_closed 0 Applying loadouts: default Running task runtime-implement-test-0 in session task-runtime-implement-test-0-9e92... Compiling bep v0.0.1 (/workbench/crates/bep) Finished 'test' profile [unoptimized] target(s) in 4.72s ──────────── Nextest run ID 95da5c05-c57d-42c3-b29a-6a724435bafe with nextes
cargo nextest run -p bep h2c_upgrade_is_stripped 0 Applying loadouts: default Running task runtime-implement-test-1 in session task-runtime-implement-test-1-30cc... Finished 'test' profile [unoptimized] target(s) in 0.34s ──────────── Nextest run ID 373276cd-8e15-4cf0-88e2-a8e7d3d6b7a9 with nextest profile: default Starting 1 test across 1
cargo nextest run -p bep alt_svc_is_stripped_from_responses 0 Applying loadouts: default Running task runtime-implement-test-2 in session task-runtime-implement-test-2-34c6... Finished 'test' profile [unoptimized] target(s) in 0.30s ──────────── Nextest run ID fb9c298e-0dea-413f-a530-bc3522559b08 with nextest profile: default Starting 1 test across 1
cargo nextest run -p bep prop_forwarded_head_is_canonical 0 Applying loadouts: default Running task runtime-implement-test-3 in session task-runtime-implement-test-3-02f5... Finished 'test' profile [unoptimized] target(s) in 0.31s ──────────── Nextest run ID b082d85e-8518-471e-b75f-7990d54cf4c0 with nextest profile: default Starting 1 test across 1
cargo nextest run -p bep prop_ambiguous_framing_is_refused 0 Applying loadouts: default Running task runtime-implement-test-4 in session task-runtime-implement-test-4-432a... Finished 'test' profile [unoptimized] target(s) in 0.33s ──────────── Nextest run ID 75e144e6-e0ed-4780-9caa-5031bc869edd with nextest profile: default Starting 1 test across 1
cargo nextest run -p bep upstream_connections_never_shared_across_boxes 0 Applying loadouts: default Running task runtime-implement-test-5 in session task-runtime-implement-test-5-c66c... Finished 'test' profile [unoptimized] target(s) in 0.32s ──────────── Nextest run ID ae052af6-7ba7-4082-ac1a-a95089741915 with nextest profile: default Starting 1 test across 1

Verify min task run agent-verify passed.

Outside the task's layers::

  • crates/bep/Cargo.toml

Converted to a draft: something above needs a person before review.

Review threads the coder judged:

  • 👍 valid — crates/bep/src/http.rs:743: framing() now refuses a Connection option naming content-length or transfer-encoding as framing_ambiguous (connection_names_framing_header), and parse_request refuses one naming host; commit e94f459.
  • 👍 valid — crates/bep/src/http.rs:421: parse_response now runs framing(), so relay_response_head refuses CL+TE and duplicate Content-Length before any byte reaches the box; test ambiguous_response_framing_is_refused, commit e94f459.
  • 👍 valid — crates/bep/src/http.rs:305: parse_request now requires one Host (missing_host) and refuses an absolute-form target whose authority differs from Host, case and default port aside (target_host_mismatch); commit e94f459.
  • 👍 valid — crates/bep/src/upstream.rs:144: drop_box now moves the box id's epoch on; checkin drops a connection checked out under an older epoch, so a reused id never gets it; commit ebd39bd.
  • 👍 valid — crates/bep/src/http.rs:278: relay_request_body now copies exactly Content-Length bytes or parses the box's chunks and writes canonical chunks upstream; raw chunked bytes never go upstream; commit e94f459.
  • 👍 valid — crates/bep/src/http.rs:365: forward_requests is the connection loop and writes one tracing::debug! line per close with the box id and CloseReason::rule(); commit e94f459.

A head with ambiguous framing or the h2 preface closes the connection, and nothing reaches the upstream. Every forwarded head is the canonical serialization. A request body now goes upstream re-framed: exactly Content-Length bytes, or canonical chunks parsed from the box's chunked body. forward_requests writes one debug line per closed connection with the box id and the rule that closed it.

Notes from the agent: Body relay: relay_request_body and the connection loop forward_requests are new in http.rs. The tests content_length_body_is_relayed_exactly, chunked_body_is_reframed and client_closing_mid_body_closes cover them. A close between two requests is now CloseReason::ClientClosed, and a close inside a body is BodyEof. Chunk extensions and trailer fields are dropped, and are not forwarded. On a body error, part of the body can already be upstream, and the caller must drop that upstream connection. The doc comment says so. Outside the layers: crates/bep/Cargo.toml adds tracing.workspace, and Cargo.lock gets the matching line. The directive accepted this, and the commit body says so. Response heads now fail closed when Transfer-Encoding is anything other than one chunked. This is stricter than RFC 9112, which allows a close-delimited body there. The production AuditSink is #2435, so it is not here. I did not resolve any thread, because only the reviewer resolves threads. Threads PRRT_kwDOPdfcxM6uGRq6 and PRRT_kwDOPdfcxM6uGb8Z (audit sink) keep their verdict, and their pointer is #2435. I give a new verdict for the body-relay and debug-line C1 threads, because this round fixes them. just clippy-strict and just ci were not run. I ran clippy -p bep --all-targets, which was clean, and cargo fmt --check.

@minimal-foundry
minimal-foundry Bot marked this pull request as draft October 19, 2026 23:33
@minimal-arch minimal-arch 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.

arch-review: PASS_WITH_CONDITIONS · 1 condition · the full ruling

The compare conforms to §5.7. Request bodies are re-framed from the parsed head, so raw box bytes never go upstream, as the framing discipline at line 216 requires. A Connection option that names a framing header is now refused and audited as framing_ambiguous. The pool epoch keeps a connection from a dropped box away from a later box with the same id. C1 stays open as filed residual #2435: the production AuditSink that writes the marker to the min/v1 log. Update the PR body. Its notes about logging and body relay are stale at this head.

Ruled at e94f459 against gominimal/arch@3dab1097cda1 by claude-opus-5-5.

Comment thread crates/bep/src/http.rs Resolved
@minimal-foundry minimal-foundry Bot 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.

minimal-review — advisory review

Verdict COMMENT — 2 findings, 2 inline.

Panel: 3 of 3 personas ran (correctness, security, supply-chain).

Comment thread crates/bep/src/upstream.rs Resolved
Comment thread crates/bep/src/http.rs Resolved
drop_box now removes the box id's generation instead of moving an epoch
on, so the pool keeps no entry for a box that ended. Generations come
from one counter and are never reused, so a connection checked out
before the drop is still refused when it comes back.
parse_request closes with invalid_host on an empty Host, or one with
user info, a path, a comma, a space or a bad port, so the upstream and
the policy read one authority.
@norrietaylor
norrietaylor marked this pull request as ready for review October 19, 2026 23:39
@minimal-arch minimal-arch 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.

arch-review: PASS_WITH_CONDITIONS · 1 condition · the full ruling

The two new commits conform to §5.7 line 216. A box id dropped from the upstream pool no longer leaves an entry behind. A connection from before the drop never returns to a reused box id. The Host check refuses any value that is not a host and optional port, and it closes fast as line 218 requires. C1 is the audit sink that is still open. It is filed as #2435, and this change can merge with it open.

Ruled at 9283b40 against gominimal/arch@3dab1097cda1 by claude-opus-5-5.

Comment thread crates/bep/src/http.rs Resolved
@minimal-foundry
Copy link
Copy Markdown
Contributor Author

Round 4 of 10 on the runtime's review findings, pushed as 9283b40ef14c.

line exit tail
cargo nextest run -p bep h2_prior_knowledge_preface_is_closed 0 Applying loadouts: default Running task runtime-implement-test-0 in session task-runtime-implement-test-0-9296... Compiling bep v0.0.1 (/workbench/crates/bep) Finished 'test' profile [unoptimized] target(s) in 3.70s ──────────── Nextest run ID f5c5c8c6-0a23-494d-b996-81c9b5e5f118 with nextes
cargo nextest run -p bep h2c_upgrade_is_stripped 0 Applying loadouts: default Running task runtime-implement-test-1 in session task-runtime-implement-test-1-0183... Finished 'test' profile [unoptimized] target(s) in 0.24s ──────────── Nextest run ID 6652887d-84d0-477d-b783-cdd7a8e5450e with nextest profile: default Starting 1 test across 1
cargo nextest run -p bep alt_svc_is_stripped_from_responses 0 Applying loadouts: default Running task runtime-implement-test-2 in session task-runtime-implement-test-2-9eed... Finished 'test' profile [unoptimized] target(s) in 0.25s ──────────── Nextest run ID 044c3b94-dafb-4c9c-bf2c-451ee5556c07 with nextest profile: default Starting 1 test across 1
cargo nextest run -p bep prop_forwarded_head_is_canonical 0 Applying loadouts: default Running task runtime-implement-test-3 in session task-runtime-implement-test-3-f3ee... Finished 'test' profile [unoptimized] target(s) in 0.25s ──────────── Nextest run ID bc15e1f9-a460-468b-a7be-0991f4a284aa with nextest profile: default Starting 1 test across 1
cargo nextest run -p bep prop_ambiguous_framing_is_refused 0 Applying loadouts: default Running task runtime-implement-test-4 in session task-runtime-implement-test-4-8e71... Finished 'test' profile [unoptimized] target(s) in 0.21s ──────────── Nextest run ID aac1c403-486c-46f1-b5bd-1950566b248c with nextest profile: default Starting 1 test across 1
cargo nextest run -p bep upstream_connections_never_shared_across_boxes 0 Applying loadouts: default Running task runtime-implement-test-5 in session task-runtime-implement-test-5-0a70... Finished 'test' profile [unoptimized] target(s) in 0.23s ──────────── Nextest run ID 7278cd25-ce6b-4571-9b76-ba69f4f022c5 with nextest profile: default Starting 1 test across 1

Verify min task run agent-verify passed.

Outside the task's layers::

  • crates/bep/Cargo.toml

Converted to a draft: something above needs a person before review.

Review threads the coder judged:

  • 👎 invalid — crates/bep/src/http.rs:401: The production AuditSink that appends framing_ambiguous to the min/v1 log is outside this task's layers (http.rs, upstream.rs, lib.rs). As the finding says, residual T106 — Audit a refused ambiguous request head on the live path and show it in min box audit #2435 owns it. This PR records the event through AuditSink only.
  • 👍 valid — crates/bep/src/upstream.rs:183: drop_box now removes the box id's entry. Each checkout takes a generation from one counter that never repeats, so a stale connection is still refused and the map holds only box ids not dropped.
  • 👍 valid — crates/bep/src/http.rs:345: parse_request now closes with invalid_host unless Host is a name, IPv4 or bracketed IPv6 literal with an optional port up to 65535. Test host_must_be_a_host_and_port covers it.

A request head with ambiguous framing or the h2 prior-knowledge preface closes the connection, and every head the proxy forwards is the canonical re-serialization of the parsed request. This round, the parser also closes on a Host value that is not a host with an optional port, and the upstream pool keeps no entry for a box id after drop_box.

Notes from the agent: Round 4. Added the test host_must_be_a_host_and_port (exit 0) and changed two inputs in host_and_absolute_target_must_agree, because an empty or user-info Host now closes as invalid_host before the target check. The whole bep suite passed once at the end: 26 passed, 1 skipped. The production audit sink that writes framing_ambiguous to the min/v1 log, so that min box audit <box> shows the marker, is outside this task's layers and stays with residual #2435.

@minimal-foundry
minimal-foundry Bot marked this pull request as draft October 19, 2026 23:42
@minimal-arch minimal-arch 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.

arch-review: PASS · the full ruling

The compare adds one flush after the forwarded head, a doc comment on parse_response, and one test. The flush keeps the head out of a buffer while the box waits for 100 Continue. That agrees with the §5.7 determinism rule (line 218). The flush runs only after a successful parse, so the audited framing refusal of line 216 does not change. The PASS on 87972328 stands for the rest of the change.

Ruled at 774dfc9 against gominimal/arch@3dab1097cda1 by claude-opus-5-5.

@minimal-foundry
Copy link
Copy Markdown
Contributor Author

Round 10 of 10 on the runtime's review findings, pushed as 774dfc9f6753.

line exit tail
cargo nextest run -p bep h2_prior_knowledge_preface_is_closed 0 Applying loadouts: default Running task runtime-implement-test-0 in session task-runtime-implement-test-0-bda8... Compiling bep v0.0.1 (/workbench/crates/bep) Finished 'test' profile [unoptimized] target(s) in 5.24s ──────────── Nextest run ID 04a725b9-072d-4e8f-b77d-1b8f4f7c552c with nextes
cargo nextest run -p bep h2c_upgrade_is_stripped 0 Applying loadouts: default Running task runtime-implement-test-1 in session task-runtime-implement-test-1-f3ce... Finished 'test' profile [unoptimized] target(s) in 0.39s ──────────── Nextest run ID 4444628e-dc08-4c54-b940-93ac4481f42a with nextest profile: default Starting 1 test across 2
cargo nextest run -p bep alt_svc_is_stripped_from_responses 0 Applying loadouts: default Running task runtime-implement-test-2 in session task-runtime-implement-test-2-6b0f... Finished 'test' profile [unoptimized] target(s) in 0.34s ──────────── Nextest run ID 0fe5787b-a900-4478-9303-ac092805d0bc with nextest profile: default Starting 1 test across 2
cargo nextest run -p bep prop_forwarded_head_is_canonical 0 Applying loadouts: default Running task runtime-implement-test-3 in session task-runtime-implement-test-3-b080... Finished 'test' profile [unoptimized] target(s) in 0.32s ──────────── Nextest run ID d6ff1d90-76aa-4a85-adeb-d4a3f17495c0 with nextest profile: default Starting 1 test across 2
cargo nextest run -p bep prop_ambiguous_framing_is_refused 0 Applying loadouts: default Running task runtime-implement-test-4 in session task-runtime-implement-test-4-3d44... Finished 'test' profile [unoptimized] target(s) in 0.39s ──────────── Nextest run ID 664b0da9-c4a9-420a-8fe2-f82302da4f1f with nextest profile: default Starting 1 test across 2
cargo nextest run -p bep upstream_connections_never_shared_across_boxes 0 Applying loadouts: default Running task runtime-implement-test-5 in session task-runtime-implement-test-5-dd00... Finished 'test' profile [unoptimized] target(s) in 0.32s ──────────── Nextest run ID 18750e09-30ae-4643-970d-9627d19a8757 with nextest profile: default Starting 1 test across 2

Verify min task run agent-verify passed.

Review threads the coder judged:

  • 👍 valid — crates/bep/src/http.rs:440: forward_head now flushes the upstream right after the head write. The test expect_continue_head_is_flushed_before_the_body uses a BufWriter to prove the head is not left in the buffer.
  • 👍 valid — crates/bep/src/http.rs:394: The strict rules on responses are deliberate, so a hostile upstream cannot desync the box. The parse_response doc comment now documents the refusal of forms such as gzip, chunked and a repeated equal Content-Length. It also says the caller decides the body for HEAD, 1xx, 204 and 304 replies.

Ambiguous framing or the h2 preface closes the connection, and every forwarded head is canonical. The canonical head now also reaches the upstream at once, before the proxy reads the body.

Notes from the agent: Revise round 10. forward_head now flushes the upstream after it writes the head. The new test expect_continue_head_is_flushed_before_the_body proves this with a BufWriter. parse_response keeps the strict request framing rules on purpose, and its doc comment now states the refusal and its examples. cargo clippy -p bep --all-targets and cargo fmt --check are clean. just clippy-strict was not run.

@minimal-foundry minimal-foundry Bot 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.

minimal-review — advisory review

Verdict COMMENT — 1 finding, 1 inline.

Panel: 3 of 3 personas ran (correctness, security, supply-chain).

Comment thread crates/bep/src/http.rs Resolved
@minimal-foundry minimal-foundry Bot added the needs-human An agent handed off; a human must act, then clear this label. Set by defer and handoffs. label Oct 20, 2026
@minimal-foundry
Copy link
Copy Markdown
Contributor Author

The runtime will not revise this pull request again on its own: round 11 would exceed the cap of 10; 0 base merges were not counted (the last trigger was the runtime's review findings). A person decides now. Remove needs-human from this pull request to let /revise <note> or the automatic rounds resume, or finish #2261 by hand.

@norrietaylor norrietaylor left a comment
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Babysitter review of 774dfc9 (T8, rounds 3 to 9 on top of the 08ed473 head I reviewed earlier). Read as a COMMENT review so it starts no coder round.

What changed since my first review, and what I checked. The two non-blocking findings I raised on 08ed473 are closed: one Host is now required, it must be a plain host with an optional port, an absolute-form target's authority must agree with it (default port equals none, user info never matches), and a CONNECT target must be authority-form with an explicit port and agree with Host too. A Connection option naming Host, Content-Length or Transfer-Encoding, and an empty list element in a framing header, are refused before the head is forwarded. relay_request_body copies exactly Content-Length bytes or re-frames the box's chunks canonically with extensions and trailers dropped, chunk-size lines bounded whether or not their CRLF is already buffered, and the trailer section bounded by the head limit. forward_requests is the connection loop: it flushes the head before reading the body (so Expect: 100-continue can be answered), stops at a protocol switch without parsing the bytes after it, and writes one debug line per close with the rule. parse_response applies the same framing rules to the upstream on purpose. The pool now drops a connection checked out before drop_box ran for its box, and caps idle connections per box over all authorities. The quarantined round-5 commit is on the branch and main is merged in. No blocking finding from me.

Findings, none blocking

  1. read_head reports ClientClosed for an empty stream on both sides, so an upstream that closes between responses is logged as the client closing (the runtime's open LOW). A PeerClosed name or a side parameter fixes the log line; the behaviour is right.

  2. HTTP/1.0 request lines still close as Malformed("http_version") without a sentence in the module doc saying so (carried from my first review).

  3. PoolState::generations keeps an entry per box id from its first checkout until drop_box; a host that never calls drop_box for a box grows the map by one entry per box. tracked_boxes makes it visible, and the box lifecycle owns the call, so this is a doc point: say on checkout that drop_box is required at box end.

Residual. The production AuditSink stays #2435, as the epic owner decided.


Generated by Claude Code

@minimal-foundry
Copy link
Copy Markdown
Contributor Author

The runtime will not revise this pull request again on its own: round 11 would exceed the cap of 10; 0 base merges were not counted (the last trigger was a failed check suite). A person decides now. Remove needs-human from this pull request to let /revise <note> or the automatic rounds resume, or finish #2261 by hand.

@norrietaylor norrietaylor changed the title T8 — Parse requests strictly and forward a canonical head over HTTP/1.1 feat(bep): parse request heads strictly and forward a canonical head and body Oct 20, 2026
@norrietaylor norrietaylor removed the needs-human An agent handed off; a human must act, then clear this label. Set by defer and handoffs. label Oct 20, 2026
@norrietaylor
Copy link
Copy Markdown
Member

/revise resolve comments and conflicts

…ests-strictly-and-forward-a-canonical

# Conflicts:
#	crates/bep/src/lib.rs
@minimal-arch minimal-arch 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.

arch-review: PASS · the full ruling

This head merges main into the branch. The PR's own changes to http.rs, upstream.rs and lib.rs are identical to head 774dfc9f, which passed. The merge adds crates/bep/src/audit.rs from #2361, and it does not change the meaning of any PR line. BEP-074, BEP-076 and BEP-077 still conform to §5.7 lines 208, 216, 218 and 219. The connection-loop task will connect AuditSink to AuditLog. That work is not in the layers of this task.

Ruled at 0398445 against gominimal/arch@3dab1097cda1 by claude-opus-5-5.

@minimal-foundry
Copy link
Copy Markdown
Contributor Author

Round 1 of 10 on a /revise note, pushed as 0398445aeab9.

Resolved conflicts in: crates/bep/src/lib.rs

line exit tail
cargo nextest run -p bep h2_prior_knowledge_preface_is_closed 0 Applying loadouts: default Running task runtime-implement-test-0 in session task-runtime-implement-test-0-039b... Compiling bep v0.0.1 (/workbench/crates/bep) Finished 'test' profile [unoptimized] target(s) in 4.54s ──────────── Nextest run ID c3ffa068-6521-41a7-931c-af2a51a052bb with nextes
cargo nextest run -p bep h2c_upgrade_is_stripped 0 Applying loadouts: default Running task runtime-implement-test-1 in session task-runtime-implement-test-1-3e31... Finished 'test' profile [unoptimized] target(s) in 0.23s ──────────── Nextest run ID 38563b5e-0b98-47c3-8fd4-6d5a332bbc17 with nextest profile: default Starting 1 test across 2
cargo nextest run -p bep alt_svc_is_stripped_from_responses 0 Applying loadouts: default Running task runtime-implement-test-2 in session task-runtime-implement-test-2-4f9c... Finished 'test' profile [unoptimized] target(s) in 0.25s ──────────── Nextest run ID 8ae81bc9-6ff8-49fb-9381-26e1f6328f92 with nextest profile: default Starting 1 test across 2
cargo nextest run -p bep prop_forwarded_head_is_canonical 0 Applying loadouts: default Running task runtime-implement-test-3 in session task-runtime-implement-test-3-70cf... Finished 'test' profile [unoptimized] target(s) in 0.25s ──────────── Nextest run ID 5c731095-d1d7-4dea-a96a-f9c575cc7b0d with nextest profile: default Starting 1 test across 2
cargo nextest run -p bep prop_ambiguous_framing_is_refused 0 Applying loadouts: default Running task runtime-implement-test-4 in session task-runtime-implement-test-4-9d6e... Finished 'test' profile [unoptimized] target(s) in 0.27s ──────────── Nextest run ID 60135936-7a73-48e0-a0a7-60c1da812a05 with nextest profile: default Starting 1 test across 2
cargo nextest run -p bep upstream_connections_never_shared_across_boxes 0 Applying loadouts: default Running task runtime-implement-test-5 in session task-runtime-implement-test-5-b330... Finished 'test' profile [unoptimized] target(s) in 0.26s ──────────── Nextest run ID f765f103-f597-4e5a-b847-24cc7e732155 with nextest profile: default Starting 1 test across 2

Verify min task run agent-verify passed.

Ambiguous framing or the HTTP/2 prior-knowledge preface closes the connection, and every forwarded head is a canonical re-serialization. This stays true after the merge of origin/main.

Notes from the agent: This round resolved the merge conflict in crates/bep/src/lib.rs. The fix keeps both module lines: pub mod audit; from origin/main and pub mod http; from this branch. The merge commit also carries the other origin/main changes in Cargo.lock, boxes, minimal, minimald, minvmd, mfile and orchestrator. Those files come from the base branch and are not edits of this task. The directive listed no review threads, so threads is empty. http.rs still records the framing_ambiguous marker through its own AuditSink trait. It does not yet write into the hash-chained log that origin/main added in crates/bep/src/audit.rs. A later task can connect the two.

@norrietaylor norrietaylor left a comment
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Own review carried to 0398445. This head is the runtime's round 1 on the person's /revise resolve comments and conflicts: a merge of main (767c59f, T13) into the branch, resolving crates/bep/src/lib.rs. compare 774dfc9f...0398445a lists exactly the 17 files that main changed between f8431d0 and 767c59f, and the branch's own diff against main is the same 3 files (http.rs, upstream.rs, lib.rs) as before. The round-10 review on 774dfc9 and its thread re-verifications stand. minimal-arch ruled PASS on this head. All 23 threads stay resolved.


Generated by Claude Code

@norrietaylor
Copy link
Copy Markdown
Member

/review

Copy link
Copy Markdown
Member

/revise Base merge only, per the epic owner's order (first green first, base rounds after each landing). T3 merged as 788b6ec (crates/bep/src/ca.rs, crates/bep/Cargo.toml, crates/bep/src/lib.rs, Cargo.lock), and this branch is now dirty against main. Do this, in order:

  1. Merge origin/main (788b6ec) into the branch. The conflicts are crates/bep/src/lib.rs (keep every pub mod from both sides in alphabetical order: audit, ca, http, keys, module, publish, seal, signin, upstream, plus whatever else each side declares) and Cargo.lock (regenerate with cargo, never by hand). crates/bep/Cargo.toml takes every dependency from both sides, each once.
  2. Run just fix and just test on the merged tree and fix only what the merge breaks. Change nothing in http.rs or upstream.rs: the branch is reviewed and arch PASS at 0398445.
  3. Reply on no threads.

Generated by Claude Code

…ests-strictly-and-forward-a-canonical

# Conflicts:
#	crates/bep/src/lib.rs
@minimal-arch minimal-arch 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.

arch-review: PASS · the full ruling

This head is a clean merge of main. The merge does not change http.rs or upstream.rs. lib.rs keeps the module lines from both sides. The PASS on 0398445 stands for BEP-074, BEP-076 and BEP-077 under §5.7. The production AuditSink stays the filed residual #2435.

Ruled at a6c15c9 against gominimal/arch@3dab1097cda1 by claude-opus-5-5.

@minimal-foundry
Copy link
Copy Markdown
Contributor Author

Round 2 of 10 on a /revise note, pushed as a6c15c9df4a0.

Resolved conflicts in: crates/bep/src/lib.rs

line exit tail
cargo nextest run -p bep h2_prior_knowledge_preface_is_closed 0 Applying loadouts: default Running task runtime-implement-test-0 in session task-runtime-implement-test-0-e047... Compiling bep v0.0.1 (/workbench/crates/bep) Finished 'test' profile [unoptimized] target(s) in 13.60s ──────────── Nextest run ID fc4030f6-d46b-4165-aa05-69269d26f6ae with nexte
cargo nextest run -p bep h2c_upgrade_is_stripped 0 Applying loadouts: default Running task runtime-implement-test-1 in session task-runtime-implement-test-1-0df6... Finished 'test' profile [unoptimized] target(s) in 0.54s ──────────── Nextest run ID fb4c9517-0c0c-4526-98ff-6ea34355d0b7 with nextest profile: default Starting 1 test across 2
cargo nextest run -p bep alt_svc_is_stripped_from_responses 0 Applying loadouts: default Running task runtime-implement-test-2 in session task-runtime-implement-test-2-d5b2... Finished 'test' profile [unoptimized] target(s) in 0.50s ──────────── Nextest run ID 7dd5f566-f811-4a81-872e-41717afe9434 with nextest profile: default Starting 1 test across 2
cargo nextest run -p bep prop_forwarded_head_is_canonical 0 Applying loadouts: default Running task runtime-implement-test-3 in session task-runtime-implement-test-3-a3f5... Finished 'test' profile [unoptimized] target(s) in 0.70s ──────────── Nextest run ID 32ca849f-7b10-44c7-b21a-ad60d9270e75 with nextest profile: default Starting 1 test across 2
cargo nextest run -p bep prop_ambiguous_framing_is_refused 0 Applying loadouts: default Running task runtime-implement-test-4 in session task-runtime-implement-test-4-1a2f... Finished 'test' profile [unoptimized] target(s) in 0.55s ──────────── Nextest run ID 5cbf073e-3354-46ae-81ac-4c53e36ea28c with nextest profile: default Starting 1 test across 2
cargo nextest run -p bep upstream_connections_never_shared_across_boxes 0 Applying loadouts: default Running task runtime-implement-test-5 in session task-runtime-implement-test-5-4771... Finished 'test' profile [unoptimized] target(s) in 0.56s ──────────── Nextest run ID ba2a0cea-5fb3-4ecc-a1a8-faddd5d42bf2 with nextest profile: default Starting 1 test across 2

Verify min task run agent-verify passed.

On the merged branch, a request with ambiguous framing or the h2 preface closes the connection, and every forwarded head is canonical. The bep crate now also carries the ca module from main.

Notes from the agent: Base merge only. Resolved the conflict in crates/bep/src/lib.rs to keep both sides: audit, ca, http, keys, module, publish, seal, upstream. Neither side declares a signin module, so I added none. Git merged crates/bep/Cargo.toml and Cargo.lock without conflict. Every dependency appears once, and cargo clippy -p bep --all-targets left Cargo.lock unchanged and reported no warnings. http.rs and upstream.rs are unchanged. I did not run the workspace-wide just fix or just test. In their place I ran cargo clippy -p bep --all-targets, cargo fmt -p bep --check and the full bep nextest suite, and all of them passed. Outside bep, the merge took the main side without conflict.

Copy link
Copy Markdown
Member

/revise Base merge only, per the epic owner's order (first green first, base rounds after each landing). T4 merged as 554eff3 and T12 as 5919d28 after this branch's last base merge, and the branch is dirty against main again. Do this, in order:

  1. Merge origin/main (5919d28) into the branch. Resolve crates/bep/src/lib.rs keeping every pub mod from both sides in alphabetical order, each once; take every dependency from both sides in crates/bep/Cargo.toml and crates/minimal/Cargo.toml, each once; regenerate Cargo.lock with cargo, never by hand.
  2. Run just fix and just test on the merged tree and fix only what the merge breaks. Change nothing in http.rs or upstream.rs: the branch is reviewed and arch PASS at a6c15c9.
  3. Reply on no threads.

Generated by Claude Code

@minimal-foundry
Copy link
Copy Markdown
Contributor Author

Round 3 of 10 on a /revise note, pushed as 8b49a95a0694.

Resolved conflicts in: crates/bep/src/lib.rs

10BC8
line exit tail
cargo nextest run -p bep h2_prior_knowledge_preface_is_closed 0 Applying loadouts: default Running task runtime-implement-test-0 in session task-runtime-implement-test-0-e108... Compiling bep v0.0.1 (/workbench/crates/bep) Finished 'test' profile [unoptimized] target(s) in 6.82s ──────────── Nextest run ID ad6dd33a-2d5a-4ade-8651-cd173eb72ac7 with nextes
cargo nextest run -p bep h2c_upgrade_is_stripped 0 Applying loadouts: default Running task runtime-implement-test-1 in session task-runtime-implement-test-1-837e... Finished 'test' profile [unoptimized] target(s) in 0.30s ──────────── Nextest run ID 9a22e0e1-7087-4a0f-8c37-e50fc1698109 with nextest profile: default Starting 1 test across 2
cargo nextest run -p bep alt_svc_is_stripped_from_responses0 Applying loadouts: default Running task runtime-implement-test-2 in session task-runtime-implement-test-2-2113... Finished 'test' profile [unoptimized] target(s) in 0.31s ──────────── Nextest run ID 8ddfac8d-36e3-41f9-b4d6-97dfb3fe0d92 with nextest profile: default Starting 1 test across 2
cargo nextest run -p bep prop_forwarded_head_is_canonical 0 Applying loadouts: default Running task runtime-implement-test-3 in session task-runtime-implement-test-3-fb6a... Finished 'test' profile [unoptimized] target(s) in 0.31s ──────────── Nextest run ID 07a6f238-653b-49f8-ae17-ee210e362ce2 with nextest profile: default Starting 1 test across 2
cargo nextest run -p bep prop_ambiguous_framing_is_refused 0 Applying loadouts: default Running task runtime-implement-test-4 in session task-runtime-implement-test-4-b16d... Finished 'test' profile [unoptimized] target(s) in 0.32s ──────────── Nextest run ID 01cbc508-bb7b-488b-b285-37b18bb3e786 with nextest profile: default Starting 1 test across 2
cargo nextest run -p bep upstream_connections_never_shared_across_boxes 0 Applying loadouts: default Running task runtime-implement-test-5 in session task-runtime-implement-test-5-d947... Finished 'test' profile [unoptimized] target(s) in 0.30s ──────────── Nextest run ID b248a9e9-71f3-492c-8fc4-107c537f488b with nextest profile: default Starting 1 test across 2

Verify min task run agent-verify passed.

On the merged branch, a request that opens with the HTTP/2 preface or carries ambiguous framing closes the connection. Every forwarded head is the canonical re-serialization. The bep tests for this pass after the merge of origin/main 5919d28.

Notes from the agent: Base merge only. I merged origin/main (5919d28) in commit 8b49a95. The only conflict was crates/bep/src/lib.rs. It now lists every pub mod from both sides once, in alphabetical order (signin, then upstream). crates/bep/Cargo.toml and crates/minimal/Cargo.toml merged cleanly and are identical to main. Cargo.lock came from the merge. Cargo resolved it with no further change (cargo metadata left no diff, and cargo tree --locked passes). http.rs and upstream.rs are unchanged. Checks run: cargo clippy -p bep --all-targets (clean), cargo fmt -p bep --check (clean), the six test lines, and one full cargo nextest run -p bep (80 passed, 1 skipped). I did not run just fix or just test. On Linux both run across the whole workspace, and my instructions do not let me run workspace-wide commands that no test: line names. The merge changed only bep's lib.rs, so the bep checks cover what the merge can break. No threads were answered, per the directive.

@minimal-arch minimal-arch 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.

arch-review: PASS · the full ruling

This head merges main (#2359, #2364) into the branch. The merge does not change http.rs or upstream.rs. The PASS on a6c15c9d stands for BEP-074, BEP-076 and BEP-077 against §5.7 lines 208, 216, 218 and 219. The production AuditSink stays open as residual #2435.

Ruled at 8b49a95 against gominimal/arch@3dab1097cda1 by claude-opus-5-5.

@norrietaylor norrietaylor left a comment
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Own review carried to 8b49a95. This head is round 3 on the base-merge directive: a merge of main (5919d28, T4 and T12) into the branch, resolving crates/bep/src/lib.rs and the lockfile. The branch's own diff against main is the same 3 files (http.rs, upstream.rs, lib.rs) as at 774dfc9, where it was reviewed, and at a6c15c9. minimal-arch ruled PASS on this head. All 23 threads stay resolved.


Generated by Claude Code

@norrietaylor norrietaylor left a comment
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Babysit gate for T8 #2362 passed on 8b49a95: every lane green, arch PASS, review threads resolved, rows carried, no needs-human. Squash subject checked against commitlint. Approving and enqueueing under the epic owner's merge-on-green delegation.


Generated by Claude Code

@norrietaylor
norrietaylor added this pull request to the merge queue Oct 20, 2026
Merged via the queue into main with commit 35529c8 Oct 20, 2026
37 checks passed
@norrietaylor
norrietaylor deleted the agent/2261-parse-requests-strictly-and-forward-a-canonical branch October 20, 2026 16:35
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.

T8 — Parse requests strictly and forward a canonical head over HTTP/1.1

1 participant

0