feat(ack): add Feishu bug review approval gate
This commit is contained in:
@@ -24,6 +24,7 @@ import re
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
from approval_payload import approval_payload_hash
|
||||
from yaml_subset import (
|
||||
DuplicateKeyError,
|
||||
YamlSubsetError,
|
||||
@@ -117,8 +118,13 @@ FEISHU_REQUIRED_FIELDS = {
|
||||
"title", "actual", "expected", "stepsToReproduce", "acceptance", "priority",
|
||||
"attachments", "updatedAt",
|
||||
}
|
||||
FEISHU_CONFIG_FIELDS = {"provider", "profile", "baseToken", "tableId", "viewId", "fields"}
|
||||
FEISHU_SOURCE_FIELDS = {"kind", "ref", "recordId", "updatedAt"}
|
||||
FEISHU_OPTIONAL_FIELDS = {"fixLogic"}
|
||||
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_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}$")
|
||||
@@ -598,6 +604,7 @@ def validate_with_schema(data: dict, schema_path: Path) -> list[str]:
|
||||
|
||||
def validate_builtin(data: dict) -> list[str]:
|
||||
errors: list[str] = []
|
||||
project_intake_workflow = "read-only-v1"
|
||||
|
||||
def validate_string_fields(
|
||||
value: dict,
|
||||
@@ -671,6 +678,11 @@ def validate_builtin(data: dict) -> list[str]:
|
||||
reject_unknown_fields(intake, FEISHU_CONFIG_FIELDS, "project.bugIntake", errors)
|
||||
if intake.get("provider") != "feishu-base":
|
||||
errors.append("project.bugIntake.provider 必须是 feishu-base")
|
||||
workflow = intake.get("workflow", "read-only-v1")
|
||||
if workflow in FEISHU_WORKFLOWS:
|
||||
project_intake_workflow = workflow
|
||||
if workflow not in FEISHU_WORKFLOWS:
|
||||
errors.append("project.bugIntake.workflow 非法")
|
||||
profile = intake.get("profile")
|
||||
if not isinstance(profile, str) or FEISHU_PROFILE_RE.fullmatch(profile) is None:
|
||||
errors.append("project.bugIntake.profile 非法")
|
||||
@@ -679,12 +691,18 @@ 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")
|
||||
if not isinstance(fields, dict) or set(fields) != FEISHU_REQUIRED_FIELDS:
|
||||
if (
|
||||
not isinstance(fields, dict)
|
||||
or not FEISHU_REQUIRED_FIELDS.issubset(fields)
|
||||
or not set(fields).issubset(FEISHU_REQUIRED_FIELDS | FEISHU_OPTIONAL_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")
|
||||
if (
|
||||
"knowledgeFile" in project
|
||||
and project.get("knowledgeFile") != "docs/ack/knowledge.yaml"
|
||||
@@ -776,6 +794,7 @@ def validate_builtin(data: dict) -> list[str]:
|
||||
"assignee",
|
||||
"component",
|
||||
"description",
|
||||
"fixLogic",
|
||||
"expected",
|
||||
"actual",
|
||||
},
|
||||
@@ -783,7 +802,7 @@ def validate_builtin(data: dict) -> list[str]:
|
||||
)
|
||||
validate_string_lists(
|
||||
task,
|
||||
{"specRefs", "testRefs", "stepsToReproduce"},
|
||||
{"specRefs", "testRefs", "stepsToReproduce", "acceptanceCriteria"},
|
||||
where,
|
||||
)
|
||||
validate_object_fields(task, {"evidence", "verification"}, where)
|
||||
@@ -806,6 +825,52 @@ def validate_builtin(data: dict) -> list[str]:
|
||||
errors.append(f"{where}.source.recordId: 必须是合法飞书记录 ID")
|
||||
if not _nonempty_string(source.get("updatedAt")):
|
||||
errors.append(f"{where}.source.updatedAt: 必须是非空字符串")
|
||||
source_workflow = source.get("workflow", "read-only-v1")
|
||||
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"
|
||||
and status not in {"verified", "leftover"}
|
||||
):
|
||||
errors.append(
|
||||
f"{where}.source.workflow: reviewed 项目的可执行飞书任务必须先迁移审核"
|
||||
)
|
||||
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 必填")
|
||||
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 (
|
||||
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")
|
||||
for field in required_strings:
|
||||
if not _nonempty_string(task.get(field)):
|
||||
errors.append(f"{where}.{field}: reviewed workflow 必须是非空字符串")
|
||||
for field in ("stepsToReproduce", "acceptanceCriteria"):
|
||||
items = task.get(field)
|
||||
if (
|
||||
not isinstance(items, list)
|
||||
or not items
|
||||
or any(not _nonempty_string(item) for item in items)
|
||||
):
|
||||
errors.append(f"{where}.{field}: reviewed workflow 必须是非空字符串列表")
|
||||
if (
|
||||
isinstance(stored_payload_hash, str)
|
||||
and re.fullmatch(r"sha256:[0-9a-f]{64}", stored_payload_hash)
|
||||
and stored_payload_hash != approval_payload_hash(task)
|
||||
):
|
||||
errors.append(f"{where}.source.approvedPayloadHash: 与任务审核字段不匹配")
|
||||
elif stored_payload_hash is not None:
|
||||
errors.append(f"{where}.source.approvedPayloadHash: 只允许 reviewed workflow")
|
||||
|
||||
validate_knowledge_fields(task, where, status, errors)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user