Files
acdl/terraform/platform
Jon Chery e74a8c2f5d feat(P42): stub implementation — SoD, HITL gates, attestation matrix, Wiz, Kyverno
---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.
2026-07-23 04:40:44 +00:00
..

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:

  1. Onboarding. When a consumer repo is onboarded, the platform team applies consumer_invoke_policy.json to the consumer's deploy role. The policy grants lambda:InvokeFunctionUrl on the Lambda ARN, scoped via ABAC — the condition aws:PrincipalTag/acdl:owner == ${consumerRepo} ensures a repo can only invoke when it is the owner it claims to be.
  2. 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.
  3. 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).