feat(P59): L1 module lifecycle pipeline — author workflows + schema + tests

EXECUTE stage. Authors the modules-lifecycle pipeline that matrix-tests
every L1 module's examples/{simple,complex}.yml contracts through
apply→modify→destroy against live AWS. No per-module Python.

New files:
- pipelines/modules-lifecycle.yml: declarative contract (5 stages:
  platform-vpc-apply, lifecycle-apply, lifecycle-modify, lifecycle-destroy,
  platform-vpc-destroy). Matrix over 12 L1 modules.
- .gitea/workflows/modules-lifecycle.yml + .github/workflows/modules-lifecycle.yml:
  byte-identical workflows. 3 jobs: platform-vpc-apply (prerequisite),
  lifecycle (matrix of 12 modules × apply/modify/destroy), platform-vpc-destroy
  (always runs, cleanup). Triggers: pull_request to main + workflow_dispatch.
- schemas/modules-lifecycle-pipeline.schema.json: schema for the new pipeline
  shape (extends pipeline.schema.json with workflow_dispatch + matrix).

Tests (tests/test_pipeline_contract.py):
- TestModulesLifecyclePipeline: 12 tests (schema valid, contract validates,
  byte-identical, workflow name, 3 jobs, triggers, matrix lists all 12 L1
  modules, apply/modify/destroy steps present, platform-vpc-destroy always runs).

pipelines/README.md: added modules-lifecycle to the pipeline table.

Regression: 479 passed, 0 skipped, 5 deselected (slow).

---ci---
project: acdl
phase: P59
milestone: v1.11
status: execute
---/ci---
This commit is contained in:
Jon Chery
2026-07-28 15:55:54 +00:00
parent 52df314dd8
commit 7ba72bf656
6 changed files with 435 additions and 1 deletions
+67 -1
View File
@@ -518,4 +518,70 @@ class TestPlatformWorkflows:
checkout = next(
s for s in release_job["steps"] if "checkout" in s.get("uses", "")
)
assert checkout["with"]["fetch-depth"] == 0
assert checkout["with"]["fetch-depth"] == 0
class TestModulesLifecyclePipeline:
"""P59: modules-lifecycle pipeline — schema, byte-identical, matrix."""
def test_schema_is_valid_json_schema(self):
schema = json.load(open(ROOT / "schemas/modules-lifecycle-pipeline.schema.json"))
jsonschema.Draft202012Validator.check_schema(schema)
def test_contract_validates_against_schema(self):
schema = json.load(open(ROOT / "schemas/modules-lifecycle-pipeline.schema.json"))
contract = _load_yaml("pipelines/modules-lifecycle.yml")
jsonschema.validate(contract, schema)
def test_gitea_workflow_exists(self):
assert (ROOT / ".gitea/workflows/modules-lifecycle.yml").is_file()
def test_github_workflow_exists(self):
assert (ROOT / ".github/workflows/modules-lifecycle.yml").is_file()
def test_workflows_are_byte_identical(self):
gitea = open(ROOT / ".gitea/workflows/modules-lifecycle.yml", "rb").read()
github = open(ROOT / ".github/workflows/modules-lifecycle.yml", "rb").read()
assert gitea == github, "Gitea and GitHub workflows must be byte-identical"
def test_workflow_name_matches_contract(self):
wf = _load_workflow(".gitea/workflows/modules-lifecycle.yml")
contract = _load_yaml("pipelines/modules-lifecycle.yml")
assert wf["name"] == contract["name"]
def test_workflow_has_three_jobs(self):
wf = _load_workflow(".gitea/workflows/modules-lifecycle.yml")
assert set(wf["jobs"].keys()) == {"platform-vpc-apply", "lifecycle", "platform-vpc-destroy"}
def test_workflow_triggers_match_contract(self):
wf = _load_workflow(".gitea/workflows/modules-lifecycle.yml")
contract = _load_yaml("pipelines/modules-lifecycle.yml")
assert wf["on"]["pull_request"]["branches"] == contract["triggers"]["pull_request"]
assert "workflow_dispatch" in wf["on"]
def test_matrix_lists_all_12_l1_modules(self):
wf = _load_workflow(".gitea/workflows/modules-lifecycle.yml")
matrix_modules = wf["jobs"]["lifecycle"]["strategy"]["matrix"]["module"]
expected = {"s3", "kms-key", "ecr", "ecs-cluster", "iam-role", "cloudfront",
"waf", "vpc", "alb", "ecs-service", "rds", "uptime"}
assert set(matrix_modules) == expected
def test_contract_matrix_lists_all_12_l1_modules(self):
contract = _load_yaml("pipelines/modules-lifecycle.yml")
assert set(contract["matrix"]["modules"]) == {
"s3", "kms-key", "ecr", "ecs-cluster", "iam-role", "cloudfront",
"waf", "vpc", "alb", "ecs-service", "rds", "uptime"
}
def test_lifecycle_job_has_apply_modify_destroy_steps(self):
wf = _load_workflow(".gitea/workflows/modules-lifecycle.yml")
steps = wf["jobs"]["lifecycle"]["steps"]
step_names = [s.get("name", "") for s in steps]
assert any("Apply" in n for n in step_names), "Missing apply step"
assert any("Modify" in n for n in step_names), "Missing modify step"
assert any("Destroy" in n for n in step_names), "Missing destroy step"
def test_platform_vpc_destroy_always_runs(self):
wf = _load_workflow(".gitea/workflows/modules-lifecycle.yml")
destroy_job = wf["jobs"]["platform-vpc-destroy"]
assert destroy_job.get("if") == "always()", "platform-vpc-destroy must always run (cleanup)"