fix(cron): retire persisted heartbeat/dream jobs when disabled

Disabling gateway.heartbeat (or agents.defaults.dream) only skipped
job registration on startup; the previously persisted system job in
<workspace>/cron/jobs.json kept firing, and remove_job refuses to
touch protected system jobs. Add CronService.remove_system_job for
startup reconciliation and call it from the gateway disabled branches
so the config toggle takes effect after restart.
This commit is contained in:
aiguozhi123456
2026-08-22 02:38:47 +08:00
committed by chengyongru
parent 1fe36d5dec
commit e5718d4de6
5 changed files with 61 additions and 0 deletions
+1
View File
@@ -211,6 +211,7 @@ def test_load_store_falls_back_to_in_memory_on_corruption_after_start(
("enable_job", lambda service: service.enable_job("missing", enabled=False)),
("update_job", lambda service: service.update_job("missing", name="new name")),
("register_system_job", lambda service: service.register_system_job(_system_job())),
("remove_system_job", lambda service: service.remove_system_job("heartbeat")),
],
)
def test_public_apis_raise_clear_error_for_unavailable_corrupt_store(
+33
View File
@@ -785,6 +785,39 @@ def test_remove_job_refuses_system_jobs(tmp_path) -> None:
assert service.get_job("dream") is not None
def test_remove_system_job_retires_persisted_system_job(tmp_path) -> None:
"""Disabling a system job (e.g. gateway.heartbeat.enabled=false) must
actually retire the previously persisted job, not just skip registration."""
store_path = tmp_path / "cron" / "jobs.json"
service = CronService(store_path)
service.register_system_job(CronJob(
id="heartbeat",
name="heartbeat",
schedule=CronSchedule(kind="every", every_ms=1_800_000, tz="UTC"),
payload=CronPayload(kind="system_event"),
))
assert service.get_job("heartbeat") is not None
removed = service.remove_system_job("heartbeat")
assert removed is True
assert service.get_job("heartbeat") is None
# Removal must persist: a fresh instance (next gateway start) must not
# resurrect the job from the on-disk store.
assert CronService(store_path).get_job("heartbeat") is None
# Idempotent: removing a missing system job reports False without raising.
assert service.remove_system_job("heartbeat") is False
# User-facing removal still protects remaining system jobs.
other = CronService(store_path)
other.register_system_job(CronJob(
id="dream",
name="dream",
schedule=CronSchedule(kind="cron", expr="0 */2 * * *", tz="UTC"),
payload=CronPayload(kind="system_event"),
))
assert other.remove_job("dream") == "protected"
@pytest.mark.asyncio
async def test_start_server_not_jobs(tmp_path):
store_path = tmp_path / "cron" / "jobs.json"