Review M2a: accept with two follow-up tasks; record models and lessons

The branch passes every check, including make verify-device on
straylight and repeated timing runs under load. Reading and probing
found that inferproxy does not pass an upstream close on to a client
that is still sending, and that the chunked body reader delivers a
stream only when the caller's buffer fills or the stream ends. Both
were also gaps in the tasks and tests, so tasks 14 and 15 carry the
fixes with new tests checked against the reference.

The Model column is corrected: tasks 04 to 06 and 08 to 13 were Ornith.
Lessons gain four implementer tips and five task-writing tips; three
rules are promoted to AGENTS.md.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
2026-09-18 15:43:06 -07:00
co-authored by Claude Fable 5.1
parent ad7bce88a4
commit 58c7738721
8 changed files with 283 additions and 18 deletions
+52
View File
@@ -0,0 +1,52 @@
# M2a task 14: pass the upstream's close on to the client (review follow-up)
**Branch:** `m2a` (run `git switch m2a`; `git status --short` must be empty, otherwise stop)
**Commit subject:** `Close the client side when the upstream closes, in inferproxy`
## Goal
The M2a review found that `inferproxy` only closes towards the client after the client has stopped
sending. `loopd`'s HTTP client never stops sending on its own: it keeps its side open until it
drops the connection. So when `llama-server` closes, or dies in the middle of an answer, the client
is not told; it sits until its own liveness timeout and reports `Stalled` instead of
`StreamClosedEarly`, thirty seconds late. A body that runs to the close would hang the same way.
Task 02, rule 3, said: "When the server stops sending, the exchange is over: close both." The
current code waits for both copy threads to finish instead. This was partly the plan's fault: every
test client in `forward.rs` half-closed after sending, so the gap could not show. The new test
sends without half-closing.
## Files
- Copy (replacing the old one): `crates/inferproxy/tests/forward.rs`
- Modify: `crates/inferproxy/src/lib.rs`, `docs/implementer-log.md`
## Required behaviour
- When the server-to-client copy ends, for any reason, shut down the client connection (both
directions) and the server connection, so that the client's read returns 0 at once and the
client-to-server copy thread ends. Do this **after** releasing the open-connection place, as
rule 4 of task 02 requires.
- Everything else from task 02 still holds, including the half-close when the client stops
sending first.
## Steps
- [ ] **1. Copy.** `git switch m2a`, then
`cp docs/plans/M2a/files/crates/inferproxy/tests/forward.rs crates/inferproxy/tests/`
- [ ] **2. See it fail.** `cargo test -p inferproxy --test forward`. Expected: 1 of 7 fails,
`the_upstreams_close_reaches_a_client_that_is_still_sending`, with a timeout.
- [ ] **3. Fix `lib.rs`.** Run `cargo fmt --all`.
- [ ] **4. See it pass.** `cargo test -p inferproxy --test forward`, ten times in a row. Expected:
`7 passed` every time.
- [ ] **5. Run the gate.** `make gate`. Expected last line: `gate: ok`.
- [ ] **6. Log and commit.** `git add crates/inferproxy docs/implementer-log.md && git commit`
## Done when
- `cargo test -p inferproxy --test forward` reports 7 passed, ten runs in a row; `make gate` prints
`gate: ok`; `cmp crates/inferproxy/tests/forward.rs docs/plans/M2a/files/crates/inferproxy/tests/forward.rs` prints nothing.
## Stop and report if
- Passing the new test breaks `forwards_both_ways_and_passes_the_half_close_on`.