Files
acdl/adapters
Jon Chery a0799f13e5 docs(P04 W2): pilot-run docs (REQ-321) — adapters/README, METRICS, ARCHITECTURE §12.8, consumer onboarding
- adapters/README.md: fixed stale TYPE_MAP/INPUT_MAP refs (the adapter is a
  stateless assembler); added the blockchain-exchange consumer row + the
  Gitea adapter note (SPEC §10 Q1 — no cross-repo uses:)
- docs/METRICS.md: Post-Pilot denominators activated (AI Decision Accuracy +
  Human Escalation Frequency + the third metric now have non-zero data from
  the blkex-pilot-apply-v0.2 run)
- .ciagent/ARCHITECTURE.md §12.8: Pilot Estate (v1.26 live) — the first real
  consumer estate, the live apply, the Gitea adapter, the evidence stream
- .ciagent/nova-blockchain-exchange/README.md: consumer onboarding guide
  (deploy invocation, secrets, contract shape, verification)

---ci---
project: acdl
phase: 4
milestone: v1.26
status: execute
wave: W2
---
2026-08-19 03:27:27 +00:00
..

Nova Adapters

Overview

Adapters translate the engine-agnostic Target Stack IR to engine-specific formats. The Terraform adapter is the primary adapter (IR → HCL). Policy adapters translate security tool output into normalized PolicyCheckResult records that the confidence signal consumes in an engine-agnostic way.

Existing Adapters

Adapter Path Input Output Purpose
Terraform adapter adapters/terraform/adapter.py Stack instance JSON Terraform HCL (main.tf, terraform.tf, providers.tf) Compiles IR to Terraform
Checkov adapter adapters/terraform/policy/checkov_adapter.py Checkov JSON PolicyCheckResult records Translates Checkov results
Wiz adapter adapters/wiz/wiz_adapter.py Wiz API issues JSON PolicyCheckResult records Translates Wiz security findings
Kyverno adapter adapters/kyverno/kyverno_adapter.py Kyverno PolicyReport JSON PolicyCheckResult records K8s-native policy translation
kyverno-json engine adapters/kyverno-json/kyverno_json_engine.py Any JSON/YAML payload PolicyCheckResult records v1.25 primary policy engine (swappable via PolicyEngine protocol)

Policy Engine Protocol (v1.25)

The core/policy_engine.py module defines the swap boundary between Nova and its policy engines. A PolicyEngine Python Protocol (PEP 544) with three members (name, is_configured(), evaluate()) is the contract; a PolicyEngineRegistry selects the active engine from config.json's policy.engine key. The confidence signal and pipeline never import an engine directly — they go through the registry.

Implementations:

  • KyvernoJsonEngine (adapters/kyverno-json/) — shells to the kj CLI; the v1.25 default.
  • NullEngine (core/policy_engine.py) — fallback when the policy key is absent (emits SKIPPED).
  • Future: OpaEngine — implements the same protocol, shells to opa eval. The OPA-equivalent surface is documented in .ciagent/RESEARCH.md §4.2.

How to add a new engine:

  1. Create adapters/<name>/<name>_engine.py implementing the PolicyEngine protocol (name, is_configured(), evaluate()).
  2. evaluate() returns list[dict] where each dict conforms to schemas/policy_check_result.schema.json.
  3. Register the engine in core/policy_engine.py's _autoload_* function (or call register(name, factory) at startup).
  4. Set config.json.policy.engine to the engine's name.
  5. Add the engine to the engine enum in schemas/policy_check_result.schema.json if it needs a distinct enum value (v1.25 reuses "kyverno" — see D-116).

How to Write an Adapter

Terraform Adapter Extension (stateless assembler — v1.11 rewrite)

The adapter owns no module content. There is no TYPE_MAP, no INPUT_MAP, no OUTPUT_MAP, and no per-type branch logic (all deleted in the v1.11 rewrite — the 918-line monolith collapsed to a ~80-line assembler). Engine-specific shape lives in each L1 module's own terraform/ dir (versions.tf/variables.tf/locals.tf/main.tf/ outputs.tf); the adapter only assembles them.

