fix: M2 on_failure assignee 从 tasks 表读取 + infrastructure 防递归(司马懿 Review #65)
M2: on_failure 中 assignee = meta.get('from', '') 读到 'system' 而非实际 Agent
→ 改为 SELECT must_haves, assignee FROM tasks 直接读 tasks.assignee 字段
附带:infrastructure failure 改为直接 DB INSERT,不走 _send_toolchain_task 防递归
This commit is contained in:
@@ -297,22 +297,24 @@ class ToolchainHandler(BaseTaskHandler):
|
|||||||
logger.info("Toolchain %s: verify failed (%s), marked failed",
|
logger.info("Toolchain %s: verify failed (%s), marked failed",
|
||||||
task_id, verify.reason)
|
task_id, verify.reason)
|
||||||
|
|
||||||
# 读取 must_haves 获取事件上下文
|
# 读取 must_hives 获取事件上下文 + assignee 从 tasks 表读取
|
||||||
meta = {}
|
meta = {}
|
||||||
|
assignee = agent_id
|
||||||
try:
|
try:
|
||||||
conn = get_connection(db_path)
|
conn = get_connection(db_path)
|
||||||
row = conn.execute(
|
row = conn.execute(
|
||||||
"SELECT must_haves FROM tasks WHERE id=?", (task_id,)
|
"SELECT must_haves, assignee FROM tasks WHERE id=?", (task_id,)
|
||||||
).fetchone()
|
).fetchone()
|
||||||
if row and row["must_haves"]:
|
if row:
|
||||||
meta = json.loads(row["must_haves"])
|
if row["must_haves"]:
|
||||||
|
meta = json.loads(row["must_haves"])
|
||||||
|
assignee = row["assignee"] or agent_id
|
||||||
conn.close()
|
conn.close()
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
action_type = meta.get("action_type", "")
|
action_type = meta.get("action_type", "")
|
||||||
context_data = meta.get("context", {})
|
context_data = meta.get("context", {})
|
||||||
assignee = meta.get("assignee", "") or meta.get("from", "")
|
|
||||||
|
|
||||||
# 三分路决策
|
# 三分路决策
|
||||||
route = self._classify_failure(verify)
|
route = self._classify_failure(verify)
|
||||||
@@ -403,29 +405,43 @@ class ToolchainHandler(BaseTaskHandler):
|
|||||||
self, task_id: str, agent_id: str,
|
self, task_id: str, agent_id: str,
|
||||||
verify: VerifyResult, db_path: Path,
|
verify: VerifyResult, db_path: Path,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""基础设施失败 → _send_toolchain_task @jiangwei-infra(防递归)"""
|
"""基础设施失败 → 直接在 _toolchain DB 创建 task @jiangwei-infra(防递归)"""
|
||||||
# 直接在 _toolchain DB 创建 task(不走 Gitea webhook)
|
|
||||||
try:
|
try:
|
||||||
from src.api.toolchain_routes import _send_toolchain_task
|
from datetime import datetime
|
||||||
_send_toolchain_task(
|
new_task_id = f"tc-{int(datetime.now().timestamp() * 1000)}"
|
||||||
to_agent="jiangwei-infra",
|
must_hives = json.dumps({
|
||||||
title=f"[基础设施] Gitea API 不可用 - {task_id}",
|
"event_type": "infrastructure_failure",
|
||||||
description=(
|
"action_type": "infrastructure_failure",
|
||||||
f"Gitea API 不可用,原任务 {task_id} 无法通过正常路径处理。\n"
|
"steps": [
|
||||||
f"请检查 Gitea 服务状态和网络连通性。"
|
|
||||||
),
|
|
||||||
event_type="infrastructure_failure",
|
|
||||||
action_type="infrastructure_failure",
|
|
||||||
steps=[
|
|
||||||
"检查 Gitea 服务状态(http://192.168.2.154:3000)",
|
"检查 Gitea 服务状态(http://192.168.2.154:3000)",
|
||||||
"检查网络连通性",
|
"检查网络连通性",
|
||||||
"恢复后提交 action report",
|
"恢复后提交 action report",
|
||||||
],
|
],
|
||||||
context_data={"original_task_id": task_id, "verify_reason": verify.reason},
|
"context": {"original_task_id": task_id, "verify_reason": verify.reason},
|
||||||
source="toolchain_handler",
|
"from": "system",
|
||||||
|
"source": "toolchain_handler_on_failure",
|
||||||
|
}, ensure_ascii=False)
|
||||||
|
conn = get_connection(db_path)
|
||||||
|
conn.execute(
|
||||||
|
"INSERT INTO tasks (id, title, description, assignee, assigned_by, "
|
||||||
|
"must_haves, task_type, status) VALUES (?, ?, ?, ?, ?, ?, ?, ?)",
|
||||||
|
(
|
||||||
|
new_task_id,
|
||||||
|
f"[基础设施] Gitea API 不可用 - {task_id}",
|
||||||
|
f"Gitea API 不可用,原任务 {task_id} 无法通过正常路径处理。\n"
|
||||||
|
f"请检查 Gitea 服务状态和网络连通性。",
|
||||||
|
"jiangwei-infra",
|
||||||
|
"system",
|
||||||
|
must_hives,
|
||||||
|
"toolchain",
|
||||||
|
"pending",
|
||||||
|
)
|
||||||
)
|
)
|
||||||
logger.info("Toolchain %s: infrastructure failure → task created for jiangwei-infra",
|
conn.commit()
|
||||||
task_id)
|
conn.close()
|
||||||
|
logger.info(
|
||||||
|
"Toolchain %s: infrastructure failure → task %s created for jiangwei-infra",
|
||||||
|
task_id, new_task_id)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(
|
logger.error(
|
||||||
"Toolchain %s: failed to create infrastructure_failure task: %s",
|
"Toolchain %s: failed to create infrastructure_failure task: %s",
|
||||||
|
|||||||
@@ -398,12 +398,14 @@ class TestSendToolchainTask:
|
|||||||
|
|
||||||
class TestOnFailureRouting:
|
class TestOnFailureRouting:
|
||||||
def test_business_failure_creates_gitea_comment(self, handler, tmp_db):
|
def test_business_failure_creates_gitea_comment(self, handler, tmp_db):
|
||||||
"""Business failure → Gitea PR comment"""
|
"""Business failure → Gitea PR comment @task assignee (not must_hives field)"""
|
||||||
|
# S4: must_hives does NOT contain assignee — production data doesn't have it
|
||||||
must_haves = json.dumps({
|
must_haves = json.dumps({
|
||||||
"action_type": "review_result",
|
"action_type": "review_result",
|
||||||
"context": {"repo": "sanguo/test", "pr_number": 42},
|
"context": {"repo": "sanguo/test", "pr_number": 42},
|
||||||
"assignee": "zhangfei-dev",
|
"from": "system",
|
||||||
})
|
})
|
||||||
|
# assignee is set on the tasks table row (as production code writes it)
|
||||||
_insert_task(tmp_db, "t-fail", must_haves)
|
_insert_task(tmp_db, "t-fail", must_haves)
|
||||||
|
|
||||||
with patch.object(handler, "_create_gitea_comment") as mock_comment:
|
with patch.object(handler, "_create_gitea_comment") as mock_comment:
|
||||||
@@ -414,9 +416,12 @@ class TestOnFailureRouting:
|
|||||||
call_args = mock_comment.call_args
|
call_args = mock_comment.call_args
|
||||||
assert call_args[0][0] == "sanguo/test"
|
assert call_args[0][0] == "sanguo/test"
|
||||||
assert call_args[0][1] == 42
|
assert call_args[0][1] == 42
|
||||||
|
# M2: comment body should @ the task's assignee from tasks table
|
||||||
|
comment_body = call_args[0][2]
|
||||||
|
assert "@zhangfei-dev" in comment_body
|
||||||
|
|
||||||
def test_infrastructure_failure_creates_task(self, handler, tmp_db):
|
def test_infrastructure_failure_creates_task(self, handler, tmp_db):
|
||||||
"""Infrastructure failure → _send_toolchain_task for jiangwei-infra"""
|
"""Infrastructure failure → direct DB task for jiangwei-infra (no reverse dep)"""
|
||||||
must_haves = json.dumps({
|
must_haves = json.dumps({
|
||||||
"action_type": "review_result",
|
"action_type": "review_result",
|
||||||
"context": {"repo": "sanguo/test", "pr_number": 42},
|
"context": {"repo": "sanguo/test", "pr_number": 42},
|
||||||
@@ -427,15 +432,22 @@ class TestOnFailureRouting:
|
|||||||
mock_comment.return_value = False # Gitea API down
|
mock_comment.return_value = False # Gitea API down
|
||||||
with patch.object(handler, "_create_gitea_issue") as mock_issue:
|
with patch.object(handler, "_create_gitea_issue") as mock_issue:
|
||||||
mock_issue.return_value = False # Gitea API still down
|
mock_issue.return_value = False # Gitea API still down
|
||||||
with patch("src.api.toolchain_routes._send_toolchain_task") as mock_send:
|
verify = VerifyResult(False, "no_action", "no action_report")
|
||||||
mock_send.return_value = "tc-infra"
|
handler.on_failure("t-infra", "zhangfei-dev", tmp_db, verify)
|
||||||
verify = VerifyResult(False, "no_action", "no action_report")
|
|
||||||
handler.on_failure("t-infra", "zhangfei-dev", tmp_db, verify)
|
# S3: should directly INSERT into DB, not call _send_toolchain_task
|
||||||
# Should eventually try to create infrastructure_failure task
|
# Verify a new task was created in DB for jiangwei-infra
|
||||||
mock_send.assert_called()
|
conn = get_connection(tmp_db)
|
||||||
call_kwargs = mock_send.call_args
|
rows = conn.execute(
|
||||||
assert call_kwargs[1]["action_type"] == "infrastructure_failure"
|
"SELECT * FROM tasks WHERE assignee=?",
|
||||||
assert call_kwargs[1]["to_agent"] == "jiangwei-infra"
|
("jiangwei-infra",)
|
||||||
|
).fetchall()
|
||||||
|
conn.close()
|
||||||
|
assert len(rows) >= 1, "No infrastructure_failure task created"
|
||||||
|
infra_task = rows[0]
|
||||||
|
assert infra_task["task_type"] == "toolchain"
|
||||||
|
meta = json.loads(infra_task["must_haves"])
|
||||||
|
assert meta["action_type"] == "infrastructure_failure"
|
||||||
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
|
|||||||
Reference in New Issue
Block a user