031887ec56
Contract surface redesign: - New top-level fields: id (3-6 char acronym → stack.name), name (full → stack.title), infrastructure (map keyed by module name, replaces module:) - Drop uses: field (dead reference; version pin lives in CI workflow uses: line) - Drop top-level module/inputs (now nested under infrastructure map) - Per-module optional version (defaults to latest published from registry) - Multi-module contracts: one file deploys N modules in one pipeline run, resource IDs namespaced with module name to avoid collisions - stack.schema.json: add optional title field for display name Rename: - pipelines/deploy.yaml → pipelines/contract.yml (declarative spec, not a pipeline) - pipelines/ci.yaml → pipelines/ci.yml - All 44 .yaml files → .yml repo-wide (contracts, module examples, kyverno policies) - .acdl/contract.yaml → .acdl/contract.yml Resolver (core/contract_resolver.py): - Rewrite resolve() to loop infrastructure map, default version to latest, merge module fragments into one stack with namespaced resource IDs - _latest_version() picks highest non-deprecated from registry - _namespace_resources() prefixes IDs + rewrites ref: expressions for multi-module - Single-module path: unprefixed IDs (backward compatible) Verification: - 494 tests pass (0 contract-shape failures) - Local E2E passes (contract → resolver → adapter → local ECS HTTP 200 → outbox) ---ci--- project: acdl phase: 57 milestone: v1.10.2 status: execute ---/ci---
146 lines
5.9 KiB
YAML
146 lines
5.9 KiB
YAML
# 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/*.yml 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/*.yml; 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/*.yml'):
|
|
contract = yaml.safe_load(open(contract_file))
|
|
jsonschema.validate(contract, schema)
|
|
print(f'{contract_file}: valid contract')
|
|
" |