To extend the Terraform adapter, do not edit the adapter — instead:

  1. Add an L1 module with a real terraform/ dir (owning its resource shape, nested HCL blocks, and defaults).
  2. Register it in modules/registry.json under the module name with its terraform_dir path. The adapter reads registry.json to find each module's directory.
  3. The adapter emits module "<rid>" { source = "<path>" } blocks at the root, with resolved inputs + wired ref: refs between modules. No type-specific translation lives in the adapter.

If you find yourself reaching for a "TYPE_MAP"-style constant, the L1 module is missing a piece — fix the module, not the adapter.

Policy Adapter Pattern

  1. Define SEVERITY_MAP and RESULT_MAP dicts that translate the engine's native severity/result vocabulary to the PolicyCheckResult enums.
  2. Implement _to_pcr(raw_record, contract_id)PolicyCheckResult dict.
  3. Implement adapt(input_path, contract_id) → list of PolicyCheckResult dicts.
  4. Implement is_configured() → bool (env var check) so the platform can skip the adapter when credentials are absent.

How to Wire an Adapter

  • Terraform adapter — invoked by scripts/run_platform.sh Step 3 (terraform-plan).
  • Checkov adapter — invoked by scripts/run_platform.sh Step 5 (checkov).
  • Wiz / Kyverno adapters — optional Steps 5b/5c, run only when the relevant env vars are set.
  • All policy adapters output records that are validated against schemas/policy_check_result.schema.json.

Dependencies

  • jsonschema, pyyaml — used by all adapters for loading and validating inputs.
  • boto3 — used by the Wiz adapter for AWS API access.
  • checkov — used by the Checkov adapter to run policy scans.
  • No external deps for the Terraform adapter (pure Python).

How to Test Adapters

  • tests/test_adapter.py — Terraform adapter (stateless assembly: registry read, module "<rid>" { source } emission, ref: wiring, outputs). No TYPE_MAP/INPUT_MAP tests — the adapter owns no type mappings.
  • tests/test_checkov_adapter.py — Checkov adapter.
  • tests/test_wiz_adapter.py — Wiz adapter.
  • tests/test_kyverno_adapter.py — Kyverno adapter.
  • All adapter tests load fixtures from tests/fixtures/ and use moto for AWS mocking.

Where to Write Tests

  • tests/test_<adapter_name>.py paired with tests/fixtures/<adapter>_fixture.json.

Adding a New Adapter

  1. Create adapters/<name>/<name>_adapter.py.
  2. Implement adapt() and (for policy adapters) is_configured().
  3. Add the adapter's engine name to the engine enum in schemas/policy_check_result.schema.json if it is a policy adapter.
  4. Write a test (tests/test_<name>_adapter.py) plus a fixture (tests/fixtures/<name>_fixture.json).
  5. Add it to scripts/run_platform.sh if it is invoked at runtime.
  6. Update this README.

Consumers

The Terraform adapter compiles contract IR for consumer estates. The first real consumer estate is now live:

Consumer Version Environment Account Forge / Adapter Status
nova-blockchain-exchange v0.2 dev 581513795199 inline adapter (see note below) live (pilot apply blkex-pilot-apply-v0.2, 2026-08-19)

Forge adapter note (SPEC §10 Q1)

Forge Actions (the consumer's forge runtime) does not support cross-repo uses: references — the forge rejects uses: <owner>/<repo>/.github/workflows/<file>@<ref> with expected format {owner}/{repo}/.{git_platform}/workflows/{filename}@{ref}. The consumer (nova-blockchain-exchange) therefore uses an inline adapter in its deploy.yml: the workflow does actions/checkout@v4 on the consumer, then actions/checkout@v4 acdl/acdl @ ref: v1.25 into platform/, and runs bash platform/scripts/run_platform.sh ... directly — no uses: indirection.

The platform's own .github/workflows/deploy.yml (this repo) stays as the GitHub Actions reference implementation — the reusable workflow_call workflow used by GitHub-hosted consumers. The two files share the same contract shape; the only declared difference is the forge/runtime, not the stages or commands. See .ciagent/ARCHITECTURE.md §12.8 for the live pilot-estate wiring.