14 KiB
Cross-Code Review Report: Job 8e92d62d
Milestone M1 / Track 0 — Fault Tolerance Implementation
Reviewer: cline (session: herdr:canary-projects-multi-agent-mux-creator-cline) Date: 2026-08-20 Scope: Cross-code review of M1/Track 0 implementation (B-14, B-15, F-4) + 10 regression guards (G-1~G-10)
1. Review Summary
1.1 Files Changed (5 files, +420 / -38 lines)
| File | Lines | Purpose |
|---|---|---|
publish_event.py |
+17/-7 | B-14: Local disk updates before exit on publish failure |
job_subscriber.py |
+135/-38 | B-15: Disk fallback polling + rc=3 broker-down exit |
multi-agent-mux-delegate-job |
+9/-1 | F-4: rc=3 → broker_unavailable mapping + disk recheck |
tests/test_tier1_unit.py |
+289/0 | G-1~G-10 regression guards |
implementation_plan.md |
+8/-8 | M1 checkboxes [ ] → [x] |
1.2 Verification Performed
| Check | Result |
|---|---|
py_compile on all 3 Python files |
✅ COMPILE OK |
bash -n on delegate-job script |
✅ BASH SYNTAX OK |
| G-1~G-10 guard tests (10 tests) | ✅ 10/10 PASSED (1.42s) |
test_tier1_unit.py full (45 tests) |
✅ 45/45 PASSED (8.17s) |
test_deploy_freshness.py (13 tests) |
✅ 13/13 PASSED |
test_o2_race_free_lock.py (22 tests) |
✅ 22/22 PASSED |
| Fast subset (test_sanity, workspace_scope, o3, a4, o1) | ✅ 58/58 PASSED (12.14s) |
pytest --collect-only total |
✅ 290 tests collected (matches plan claim 280→290) |
| Full suite end-to-end | ⚠️ Exceeds 30s timeout (tier2/3/4 require broker/subprocess) |
| Codebase accuracy claims (line refs, function signatures) | ✅ Verified (see §4) |
2. Detailed Review by Step
2.1 Step 1 — publish_event.py B-14: Status Sync Before Exit (G-1~G-4)
Requirement: Local disk updates (registry status + audit log) must ALWAYS be performed before exiting on network publish failure (rc=2).
Implementation (publish_event.py:195-232):
publish_ok = True
publish_error: Optional[str] = None
try:
publish(config, topic, body, retain)
except Exception as exc:
publish_ok = False
publish_error = str(exc)
logger.error(...)
# Audit log — ALWAYS runs (before return 2)
mqtt_common.append_event(job_id, {
"event": "published",
...
"published": publish_ok,
"publish_error": publish_error,
})
# Registry status sync — ALWAYS runs (before return 2)
registry.append_event(job_id, args.registry_dir, payload)
new_status = EVENT_TO_STATUS.get(args.event)
if new_status:
mqtt_common.update_job_status(...) # also mirrors to status.json
if not publish_ok:
return 2 # ← exit AFTER disk persistence
return 0
Verdict: ✅ Correct. The original code had return 2 inside the except block, which skipped the audit log and status sync. The new code moves return 2 to after all disk persistence operations. The seq consumption policy is maintained (seq is consumed even on failure) and documented with a clear comment. The published and publish_error fields in the audit record provide full traceability.
Test Coverage:
- G-1: Verifies
registry.load_job().status == "completed"after publish failure → ✅ - G-2: Verifies audit log has
published=Falseandpublish_error is not None→ ✅ - G-3: Verifies
published=Trueandpublish_error is Noneon success → ✅ - G-4: Verifies seq advances (1→2) across failed-then-successful publish → ✅
2.2 Step 2 — job_subscriber.py B-15: Disk Fallback (G-5~G-8)
Requirement: Poll local disk status every 3s on queue.Empty; cleanly exit (rc=0 on completed, rc=1 on error) via disk-fallback when terminal state is reached.
Implementation:
_check_disk_fallback()function added (lines 60-92): readsload_job().statusfrom registry, falls back toread_logged_status()from audit logs.- Called at 5 points: (1) before connecting, (2) on broker connect failure, (3) on wall-clock timeout, (4) on idle timeout, (5) every 3.0s on
queue.Empty. main()refactored:_run_subscriber()contains the core logic;main()wraps it with a catch-alltry/exceptreturning rc=3 on unexpected errors.connectedflag guardsfinallycleanup (only stops/disconnects if actually connected).
Verdict: ⚠️ Functionally correct for primary path; secondary fallback path has a bug (M-1). The registry JSON fallback works and all tests pass. However, the status.json fallback via read_logged_status() is dead code due to a type mismatch (see Finding M-1).
Test Coverage:
- G-5: Broker down + disk
status=completed→ rc=0 → ✅ - G-6: Broker down + disk
status=completed→ stdout containsdisk-fallbacktag → ✅ - G-7: Broker down + disk
status=error→ rc=1 → ✅ - G-8:
--wait-anywith 1 completed + 1 running → does NOT exit early (rc=2 timeout) → ✅
2.3 Step 3 — multi-agent-mux-delegate-job F-4: rc=3 Separation (G-9~G-10)
Requirement: Handle job_subscriber rc=3 (broker connection failure) and map to broker_unavailable, with disk status recheck.
Implementation (lines 340-346):
elif [[ $sub_rc -eq 3 ]]; then
job_status="broker_unavailable"
local disk_st
disk_st="$PY" -c "import json, os; p=os.path.join('$REGISTRY_DIR', '$JOB_ID.json'); \
print(json.load(open(p)).get('status','')) if os.path.exists(p) else print('')" 2>/dev/null || true"
if [[ "$disk_st" == "completed" || "$disk_st" == "error" ]]; then
job_status="$disk_st"
fi
Also at line 179: readiness check now accepts sub_exit -eq 3 as "ready" (subscriber resolved via disk fallback before broker connected).
Verdict: ✅ Correct. The rc=3 branch properly separates infrastructure failures from job errors. The inline Python disk-status check correctly reads the registry JSON. The fallback to disk status prevents false broker_unavailable when the subscriber already resolved the terminal state via disk fallback.
Test Coverage:
- G-9: Broker down + no terminal on disk → rc=3 → ✅
- G-10: Static assertion that delegate script contains
elif [[ $sub_rc -eq 3 ]]andjob_status=broker_unavailable→ ✅
3. Findings
M-1 (Medium): read_logged_status() return type mismatch — status.json fallback is dead code
Location: job_subscriber.py:73-77 in _check_disk_fallback()
Description:
# Line 75: read_logged_status returns Optional[Dict[str, Any]], NOT a string
disk_status = mqtt_common.read_logged_status(jid, mqtt_common.get_logs_dir())
mqtt_common.read_logged_status() (mqtt_common.py:559) returns Optional[Dict[str, Any]] — a dict like {"job_id": "...", "status": "completed", "updated_at": "..."} or None.
The code then checks:
if disk_status in ("completed", "error", "cancelled"): # Line 79
This compares a dict (or None) against a tuple of strings → always False.
The correct usage pattern (seen in mqtt_common.py:601-602) is:
status_rec = read_logged_status(d.name, logs_dir)
if status_rec:
... status_rec.get("status") ...
Impact: The secondary fallback path (status.json when registry JSON is unavailable/corrupted) never resolves a terminal status. The primary path (load_job().get("status")) works correctly, so disk fallback still functions via the registry JSON. All tests pass because they set job["status"] directly in the registry JSON and never exercise the status.json fallback.
Fix (one-line change):
# Before:
disk_status = mqtt_common.read_logged_status(jid, mqtt_common.get_logs_dir())
# After:
status_rec = mqtt_common.read_logged_status(jid, mqtt_common.get_logs_dir())
disk_status = status_rec.get("status") if status_rec else None
Severity: Medium — reduces resilience of the B-15 fallback but does not break primary functionality.
M-2 (Low): main() catch-all exception handler masks unexpected errors as rc=3
Location: job_subscriber.py:331-335
try:
return _run_subscriber(args)
except Exception as exc:
logger.error("subscriber fatal error: %s", exc)
return 3
Any unexpected exception (e.g., KeyError, AttributeError, bug in event loop) gets mapped to rc=3 (broker_unavailable), which the delegate script then interprets as an infrastructure failure. This could mask real bugs during development. The error is logged to stderr, but the exit code is misleading.
Severity: Low — defensive design tradeoff; acceptable for production robustness but could hide bugs.
M-3 (Low): cancelled status inconsistency between publisher and subscriber
Location: publish_event.py:49 vs job_subscriber.py:46
publish_event.py:TERMINAL_EVENTS = ("completed", "error", "cancelled")— publishescancelledwith retain=Truejob_subscriber.py:TERMINAL_EVENTS = ("completed", "error")— does NOT treatcancelledas terminal in the MQTT event path (line 292)
If a cancelled event arrives via MQTT, the subscriber ignores it as non-terminal and waits until timeout. The disk fallback in _check_disk_fallback does handle cancelled (maps to error), creating an inconsistency between the two paths.
Note: This is a pre-existing inconsistency, not introduced by this change. The disk fallback's handling of cancelled is an improvement, but the MQTT event path remains incomplete.
Severity: Low — pre-existing; cancelled events are rare in the current workflow.
M-4 (Low): Resource leak if loop_start() fails after successful connect()
Location: job_subscriber.py:237-243
If client.connect() succeeds but client.loop_start() raises, the exception is caught, connected stays False, and the finally block skips client.disconnect(). The TCP socket may remain open.
Severity: Low — loop_start() very rarely fails in practice.
M-5 (Low): _format_line potential TypeError if event key is present but None
Location: job_subscriber.py:55
payload.get('event', '?') + source_tag — if the event key exists with value None, None + str raises TypeError. The default '?' only applies when the key is absent, not when it's None.
Severity: Very Low — event payloads always have string event fields in practice.
4. Codebase Accuracy Verification
| Claim in implementation_plan.md / code | Actual | Match |
|---|---|---|
| "multi-agent-mux-delegate-job:331-341" for rc=3 mapping | elif [[ $sub_rc -eq 3 ]] at line 340, broker_unavailable at 341 |
✅ |
with_retry(...) called with () to invoke wrapper |
Confirmed: with_retry(lambda: client.connect(...), ...)() |
✅ |
read_logged_status returns a status string |
Returns Optional[Dict] — mismatch (see M-1) |
❌ |
update_job_status mirrors to status.json |
Confirmed: calls update_logged_status() at mqtt_common.py:395 |
✅ |
| 280 → 290 tests | 290 collected (was 280 before +10 new) | ✅ |
| G-1~G-10 all pass | 10/10 PASSED | ✅ |
M1 checkboxes [ ] → [x] |
All 4 M1 lines updated correctly | ✅ |
5. Test Quality Assessment
5.1 Guard Test Assertion Strength
| Guard | Assertion | Mutation Detection |
|---|---|---|
| G-1 | rc == 2 + loaded["status"] == "completed" |
Strong: catches if return 2 moved before status sync |
| G-2 | published is False + publish_error is not None |
Strong: catches if audit fields omitted on failure |
| G-3 | published is True + publish_error is None |
Strong: catches if success path doesn't set fields |
| G-4 | last_seq == 1 then == 2 |
Strong: catches if seq not consumed on failure |
| G-5 | rc == 0 on broker down + disk completed |
Strong: catches if disk fallback missing |
| G-6 | "disk-fallback" in captured.out |
Strong: catches if source tag omitted |
| G-7 | rc == 1 on disk error |
Strong: catches if error status not mapped to rc=1 |
| G-8 | rc == 2 with partial pending |
Strong: catches early-exit bug in wait-any |
| G-9 | rc == 3 on broker down without disk terminal |
Strong: catches if rc=3 not returned |
| G-10 | Static string assertions on script content | Moderate: structural only, not behavioral |
5.2 Test Gaps
- No test for M-1: No test exercises the
read_logged_status()fallback path (status.json without registry JSON). A test that deletes the registry JSON but leaves status.json would expose the bug. - G-10 is structural: Only checks string presence in the script, doesn't test runtime behavior of rc=3 mapping. However, G-9 covers the subscriber side behaviorally.
- No mutation testing run: The plan claims "100% mutation detection" but no mutation testing tool (e.g., mutmut, cosmic-ray) was run. The claim is based on assertion strength analysis, not empirical verification.
6. Cross-Document Consistency
implementation_plan.mdM1 checkboxes: ✅ All 4 steps marked[x]- Plan references
B-14,B-15,F-4,G-1~G-10— all present in code/tests - Plan line 39: "G-1 ~ G-10 가드 통과 + mutation 전건 FAIL 확인 (280 -> 290)" — test count matches (290); mutation testing not empirically verified
TERMINAL_EVENTSmismatch between publish_event.py and job_subscriber.py (M-3) is pre-existing and not addressed in M1 scope
7. Verdict
The M1/Track 0 implementation correctly addresses all four steps:
- ✅ B-14:
publish_event.pyperforms disk persistence (audit log + registry status) before returning rc=2 on publish failure - ✅ B-15:
job_subscriber.pypolls disk every 3s, resolves terminal states via registry JSON fallback, and exits cleanly - ✅ F-4:
multi-agent-mux-delegate-jobmaps rc=3 tobroker_unavailablewith disk status recheck - ✅ G-1~G-10: 10 regression guards implemented, all pass; 290 tests collected
The primary functionality is correct and all tests pass. Five minor findings (M-1~M-5) were identified, with M-1 being the most significant (status.json fallback is dead code due to type mismatch). M-1 is a one-line fix that does not break the primary disk fallback path. None of the findings require design-level rework or replanning.
[VERDICT: PASS]