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
+28
View File
@@ -0,0 +1,28 @@
# ACDL Patterns Plan Pipeline — GitHub Actions (production)
#
# Runs on PRs to main. For each L2 module, runs a plan-only (offline
# --check-only mode: resolves the sample contract for the module, runs the
# adapter, validates the emitted Terraform structure).
name: acdl-patterns-plan
on:
pull_request:
branches: [main]
jobs:
pattern-plan:
name: Pattern plan (${{ matrix.module }})
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
module: [static-assets, microservice]
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: "3.12"
- name: Install dependencies
run: pip install jsonschema pyyaml boto3
- name: Pattern plan check (${{ matrix.module }})
run: bash scripts/run_pattern_plan.sh --check-only ${{ matrix.module }}
+146
View File
@@ -0,0 +1,146 @@
# ACDL Platform Test Pipeline — GitHub Actions (production)
#
# Runs on PRs to main. Replaces ci.yml for PRs (ci.yml stays for push-to-main).
# Four stages: lint, unit-test, integration-test, schema-validation.
#
# Shell reproducibility: scripts/run_ci.sh runs lint + test + check-only locally.
# The integration-test stage runs run_platform.sh --check-only for every
# contracts/*.yaml file. The schema-validation stage validates schemas, module
# interfaces, compositions, and example contracts.
name: acdl-platform-test
on:
pull_request:
branches: [main]
jobs:
lint:
name: Lint
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: "3.12"
- name: Compile all Python files
run: |
python3 -m py_compile \
core/confidence_signal.py \
core/outbox_writer.py \
core/contract_resolver.py \
core/environment_check.py \
core/output_publisher.py \
core/lambda/contract_ingestor.py \
adapters/terraform/adapter.py \
adapters/terraform/policy/checkov_adapter.py \
adapters/wiz/wiz_adapter.py \
adapters/kyverno/kyverno_adapter.py \
scripts/push_consumer_image.py
unit-test:
name: Unit tests
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: "3.12"
- name: Install test dependencies
run: pip install -r requirements-test.txt
- name: Run pytest
run: python3 -m pytest tests/ -v --tb=short
integration-test:
name: Integration test (all sample contracts)
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: "3.12"
- name: Install runtime dependencies
run: pip install jsonschema pyyaml boto3
- name: Run platform check-only for every sample contract
run: |
for contract in contracts/*.yaml; do
echo "--- Testing $contract ---"
bash scripts/run_platform.sh --check-only "$contract"
done
schema-validation:
name: Schema + module validation
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: "3.12"
- name: Install dependencies
run: pip install jsonschema pyyaml
- name: Validate all schemas
run: |
python3 -c "
import json, glob, jsonschema
for schema_file in glob.glob('schemas/*.json'):
if 'contract.schema' in schema_file:
continue # has no self-validation
schema = json.load(open(schema_file))
# self-validate if it has a \$id
try:
jsonschema.Draft202012Validator.check_schema(schema)
except jsonschema.SchemaError as e:
raise SystemExit(f'{schema_file}: {e}')
print(f'{schema_file}: valid')
"
- name: Validate all module interfaces against stack.schema.json
run: |
python3 -c "
import json, glob, jsonschema, os
stack_schema = json.load(open('schemas/stack.schema.json'))
for iface_file in glob.glob('modules/l1/*/interface.json'):
try:
iface = json.load(open(iface_file))
# Validate basic structure (name, version, kind, type, inputs, outputs)
assert 'name' in iface, f'{iface_file}: missing name'
assert 'version' in iface, f'{iface_file}: missing version'
assert 'kind' in iface, f'{iface_file}: missing kind'
assert iface['kind'] == 'l1', f'{iface_file}: expected kind=l1'
assert 'type' in iface, f'{iface_file}: missing type'
assert 'inputs' in iface, f'{iface_file}: missing inputs'
assert 'outputs' in iface, f'{iface_file}: missing outputs'
print(f'{iface_file}: valid L1')
except Exception as e:
raise SystemExit(f'{iface_file}: {e}')
for comp_file in glob.glob('modules/l2/*/composition.json'):
try:
comp = json.load(open(comp_file))
assert 'name' in comp, f'{comp_file}: missing name'
assert 'version' in comp, f'{comp_file}: missing version'
assert 'kind' in comp, f'{comp_file}: missing kind'
assert comp['kind'] == 'l2', f'{comp_file}: expected kind=l2'
assert 'children' in comp, f'{comp_file}: missing children'
assert 'wires' in comp, f'{comp_file}: missing wires'
assert 'outputs' in comp, f'{comp_file}: missing outputs'
print(f'{comp_file}: valid L2')
except Exception as e:
raise SystemExit(f'{comp_file}: {e}')
"
- name: Validate module example contracts
run: |
python3 -c "
import json, yaml, glob, jsonschema
schema = json.load(open('schemas/contract.schema.json'))
# Validate example contracts if they exist
for example in glob.glob('modules/*/examples/*.yaml'):
try:
contract = yaml.safe_load(open(example))
jsonschema.validate(contract, schema)
print(f'{example}: valid contract')
except Exception as e:
print(f'{example}: SKIP (not a contract or invalid: {e})')
# Also validate all sample contracts in contracts/
for contract_file in glob.glob('contracts/*.yaml'):
contract = yaml.safe_load(open(contract_file))
jsonschema.validate(contract, schema)
print(f'{contract_file}: valid contract')
"
+28
View File
@@ -0,0 +1,28 @@
# ACDL Primitives Plan Pipeline — GitHub Actions (production)
#
# Runs on PRs to main. For each L1 primitive, runs a plan-only (offline
# --check-only mode: resolves the primitive's instance.json, runs the adapter,
# validates the emitted Terraform structure).
name: acdl-primitives-plan
on:
pull_request:
branches: [main]
jobs:
primitive-plan:
name: Primitive plan (${{ matrix.primitive }})
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
primitive: [s3, vpc, ecs-cluster, ecs-service, iam-role, alb, ecr, cloudfront, waf]
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: "3.12"
- name: Install dependencies
run: pip install jsonschema pyyaml boto3
- name: Primitive plan check (${{ matrix.primitive }})
run: bash scripts/run_primitive_plan.sh --check-only ${{ matrix.primitive }}
+92
View File
@@ -0,0 +1,92 @@
# ACDL Release Pipeline — GitHub Actions (production)
#
# Runs on push to main. Computes the next semver tag from the latest tag +
# commit history, creates the tag, updates floating MAJOR.MINOR and MAJOR tags,
# and creates a GitHub release with auto-generated notes.
#
# Semver policy:
# - Regular phase commit -> bump PATCH (v1.6.0 -> v1.6.1)
# - Milestone completion ("docs(milestone): complete") -> bump MINOR (v1.6.1 -> v1.7.0)
# - Major bumps are manual (not implemented here).
name: acdl-release
on:
push:
branches: [main]
jobs:
release:
name: Compute semver + update tags
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0 # need full history for tag computation
- uses: actions/setup-python@v5
with:
python-version: "3.12"
- name: Compute next version
id: version
run: |
# Get the latest tag
LATEST_TAG=$(git describe --tags --abbrev=0 2>/dev/null || echo "v0.0.0")
echo "Latest tag: $LATEST_TAG"
# Parse the version
MAJOR=$(echo "$LATEST_TAG" | sed -n 's/v\([0-9]*\)\.\([0-9]*\)\.\([0-9]*\)/\1/p')
MINOR=$(echo "$LATEST_TAG" | sed -n 's/v\([0-9]*\)\.\([0-9]*\)\.\([0-9]*\)/\2/p')
PATCH=$(echo "$LATEST_TAG" | sed -n 's/v\([0-9]*\)\.\([0-9]*\)\.\([0-9]*\)/\3/p')
# Check if this is a milestone completion (look for "docs(milestone): complete" in the latest commits)
if git log --format='%s' -5 | grep -q 'docs(milestone): complete'; then
# Milestone completion -> bump minor
MINOR=$((MINOR + 1))
PATCH=0
else
# Regular phase -> bump patch
PATCH=$((PATCH + 1))
fi
NEW_TAG="v${MAJOR}.${MINOR}.${PATCH}"
MAJOR_MINOR_TAG="v${MAJOR}.${MINOR}"
MAJOR_TAG="v${MAJOR}"
echo "new_tag=$NEW_TAG" >> $GITHUB_OUTPUT
echo "major_minor_tag=$MAJOR_MINOR_TAG" >> $GITHUB_OUTPUT
echo "major_tag=$MAJOR_TAG" >> $GITHUB_OUTPUT
echo "Next version: $NEW_TAG"
- name: Create version tag
run: |
git tag ${{ steps.version.outputs.new_tag }}
git push origin ${{ steps.version.outputs.new_tag }}
- name: Update floating MAJOR.MINOR tag
run: |
git tag -f ${{ steps.version.outputs.major_minor_tag }} ${{ steps.version.outputs.new_tag }}
git push origin ${{ steps.version.outputs.major_minor_tag }} --force
- name: Update floating MAJOR tag
run: |
git tag -f ${{ steps.version.outputs.major_tag }} ${{ steps.version.outputs.new_tag }}
git push origin ${{ steps.version.outputs.major_tag }} --force
- name: Create GitHub release
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
# Generate release body from commit history since last tag
PREV_TAG=$(git describe --tags --abbrev=0 HEAD^ 2>/dev/null || echo "")
if [ -n "$PREV_TAG" ]; then
BODY=$(git log --format='- %s' "$PREV_TAG"..HEAD)
else
BODY=$(git log --format='- %s' HEAD)
fi
gh release create ${{ steps.version.outputs.new_tag }} \
--title "ACDL ${{ steps.version.outputs.new_tag }}" \
--notes "$BODY" \
--generate-notes || true