feat(ack): add Feishu bug review approval gate
This commit is contained in:
@@ -16,6 +16,8 @@ REPO_ROOT = Path(__file__).resolve().parents[1]
|
||||
VALIDATOR = REPO_ROOT / "skills" / "ack" / "scripts" / "validate_tasks.py"
|
||||
EXAMPLE = REPO_ROOT / "skills" / "ack" / "examples" / "tasks.example.yaml"
|
||||
SCHEMA = REPO_ROOT / "skills" / "ack" / "templates" / "tasks.schema.json"
|
||||
sys.path.insert(0, str(VALIDATOR.parent))
|
||||
from approval_payload import approval_payload_hash # noqa: E402
|
||||
|
||||
|
||||
def valid_knowledge_board() -> dict:
|
||||
@@ -148,6 +150,93 @@ class AckTaskValidationTests(unittest.TestCase):
|
||||
self.assertEqual(result.returncode, 0, result.stderr)
|
||||
self.assertIn("任务板校验通过", result.stdout)
|
||||
|
||||
def test_approved_feishu_plan_fields_are_validated(self) -> None:
|
||||
board = valid_knowledge_board()
|
||||
board["tasks"][0]["fixLogic"] = "change the parser and preserve legacy input"
|
||||
board["tasks"][0]["acceptanceCriteria"] = [
|
||||
"the submitted value is saved",
|
||||
"the original crash no longer occurs",
|
||||
]
|
||||
self.assert_board_accepted_in_all_modes(board)
|
||||
|
||||
invalid = copy.deepcopy(board)
|
||||
invalid["tasks"][0]["acceptanceCriteria"] = ["valid", 7]
|
||||
self.assert_board_rejected_in_all_modes(invalid, "列表项必须是字符串")
|
||||
|
||||
def test_reviewed_feishu_workflow_requires_fix_logic_and_approval_revision(self) -> None:
|
||||
fields = {
|
||||
"title": "fTitle",
|
||||
"actual": "fActual",
|
||||
"expected": "fExpected",
|
||||
"stepsToReproduce": "fSteps",
|
||||
"acceptance": "fAcceptance",
|
||||
"priority": "fPriority",
|
||||
"attachments": "fAttachments",
|
||||
"updatedAt": "fUpdated",
|
||||
}
|
||||
board = valid_knowledge_board()
|
||||
board["project"]["bugIntake"] = {
|
||||
"provider": "feishu-base",
|
||||
"workflow": "reviewed-writeback-v1",
|
||||
"profile": "tenant-b",
|
||||
"baseToken": "baseToken",
|
||||
"tableId": "tblBugs",
|
||||
"viewId": "vewReview",
|
||||
"fields": fields,
|
||||
}
|
||||
self.assert_board_rejected_in_all_modes(
|
||||
board, "reviewed-writeback-v1 必须映射",
|
||||
)
|
||||
|
||||
board["project"]["bugIntake"]["fields"]["fixLogic"] = "fFixLogic"
|
||||
board["tasks"][0].update({
|
||||
"priority": "P1",
|
||||
"actual": "save crashes",
|
||||
"expected": "save succeeds",
|
||||
"stepsToReproduce": ["open", "save"],
|
||||
"fixLogic": "preserve input while fixing the parser",
|
||||
"acceptanceCriteria": ["save succeeds", "the crash no longer occurs"],
|
||||
})
|
||||
board["tasks"][0]["source"] = {
|
||||
"kind": "feishu-base",
|
||||
"workflow": "reviewed-writeback-v1",
|
||||
"ref": "feishu-base:sha256:" + "a" * 64,
|
||||
"recordId": "recA",
|
||||
"updatedAt": "2026-08-03T12:00:00+08:00",
|
||||
"approvedRevision": "sha256:" + "b" * 64,
|
||||
}
|
||||
board["tasks"][0]["source"]["approvedPayloadHash"] = approval_payload_hash(
|
||||
board["tasks"][0]
|
||||
)
|
||||
self.assert_board_accepted_in_all_modes(board)
|
||||
|
||||
board["tasks"][0]["source"]["approvedRevision"] = "latest"
|
||||
self.assert_board_rejected_in_all_modes(board, "必须是 sha256 revision")
|
||||
|
||||
board["tasks"][0]["source"]["approvedRevision"] = "sha256:" + "b" * 64
|
||||
board["tasks"][0]["fixLogic"] = "silently changed after approval"
|
||||
self.assert_board_rejected_in_all_modes(board, "与任务审核字段不匹配")
|
||||
|
||||
board["tasks"][0]["fixLogic"] = "preserve input while fixing the parser"
|
||||
board["tasks"][0]["source"]["approvedPayloadHash"] = approval_payload_hash(
|
||||
board["tasks"][0]
|
||||
)
|
||||
board["tasks"][0]["description"] = "modify unrelated modules"
|
||||
self.assert_board_rejected_in_all_modes(board, "与任务审核字段不匹配")
|
||||
|
||||
legacy = copy.deepcopy(board)
|
||||
legacy["tasks"][0]["description"] = "validate knowledge fields"
|
||||
legacy["tasks"][0]["source"] = {
|
||||
"kind": "feishu-base",
|
||||
"ref": "feishu-base:sha256:" + "c" * 64,
|
||||
"recordId": "recLegacy",
|
||||
"updatedAt": "2026-08-01T12:00:00+08:00",
|
||||
}
|
||||
self.assert_board_rejected_in_all_modes(legacy, "必须先迁移审核")
|
||||
|
||||
legacy["tasks"][0]["status"] = "verified"
|
||||
self.assert_board_accepted_in_all_modes(legacy)
|
||||
|
||||
def test_legacy_sources_remain_open_while_feishu_sources_are_strict(self) -> None:
|
||||
for legacy_source in (
|
||||
"manual",
|
||||
|
||||
Reference in New Issue
Block a user