feat(P26): 3 platform pipelines + release job with semver/tag updates

Phase 26 — platform-pipelines-and-release-automation:

- platform-test.yml: PR pipeline (lint + unit-test + integration-test +
  schema-validation) replacing ci.yml for PRs; integration-test runs
  run_platform.sh --check-only for every contracts/*.yaml
- primitives-plan.yml: PR pipeline with matrix over all 9 L1 primitives
  (s3, vpc, ecs-cluster, ecs-service, iam-role, alb, ecr, cloudfront, waf)
- patterns-plan.yml: PR pipeline with matrix over all 2 L2 modules
  (static-assets, microservice)
- release.yml: push-to-main pipeline computing next semver tag (PATCH for
  regular phases, MINOR for milestone completions), updating floating
  MAJOR.MINOR + MAJOR tags, and creating GitHub releases
- run_primitive_plan.sh: plan-only/check-only runner for a single L1
  primitive (adapter compile + structure validation offline)
- run_pattern_plan.sh: plan-only/check-only runner for a single L2 pattern
  (environment check + contract validate + resolve + adapter + structure
  validation offline)
- contracts/microservice.yaml: sample consumer contract for the
  microservice L2 module (schema-compliant scalar inputs)
- instance.json for 8 L1 primitives (vpc, ecs-cluster, ecs-service,
  iam-role, alb, ecr, cloudfront, waf) so the primitives-plan matrix can
  run the adapter offline; s3 already had one
- tests/test_release_logic.py: unit test for semver computation
  (PATCH bump, MINOR bump on milestone, floating tag format)
- tests/test_pipeline_contract.py: 19 new tests validating the 4 platform
  workflows exist and conform (stages, matrices, triggers, permissions)

DEVIATION: The microservice pattern (run_pattern_plan.sh --check-only
microservice + run_platform.sh --check-only contracts/microservice.yaml)
fails at the adapter stage due to a pre-existing resolver ref-id mismatch
for multi-resource L1s (resolver emits ref:vpc.subnet_ids but the expanded
resource id is vpc-subnet). This predates Phase 26 and is out of scope for
pipeline automation; the static-assets pattern passes end-to-end. The
microservice contract is schema-valid and resolves correctly (11
resources); only the adapter compilation of multi-resource L1 refs fails.

VERIFICATION:
- bash scripts/run_ci.sh: PASS (lint + test + check-only)
- python3 -m pytest tests/ -v: 266 passed
- bash scripts/run_primitive_plan.sh --check-only s3: PASS
- bash scripts/run_pattern_plan.sh --check-only static-assets: PASS
- All 9 primitives pass run_primitive_plan.sh --check-only
- All instance.json validate against stack.schema.json

---ci---
project: acdl
phase: 26
milestone: v1.7
status: execute
---/ci---
This commit is contained in:
Jon Chery
2026-07-22 20:13:36 +00:00
parent 4fe794c7a4
commit 90be5839ab
17 changed files with 946 additions and 1 deletions
+84
View File
@@ -0,0 +1,84 @@
#!/usr/bin/env bash
# Run the platform pipeline for a single pattern (plan-only or check-only).
#
# Usage: run_pattern_plan.sh [--check-only] <module-name>
#
# --check-only: offline mode (no AWS) — resolves the sample contract for the
# module, runs the adapter, validates the emitted Terraform structure.
# (default): requires AWS — runs terraform plan on the emitted Terraform.
set -euo pipefail
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
cd "$ROOT"
CHECK_ONLY=0
MODULE=""
for arg in "$@"; do
case "$arg" in
--check-only) CHECK_ONLY=1 ;;
--*) echo "FAIL: unknown flag: $arg" >&2; exit 1 ;;
*) MODULE="$arg" ;;
esac
done
[ -n "$MODULE" ] || { echo "FAIL: module name required" >&2; exit 1; }
CONTRACT="contracts/$MODULE.yaml"
[ -f "$CONTRACT" ] || { echo "FAIL: no sample contract at $CONTRACT for module '$MODULE'" >&2; exit 1; }
WORK="/tmp/acdl_pattern_plan_$MODULE"
rm -rf "$WORK"; mkdir -p "$WORK"
echo "=== Pattern plan: $MODULE ==="
echo ""
echo "--- Step 1: environment check ---"
python3 core/environment_check.py "$CONTRACT" || { echo "FAIL: environment not bound" >&2; exit 1; }
echo ""
echo "--- Step 2: validate contract ---"
python3 -c "
import json, yaml, jsonschema
schema = json.load(open('schemas/contract.schema.json'))
contract = yaml.safe_load(open('$CONTRACT'))
jsonschema.validate(contract, schema)
print(f'contract: module={contract[\"module\"]} env={contract[\"environment\"]}')
"
echo ""
echo "--- Step 3: resolve contract -> stack ---"
python3 core/contract_resolver.py "$CONTRACT" "$WORK/stack.json" || { echo "FAIL: resolver failed" >&2; exit 1; }
python3 -c "import json; d=json.load(open('$WORK/stack.json')); print(f'stack: {d[\"stack\"][\"name\"]} {len(d[\"resources\"])} resource(s)')"
echo ""
echo "--- Step 4: adapter compiles stack -> terraform ---"
OUT_DIR="$WORK/terraform"
python3 adapters/terraform/adapter.py "$WORK/stack.json" "$OUT_DIR" || { echo "FAIL: adapter failed" >&2; exit 1; }
echo "adapter: emitted $OUT_DIR/{main.tf,terraform.tf,providers.tf}"
if [ "$CHECK_ONLY" = "1" ]; then
echo ""
echo "--- Step 5: validate adapter output structure (offline) ---"
python3 -c "
import json, os
d = json.load(open('$WORK/stack.json'))
assert d['stack']['kind'] == 'l2', f\"expected l2, got {d['stack']['kind']}\"
assert len(d['resources']) >= 1
tf_dir = '$OUT_DIR'
for f in ('main.tf', 'terraform.tf', 'providers.tf'):
assert os.path.isfile(os.path.join(tf_dir, f)), f'{f} missing'
main = open(os.path.join(tf_dir, 'main.tf')).read()
assert len(main) > 0, 'main.tf is empty'
print(f'pattern $MODULE: adapter output OK ({len(d[\"resources\"])} resource(s))')
"
echo ""
echo "=== PATTERN CHECK OK ($MODULE) ==="
exit 0
fi
echo ""
echo "--- Step 5: terraform init + validate + plan ---"
cd "$OUT_DIR"
terraform init -backend=false -input=false
terraform validate
terraform plan -lock=false -input=false -out=tfplan
echo "=== PATTERN PLAN OK ($MODULE) ==="
+65
View File
@@ -0,0 +1,65 @@
#!/usr/bin/env bash
# Run the platform pipeline for a single primitive (plan-only or check-only).
#
# Usage: run_primitive_plan.sh [--check-only] <primitive-name>
#
# --check-only: offline mode (no AWS) — resolves the primitive's instance.json,
# runs the adapter, validates the emitted Terraform structure.
# (default): requires AWS — runs terraform plan on the emitted Terraform.
set -euo pipefail
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
cd "$ROOT"
CHECK_ONLY=0
PRIMITIVE=""
for arg in "$@"; do
case "$arg" in
--check-only) CHECK_ONLY=1 ;;
--*) echo "FAIL: unknown flag: $arg" >&2; exit 1 ;;
*) PRIMITIVE="$arg" ;;
esac
done
[ -n "$PRIMITIVE" ] || { echo "FAIL: primitive name required" >&2; exit 1; }
INSTANCE="modules/l1/$PRIMITIVE/instance.json"
[ -f "$INSTANCE" ] || { echo "FAIL: no instance.json for primitive '$PRIMITIVE'" >&2; exit 1; }
WORK="/tmp/acdl_primitive_plan_$PRIMITIVE"
rm -rf "$WORK"; mkdir -p "$WORK"
echo "=== Primitive plan: $PRIMITIVE ==="
echo ""
echo "--- Step 1: adapter compiles instance -> terraform ---"
OUT_DIR="$WORK/terraform"
python3 adapters/terraform/adapter.py "$INSTANCE" "$OUT_DIR" || { echo "FAIL: adapter failed" >&2; exit 1; }
echo "adapter: emitted $OUT_DIR/{main.tf,terraform.tf,providers.tf}"
if [ "$CHECK_ONLY" = "1" ]; then
echo ""
echo "--- Step 2: validate adapter output structure (offline) ---"
python3 -c "
import json, os
d = json.load(open('$INSTANCE'))
assert d['stack']['kind'] == 'l1', f\"expected l1, got {d['stack']['kind']}\"
assert len(d['resources']) >= 1
tf_dir = '$OUT_DIR'
for f in ('main.tf', 'terraform.tf', 'providers.tf'):
assert os.path.isfile(os.path.join(tf_dir, f)), f'{f} missing'
main = open(os.path.join(tf_dir, 'main.tf')).read()
assert len(main) > 0, 'main.tf is empty'
print(f'primitive $PRIMITIVE: adapter output OK ({len(d[\"resources\"])} resource(s))')
"
echo ""
echo "=== PRIMITIVE CHECK OK ($PRIMITIVE) ==="
exit 0
fi
echo ""
echo "--- Step 2: terraform init + validate + plan ---"
cd "$OUT_DIR"
terraform init -backend=false -input=false
terraform validate
terraform plan -lock=false -input=false -out=tfplan
echo "=== PRIMITIVE PLAN OK ($PRIMITIVE) ==="