---ci--- project: acdl phase: 42 milestone: v1.9 status: execute ---/ci--- Phase 42 — stub-implementation (REQ-107..111, D-084): route_halt_artifact (REQ-107): - core/separation_of_duties.py: real SNS publish (ACDL_SOD_HALT_TOPIC_ARN) + outbox fallback (SEPARATION_OF_DUTIES_VIOLATION event via outbox_writer) + stderr emission. No silent print-only stub. - terraform/platform/main.tf: aws_sns_topic.acdl-sod-halt + output. HITL attestation gates (REQ-108): - core/hitl_gates.py: attest(contract_id, env, approver, evidence, outbox_client) records approver_qa/approver_prod/approver_dr to outbox, runs SoD check on prod, invokes attestation matrix, returns (ok, reason). Dev skips (autonomous). approver_from_env() reads GITHUB_ACTOR/GITEA_ACTOR. - scripts/run_platform.sh: Step 7b HITL gate before apply for qa/prod/dr. 8-concern attestation matrix (REQ-109, D-084): - core/attestation_matrix.py: check(env, evidence) runs the 8 concerns from hitl_matrix_design.md §10.4. Offline-testable (contract_nfrs, schema_validity, policy_pass) run for real. Operator-supplied accept signed artifacts validated for freshness (FRESHNESS_DAYS table) + schema. Signature skip when ACDL_ATTESTATION_SIGNING_KEY_ID unset (D-089). Fail loud if missing/expired for prod/dr. Wiz real client (REQ-110): - adapters/wiz/wiz_adapter.py: WizClient (GraphQL API, Bearer auth, pagination via pageInfo.hasNextPage + endCursor). fetch_and_adapt translates issues → PolicyCheckResult; graceful degrade when WIZ_API_TOKEN/WIZ_API_URL unset. Kyverno fleshed out (REQ-111): - adapters/kyverno/kyverno_adapter.py: full PolicyReport → PolicyCheckResult mapping (pass/fail/skip/warn + severity + skip-with- reason + resource ref construction from kind/name/namespace). adapt_inactive() emits KYVERNO_INACTIVE_TF_STACK guard. --kube-version stub parsed for future GitOps. Tests: +47 (test_route_halt_artifact.py, test_hitl_gates.py, test_attestation_matrix.py, test_wiz_adapter_real_client.py, expanded test_kyverno_adapter.py). Existing wiz_adapter tests updated for the real client's control.name ruleId. 493 passed; run_ci.sh green; run_platform.sh --check-only green.
ACDL Platform Infrastructure (D-051)
Terraform configuration for the platform-side infrastructure that ingests consumer deployment contracts and (Phase 25) reports errors as GitHub issues.
This stack is separate from terraform/spike/ (the consumer stack
spike) and terraform/microservice/ (the demo microservice). It manages
resources that live in the platform AWS account and serve all
consumers — the contract ingestion pipeline and the secrets it needs.
What it deploys
| Resource | Name | Purpose |
|---|---|---|
aws_dynamodb_table |
acdl-contracts |
Stores submitted consumer contracts. PK consumerRepo, SK contractId#submittedAt. SSE via CMK, PITR enabled. |
aws_kms_key + aws_kms_alias |
alias/acdl-platform |
Customer-managed key — encrypts DynamoDB SSE, Secrets Manager, and SSM. Key rotation enabled. |
aws_secretsmanager_secret |
acdl/github-token |
GitHub PAT used by the Lambda to create issues on the platform repo (D-055, wired in Phase 25). |
aws_iam_role + aws_iam_role_policy |
acdl-contract-ingestor-role |
Execution role for the Lambda — DynamoDB write, Secrets Manager read, KMS decrypt, CloudWatch logs. |
aws_lambda_function |
acdl-contract-ingestor |
Python 3.12 Lambda. Handler contract_ingestor.lambda_handler. Source: core/lambda/contract_ingestor.py, packaged as contract_ingestor.zip. |
aws_lambda_function_url |
— | Function URL with AWS_IAM authorization. Consumers invoke it via SigV4-signed requests. |
State
| Key | Value |
|---|---|
| Backend | S3 |
| Bucket | acdl-tfstate-581513795199-us-east-1 |
| State key | platform/terraform.tfstate |
| Region | us-east-1 |
The state key is distinct from spike/terraform.tfstate and
microservice/terraform.tfstate — the three stacks are independent.
Apply
# Package the Lambda source first (from the repo root):
cd core/lambda
zip contract_ingestor.zip contract_ingestor.py
cd ../../terraform/platform
terraform init
terraform plan
terraform apply
The Lambda's filename points at contract_ingestor.zip in the working
directory (terraform/platform/); either place the zip there or adjust
the path. source_code_hash = filebase64sha256("contract_ingestor.zip")
forces a redeploy whenever the package changes.
Cross-account invocation model
The Lambda is invoked cross-account by consumer pipelines. The flow:
- Onboarding. When a consumer repo is onboarded, the platform team
applies
consumer_invoke_policy.jsonto the consumer's deploy role. The policy grantslambda:InvokeFunctionUrlon the Lambda ARN, scoped via ABAC — the conditionaws:PrincipalTag/acdl:owner == ${consumerRepo}ensures a repo can only invoke when it is the owner it claims to be. - Runtime. The consumer's deploy workflow (running in the consumer AWS account under the consumer's deploy role) signs the Function URL request with SigV4 using its deploy-role credentials. The IAM auth on the Function URL validates the signature and the ABAC condition.
- Lambda. The Lambda parses the JSON body, validates the fields,
and writes the contract to
acdl-contracts.
This is a one-way channel (D-051): the consumer pushes contracts
to the platform; the platform never reaches back into the consumer
account. Error reporting (D-055, action: "report_error") flows over
the same channel and is implemented in Phase 25 (GitHub issue creation on
the platform repo).
Related files
core/lambda/contract_ingestor.py— the Lambda handler.tests/test_contract_ingestor.py— unit tests (moto-backed DynamoDB mock).terraform/platform/consumer_invoke_policy.json— the ABAC policy applied to consumer deploy roles during onboarding.docs/environments/index.md— documents the cross-account grant as part of onboarding.