fix(sprint3): 首席架構師 Review C1/C2/C3/M3/m1 修正

C1: _ssh_execute 直接接收 key_path 參數,不反查 LAYER_SSH_CONFIG
C2: PlaybookService.create() proxy,Router 不再穿透呼叫 _repository
C3: CD Step 1b sed 替換 IMAGE_TAG_PLACEHOLDER,消除失敗中斷風險
M3: repair-bot 110/188 regex 統一 [a-z0-9][a-z0-9-]{0,30},禁止底線
m1: defaultMode 0400 加八進位說明注釋
m2: _ssh_execute 用 deadline 計算剩餘 timeout

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
OG T
2026-04-05 13:07:59 +08:00
parent 665f93e83f
commit 4b24ecd67f
8 changed files with 23 additions and 15 deletions

View File

@@ -2,11 +2,12 @@
src/services/host_repair_agent.py
Host Repair Agent — 透過 SSH 執行主機層修復
2026-04-05 Claude Code: Sprint 3 Host Auto-Repair
2026-04-05 Claude Code: C1 修正 — key_path 直接傳入 _ssh_execute不反查
"""
import asyncio
import re
import logging
from dataclasses import dataclass, field
from dataclasses import dataclass
logger = logging.getLogger(__name__)
@@ -81,6 +82,7 @@ class HostRepairAgent:
output = await self._ssh_execute(
host=config["host"],
user=config["user"],
key_path=config["key_path"],
command=command,
)
except asyncio.TimeoutError:
@@ -107,13 +109,10 @@ class HostRepairAgent:
error="" if success else output,
)
async def _ssh_execute(self, host: str, user: str, command: str) -> str:
"""執行 SSH 命令,回傳 stdout。"""
key_path = LAYER_SSH_CONFIG.get(
next((k for k, v in LAYER_SSH_CONFIG.items() if v["host"] == host and v["user"] == user), None),
{}
).get("key_path", "/etc/repair-ssh/id_ed25519")
async def _ssh_execute(self, host: str, user: str, key_path: str, command: str) -> str:
"""執行 SSH 命令,回傳 stdout。key_path 由呼叫方傳入,不反查。"""
import time
deadline = time.monotonic() + SSH_TIMEOUT
proc = await asyncio.wait_for(
asyncio.create_subprocess_exec(
"ssh",
@@ -128,7 +127,8 @@ class HostRepairAgent:
),
timeout=SSH_TIMEOUT,
)
stdout, stderr = await asyncio.wait_for(proc.communicate(), timeout=SSH_TIMEOUT)
remaining = max(1.0, deadline - time.monotonic())
stdout, stderr = await asyncio.wait_for(proc.communicate(), timeout=remaining)
output = stdout.decode().strip()
logger.info("SSH repair %s@%s %s%s", user, host, command, output)
return output