Conversation
`run_lifecycle.rs` owns the four edges of a run — open it, fail it, cancel it, complete it — plus the two tool-call edges, shared by the API engine and all three CLI drivers. None of that wiring had a test. The pure helpers did (`final_status`, `completion_event`, `tool_call_update`), but what those helpers feed — which row each edge writes, which event follows it, and in what order — did not, and #180's review recorded three surviving mutations for exactly this reason. The obstacle was one field. Every function here takes `&AssistantDeps`, whose `app` is a `tauri::AppHandle<Wry>`; that cannot be constructed outside a running app, and `tauri::test::mock_app()` does not help because it hands back an `AppHandle<MockRuntime>`, a different type. So the module was unreachable from a unit test even though its database half has had an in-memory harness all along. `RunLifecycleHost` is the seam: a database to record the change in, and a channel to announce it on. `AssistantDeps` implements it, so **no call site changes** — the six functions take `&impl RunLifecycleHost` instead of `&AssistantDeps` and nothing else moves. `announce` returns `()` rather than `Result` because all eight emit sites already discarded the error on purpose: a frontend that is not listening must not fail a run that has already reached its terminal state. It takes `run_id: &str` rather than `Option<&str>` because all six callers passed `Some`. The tests run against the production schema — a tempdir database built by `db::init_workspace_db`, i.e. the embedded workspace migrations — rather than a second hand-written copy of the DDL that can drift from them. Fourteen tests, 1038 -> 1052. Fifteen mutations were applied and each was killed by a named test. On the opening edge: deleting or inverting the connection-mismatch guard, opening a run as `Running` instead of `Queued`, swapping the connection's protocol and model onto the row, and dropping the run id from the not-found error. On the terminal edges: reordering `fail_run` so the run is announced before its tool calls are closed, `cancel_run` writing the wrong reason or the wrong status, and completion dropping the notices it was handed. On the tool-call edges: recording a call as pending, never announcing the tool-role message, dropping the result part's timestamps, swapping the two `MissingToolCall` policies in either direction, and tagging an announcement with the wrong run id. Three further mutations (`completion_event` always announcing success, a failed call storing its payload as a result, `final_status` ignoring notices) are also covered end to end, but they already died against the pure tests on `main`, so they are not counted above. `a_clean_run_completes_without_warnings` is the one test with no mutation of its own: it overlaps the existing pure test and adds only the round trip through the real schema. `AssistantDeps::announce` is now the only line in the module no test can reach — concentrating the untestable surface into one three-line impl is the best outcome available, not zero. Implements R3.11's enabling half. The remaining half — a stub `ProviderAdapter` to drive `engine::run_session_turn`'s stream loop — is still open and is a much larger change. Review: reviews/pre-pr-review-2026-09-11-r3.11.md (shape *ship*, correctness *ship with changes*). Its must-fix was that `resolve_run_id`, one of the four edges named above, had no coverage at all — four of the fifteen mutations come straight from that finding.
Pre-PR review —
|
| Claimed mutation | Already killed on main by |
|---|---|
completion_event always announcing success |
a_failed_row_is_announced_as_a_failure |
| storing a failed call's payload as its result | a_failed_call_stores_no_result_even_though_it_has_a_payload |
final_status ignoring notices |
a_single_notice_is_enough_to_complete_with_warnings |
The eight genuinely new kills are still a strong result. Say eight, and let the other three be
listed as "also re-covered end-to-end".
On the two tests called over-correction guards: the manager undersells one of them.
a_terminal_edge_leaves_already_finished_tool_calls_alone (run_lifecycle_tests.rs:~215) is not a
guard — it is the only test in the repo that pins fail_running_tool_calls_for_run's
WHERE run_id = ? AND status IN ('"pending"','"running"') filter (repository.rs:1473). Deleting
that status filter is an entirely plausible mutation whose effect is severe (a run's error
overwrites a completed call's error, and the UI gets a spurious ToolCallFailed for work that
succeeded), and this test kills it. Promote it in the message.
a_clean_run_completes_without_warnings is a fair guard call — it overlaps the pre-existing
a_run_without_notices_completes_cleanly and only adds the round trip through the real schema.
Keep it, it costs nothing.
Is any other test a guard dressed up as a proof? No. I checked
the_same_missing_call_fails_the_turn_on_the_api_path (run_lifecycle_tests.rs:447) for a vacuous
is_err() specifically: the error can only originate from repository::update_tool_call, because
the function returns at line 313 before reaching create_message, and the companion assertion
host.event_names().is_empty() rules out the "errored after announcing" shape. It would be
marginally better for it to assert the error string mentions the tool call id, but it is not
vacuous.
What I did not check
- Anything requiring compilation: no
cargo check,clippy,fmtor test run. I did not verify
the manager's 1048/1038 counts, the clean gates, or thatbindings.tsis unchanged. - The eleven claimed mutations were judged from source, not applied and re-run.
- Behaviour of the six functions under
engine.rs/local_agent.rs's real call graph — read only
their call sites to confirm the signature change is source-compatible.
The one cheap experiment before merge
Apply exactly one mutation and run cargo test --lib run_lifecycle: delete the connection-mismatch
guard at run_lifecycle.rs:78-80. If the suite stays green — and I am confident it will — finding 1
is confirmed in under a minute and the two tests it asks for are ~40 lines against a harness that
already exists.
Review — PR #199
|
| # | Finding (against 0f3ae5e) |
Applied at 539fafb? |
Evidence |
|---|---|---|---|
| 1 (must-fix) | resolve_run_id had zero coverage |
yes | 4 new tests: run_lifecycle_tests.rs:546 (opens Queued, asserts connection_id/protocol_id/model_id separately), :575 (reuse), :596 (mismatch rejected, asserts the error carries the run id), :622 (missing run → Persistence naming the id) |
| 2 (should-fix) | cancel test's name asserted a non-existent ToolCallStatus::Cancelled |
yes | renamed cancelling_a_run_closes_its_open_tool_calls_with_the_cancellation_reason (:222); assert!(matches!(call.status, ToolCallStatus::Failed)) at :235; comment at :233-234 explains the absent variant |
| 3 (should-fix) | TestHost::announce discarded run_id |
yes | events: Mutex<Vec<(String, AssistantUiEvent)>> (:32), stored at :43-48, event_run_ids() at :94-101, asserted at :210. But see §7.4 — it is asserted in exactly one test. |
| 4 (should-fix) | started_at/completed_at skipped by .. |
yes | fields named in the pattern at :398-399 and asserted at :406-408 (*started_at == Some(call.started_at), *completed_at == call.completed_at, call.completed_at.is_some()) |
| 5 (nit) | a_clean_run_completes_without_warnings asserted no event sequence |
yes | assert_eq!(host.event_names(), vec!["RunCompleted"]); at :314 |
| 6 (nit) | the two MissingToolCall tests differed in two variables |
yes | both now pass metadata_source: None — :468 and :496. The policy is the only variable. |
Finding 7 (_ => "other") was explicitly "leave it" and is left (:79). Finding 8 (harness duplication)
was explicitly "not this PR" and is not done.
3. "No call site changes" — confirmed, structurally
The diff touches three files, and engine.rs / local_agent.rs are not among them. So the claim is
true by construction, not by inspection. The callers that must therefore still resolve against
&impl RunLifecycleHost:
| Function | Call sites |
|---|---|
resolve_run_id |
engine.rs:111, local_agent.rs:261 |
fail_run |
engine.rs:351, :379, :551, :567; local_agent.rs:284, :397, :581 |
cancel_run |
engine.rs:123, :199, :542, :598, :636; local_agent.rs:272, :556 |
complete_run_with_notices |
engine.rs:694; local_agent.rs:554 |
record_tool_call_started |
engine.rs:603; local_agent.rs:3476, :3820, :4376 |
record_tool_call_result |
engine.rs:656; local_agent.rs:3532, :3884, :4564 |
All pass deps: &AssistantDeps unchanged; impl RunLifecycleHost for AssistantDeps
(run_lifecycle.rs:49-59) makes every one of them resolve. That they do resolve is proven by the
compile in §6, not by reading. (runtime::cancel_run and commands/*::cancel_run are an unrelated
same-named function — not affected.)
4. Behaviour preservation of the mechanical rewrite — the highest-risk part
Confirmed, all eight sites, byte for byte in payload, position and order.
main has exactly 8 emit_event calls in run_lifecycle.rs (lines 84, 93, 114, 122, 154, 191, 309,
335 — all of the form let _ = emit_event(&deps.app, session, Some(run_id), …)). Head has exactly 8
announce calls (115, 124, 140, 148, 175, 207, 329, 350) in the same order, plus exactly one
surviving emit_event — inside AssistantDeps::announce at run_lifecycle.rs:57.
| # | Fn | Event | Position relative to the DB write | Same as main? |
|---|---|---|---|---|
| 1 | fail_run |
ToolCallFailed (per row) |
after fail_running_tool_calls_for_run, before complete_run |
yes |
| 2 | fail_run |
RunFailed |
after complete_run(.., Failed, Some(error_msg), &[]) |
yes |
| 3 | cancel_run |
ToolCallFailed (per row) |
after fail_running_tool_calls_for_run(.., "Run cancelled"), before complete_run |
yes |
| 4 | cancel_run |
RunCancelled |
after complete_run(.., Cancelled, None, &[]) |
yes |
| 5 | complete_run_with_notices |
RunCompleted |
after complete_run(.., final_status(notices), None, notices) |
yes |
| 6 | record_tool_call_started |
ToolCallStarted { tool_call: invocation } |
after create_tool_call |
yes |
| 7 | record_tool_call_result |
completion_event(status, updated) |
after update_tool_call, before create_message |
yes |
| 8 | record_tool_call_result |
MessageCreated { message } |
after create_message |
yes |
The two things I specifically went looking for and did not find:
- No reordering between a DB write and its event. Every
announcesits at the same statement
index in the same block as theemit_eventit replaced. The tool-call loops still close calls
before the terminal run event (the stated invariant: no spinner outliving its run). - No lost
let started_at = updated.started_at;dance.run_lifecycle.rs:327-328still capture
both timestamps beforeupdatedis moved intocompletion_event(status, updated)at:329, and
:342-343still writestarted_at: Some(started_at), completed_at. Thestatus.clone()into
update_tool_call/statusintocompletion_eventsplit is unchanged.
The only non-mechanical edit in the production file is a rustfmt reflow of the
repository::update_tool_call(...) match scrutinee in record_tool_call_result (:303-311), forced
by &deps.pool → deps.pool() widening the line. Semantics identical.
5. Trait seam soundness
announce -> ()swallows nothing a caller needed. All eight former sites were literally
let _ = emit_event(…)onmain(verified individually ingit show 9f484d9:…). None inspected,
logged, or propagated theResult. The rationale is also sound on its own terms: five of the eight
fire after the run has already reached a terminal row, so returning an error there would have
nowhere to go.run_id: &strloses no information. All eight sites passedSome(run_id)whererun_id: &str
was already a non-optional parameter of the enclosing function. Making it non-optional at the seam
removes aNonethat was unreachable. This is a strict improvement: a mutation toNoneat any of
the six functions is now a type error rather than a silent event-routing bug.- No error path is less informative.
resolve_run_idstill returns
AssistantEngineError::Persistence(format!("run not found: {}", id))and
RunConnectionMismatch(id);record_tool_call_resultstill returns theupdate_tool_callstring
underPropagateand stilltracing::warn!s withtool_call_id,sourceanderrorunder
SkipQuietly. Nothing was downgraded. - Residual, correctly stated by the author.
AssistantDeps::announce(run_lifecycle.rs:56-58)
is the one line no test can reach; mutating itsSome(run_id)toNonewould compile and pass.
Concentrating the untestable surface into a three-line impl is the right trade, and the PR body says
so rather than claiming zero. - Static dispatch:
&impl RunLifecycleHostmonomorphises to two instantiations (AssistantDeps,
TestHost); no object-safety constraint, nodyn, no allocation, no runtime cost.
6. Gates — run here, not taken on trust
From CLAI/src-tauri at a pristine 539fafb, CARGO_INCREMENTAL=0 on every invocation:
| Gate | Result |
|---|---|
cargo fmt --check |
pass (exit 0) |
cargo clippy --all-targets -- -D warnings |
pass (exit 0, Finished dev profile) |
cargo test --lib |
1052 passed; 0 failed; 0 ignored |
cargo test --lib run_lifecycle_tests:: |
14 passed; 0 failed; 1038 filtered out |
The claimed count is confirmed exactly, and the last row is the cleanest possible proof of the
"1038 → 1052" claim: the runner itself reports 14 new tests and 1038 others, so no rebuild of main
was needed to establish the delta. bindings.ts unchanged, verified by diff (§1).
That the whole thing compiles also discharges §3: &impl RunLifecycleHost resolves at all 24 call
sites in engine.rs and local_agent.rs without either file being touched.
7. Mutation spot-checks — five applied by hand, run, and reverted
I did not attempt all fifteen. I picked the load-bearing ones, plus one I expected to survive.
| # | Mutation | Result | Killed by |
|---|---|---|---|
| M1 | run_lifecycle.rs:78 invert the connection-mismatch guard (!= → ==) |
KILLED | continuing_a_run_on_a_different_connection_is_rejected (:608) and a_supplied_run_is_reused_rather_than_replaced (:587) |
| M2 | fail_run announces RunFailed before closing its tool calls (block swap, :112-124) |
KILLED | failing_a_run_closes_its_open_tool_calls_before_the_run_itself (:207) — left: ["RunFailed","ToolCallFailed"] vs right: ["ToolCallFailed","RunFailed"] |
| M3 | run_lifecycle.rs:314-315 swap the two MissingToolCall policies |
KILLED | a_result_for_a_call_we_never_recorded_is_dropped_quietly_on_the_cli_paths (:472) and the_same_missing_call_fails_the_turn_on_the_api_path (:501) — one per direction |
| M5 | resolve_run_id opens the run Running instead of Queued (:86) |
KILLED | a_turn_without_a_run_opens_one_queued_against_its_connection (:563) |
| M4 | tag an announcement with the wrong run id, at the tool-call edge (record_tool_call_started:207, run_id → "wrong-run-id") |
SURVIVES the full 1052-test suite | — |
Four of five died exactly as claimed, several to two independent tests. The fifth is the finding.
7.4 Finding (should-fix): the "wrong run id" mutation is killed at only 2 of the 8 announce sites
The PR body's evidence table lists "tag an announcement with the wrong run id" in the tool call
row. It is not killed there.
TestHost does store the run id (run_lifecycle_tests.rs:32,43-48) and does expose
event_run_ids() (:94-101) — author-loop finding 3 was genuinely applied. But event_run_ids() is
asserted in exactly one test: run_lifecycle_tests.rs:210, inside
failing_a_run_closes_its_open_tool_calls_before_the_run_itself. That test exercises fail_run and
nothing else.
I verified the consequence empirically rather than inferring it. Replacing run_id with a literal
"wrong-run-id" at all six announce sites outside fail_run simultaneously —
cancel_run:140(ToolCallFailed) and:148(RunCancelled)complete_run_with_notices:175(RunCompleted)record_tool_call_started:207(ToolCallStarted)record_tool_call_result:329(completion_event) and:350(MessageCreated)
— leaves cargo test --lib at 1052 passed; 0 failed. Six of the eight announce sites are
unpinned for the one argument the seam newly made non-optional.
The near-miss that makes this easy to overlook:
starting_a_tool_call_records_it_running_and_announces_the_same_row does assert
tool_call.run_id == run.id (:346) — but that is the run_id column of the ToolInvocation
payload, read back from the DB row, not the run_id argument the event envelope is tagged with.
The two are independent; only the former is checked.
Severity: should-fix, not must-fix, and not a regression. The shipped code is correct — I am
reporting an over-claim in the evidence, not a defect in the behaviour. On main these six sites were
equally untested (the entire module was unreachable from a test), so the PR strictly improves matters.
The blast radius if it were ever broken is UI-only: emit_event(&self.app, session, Some(run_id), …)
puts the id in the event envelope the frontend filters runs by, so a wrong id means a tool call that
never appears under its run — the DB rows stay correct.
But this PR's entire currency is its mutation table, and that table is what a reviewer will rely on
instead of re-deriving the coverage. One row of it does not hold.
Suggested change (three lines, no new harness):
// run_lifecycle_tests.rs, in starting_a_tool_call_records_it_running_and_announces_the_same_row
assert_eq!(host.event_run_ids(), vec![run.id.clone()]);
// in a_completed_call_announces_itself_then_hands_the_payload_back_to_the_provider
assert_eq!(host.event_run_ids(), vec![run.id.clone(), run.id.clone()]);
// in cancelling_a_run_closes_its_open_tool_calls_with_the_cancellation_reason
assert_eq!(host.event_run_ids(), vec![run.id.clone(), run.id.clone()]);and move the "wrong run id" row of the table out of the tool call group into fail, or keep it
where it is once the asserts above exist.
8. Test quality — assertions are specific, not shape-only
I went looking for tests that would pass against a broken implementation. With the one exception in
§7.4, I did not find any.
db::init_workspace_dbclaim holds.run_lifecycle_tests.rs:52-56:tempfile::tempdir()then
crate::db::init_workspace_db(dir.path()). That is the production migration path — the same one
db::tests::workspace_init_creates_expected_tablesexercises — not a second hand-written DDL. The
round trips throughrepository::get_run/list_tool_calls/list_messagestherefore go through
the real schema, which is why M5 and M1 died on reloaded rows rather than on in-memory values.- Assertions name exact values, not shapes.
assert_eq!(row.error.as_deref(), Some("provider exploded"))(:218),assert_eq!(row.error, None)for cancel (:241, pinning that a cancellation
carries no error),assert_eq!(call.result, None)on a failed call whose payload is non-empty
(:444, pinning that the payload lives in the message not the row),row.notices[0].message == "denied"(:291),protocol_id/model_idasserted separately (:568-569) so a swap is
caught. - Events are checked as sequences, including the empty one. Every test opens with
assert_eq!(host.event_names(), …); the two negative tests assert
host.event_names().is_empty()(:475,:505), which is what makes
the_same_missing_call_fails_the_turn_on_the_api_path'sis_err()non-vacuous — it rules out the
"errored after announcing" shape. notices_ride_along_with_the_completion_they_describe(:295-301) opens the event payload and
asserts the run inside it is the persisted one (CompletedWithWarnings, 1 notice), not a stale
pre-write copy. That is the right assertion for a "record then announce" edge.a_terminal_edge_leaves_already_finished_tool_calls_alone(:245) is the strongest test here.
It is the only thing in the repo pinningfail_running_tool_calls_for_run's
status IN ('pending','running')filter: it asserts the completed call keepsCompleted/error: Noneand that only["RunFailed"]is announced. Deleting that filter is a plausible mutation with a
severe effect (a run's error overwriting a successful call's row, plus a spuriousToolCallFailed).- Isolation: each test builds its own tempdir + pool; no shared state, no fixed path, no ordering
dependency — consistent with the suite passing under the default parallel runner.TestHostfield
order (pool:28before_dir:35) drops the pool before the tempdir, which is correct. - The author's stated residual is accurate:
a_clean_run_completes_without_warnings(:305) has no
mutation of its own; it overlaps the pre-existing pure test and adds only the real-schema round trip.
Stating that rather than padding the count is the right call.
9. The two deliberate deferrals
Both are reasonable; I would defer both the same way.
resolve_run_id's two sources of truth forconnection_id.run_lifecycle.rs:78validates a
supplied run againstinput.connection_id, while theNonebranch writesconnection.id
(:88). I confirmed both callers (engine.rs:111,local_agent.rs:261) deriveconnectionfrom
input.connection_id, so they agree today and this is latent, not live. It is pre-existing on
main— the diff does not touch either line's logic. Folding a behavioural change into a
test-only PR would be the wrong move; filing it is right. Worth an issue so it does not evaporate.RunLifecycleHostnamed for its first consumer. Its content — a pool and an event sink — is
what every untestable function inengine.rs/local_agent.rsneeds. The PR body now commits to
widening this trait for the second half of R3.11 rather than adding a siblingEngineHost, which
is the answer the author-loop's shape note asked for. Naming is cheap to change later; committing
to the direction in writing is the part that mattered, and it was done.
10. Verdict
ship with changes — one change, ~3 lines of test plus one table correction.
- The mechanical rewrite is behaviour-preserving on all eight emit sites (§4): identical payload,
identical position relative to the DB write, identical ordering. This is the part that could have
hidden a real bug and it does not.8 emit_eventonmain→8 announcein the same order, with
exactly oneemit_eventsurviving, in the three-lineAssistantDepsimpl. - The trait seam is sound (§5): nothing swallowed that a caller wanted, no information lost, no error
path downgraded. Makingrun_idnon-optional is a strict improvement. - All three gates pass here, executed (§6), and the test count is exactly the claimed 1052 with
exactly 14 new tests. - All six of the author-loop's findings against
0f3ae5eare genuinely applied at539fafb(§2). - Four of five spot-checked mutations died exactly as claimed, several to two independent tests (§7).
The change: add assert_eq!(host.event_run_ids(), …) to the three tool-call/cancel tests named in
§7.4, and fix the mutation table's "wrong run id" row. Today a wrong run id at 6 of the 8 announce
sites passes the entire 1052-test suite — verified by applying it, not by reading. The code is correct
and this is not a regression, but the PR's evidence table claims a kill it does not have, and this is a
PR whose whole value is the evidence table.
Nothing here blocks merge on correctness grounds. If the author prefers to land as-is and follow up,
the only thing that must change is the body's table, so the next reader is not misled about what is
pinned.
What I did and did not do
- Compiled and executed, at pristine
539fafb:cargo fmt --check,cargo clippy --all-targets -- -D warnings,cargo test --lib(full, twice) and filtered. Five mutations applied by hand,
compiled, run, and reverted individually; plus one six-site combined mutation. - Tree left clean (
git status --shortempty) and the suite re-verified green at 1052 after the last
revert. Clone returned tomainat9f484d9. CARGO_INCREMENTAL=0throughout; free disk 29.05 GB at start, 29.05 GB at end (build cache was warm,
net zero growth).- Not done: the remaining 10 of the 15 claimed mutations;
cargo test --all-targets; any release
build; end-to-end behaviour under the realengine.rs/local_agent.rscall graph (read the call
sites and relied on the compile).
|
Re-verified against today's Merged
No rebase or force-push needed; the branch is still mergeable as-is. Nothing required from me — this is just so the staleness isn't a surprise at merge time. |
Implements the enabling half of roadmap item R3.11, named the highest-value open item by three consecutive reviews.
The gap
assistant/run_lifecycle.rsowns the four edges of a run — open it, fail it, cancel it, complete it — plus the two tool-call edges, shared by the API engine and all three CLI drivers. None of that wiring had a test. The pure helpers did (final_status,completion_event,tool_call_update), but what they feed — which row each edge writes, which event follows it, in what order — did not. #180's review recorded three surviving mutations for exactly this reason.The obstacle was one field. Every function takes
&AssistantDeps, whoseappis atauri::AppHandle<Wry>: it cannot be constructed outside a running app, andtauri::test::mock_app()does not help — it returnsAppHandle<MockRuntime>, a different type. So the module was unreachable from a unit test even though its database half has had an in-memory harness all along.The seam
RunLifecycleHost— a database to record the change in, a channel to announce it on.AssistantDepsimplements it, so no call site changes: the six functions take&impl RunLifecycleHostand nothing else moves.announcereturns()because all eight emit sites already discarded the error deliberately (a frontend that is not listening must not fail a run that is already terminal), and takesrun_id: &strrather thanOption<&str>because all six callers passedSome.Tests run against the production schema — a tempdir database built by
db::init_workspace_db, i.e. the embedded workspace migrations — not a second hand-written copy of the DDL that can drift from them.Evidence
14 tests, 1038 → 1052.
cargo fmt --check,cargo clippy --all-targets -- -D warningsandcargo test --liball clean;src/generated/bindings.tsunchanged.15 mutations applied, each killed by a named test:
RunningnotQueued; swap protocol/model onto the row; drop the run id from the not-found errorFailed; completion dropping its noticesPending; never announce the tool-role message; drop the result part's timestamps; swap the twoMissingToolCallpolicies in either direction; tag an announcement with the wrong run idThree further mutations (
completion_eventalways announcing success, a failed call storing its payload as a result,final_statusignoring notices) are covered end to end but already died against the pure tests onmain, so they are not counted.Residuals, stated plainly.
a_clean_run_completes_without_warningshas no mutation of its own — it overlaps an existing pure test and adds only the round trip through the real schema.AssistantDeps::announceis now the only line in the module no test can reach; mutating it to passNonewould compile and pass. Concentrating the untestable surface into one three-line impl is the best outcome available here, not zero.Review
reviews/pre-pr-review-2026-09-11-r3.11.md(agent workspace) — shape ship, correctness ship with changes. Source-only; the reviewer did not compile, because the host disk is at 97%.Its must-fix:
resolve_run_id— the "open it" edge the first draft's commit message claimed to have closed — had zero coverage, from the new file, the existing inline tests, or transitively. Four of the fifteen mutations above come straight from that finding. Also applied: the cancel test's name asserted aToolCallStatus::Cancelledthat does not exist (the row is writtenFailed; the name and a status assertion were both fixed), the recording host threw therun_idaway, the tool-result timestamps were skipped by a..pattern, and the twoMissingToolCalltests differed in two variables instead of one.Two things the reviewer raised that are deliberately not changed here:
resolve_run_idvalidates a supplied run againstinput.connection_idbut writesconnection.idwhen opening a new one — two sources of truth for one fact. Both callers deriveconnectionfrominput.connection_id, so they agree today. Pre-existing; filing it rather than folding a behavioural change into a test PR.RunLifecycleHostis named for its first consumer. The remaining half of R3.11 needs the same two capabilities forengine::run_session_turn; the intent is to widen this trait, not to add a siblingEngineHost.