feat(ack): add project delivery workflow

This commit is contained in:
2026-08-01 12:35:42 +08:00
parent f08edb6452
commit 2c3d91c75c
28 changed files with 2559 additions and 67 deletions
+130 -1
View File
@@ -180,7 +180,7 @@ class AckTaskValidationTests(unittest.TestCase):
with self.subTest(invalid_semver=invalid):
self.assertIsNone(ack_pattern.fullmatch(invalid))
current_gate, orchestration_gate, receipts_gate = schema["allOf"]
current_gate, orchestration_gate, receipts_gate, delivery_gate = schema["allOf"]
current_pattern = re.compile(
current_gate["if"]["properties"]["ackVersion"]["pattern"]
)
@@ -218,6 +218,15 @@ class AckTaskValidationTests(unittest.TestCase):
gate["then"]["properties"]["tasks"]["$ref"],
"#/definitions/launchableTasks",
)
self.assertIn("deliveryRuns", delivery_gate["then"]["required"])
delivery_run = schema["definitions"]["deliveryRun"]
revision_gate = delivery_run["allOf"][0]
self.assertIn("planned", revision_gate["if"]["properties"]["status"]["enum"])
self.assertEqual(
revision_gate["then"]["properties"]["sourceRevision"]["type"],
"string",
)
role_dispatch = schema["definitions"]["roleDispatch"]
self.assertIn("attemptId", role_dispatch["required"])
@@ -231,6 +240,126 @@ class AckTaskValidationTests(unittest.TestCase):
"string",
)
def test_delivery_run_is_separate_and_requires_verified_tasks(self) -> None:
board = valid_manual_routing_board()
board["project"]["deliveryFile"] = "docs/ack/delivery.yaml"
board["tasks"][0]["status"] = "verified"
board["deliveryRuns"] = [
{
"id": "DR-demo-1",
"profile": "review",
"taskIds": ["T-1"],
"status": "review_ready",
"sourceRevision": "a" * 40,
"configRevision": "b" * 40,
"pullRequest": "https://forge.example/demo/pulls/1",
"artifacts": [
{
"id": "service-deb",
"type": "deb",
"reference": "demo_1.0.0_amd64.deb",
"digest": "sha256:" + "c" * 64,
}
],
"deployments": [
{
"environment": "test-server",
"result": "succeeded",
"evidence": "health endpoint returned 200",
}
],
"evidence": ["CI run 42 passed"],
"updatedAt": "2026-08-01T10:00:00+08:00",
}
]
self.assert_board_accepted_in_all_modes(board)
board["tasks"][0]["status"] = "open"
self.assert_board_rejected_in_all_modes(
board,
"delivery run 只能引用 verified 任务",
)
def test_delivery_runs_and_delivery_file_must_appear_together(self) -> None:
board = valid_manual_routing_board()
board["deliveryRuns"] = []
self.assert_board_rejected_in_all_modes(
board,
"deliveryRuns 存在时 project.deliveryFile 必须存在",
)
board = valid_manual_routing_board()
board["project"]["deliveryFile"] = "docs/ack/delivery.yaml"
self.assert_board_rejected_in_all_modes(
board,
"引用 deliveryFile 的任务板必须包含 deliveryRuns 列表",
)
def test_delivery_run_binds_revisions_and_final_artifact_digest(self) -> None:
board = valid_manual_routing_board()
board["project"]["deliveryFile"] = "docs/ack/delivery.yaml"
board["deliveryRuns"] = [
{
"id": "DR-demo-2",
"profile": "review",
"taskIds": ["T-1"],
"status": "planned",
"sourceRevision": None,
"configRevision": None,
"pullRequest": None,
"artifacts": [],
"deployments": [],
"evidence": [],
"updatedAt": "2026-08-01T10:00:00+08:00",
}
]
self.assert_board_rejected_in_all_modes(
board,
"delivery run 只能引用 verified 任务",
"sourceRevision: status='planned' 时必须填写",
"configRevision: status='planned' 时必须填写",
)
board["tasks"][0]["status"] = "verified"
run = board["deliveryRuns"][0]
run.update(
{
"status": "review_ready",
"sourceRevision": "a" * 40,
"configRevision": "b" * 40,
"pullRequest": "https://forge.example/demo/pulls/2",
"artifacts": [
{
"id": "service-deb",
"type": "deb",
"reference": "demo_1.0.0_amd64.deb",
"digest": None,
}
],
"evidence": ["CI run 43 passed"],
}
)
self.assert_board_rejected_in_all_modes(
board,
"digest: status='review_ready' 时必须填写",
)
run.update(
{
"status": "skipped",
"sourceRevision": None,
"configRevision": None,
"pullRequest": None,
"artifacts": [],
"evidence": [],
}
)
self.assert_board_rejected_in_all_modes(
board,
"evidence: status='skipped' 时不能为空",
)
@unittest.skipUnless(
importlib.util.find_spec("jsonschema") is not None,
"jsonschema is required for the schema-only contract test",