Skip to content

test(assistant): cover the compaction repository functions - #204

Open
juacker wants to merge 1 commit into
mainfrom
clai/test/compaction-repository-coverage
Open

juacker wants to merge 1 commit into
mainfrom
clai/test/compaction-repository-coverage

Conversation

@juacker

@juacker juacker commented Sep 13, 2026

Copy link
Copy Markdown
Collaborator

Implements R6.2. Test-only: one file, +490, no production code changed.

What was missing

create_compaction, complete_compaction, get_compaction and latest_completed_compaction (repository.rs:1171-1295) had zero tests. The blocker was the hand-written fixture, which never created assistant_compactions; db::test_support::workspace_pool from #200 builds the real schema from the migrations, so the table is simply there now.

What is actually load-bearing here

latest_completed_compaction selects the row that decides which messages the provider is allowed to see. It is not the last line of defence — I initially wrote that it was, and the review disproved it: the one consumer, provider_history_messages_with_compaction (compaction.rs:320-340), re-checks the same two nullable fields and falls back to the unfiltered history in every failure case. It is the first line, and none of its four WHERE/ORDER BY terms had a test.

Each now does. A compaction that is still running, that has no summary message, or that has no window end must stay invisible; and the row that wins is the last one completed, not the last one created.

Two of these states are reachable from the UI

summary_message_id and source_to_message_id are both ON DELETE SET NULL, and delete_message is called from both assistant drivers (engine.rs:2254, local_agent.rs:2227). So deleting the last compacted message — or the summary itself — silently un-compacts the session: the compaction row survives, the summary message may survive, and the next request carries the full history again.

The tests build those two states through delete_message rather than raw SQL. That makes the comments true rather than hypothetical, and pins two of the table's three FK actions for free; the third, the session cascade, gets its own test.

Mutation table

Re-derived from thirteen runs after review, not asserted. Each mutation fails exactly the named test and nothing else.

Mutation Killed by
status = '"completed"' dropped ignores_a_running_one
summary_message_id IS NOT NULL dropped ignores_one_without_a_summary
source_to_message_id IS NOT NULL dropped ignores_one_without_a_window_end + deleting_the_window_end_message_…
ordering flipped to ASC picks_the_newest_completion
ordering by created_at picks_the_newest_completion
error = NULL dropped complete_compaction_sets_the_summary…
touch_session dropped, create side create_compaction_touches_the_session
touch_session dropped, complete side complete_compaction_sets_the_summary…
protocol/model binds swapped round_trips_every_field
summary/run-id binds swapped round_trips_every_field
session ON DELETE CASCADE dropped deleting_a_session_deletes_its_compactions
window-end ON DELETE SET NULL dropped deleting_the_window_end_message_…
summary ON DELETE SET NULL dropped ignores_one_without_a_summary

Review

Delegated adversarial pre-PR review against d25e0c8: shape ship_with_changes, correctness rework. Three must-fixes, all mine, all applied:

  1. ignores_a_running_one could not fail with the status filter deleted — the row also had a NULL summary, so a second guard was doing the work. My own first mutation sweep never mutated that term, so I claimed a kill I had not observed. The row now carries a summary, making status the only excluding term, and the whole table was re-derived rather than patched.
  2. The claim that renaming CompactionStatus::Completed would silently make every compaction invisible was wrong: the CHECK constraint rejects the next completion and map_compaction_row stops parsing old rows — both loud. The assertion stays (it moves a runtime failure to the rename site) with an honest justification.
  3. "A state no code path writes today" was false, per the ON DELETE SET NULL above.

All five should-fixes applied too: a weak created_at/completed_at discrimination in the ordering test (both timestamps are now forced, with no ties, pointing the two orders in opposite directions), the speculative production story behind completion-ordering, created_run_id never round-tripped non-NULL despite being the dangerous adjacent bind, complete_compaction's second touch_session untested, and the invented failed state. Nits folded in.

Follow-ups this found and does not close

  • provider_history_messages_with_compaction — the pure function where a compaction row becomes an actual truncation — still has no tests. It takes a slice and is cheaper to test than anything here. This is the next item.
  • Nothing in the tree ever constructs CompactionStatus::Failed. A compaction whose summarize_window fails stays running forever, across restarts, and recover_stale_runs sweeps only assistant_runs/assistant_tool_calls. No fail_compaction, no stale-compaction recovery.

Gates

cargo fmt --check clean · cargo clippy --all-targets -- -D warnings clean (under #202's new policy) · cargo test --lib 1060 → 1074, 0 failed, verified by name-list diff — 14 added, none removed · src/generated/bindings.ts untouched.

`create_compaction`, `complete_compaction`, `get_compaction` and
`latest_completed_compaction` had zero tests. The only thing that blocked
them was the hand-written fixture, which never created
`assistant_compactions`; `db::test_support::workspace_pool` (#200) does.

`latest_completed_compaction` selects the row that decides which messages
the provider is allowed to see. It is not the last line of defence — its
one consumer, `provider_history_messages_with_compaction`, re-checks the
same two nullable fields and falls back to the unfiltered history — but
it is the first, and none of its four `WHERE`/`ORDER BY` terms had a
test. Each now does: a compaction that is still running, that has no
summary message, or that has no window end must stay invisible, and the
row that wins is the last one *completed*, not the last one created, so
a late-finishing re-run cannot be shadowed by a row created after it.

Two of the three states those guards exclude are reachable from the UI,
not hypothetical: `summary_message_id` and `source_to_message_id` are
both `ON DELETE SET NULL`, and `delete_message` is called from both
assistant drivers. So deleting the last compacted message, or the
summary itself, silently un-compacts the session — the compaction row
survives and the next request carries the full history again. The tests
build those states through `delete_message` rather than raw SQL, which
also pins two of the table's three FK actions; the third, the session
cascade, gets its own test.

One assertion is not about the database: `CompactionStatus::Completed`'s
serialised form is hand-written into two places no compiler checks — the
`'"completed"'` literal in the query, and the CHECK constraint in the
migration. A rename fails loudly (the CHECK rejects the next completion,
and `map_compaction_row` stops parsing old rows), but it fails at
runtime, far from the rename. The assertion moves that to the rename.

All fourteen tests were mutation-checked, and the table below is derived
from thirteen runs, not asserted: each mutation fails exactly the named
test, and nothing else.

  status filter dropped      -> ignores_a_running_one
  summary guard dropped      -> ignores_one_without_a_summary
  window-end guard dropped   -> ignores_one_without_a_window_end
                                + deleting_the_window_end_message_...
  ordering flipped to ASC    -> picks_the_newest_completion
  ordering by created_at     -> picks_the_newest_completion
  `error = NULL` dropped     -> complete_compaction_sets_the_summary...
  touch_session, create side -> create_compaction_touches_the_session
  touch_session, complete    -> complete_compaction_sets_the_summary...
  protocol/model binds swap  -> round_trips_every_field
  summary/run-id binds swap  -> round_trips_every_field
  session CASCADE dropped    -> deleting_a_session_deletes_its_compactions
  window-end SET NULL dropped-> deleting_the_window_end_message_...
  summary SET NULL dropped   -> ignores_one_without_a_summary

Two gaps this exercise found and does not close, both noted for
follow-up: `provider_history_messages_with_compaction` — the pure
function that turns a compaction row into an actual truncation — still
has no tests, and nothing ever writes `CompactionStatus::Failed`, so a
compaction whose summary fails stays `running` forever with no sweep.

1060 -> 1074 tests, fmt and clippy --all-targets clean, bindings.ts
untouched.

Implements R6.2.
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.

1 participant