diff --git a/bin/followup-sweeper.py b/bin/followup-sweeper.py index 32270ff..227032a 100755 --- a/bin/followup-sweeper.py +++ b/bin/followup-sweeper.py @@ -175,13 +175,27 @@ def fallback_due(rec, now=None): _EXEC_OPS_MOD = None +def find_job_file(job_name): + """Locate a job definition in JOBS_DIR or any archive subdirectory.""" + p = JOBS_DIR / f"{job_name}.json" + if p.exists(): + return p + for match in JOBS_DIR.glob(f"archive/**/{job_name}.json"): + if match.is_file(): + return match + for match in JOBS_DIR.glob(f"**/archive/**/{job_name}.json"): + if match.is_file(): + return match + return None + + def derive_job_name(job_id): """Extract the job name from a dispatched job_id (-YYYYMMDD-HHMMSS-).""" m = _JOB_ID_RE.match(job_id or "") if not m: return None name = m.group(1) - if not (JOBS_DIR / f"{name}.json").exists(): + if not find_job_file(name): return None return name @@ -193,8 +207,11 @@ def load_job_fallback(job_name): {"job": ""} -> dispatch a fallback job, or {"op": "", "args": {...}} -> run one exec-constrained op. """ + job_path = find_job_file(job_name) + if not job_path: + return None, f"unreadable job {job_name}: [Errno 2] No such file or directory: '{JOBS_DIR / (job_name + '.json')}'" try: - with open(JOBS_DIR / f"{job_name}.json", "r", encoding="utf-8") as f: + with open(job_path, "r", encoding="utf-8") as f: cfg = json.load(f) except Exception as e: return None, f"unreadable job {job_name}: {e}" @@ -209,7 +226,7 @@ def load_job_fallback(job_name): jn = spec["job"] if not isinstance(jn, str) or not _JOB_NAME_RE.fullmatch(jn): return None, "on_no_result.job must be a valid job name" - if not (JOBS_DIR / f"{jn}.json").exists(): + if not find_job_file(jn): return None, f"on_no_result.job {jn!r} does not exist" else: if not isinstance(spec.get("op"), str) or not _OP_NAME_RE.fullmatch(spec["op"]): diff --git a/tests/test_completion.py b/tests/test_completion.py index 5a12c46..703d193 100644 --- a/tests/test_completion.py +++ b/tests/test_completion.py @@ -212,9 +212,11 @@ class ResultEvidence(unittest.TestCase): class ProofRequest(unittest.TestCase): def _patch(self, tmp_path): - orig = (harv.NUDGE_TRACKER_FILE, harv.JOB_LOG, harv.execute_agent_tool) + orig = (harv.NUDGE_TRACKER_FILE, harv.JOB_LOG, harv.execute_agent_tool, + getattr(harv, "PROOF_REQUESTS_ENABLED", False)) harv.NUDGE_TRACKER_FILE = tmp_path / "tracker.json" harv.JOB_LOG = tmp_path / "job-log.jsonl" + harv.PROOF_REQUESTS_ENABLED = True calls = [] harv.execute_agent_tool = lambda a, op, args: ( calls.append((a, op, args)), (True, "scheduled"))[1] @@ -230,7 +232,8 @@ class ProofRequest(unittest.TestCase): "job-1", "OK all good") finally: (harv.NUDGE_TRACKER_FILE, harv.JOB_LOG, - harv.execute_agent_tool) = orig + harv.execute_agent_tool, + harv.PROOF_REQUESTS_ENABLED) = orig self.assertTrue(ok) self.assertEqual(calls[0][1], "followup.create") self.assertEqual(calls[0][2]["in_m"], 30) @@ -261,7 +264,8 @@ class ProofRequest(unittest.TestCase): self.assertFalse(harv.maybe_request_proof(**kw)) finally: (harv.NUDGE_TRACKER_FILE, harv.JOB_LOG, - harv.execute_agent_tool) = orig + harv.execute_agent_tool, + harv.PROOF_REQUESTS_ENABLED) = orig self.assertEqual(len(calls), 1)