From c17c87439d152df59b02d239a201cd339103aba7 Mon Sep 17 00:00:00 2001 From: Godopu Date: Sun, 13 Sep 2026 19:43:16 +0900 Subject: [PATCH] test(issue-18): acknowledge with started in the remaining working-agent scenarios; refresh the subscriber help capture T1-14 and T1-20 model an agent that goes `working` and completes late. Under the liveness contract an agent that never publishes `started` is a delivery failure, so the watcher threads now acknowledge (non-blocking: the record lands in events.log before the wire publish) and the late completion is honoured inside the K x N grace as before. help-job_subscriber.txt records the intended --ack-timeout option. Full suite at 66f23c7: 888 passed, 3 failed (these), 0 leaked daemons. Co-Authored-By: Claude Fable 5.1 Claude-Session: https://claude.ai/code/session_01VCgu9Fa9iT7TbJUSzdq4Ff --- .../issue_5/phase1/help-job_subscriber.txt | 7 ++- tests/test_issue6_fail_closed.py | 53 +++++++++++-------- 2 files changed, 36 insertions(+), 24 deletions(-) diff --git a/tests/fixtures/issue_5/phase1/help-job_subscriber.txt b/tests/fixtures/issue_5/phase1/help-job_subscriber.txt index e09b5d6..82a895f 100644 --- a/tests/fixtures/issue_5/phase1/help-job_subscriber.txt +++ b/tests/fixtures/issue_5/phase1/help-job_subscriber.txt @@ -1,5 +1,6 @@ usage: job_subscriber.py [-h] (--job JOB | --wait-any) [--timeout TIMEOUT] - [--idle-timeout IDLE_TIMEOUT] [--expect-retention] + [--idle-timeout IDLE_TIMEOUT] + [--ack-timeout ACK_TIMEOUT] [--expect-retention] [--expect-dispatch-token EXPECT_DISPATCH_TOKEN] [--attempt-id ATTEMPT_ID] [--registry-dir REGISTRY_DIR] [--remote] [-v] @@ -15,6 +16,10 @@ optional arguments: --idle-timeout IDLE_TIMEOUT max seconds with no new event (default: job.idle_timeout_sec or 120) + --ack-timeout ACK_TIMEOUT + extra idle budget before the first event, for the + injection to produce `started` (default: + job.ack_timeout_sec; Issue #18) --expect-retention warn if no retained terminal event arrives promptly --expect-dispatch-token EXPECT_DISPATCH_TOKEN expected dispatch token for CAS verification diff --git a/tests/test_issue6_fail_closed.py b/tests/test_issue6_fail_closed.py index 5747775..b382a80 100644 --- a/tests/test_issue6_fail_closed.py +++ b/tests/test_issue6_fail_closed.py @@ -522,6 +522,34 @@ def test_t3_3_runner_delegate_job_session_busy_retry(tmp_path, monkeypatch): assert jid == "jid_busy_2" +def _publish_started_for_registered_job(reg_dir, cwd, env=None): + """Issue #18: a working agent that never publishes `started` is a delivery + failure (rc 1) under the liveness contract. The R1 re-observation cases + below are about an agent that *did* start and then went silent, so the + watcher threads acknowledge the job the way the injected command does.""" + deadline = time.time() + 20 + while time.time() < deadline: + files = list(Path(reg_dir).glob("*.json")) + if files: + jid = files[0].stem + tok = json.loads(files[0].read_text()).get("dispatch_token") or "" + args = [sys.executable, str(SKILLS / "multi-agent-mux-delegate-job" / "scripts" / "publish_event.py"), + "--registry-dir", str(reg_dir), "--job", jid, "--event", "started", "--attempts", "1"] + if tok: + args += ["--expect-dispatch-token", tok] + run_env = dict(os.environ) + run_env.update(env or {}) + # Fire and forget: the record lands in events.log before the wire + # publish, and blocking here on the broker ACK (seconds) would + # delay the scenario steps that follow in the watcher thread. + subprocess.Popen(args, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, + cwd=str(cwd), env=run_env) + return jid + time.sleep(0.1) + return None + + + # --------------------------------------------------------------------------- # T1-14: R1 working 중 subscriber 타임아웃 -> 디스크 completed 재관측 수용 (rc=0) # --------------------------------------------------------------------------- @@ -547,6 +575,7 @@ def test_t1_14_working_agent_reobserve_completed(tmp_path, mock_herdr): if any("agent" in c and "prompt" in c for c in st.get("calls", [])): st["agents"]["agt_w1"]["agent_status"] = "working" state_file.write_text(json.dumps(st, indent=2)) + _publish_started_for_registered_job(reg_dir, tmp_path) # Issue #18: acknowledge break except Exception: pass @@ -603,29 +632,6 @@ def test_t1_14_working_agent_reobserve_completed(tmp_path, mock_herdr): assert len(agent_gets) >= 1, "R1 loop must query agent get" -def _publish_started_for_registered_job(reg_dir, cwd, env=None): - """Issue #18: a working agent that never publishes `started` is a delivery - failure (rc 1) under the liveness contract. The R1 re-observation cases - below are about an agent that *did* start and then went silent, so the - watcher threads acknowledge the job the way the injected command does.""" - deadline = time.time() + 20 - while time.time() < deadline: - files = list(Path(reg_dir).glob("*.json")) - if files: - jid = files[0].stem - tok = json.loads(files[0].read_text()).get("dispatch_token") or "" - args = [sys.executable, str(SKILLS / "multi-agent-mux-delegate-job" / "scripts" / "publish_event.py"), - "--registry-dir", str(reg_dir), "--job", jid, "--event", "started", "--attempts", "1"] - if tok: - args += ["--expect-dispatch-token", tok] - run_env = dict(os.environ) - run_env.update(env or {}) - subprocess.run(args, capture_output=True, text=True, cwd=str(cwd), env=run_env, timeout=60) - return jid - time.sleep(0.1) - return None - - # --------------------------------------------------------------------------- # T1-15: R1 working 고정 + 미발행 -> Issue #18: K×N 후 rc=2 error (working 은 진단일 뿐) # --------------------------------------------------------------------------- @@ -886,6 +892,7 @@ def test_t1_20_loop_worker_late_completed_continues_to_reviewer(tmp_path, mock_h if any("agent" in c and "prompt" in c for c in st.get("calls", [])): st["agents"]["agt_worker"]["agent_status"] = "working" state_file.write_text(json.dumps(st, indent=2)) + _publish_started_for_registered_job(reg_dir, tmp_path) # Issue #18: acknowledge break except Exception: pass