fix: bounded audit loop (deterministic gates only, 3 pushbacks, escalate)
Audit loop engine (cloud/audit_loop.py): Finding/PushbackPacket/LoopState pure functions; deterministic gates only, subjective -> needs_review; packet completeness enforced; standards-weakening -> escalate; regression and missing-gate checks enforce full re-run; MAX_PUSHBACKS=3, round>=3 -> escalate. Persistence in new single-row task_audit_loop_state (attempts DDL pattern: module CREATE_TABLE_SQL + db.py POSTGRES_SCHEMA + migrations list + server init + worker ensure); task_attempts NOT reused (attempt_no PK collision) and task_followups NOT reused (user messages). Worker.audit_loop_evaluate is the explicit opt-in hook (task audit_loop field only, no env switch, never called by run_one); escalate audits worker_audit_loop_escalated; corrupted/missing count fail-closes to escalate.
This commit is contained in:
1 parent
1b8b6e525d
commit
37b1aadc02
6 files changed
+675
No files matched your search
@@ -143,6 +143,10 @@ from cloud.attempts import build_resume_preamble as _resume_preamble
|
||||
from cloud.attempts import ensure_attempts_table as _ensure_attempts
|
||||
from cloud.attempts import latest_attempt as _latest_attempt
|
||||
from cloud.attempts import record_attempt as _record_attempt
|
||||
from cloud.audit_loop import ensure_audit_loop_table as _ensure_audit_loop
|
||||
from cloud.audit_loop import evaluate as _audit_loop_evaluate
|
||||
from cloud.audit_loop import load_loop_state as _audit_loop_load
|
||||
from cloud.audit_loop import save_loop_state as _audit_loop_save
|
||||
from cloud.auth import AuthStore
|
||||
from cloud.coordinator import build_report as _coord_build_report
|
||||
from cloud.coordinator import DEFAULT_POLL_INTERVAL as _COORD_POLL_INTERVAL
|
||||
@@ -1792,6 +1796,101 @@ class Worker:
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
def audit_loop_evaluate(self, task, gate_results, findings=None,
|
||||
fixed=None):
|
||||
"""有界审计循环的显式入口(默认关闭,绝不由 run_one 自动调用)。
|
||||
|
||||
启用条件二选一中只选其一:任务字典含 audit_loop 真值。
|
||||
没有 env 开关——全局 env 会让所有任务悄悄启用循环,本方法
|
||||
刻意拒绝它。开关关闭时返回 enabled=False 且不碰数据库,
|
||||
run_one、auto_resume、协调员、goal 断点行为逐字节不变。
|
||||
|
||||
本方法不改任务状态、不重排、不调 runner、不重新决策:
|
||||
它只给决策——pass(放行)、pushback(调用方持 packet 重排
|
||||
任务,packet 里有失败项、证据、复现、验收标准)、escalate
|
||||
(调用方置任务 failed/needs_review,不再自动循环)、
|
||||
needs_review(主观发现,走人工)、off(未启用)。
|
||||
升级时记 worker_audit_loop_escalated 审计(best-effort,
|
||||
审计失败不改决策)。轮次存在 task_audit_loop_state 单行表,
|
||||
计数损坏时 fail-closed 到 escalate,绝不默认循环。
|
||||
调用参数非法(空证据、空门集、非法门结果)时同样
|
||||
fail-closed 到 escalate,不进循环。
|
||||
gate_results 必须含本轮全部已跑的门;上一轮通过的门本轮
|
||||
缺跑或变失败时,引擎按 gate_missing / regression 升级或推回。
|
||||
"""
|
||||
task = task if isinstance(task, dict) else {}
|
||||
if not task.get("audit_loop"):
|
||||
return {"enabled": False, "decision": "off", "packet": None,
|
||||
"new_state": None, "reason": None}
|
||||
task_id = task.get("id")
|
||||
project_id = task.get("project_id")
|
||||
try:
|
||||
conn = _db_connect(self.db_path)
|
||||
except Exception as exc:
|
||||
# 连库都连不上:无法持久化轮次,fail-closed 到升级。
|
||||
try:
|
||||
self._audit(project_id, "worker_audit_loop_escalated",
|
||||
"%s reason=state_unreadable error=%s"
|
||||
% (task_id, exc))
|
||||
except Exception:
|
||||
pass
|
||||
return {"enabled": True, "decision": "escalate", "packet": None,
|
||||
"new_state": None, "reason": "state_unreadable"}
|
||||
try:
|
||||
_ensure_audit_loop(conn)
|
||||
state, corrupted, error = _audit_loop_load(conn, task_id)
|
||||
if corrupted:
|
||||
try:
|
||||
self._audit(
|
||||
project_id, "worker_audit_loop_escalated",
|
||||
"%s reason=count_corrupted error=%s"
|
||||
% (task_id, error))
|
||||
except Exception:
|
||||
pass
|
||||
return {"enabled": True, "decision": "escalate",
|
||||
"packet": None, "new_state": None,
|
||||
"reason": "count_corrupted"}
|
||||
decision = _audit_loop_evaluate(
|
||||
state, task_id, gate_results, findings=findings,
|
||||
fixed=fixed)
|
||||
if decision["new_state"] is not None:
|
||||
_audit_loop_save(conn, decision["new_state"])
|
||||
conn.commit()
|
||||
except Exception as exc:
|
||||
# 评估或写状态失败:轮次不可信,fail-closed 到升级。
|
||||
try:
|
||||
conn.rollback()
|
||||
except Exception:
|
||||
pass
|
||||
try:
|
||||
self._audit(project_id, "worker_audit_loop_escalated",
|
||||
"%s reason=evaluate_failed error=%s"
|
||||
% (task_id, exc))
|
||||
except Exception:
|
||||
pass
|
||||
return {"enabled": True, "decision": "escalate", "packet": None,
|
||||
"new_state": None, "reason": "evaluate_failed"}
|
||||
finally:
|
||||
try:
|
||||
conn.close()
|
||||
except Exception:
|
||||
pass
|
||||
if decision["decision"] == "escalate":
|
||||
try:
|
||||
self._audit(
|
||||
project_id, "worker_audit_loop_escalated",
|
||||
"%s reason=%s round=%s"
|
||||
% (task_id, decision["reason"], decision["round"]))
|
||||
except Exception:
|
||||
pass
|
||||
result = dict(decision)
|
||||
result["enabled"] = True
|
||||
if result.get("packet") is not None:
|
||||
result["packet"] = result["packet"].to_dict()
|
||||
if result.get("new_state") is not None:
|
||||
result["new_state"] = result["new_state"].to_dict()
|
||||
return result
|
||||
|
||||
def _notify_task_finished(self, task_id, project_id, event_type,
|
||||
error=None):
|
||||
"""任务终态推送:best-effort,绝不因通知失败影响任务状态。
|
||||
|
||||
Reference in new issue
Block a user