feat: P1-19 P1-20 实现 - 边界测试强制 + 高风险审计 + UI Skill 路由
P1-19.1: Arc 边界测试强制 - TaskNode 新增 test_required 字段 - _inject_boundary_tests() 为每个模块注入接口测试和单元测试任务 - Done When 验证必须包含"测试通过" P1-19.2: AirRvr 高风险审计 - 新增 HighRiskAudit, HighRiskFinding 数据类 - ReviewReport 新增 highRiskAudit 字段,含 lifecycle/nullPointer/danglingPointer/exceptionSafety/concurrency + overallRisk + deliveryVerdict - 序列化/反序列化支持 P1-19.3: block-release 集成 - dispatch_worker_group() 派发前扫描最新审查报告 - deliveryVerdict=block-release 时阻止所有后续派发 - 记录 eng.blocked 事件 P1-20: frontend-design Skill 集成 - is_ui_task() UI 任务检测 - ensure_frontend_design_skill() 自动安装 Skill - route_ui_task() UI 任务路由决策 - enter_worker() 集成 UI 检测,skill 不可用时阻止执行 - commands/do.md 更新 UI 处理说明 - SKILL.md 新增 INV-12/INV-13/INV-14 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
109
lib/air_runtime/contracts.py
Normal file
109
lib/air_runtime/contracts.py
Normal file
@@ -0,0 +1,109 @@
|
||||
"""
|
||||
数据契约 — 保持 V1 契约完整性,新增 DeploymentRecord、AirRvr 审查报告等结构。
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import re
|
||||
from dataclasses import dataclass, field
|
||||
from datetime import datetime, timezone
|
||||
from typing import Any
|
||||
|
||||
|
||||
def now_iso() -> str:
|
||||
return datetime.now(timezone.utc).isoformat()
|
||||
|
||||
|
||||
@dataclass
|
||||
class WorkerResult:
|
||||
task_id: str
|
||||
status: str # done | blocked | failed
|
||||
summary: str = ""
|
||||
files_changed: list[str] = field(default_factory=list)
|
||||
validations: list[dict] = field(default_factory=list)
|
||||
document_updates: list[dict] = field(default_factory=list)
|
||||
evidence: list[dict] = field(default_factory=list)
|
||||
risks: list[str] = field(default_factory=list)
|
||||
blockers: list[str] = field(default_factory=list)
|
||||
deploy_required: bool = False
|
||||
deploy_info: dict | None = None
|
||||
|
||||
def validate_for_finalize(self, brief: dict | None = None) -> None:
|
||||
if not re.fullmatch(r"[A-Za-z0-9_\-\.]+", self.task_id):
|
||||
raise ValidationError(f"invalid task_id: {self.task_id!r}")
|
||||
|
||||
if self.status not in ("done", "blocked", "failed"):
|
||||
raise ValidationError(f"invalid status: {self.status}")
|
||||
|
||||
if self.status == "done" and not self.validations and not self.files_changed:
|
||||
raise ValidationError("done without validations or file changes")
|
||||
|
||||
if self.deploy_required:
|
||||
deploy_validations = [
|
||||
v for v in self.validations
|
||||
if v.get("kind") in ("remote-deploy-verify", "remote-binary-md5")
|
||||
]
|
||||
if not deploy_validations:
|
||||
raise ValidationError("deploy_required but no deploy verification in validations")
|
||||
|
||||
def to_dict(self) -> dict[str, Any]:
|
||||
return {
|
||||
"taskId": self.task_id,
|
||||
"status": self.status,
|
||||
"summary": self.summary,
|
||||
"filesChanged": self.files_changed,
|
||||
"validations": self.validations,
|
||||
"documentUpdates": self.document_updates,
|
||||
"evidence": self.evidence,
|
||||
"risks": self.risks,
|
||||
"blockers": self.blockers,
|
||||
"deployRequired": self.deploy_required,
|
||||
"deployInfo": self.deploy_info,
|
||||
}
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls, data: dict) -> WorkerResult:
|
||||
return cls(
|
||||
task_id=data.get("taskId", ""),
|
||||
status=data.get("status", ""),
|
||||
summary=data.get("summary", ""),
|
||||
files_changed=data.get("filesChanged", []),
|
||||
validations=data.get("validations", []),
|
||||
document_updates=data.get("documentUpdates", []),
|
||||
evidence=data.get("evidence", []),
|
||||
risks=data.get("risks", []),
|
||||
blockers=data.get("blockers", []),
|
||||
deploy_required=data.get("deployRequired", False),
|
||||
deploy_info=data.get("deployInfo"),
|
||||
)
|
||||
|
||||
|
||||
@dataclass
|
||||
class TaskRecord:
|
||||
task_id: str
|
||||
task: str = ""
|
||||
files_dirs: str = ""
|
||||
done_when: str = ""
|
||||
status: str = "TODO"
|
||||
validations: str = ""
|
||||
adr: str = ""
|
||||
|
||||
@property
|
||||
def text_for_classification(self) -> str:
|
||||
return f"{self.task} {self.files_dirs} {self.done_when}"
|
||||
|
||||
|
||||
@dataclass
|
||||
class DeploymentRecord:
|
||||
task_id: str
|
||||
host: str
|
||||
binary_path: str
|
||||
md5: str = ""
|
||||
service_name: str = ""
|
||||
service_status: str = ""
|
||||
deploy_at: str = field(default_factory=now_iso)
|
||||
smoke_test_passed: bool | None = None
|
||||
|
||||
|
||||
class ValidationError(Exception):
|
||||
pass
|
||||
Reference in New Issue
Block a user