feat(ack): add project delivery workflow
This commit is contained in:
@@ -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",
|
||||
|
||||
Reference in New Issue
Block a user