Commit Graph

2 Commits

Author SHA1 Message Date
vh 9717fb80e2 fix(cli): address Volva code-vs-contract drift (issue #3)
Volva code-review surfaced 5 findings against the TDD-passing
implementation; all 5 addressed.

Drift fixes (code):
- Add `assert argv is None or all(isinstance(a, str) for a in argv)`
  at both `main` and `_parse_args` entry points (PRE-001 was unenforced).
- `main` now catches `SystemExit` and returns `exc.code` verbatim —
  argparse's --help (SystemExit(0)) was escaping through main as an
  unhandled exception. Contract amended in-place to spell out the
  SystemExit-from-argparse-clean-exits passthrough in both
  `main` and `_parse_args` ERROR_ROUTING. New `help_exits_cleanly`
  test added per the contract amendment.
- Add the PRE-001 union-type assert at `_render_event` entry —
  unmatched Event variants would have silently no-op'd.
- `_run_turn` now awaits `cancel_task` in the `finally` block before
  returning. Under fast-stream + slow-cancel scenarios the
  `[cancel_failed]` line could miss being written before _run_turn
  returns, AND _amain could close the AsyncClient while the cancel
  POST was still in flight. `_cancel_and_log` swallows all errors
  per INV-009 so the await is safe.

Test gap fix:
- New `_FlushCountingIO` subclass counts flush() calls;
  `test_text_to_stdout_only` and `test_done_writes_newline_and_label`
  now assert `flush_count == 1` to verify INV-010 (per-chunk flush).
  Previously the tests would have passed even with flush removed.

Meta-note carried in persistent-memory: TDD caught central behavior
(stdout/stderr routing, exit-code mapping, create-session ordering,
SIGINT idempotence); the cross-model code review consistently catches
assert-boundary + observability-shape gaps across all three issues
(#1: 4 findings, #2: 3 findings, #3: 5 findings).

118/118 tests GREEN; ruff clean; drift check clean.
2026-05-20 22:59:26 -07:00
vh db27774c51 feat(cli): implement issue #3 contract via TDD
54 contract-listed tests authored + GREEN per the vertical-slice
ordering (_parse_args → _render_event → _cancel_and_log → _run_turn
→ _amain → main). 117/117 tests GREEN suite-wide; ruff clean.

The _run_turn race-loop is the load-bearing piece. Per iteration,
the await on the next event is raced against sigint_event.wait()
when NOT cancelling. Once SIGINT fires (with last_turn_id known),
_cancel_and_log is spawned, cancelling=True flips, and subsequent
iterations skip wait()-task creation entirely — the bug Volva
flagged in contract review would otherwise busy-wake on the
already-set event each iteration.

Implementation notes:
- _UsageErrorParser subclasses argparse.ArgumentParser and overrides
  error() to raise _ArgparseError instead of calling sys.exit;
  _parse_args catches and re-raises as UsageError per the contract's
  ERROR_ROUTING.
- _GatedStream test helper (custom httpx.AsyncByteStream that pauses
  on asyncio.Event entries) makes SIGINT-mid-stream tests deterministic
  without sleep-based timing — gates release via side-channels (the
  cancel-mock sets an event when its endpoint is observed).
- _sse_resp test helper wraps respx Response with the
  text/event-stream content-type, dedupes the boilerplate across the
  13 _run_turn tests.
- Strong-ref cancel_task local in _run_turn holds the fire-and-forget
  cancel task to suppress RUF006 / asyncio GC warning.

One in-flight contract amendment during TDD: no_busy_loop_after_cancel
test description originally said "exactly ONE wait()-shaped task" but
the natural race-loop shape produces 2 (iter 1 raced w/ text, iter 2
raced w/ sigint → flipped cancelling; iter 3+ skipped). Amended to
"TWO total wait() coroutines" with rationale; the busy-loop check is
preserved (iter 3+ MUST skip).

Persistent-memory updated per the commit-along rule: new module
landed, recent-decisions log entries for #3 (contract + Volva
paraphrase + TDD), next natural moves rotated to /volva-code-review
on the implementation.
2026-05-20 22:51:16 -07:00