feat(ack): refine intake and validation workflow

This commit is contained in:
2026-08-04 10:52:37 +08:00
parent b9c82b5520
commit 5018a1801d
32 changed files with 1602 additions and 302 deletions
+136 -16
View File
@@ -108,6 +108,7 @@ DELIVERY_STATUSES = {
"running",
"blocked",
"failed",
"validation_ready",
"review_ready",
"released",
"skipped",
@@ -115,16 +116,20 @@ DELIVERY_STATUSES = {
DELIVERY_ARTIFACT_FIELDS = {"id", "type", "reference", "digest"}
DELIVERY_DEPLOYMENT_FIELDS = {"environment", "result", "evidence"}
FEISHU_REQUIRED_FIELDS = {
"title", "actual", "expected", "stepsToReproduce", "acceptance", "priority",
"title", "actual", "expected", "stepsToReproduce", "acceptance",
"attachments", "updatedAt",
}
FEISHU_OPTIONAL_FIELDS = {"fixLogic"}
FEISHU_OPTIONAL_FIELDS = {"priority", "fixLogic"}
FEISHU_CLARIFIED_FIELDS = {
"title", "details", "problemStatement", "expectedOutcome", "acceptance",
"intakeStatus", "ackTaskId", "attachments", "updatedAt",
}
FEISHU_CONFIG_FIELDS = {"provider", "workflow", "profile", "baseToken", "tableId", "viewId", "fields"}
FEISHU_SOURCE_FIELDS = {
"kind", "workflow", "ref", "recordId", "updatedAt", "approvedRevision",
"approvedPayloadHash",
}
FEISHU_WORKFLOWS = {"read-only-v1", "reviewed-writeback-v1"}
FEISHU_WORKFLOWS = {"read-only-v1", "reviewed-writeback-v1", "clarified-writeback-v1"}
FEISHU_PROFILE_RE = re.compile(r"^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$")
FEISHU_SOURCE_REF_RE = re.compile(r"^feishu-base:sha256:[0-9a-f]{64}$")
FEISHU_RECORD_ID_RE = re.compile(r"^[A-Za-z0-9][A-Za-z0-9._-]{0,255}$")
@@ -135,7 +140,34 @@ DISPATCH_FIELDS = {
"developer",
"test",
"rounds",
"environmentIncidents",
}
ENVIRONMENT_INCIDENT_FIELDS = {
"id",
"attemptId",
"role",
"phase",
"status",
"summary",
"evidence",
"impact",
"recoveryAction",
"userAction",
"reportedAt",
"resolvedAt",
}
ENVIRONMENT_INCIDENT_ROLES = {"coordinator", "developer", "test"}
ENVIRONMENT_INCIDENT_PHASES = {
"launch",
"orchestration",
"service",
"test_data",
"browser",
"tooling",
"permissions",
"other",
}
ENVIRONMENT_INCIDENT_STATUSES = {"open", "resolved"}
KNOWLEDGE_CANDIDATE_REQUIRED_FIELDS = {
"kind",
"title",
@@ -541,7 +573,7 @@ def validate_delivery_runs(
or re.fullmatch(r"sha256:[0-9a-f]{64}", digest) is None
):
errors.append(f"{artifact_where}.digest: 必须是 sha256:<64 hex> 或 null")
if status in {"review_ready", "released"} and not _nonempty_string(digest):
if status in {"validation_ready", "review_ready", "released"} and not _nonempty_string(digest):
errors.append(
f"{artifact_where}.digest: status={status!r} 时必须填写"
)
@@ -584,7 +616,7 @@ def validate_delivery_runs(
not _nonempty_string(item) for item in evidence
):
errors.append(f"{where}.evidence: 必须是字符串列表")
elif status in {"blocked", "failed", "review_ready", "released", "skipped"} and not evidence:
elif status in {"blocked", "failed", "validation_ready", "review_ready", "released", "skipped"} and not evidence:
errors.append(f"{where}.evidence: status={status!r} 时不能为空")
if not _nonempty_string(run.get("updatedAt")):
errors.append(f"{where}.updatedAt: 必须是非空字符串")
@@ -691,18 +723,30 @@ def validate_builtin(data: dict) -> list[str]:
if not isinstance(value, str) or not value.strip() or any(char.isspace() for char in value):
errors.append(f"project.bugIntake.{key} 必须是无空白非空字符串")
fields = intake.get("fields")
expected_fields = (
FEISHU_CLARIFIED_FIELDS
if workflow == "clarified-writeback-v1"
else FEISHU_REQUIRED_FIELDS
)
allowed_fields = expected_fields | (
set() if workflow == "clarified-writeback-v1" else FEISHU_OPTIONAL_FIELDS
)
if (
not isinstance(fields, dict)
or not FEISHU_REQUIRED_FIELDS.issubset(fields)
or not set(fields).issubset(FEISHU_REQUIRED_FIELDS | FEISHU_OPTIONAL_FIELDS)
or not expected_fields.issubset(fields)
or not set(fields).issubset(allowed_fields)
):
errors.append("project.bugIntake.fields 必须且只能映射所需逻辑字段")
elif any(not isinstance(v, str) or not v.strip() or any(c.isspace() for c in v) for v in fields.values()):
errors.append("project.bugIntake.fields 字段值必须是无空白非空字符串")
elif len(set(fields.values())) != len(fields):
errors.append("project.bugIntake.fields 字段值不能重复")
elif workflow == "reviewed-writeback-v1" and "fixLogic" not in fields:
errors.append("reviewed-writeback-v1 必须映射 project.bugIntake.fields.fixLogic")
elif workflow == "reviewed-writeback-v1" and not {
"fixLogic", "priority"
}.issubset(fields):
errors.append(
"reviewed-writeback-v1 必须映射 project.bugIntake.fields.fixLogic 和 priority"
)
if (
"knowledgeFile" in project
and project.get("knowledgeFile") != "docs/ack/knowledge.yaml"
@@ -829,8 +873,8 @@ def validate_builtin(data: dict) -> list[str]:
if source_workflow not in FEISHU_WORKFLOWS:
errors.append(f"{where}.source.workflow: 非法")
if (
project_intake_workflow == "reviewed-writeback-v1"
and source_workflow != "reviewed-writeback-v1"
project_intake_workflow in {"reviewed-writeback-v1", "clarified-writeback-v1"}
and source_workflow != project_intake_workflow
and status not in {"verified", "leftover"}
):
errors.append(
@@ -838,24 +882,34 @@ def validate_builtin(data: dict) -> list[str]:
)
approved_revision = source.get("approvedRevision")
stored_payload_hash = source.get("approvedPayloadHash")
if source_workflow == "reviewed-writeback-v1" and approved_revision is None:
errors.append(f"{where}.source.approvedRevision: reviewed workflow 必填")
is_approved_workflow = source_workflow in {"reviewed-writeback-v1", "clarified-writeback-v1"}
if is_approved_workflow and approved_revision is None:
errors.append(f"{where}.source.approvedRevision: writeback workflow 必填")
elif approved_revision is not None and (
not isinstance(approved_revision, str)
or re.fullmatch(r"sha256:[0-9a-f]{64}", approved_revision) is None
):
errors.append(f"{where}.source.approvedRevision: 必须是 sha256 revision")
if source_workflow == "reviewed-writeback-v1":
if is_approved_workflow:
if (
not isinstance(stored_payload_hash, str)
or re.fullmatch(r"sha256:[0-9a-f]{64}", stored_payload_hash) is None
):
errors.append(f"{where}.source.approvedPayloadHash: reviewed workflow 必填")
required_strings = ("title", "priority", "actual", "expected", "fixLogic")
required_strings = (
("title", "description", "actual", "expected")
if source_workflow == "clarified-writeback-v1"
else ("title", "priority", "actual", "expected", "fixLogic")
)
for field in required_strings:
if not _nonempty_string(task.get(field)):
errors.append(f"{where}.{field}: reviewed workflow 必须是非空字符串")
for field in ("stepsToReproduce", "acceptanceCriteria"):
required_lists = (
("acceptanceCriteria",)
if source_workflow == "clarified-writeback-v1"
else ("stepsToReproduce", "acceptanceCriteria")
)
for field in required_lists:
items = task.get(field)
if (
not isinstance(items, list)
@@ -959,6 +1013,72 @@ def validate_builtin(data: dict) -> list[str]:
f"{where}.dispatch.rounds: round 必须从 1 连续递增且不重复"
)
incidents = dispatch.get("environmentIncidents", [])
if not isinstance(incidents, list):
errors.append(f"{where}.dispatch.environmentIncidents: 必须是列表")
else:
seen_incident_ids: set[str] = set()
for incident_index, incident in enumerate(incidents):
incident_where = (
f"{where}.dispatch.environmentIncidents[{incident_index}]"
)
if not isinstance(incident, dict):
errors.append(f"{incident_where}: 必须是对象")
continue
reject_unknown_fields(
incident,
ENVIRONMENT_INCIDENT_FIELDS,
incident_where,
errors,
)
incident_id = incident.get("id")
expected_id = (
f"{tid}-ENV-{incident_index + 1}"
if isinstance(tid, str)
else None
)
if not isinstance(incident_id, str) or incident_id != expected_id:
errors.append(f"{incident_where}.id: 应为 {expected_id}")
elif incident_id in seen_incident_ids:
errors.append(f"{incident_where}.id: 不能重复 {incident_id}")
else:
seen_incident_ids.add(incident_id)
if incident.get("role") not in ENVIRONMENT_INCIDENT_ROLES:
errors.append(
f"{incident_where}.role: 必须是 coordinator/developer/test"
)
if incident.get("phase") not in ENVIRONMENT_INCIDENT_PHASES:
errors.append(f"{incident_where}.phase: 非法环境阶段")
incident_status = incident.get("status")
if incident_status not in ENVIRONMENT_INCIDENT_STATUSES:
errors.append(f"{incident_where}.status: 必须是 open/resolved")
for field in (
"summary",
"evidence",
"impact",
"recoveryAction",
"userAction",
"reportedAt",
):
if not _nonempty_string(incident.get(field)):
errors.append(f"{incident_where}.{field}: 必须是非空字符串")
if "attemptId" in incident and not (
incident["attemptId"] is None
or _nonempty_string(incident["attemptId"])
):
errors.append(f"{incident_where}.attemptId: 必须是字符串或 null")
if "resolvedAt" in incident and not (
incident["resolvedAt"] is None
or _nonempty_string(incident["resolvedAt"])
):
errors.append(f"{incident_where}.resolvedAt: 必须是字符串或 null")
if incident_status == "resolved" and not _nonempty_string(
incident.get("resolvedAt")
):
errors.append(
f"{incident_where}: resolved 必须填写 resolvedAt"
)
resolution = task.get("resolution")
if "resolution" in task:
if not isinstance(resolution, dict):