All ten tasks pass the checklist, the gate, the audit and the device checks, including a four-turn conversation with a loopd restart and no cache loss. Reading and probing found four low defects: the busy guard is released before the final frame on the main path but not on the three error paths, its Drop skips a poisoned lock, an unreadable core.md is treated as missing, and bxctl's interactive loop exits on a failed turn. Task 11 carries the fixes with two new tests, checked against a fixed copy of the branch. The Model column is filled in (all Ornith) and one malformed row is repaired. Two rules are promoted to AGENTS.md. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
3.7 KiB
3.7 KiB
M2b task 11: four small fixes (review follow-up)
Branch: m2b (run git switch m2b; git status --short must be empty, otherwise stop)
Commit subject: Fix four review findings: busy release, poison recovery, core.md errors, chat loop
Goal
The M2b review found four small defects. Two have a test to copy in; two are rules to apply. None changes an interface.
Files
- Copy (replacing the old ones):
crates/loopd/tests/baseline.rs,crates/bxctl/tests/chat.rs - Modify:
crates/loopd/src/channel.rs,crates/loopd/src/baseline.rs,crates/bxctl/src/chat.rs,crates/bxctl/src/main.rs,docs/implementer-log.md
The four fixes
channel.rs: release the session before every final frame. Task 07 said to drop the busy guard beforeturn_doneor the turn's error, and that is done. But the error frames for a session that cannot be opened or created (no_such_session,session_exists,internal) are still sent while the session is marked busy.bxctl chat --session <new id>readsno_such_sessionand at once sends the same turn again withresume: false, so it can be answeredsession_busyby a server thread that has not returned yet. Drop the guard before each of those three writes. The rule: the last frame of a connection is never sent while the session is busy.channel.rs: a poisoned lock must not leave a session busy forever. The guard'sDropdoesif let Ok(mut busy) = self.ctx.busy.lock(), which skips the removal when the lock is poisoned. Recover it withunwrap_or_else(|p| p.into_inner()), ashandlealready does.baseline.rs: amemory/core.mdthat exists but cannot be read is an error. Todayif let Ok(text) = read_to_string(..)treats an unreadable file like a missing one, so a session starts without the memory the owner curated, and nothing says so. If the file exists, a read failure isBaselineError::Read(path, e). A missing file is still fine.bxctl: the interactive loop goes on after a failed turn, andnew_session_idhas noexpect. The spec says "A failed turn printsbxctl: <error>and the loop goes on"; today it exits 1. Report the error and continue; the session exists, so the next line resumes it. AndAGENTS.mdforbidsexpectin library code: replace the two innew_session_idwithunwrap_or_default()for the clock andunwrap_or_elsewith a fixed valid id for theSessionId(it cannot fail, but the type must not be forced).
Steps
- 1. Copy.
git switch m2b
cp docs/plans/M2b/files/crates/loopd/tests/baseline.rs crates/loopd/tests/
cp docs/plans/M2b/files/crates/bxctl/tests/chat.rs crates/bxctl/tests/
- 2. See them fail.
cargo test -p loopd --test baseline: 1 of 7 fails,an_unreadable_core_memory_file_is_an_error.cargo test -p bxctl --test chat: 1 of 12 fails,interactive_mode_survives_a_failed_turn. - 3. Make the four fixes. Run
cargo fmt --all. - 4. See them pass.
cargo test -p loopd --test baseline --test channel -p bxctl. Expected: 7, 6 and 12 passed. Run thechanneltests ten times in a row. - 5. Run the gate.
make gate. Expected last line:gate: ok. - 6. Log and commit.
git add crates/loopd crates/bxctl docs/implementer-log.md
git commit
Done when
make gateprintsgate: okwith 219 tests.grep -n "expect(" crates/bxctl/src/chat.rsprints nothing.grep -c "drop(held)" crates/loopd/src/channel.rsprints 4 (or the guard is scoped so that every final frame is written after it is gone).
Stop and report if
interactive_mode_survives_a_failed_turncannot pass without changing what--saydoes.