c8677d985d
R3 — delete unused schema columns: ``workflows.ci_flake_retries_remaining`` and ``workflows.awaiting_ci_started_at`` shipped in round-1 batch D as "staged for future tick handlers" — but no producer or reader ever used them. ci_poll.py uses ``entered_state_at`` (already populated on every transition) as the AWAITING_CI start timestamp. Delete both columns; the flake-retries counter should ship with its producer, not as speculative schema. R4 — safe_json_dumps raises on sets: Previously sets/frozensets were silently coerced to sorted lists. This contradicted the "raise loudly" design intent (JSON has no native set; a reader doing ``parsed["tags"]`` would get a list, losing set algebra). Now raises with an actionable message pointing the producer at ``sorted(list(...))`` for explicit conversion. R5 — TestSQLiteConcurrentDequeue docstring honest: The test name implied it pinned SQLite's busy_timeout retry. It doesn't — :memory: + StaticPool means both threads share one connection (SQLAlchemy serializes per-connection). Updated docstring to say what the test ACTUALLY pins (Python-level serialization safety, exactly-one-winner, clean loser-reason) and explicitly what it doesn't (cross-process SQLITE_BUSY retry, which would need file-backed SQLite + QueuePool — not shipped because multi-machine requires Postgres). R6 — safety timer on test_loop_runs_ci_poll_exhaustion_on_cadence: Previously the test relied entirely on on_iter setting stop when workflows_exhausted>0. If the logic regressed (SQL schema drift, on_iter never seeing the count), the test wedged CI indefinitely. Now armed with threading.Timer(5.0, stop.set) safety net + an assertion that surfaces the failure mode if the safety timer fired first. R8 — _json_safe scope documented honestly: The docstring claimed "everywhere the controller serializes" but the encoder is only wired at runner.py and scheduler.py — the two sites that serialize WORKER-ORIGINATED payloads. Other json.dumps call sites (event-row payloads, discovery markers) serialize fixed-shape dicts of native types and don't need restriction. Updated docstring to scope the claim accurately. R10 — missing test assertions added: - test_strict_parser_coverage_blocks_startup_with_stubs: now captures logs + asserts the operator-facing error message is emitted (so journald shows the cause; a silent rc=2 would be confusing). - test_externally_merged_takes_priority_over_label_removal: now asserts event_type="external-merge" + reason="externally-merged" (a regression that transitioned correctly with the wrong reason in the audit trail would now be caught). - test_second_pause_captures_post_resume_state (NEW): pause/resume/ pause cycle. After RESUME + workflow advances to REVIEWING, a second PAUSE must capture REVIEWING as pre_pause_state (not the original IMPLEMENTING). Round-2 coverage only exercised first pause. Total: 696 controller tests pass (+3 net from new + updated tests), 0 regressions. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>