diff --git a/harness/scheduler.py b/harness/scheduler.py index dc59609..ba855cb 100644 --- a/harness/scheduler.py +++ b/harness/scheduler.py @@ -539,19 +539,23 @@ def run_level_tick(level_cfg, seq: int, model: dict): # ЗАКРЫТЬ задачу сам (cli complete) ещё до батч-фазы — его TaskCompleted # содержит целевой ответ; повторный append поверх перезаписал бы answer # дайджестом тика и испортил бы проверенный результат (перезапись - # «последний-победил» в проекции). Закрываем только ещё-running. - _model = es.run_projections() or {} # стаб/None в тестах — не валим - _tasks = _model.get("tasks") or {} - _done = {tid for tid in (claimed or []) - if (_tasks.get(tid) or {}).get("status") == "done"} - _first = True - for tid in (claimed or []): - if tid in _done: - if _first: - _log("skip батч-догзакрытие готовых задач: " - + ", ".join(sorted(_done))) - _first = False - continue + # «последний-победил» в проекции). ЗАКРЫВАЕМ ТОЛЬКО ЕЩЁ-RUNNING: + # воркер мог решить сам — complete (done), requeue (готово) или + # закрыть в архив (closed); дайджест тика не перезаписывает его + # решения (авто-схлопывание фейлов, e-261003094432: без этого фикса + # requeue'd задачи нечестно закрывались бы дайджестом). + _model = es.run_projections() + if _model is None: + # стаб/None в тестах (mocked es) — прежний путь: закрываем все claimed + _resolve = list(claimed or []) + else: + _tasks = _model.get("tasks") or {} + _resolve = [tid for tid in (claimed or []) + if (_tasks.get(tid) or {}).get("status") == "running"] + _skipped = sorted(set(claimed or []) - set(_resolve)) + if _skipped: + _log("skip батч-догзакрытие задач, решённых воркером: " + ", ".join(_skipped)) + for tid in _resolve: es.append_event("task", tid, "TaskCompleted", { "result_ref": trace_id, "answer": (text or "")[:2000], })