# Nova — Architecture > **Compressed.** The full v1.0–v1.24 architecture history (v1.1 spike > scope, v1.2 build-out, v1.8–v1.16 addenda) is preserved verbatim at > `.ciagent/archive/ARCHITECTURE-v1.0-v1.24.md`. This file retains the > durable target architecture (§1–§12, the four layers + six cross-cutting > concerns) + the three addenda that describe the **current state**: > v1.11 (stateless adapter), v1.15 (Nova rebrand — current naming), and > v1.17 (telemetry/observability layer + §12.7 Policy Engine Registry). > Intermediate addenda (v1.1 spike scope, v1.2 build-out, v1.8/1.9/1.10/ > 1.12/1.13/1.14/1.16) describe evolved or superseded states and are > preserved in the archive snapshot. > > Source of truth for **how**: `docs/architecture.md` (v0.2) is the > upstream draft; this file is the Nova-repo operating copy, refined at > phase boundaries. Where this file and `docs/vision.md` conflict, the > vision wins. ## Status Architecture is at **v0.2** upstream (`docs/architecture.md`). Milestone v1.1 **finalized it to v1.0** in Phase 07 by resolving the 11 open decisions (see `PROJECT.md` open-decision resolutions table). The v1.11 addendum (stateless adapter) and the v1.17 addendum (telemetry layer + §12.7 Policy Engine Registry) record the current-state refinements. ## Overview The platform is **four layers + six cross-cutting concerns**. The sixth concern — the engine abstraction (§12) — is first-class, not an implementation detail. The vision's "Two Consumer Surfaces, One Platform" tenet binds everything: L3A and L3B converge on the same contract schema, the same policy envelope, and the same evidence stream. ``` ┌──────────── acdl-contracts ────────────┐ Developer ───▶ │ commit contract.yaml │ (L3A) Citizen dev ──▶ │ Issue → agent → contract.yaml │ (L3B) └────────────────┬───────────────────────┘ │ (push) ▼ ┌──────────────────────┐ │ central pipeline │ │ (acdl repo, Gitea │ │ Actions / act_runner) │ └────────┬─────────────┘ │ ┌─────────────────────────┼─────────────────────────┐ ▼ ▼ ▼ contract→IR resolution policy (Checkov/Kyverno) confidence signal │ │ │ ▼ ▼ ▼ Terraform adapter ──▶ terraform plan ──▶ PolicyCheckResult ──▶ {score,band} │ │ ▼ ▼ dev (autonomous, ≥0.50) qa (HITL, ≥0.75) prod (HITL, ≥0.90) dr (HITL, ≥0.95) │ ▼ DynamoDB outbox ──▶ S3 Object Lock (7-yr, source of truth) ──▶ GitHub audit repo (hot index) │ ▼ acdl-evidence (timeline UI) ``` ## Layers ### Layer 1 — Foundational Primitives Single-purpose, **engine-agnostic** primitive modules. L1 modules do not compose with other L1s; L1 takes its environment as input. The L1 interface is defined against the **Target Stack IR**, not against Terraform directly (the IR is shaped to round-trip to Terraform in v1, per §12.1). - No inter-L1 references. L1 may call Terraform data sources. - Semver: interface → MAJOR, behavior → MINOR, lifecycle → PATCH (W3.D). - Immutability on publication. 12-month deprecation window. - AI refinement is a flag; the trigger is the W1.A joint condition. ### Layer 2 — Composed Stacks Combine L1 primitives into deployable shapes. Each codebase maps to one canonical L2 stack (`multiStack: true` only per W1.B). Shape X (parameterized module) or Shape Y (thin-composition layer). Hierarchical composition, max depth 5, only registered L1s. The thin-composition tree's `wires` field is defined against the IR's relationship type, not a Terraform module block. Pipeline quality checks: secrets-in-plaintext, public ingress, IAM wildcard, KMS key reference, tag compliance, naming convention. Restricted from thin-composition: IAM principal creation, network boundary creation, key/secret creation, external data transfer. Auto-promote after 3 observed usages. ### Layer 3A — Developer Consumer Surface Tag-based reference to the central pipeline template. Developer-owned workflow file, no platform auto-sync. L3A and L3B are parallel paths, not a progression. **W2.A (Path B):** tag for dev/qa, SHA for prod; platform CLI resolves tag→SHA for prod-bound workflows. ### Layer 3B — Agentic Consumer Surface Hybrid runtime, skill as markdown, agent as executor. Trust model: trust and always verify on the platform side. Skill envelope (4 dimensions). Stateless agents, all state in the platform. `profile: agentic` marker unlocks `naturalLanguageIntent`, `confidenceAtSubmission`, `agentTrace`. Initial skill catalog (BA.A): web API, worker, scheduled job, static asset, basic observability bootstrap. Environment progression: | Environment | Autonomy | Attester | Gate | |---|---|---|---| | dev | Full autonomy (no HITL) | — | Confidence ≥ 0.50, all six inputs present | | qa | Held for attestation | QA | GitHub Deployment approval + full QA matrix (§10) | | prod | Held for attestation | SRE | GitHub Deployment approval + full SRE matrix (§10) | | dr | Held for attestation | SRE | GitHub Deployment approval + dr-drill evidence | **Staging is removed.** Dev is the only autonomous environment. ## Cross-cutting concerns ### Central pipeline template (§6) JSON Schema (draft 2020-12) with a thin domain wrapper. Central repo + generated client libraries. Multi-stage validation: schema → policy → NFR → confidence. Distributed enrichment. GitOps reconciler (K8s API; cdlc-gitops state → CRDs) + Terraform execution layer (§12.5). The pipeline emits one `PolicyCheckResult` per policy rule; the confidence signal consumes them as one normalized input. ### Contract schema (§7) Central repo + generated client libraries. Strict fail-fast at schema stage, multi-stage validation with reason codes from a published vocabulary. **W3.E:** per-env mandatory inputs — - dev: `stack`, `environment` - qa adds: `validation.e2eSuite`, `validation.loadTest` - prod adds: `runbook`, `dashboard`, `oncall` - dr adds: `drDrillRef` - `inputs` always optional; `profile: agentic` fields optional everywhere. ### Confidence signal (§8) Six canonical inputs, weighted sum with per-input breakdown. Per-env thresholds: dev ≥ 0.50, qa ≥ 0.75, prod ≥ 0.90, dr ≥ 0.95. Structured output `{ score, band, perInput, reasonCodes }`. 1-year storage, no retraining in v1. Halt with explicit reason on missing input. Policy input = list of `PolicyCheckResult` records (engine-agnostic). Severity → penalty: critical → hard override to mandatory block; high → -0.2; medium → -0.05; low → -0.01; info → 0.0. One critical finding hard-overrides the score regardless of all other inputs. **BA.B:** thresholds frozen for v1; tuning begins v1.2 (quarterly FP/FN tracking; override = Infra & Ops + SRE joint sign-off, itself a confidence-event). ### Audit and evidence stream (§9) Tiered ledger: **S3 with Object Lock in compliance mode** (cold, source of truth, 7-year retention) + **GitHub audit repo** (`acdl-evidence`, hot query index, not part of the chain). Daily checkpoints. Event schema: JWS detached signature, `prev_event_hash` chain, controlled-vocabulary `event_type`. Outbox pattern: local durable outbox + async worker. Outbox database = **DynamoDB**. RPO = 0 (synchronous write to local outbox before contract submission ack); RTO = async worker's dead-letter recovery. Single-region in v1. The outbox also stores per-contract QA and prod approver identities (the only durable record outside GitHub's audit log). > **v1.17 update:** the Decision Ledger (SQLite hash-chain, D-121) is the > pilot's audit record. S3 Object Lock / JWS (D-083) is deferred — see > the v1.17 addendum below. ### Human-in-the-Loop mechanics (§10) Pre-execution gates. qa, prod, dr are PR-based attestation gates backed by GitHub Environments with required reviewers. No partial deployment to roll back on rejection (qa, prod); dr is a separate GitHub Deployment against a separate cluster/region. Reviewer routing: GitHub CODEOWNERS + Environment required reviewers (qa → QA; prod → SRE; dr → SRE). CODEOWNERS routes, does not enforce identity distinctness. **Separation of duties** (platform-internal, not GitHub-native, not Kyverno in v1): on dev→qa promotion the platform writes the QA approver's GitHub identity to the DynamoDB outbox keyed by `contractId`; on qa→prod it reads the stored QA approver and the new SRE approver; if equal, it blocks, emits `SEPARATION_OF_DUTIES_VIOLATION`, and routes a halt artifact to SRE on-call. Full 8-concern attestation matrix (functional, performance, security posture, contract NFRs, operational readiness, incident response, capacity/cost, resilience) — see `docs/architecture.md` §10.4. Timeout: 1 business day = warn + escalate; 2 business days = auto-freeze + re-submit (linked via `supersedes`). Rejection returns the contract to HELD; the audit chain is extended, not torn up. ### Agentic stack (§11) Hybrid runtime: platform-managed control plane + consumer-owned agent. Versioned, signed skill catalog over MCP. Skill envelope enforced on invocation and result submission. Consumer-owned skill execution; the platform does not run the skill. Stateless agents, all state in the platform. Skills are reviewed for sensitive data before release (Infra & Ops owns the review; it is the mandatory release gate). ### Engine execution (§12) — the binding constraint **Target Stack IR** (locked): an engine-neutral description of resources (typed inputs/outputs/NFRs), relationships (single parent per child), composition (tree, max depth 5), and policy hooks. The L1 registry, L2 thin-composition tree, contract YML, and PolicyCheckResult schema are all defined against the IR — none against any specific engine. **Engine adapters** are the only engine-specific code. An adapter compiles the IR into an engine execution plan. **v1 ships exactly one adapter: the Terraform adapter.** v2+ may add OpenTofu, Pulumi, K8s CRDs without architectural change. v1 reality: the IR is shaped to round-trip cleanly to Terraform (nearly isomorphic). As more adapters appear, the IR gets more expressive and the adapters gain translation logic; the L1 content, the YML standard, and the thin-composition tree do not change. > **v1.11 update:** the Terraform adapter is now a **stateless assembler** > (~80 lines, emits `module "x" { source }` blocks) — see the v1.11 > addendum below. The §12 "thin layer that translates IR → Terraform > variable/output blocks" framing is superseded by the stateless-assembler > model; the L1-owns-its-shape invariant is the new contract. State storage: S3 (state) + DynamoDB (locking), cloud-managed, single-region in v1. Policy toolchain: **Checkov** for Terraform plan policy (the L2 checks + tag/naming); **Kyverno** for K8s-native/platform-internal policy; **OPA** reserved for cross-resource cases, explicitly last resort. > **v1.25 update:** the policy toolchain is now unified under the > swappable `PolicyEngine` protocol — see §12.7 below. Checkov and Wiz > remain as raw-finding adapters feeding into kyverno-json meta-policies. **Policy result normalization (§12.6):** the confidence signal consumes a normalized `PolicyCheckResult` schema, not raw engine output. ```json { "contractId": "uuid", "evaluatedAt": "ISO-8601", "engine": "checkov | kyverno | opa", "ruleId": "CKV_AWS_24 | KYVERNO_NO_PRIVILEGED | ...", "severity": "critical | high | medium | low | info", "result": "pass | fail | skipped | error", "message": "human-readable", "evidence": { "...engine-specific, opaque to the signal..." }, "resourceRef": "IR-typed resource identifier" } ``` Execution layer: GitHub/Gitea Actions in the central pipeline repo. State locking via DynamoDB. **AWS credentials via OIDC federation — long-lived credentials are forbidden** (§12.5). The platform does not run `terraform apply` against a developer's workstation; all execution is in the central pipeline. Registry maintenance: L1 publication updates the L1 registry in the same PR. The registry is the IR-typed contract, not a Terraform-specific variable schema. Contract→IR resolution: the contract declares intent in IR-typed terms; the pipeline resolves it to a target stack (list of L1 instances + inputs + relationships); the Terraform adapter compiles the target stack to a plan. --- ## v1.11 Addendum — Stateless Adapter + Pipeline-Driven Lifecycle Testing (current state) **Stateless adapter (D-098).** `adapters/terraform/adapter.py` rewritten from a 918-line monolith (3 constant tables `TYPE_MAP`/`INPUT_MAP`/ `OUTPUT_MAP`, 39 type-specific branches) to a ~80-line stateless assembler. Each L1 module ships a real `terraform/` module dir (`versions.tf`/`variables.tf`/`locals.tf`/`main.tf`/`outputs.tf`) owning its resource shape, nested blocks, and defaults. The adapter reads the registry, emits a root `main.tf` instantiating each L1 as `module "x" { source = "..." }` with resolved inputs and wired refs. **Terraform owns lifecycle (D-101).** `scripts/run_platform.sh` gains `--apply` and `--destroy` modes. Python never runs terraform. `scripts/verify_deploy_microservice.py` is deleted. **Pipeline-driven testing (D-102).** A `modules-lifecycle` pipeline (Gitea + GitHub, byte-identical) matrix-runs each L1 module's `examples/{simple,complex}.yml` contracts through apply→modify→destroy against live AWS. No per-module Python/pytest. The "test" = the pipeline cell going green. **Single platform VPC (D-105).** `terraform/platform/main.tf` owns ONE VPC; the microservice composition references it via `terraform_remote_state` (data source). State keys are deterministic and env-aware (`spike/{contract.id}/{contract.environment}/terraform.tfstate`). **NOVA_LIFECYCLE_MODE (v1.12, REQ-134; renamed ACDL→NOVA in v1.15 P2).** The lifecycle pipeline defaults to plan-only (fast, no AWS mutation, no cost). A CI variable `NOVA_LIFECYCLE_MODE` (default `plan`) overrides to `full` for the real apply→modify→destroy. (P2–P4 dual-read fallback to `ACDL_LIFECYCLE_MODE`; fallback removed in P5 per the v1.15 addendum.) --- ## v1.15 Addendum — Nova Rebrand (current naming) **Milestone:** v1.15-Nova. A full rebrand from **ACDL** / "Agentic Cloud Delivery Platform" → **Nova** / "The New Dawn of DevSecOps — security as a seamless enabler of fast deployments." This is a **Major milestone** (breaking): consumer-facing path, env var prefixes, SSM path, AWS tag keys, and AWS resource names all change. v1.15 tags run on the **v1.15.x minor line**: `v1.15.0` (P0) → `v1.15.4` (P5 final = release). (G-104 binding.) ### Naming conventions (rebranded — current) | Convention | Before (v1.0–v1.14) | After (v1.15+) | Phase | |------------|---------------------|-----------------|-------| | Project name | `ACDL` / "Agentic Cloud Delivery Platform" | `Nova` / "The New Dawn of DevSecOps" | P1 | | Tagline | "Consumers declare intent; the platform delivers safe production deployment through an agentic stack" | (retained) **+** "The New Dawn of DevSecOps — security as a seamless enabler of fast deployments" | P1 | | Schema `$id` URL | `https://acdl.cloudinit.dev/schemas/...` | `https://nova.cloudinit.dev/schemas/...` | P1 | | Gitea release title | `ACDL vX.Y.Z` | `Nova vX.Y.Z` | P1 (forward only) | | Env var prefix | `ACDL_*` (21 vars) | `NOVA_*` (dual-read fallback in P2–P4; removed P5) | P2 | | Env loader | scattered `os.environ.get("ACDL_*")` | centralized `core/env.py` `get_env()` (D-108) | P2 | | Consumer contract path | `.acdl/contract.yml` | `.nova/contract.yml` | P2 | | Checkov custom rule file | `acdl_tagging.py` | `nova_tagging.py` | P2 | | Checkov tag-key enforcement | `acdl:*` (hard) | `nova:*` (warn P2, hard P3) | P2/P3 | | SSM parameter path | `/acdl/{env}/{contractId}/{output}` | `/nova/{env}/{contractId}/{output}` | P3 | | AWS tag keys | `acdl:owner|environment|contract|cost-center|ref` | `nova:owner|environment|contract|cost-center|ref` | P3 | | ABAC session policy match | `acdl:*` tags | `nova:*` tags (parallel-tag period) | P3 | | DynamoDB tables | `acdl-contracts`, `acdl-change-requests` | `nova-contracts`, `nova-change-requests` (scan+copy) | P4 | | Lambda (ingestor) | `acdl-contract-ingestor` (role/policy/function) | `nova-contract-ingestor` | P4 | | Secrets Manager secret | `acdl/github-token` | `nova/github-token` | P4 | | SNS topic | `acdl-sod-halt` | `nova-sod-halt` | P4 | | Security group | `acdl-ecs-sg` | `nova-ecs-sg` | P4 | | KMS alias | `alias/acdl-platform` | `alias/nova-platform` | P4 | | ECS cluster/service/task | `acdl-microservice` | `nova-microservice` | P4 | | ECR repo | `acdl-microservice` | `nova-microservice` (re-push) | P4 | | IAM user/policy | `acdl-spike-runner` (+policy) | `nova-spike-runner` (re-bootstrap) | P4 | | S3 state bucket | `acdl-tfstate-581513795199-us-east-1` | `nova-tfstate-581513795199-us-east-1` (`-migrate-state`) | P4 | | ALB name prefix | `acdl-alb` | `nova-alb` | P4 | | Lambda default table names | `CONTRACTS_TABLE` default `acdl-contracts` | default `nova-contracts` (D-111) | P4 | ### Unchanged conventions (out of scope) - **S&P Global Energy visual theme** (`sp-theme.json`, deck CSS: #D6002A red, Akkurat Pro) — client branding, not the Nova product brand (D-107). - **config.json `release.gitea.repo`** = `acdl` — real Gitea repo name unchanged (D-105). Doc URLs updated to `nova` for prose only. - **Git branch/tag naming** — `milestone/v*`, `phase/*`, `v*` semver; no brand name present (D-112: flat-branch convention preserved). - **Past Gitea release titles** — existing releases keep `ACDL vX.Y.Z`. > The full migration ordering (P1–P5), capability gate, and rollback > runbook are preserved in `.ciagent/archive/ARCHITECTURE-v1.0-v1.24.md` > §v1.15 Addendum. --- ## v1.17 Addendum — Strategic Direction, Leadership Metrics & Unified Story (current telemetry layer) The v1.17 milestone added a telemetry/observability layer, a Decision Ledger, a metrics export pipeline, a unified narrative deck, and a durable strategic-direction artifact. This addendum documents the architecture; the full research findings are in RESEARCH.md §v1.17. ### New components | Component | Path | Purpose | |-----------|------|---------| | Event envelope | `core/metrics/event_envelope.py` | CloudEvents 1.0 envelope + `platform.*` semantic conventions (P1, REQ-187) | | Per-run manifest writer | `core/metrics/run_manifest.py` | Emits `nova.run.started/completed/failed` events + writes `metrics/runs/.json` (P1, REQ-187) | | Decision Ledger (SQLite) | `core/metrics/decision_ledger.py` | Extends `outbox_writer.py` → SQLite append-only hash-chain table; `ai.decision.made` + `attestation.recorded` events + outcome backfill (P1, REQ-188, D-121) | | Infracost post-processor | `core/metrics/infracost_adapter.py` | Runs Infracost on plan JSON; emits `nova.cost.estimated{delta_usd}` (P1, REQ-187, D-120) | | Metrics collector | `core/metrics/collector.py` | Reads all grounded signals (files + events) → SQLite cold store at `metrics/nova_metrics.db` (P2, REQ-189) | | PowerBI export | `core/metrics/powerbi_export.py` | Emits CSV/JSON views to `metrics/powerbi/` (fact + dim + 8 deferred placeholder views) (P3, REQ-190) | | Metrics schemas | `schemas/metrics_*.schema.json` | Schemas for all event types + fact/dim tables (P1–P2, REQ-187/189) | | Metrics catalog | `docs/METRICS.md` + `docs/metrics/.md` | Canonical catalog + per-KPI definition-of-success docs (P4, REQ-195, D-127) | | Unified narrative deck | `docs/presentations/nova-no-humans-platform.md` | Merged deck: Problem→Vision→How→Proof→Roadmap; x3 arc at deck+slide level (P5, REQ-196/197, D-130) | | Strategic direction | `.ciagent/NORTH_STAR.md` | PO-authored durable vision/objectives/anti-goals/targets; read by CIAgent in every future `/ci-run` (P0, REQ-185/186) | ### Modified components | Component | Change | Phase | |-----------|--------|-------| | `core/outbox_writer.py` | Extended to emit to SQLite append-only hash-chain table (Decision Ledger); `ai.decision.made` + `attestation.recorded` events added (P1, D-121) | P1 | | `scripts/run_platform.sh` | Per-run manifest writer invoked; `$WORK/*.json` persisted to `metrics/runs/`; Infracost post-processor invoked after plan (P1) | P1 | | `core/hitl_gates.py` | Emits `attestation.recorded` event to Decision Ledger on qa/prod/dr gate (P1, D-132) | P1 | | `core/confidence_signal.py` | Emits `nova.confidence.computed` + `nova.ai.decision.made` events (P1, D-122) | P1 | | `adapters/terraform/policy/checkov_adapter.py` | Emits `nova.policy.evaluated` event (P1) | P1 | | `core/regression_verify.py` | Emits `nova.capability.verified` event; CAP-023 (metrics collector) + CAP-024 (deck structure) added (P1, P6) | P1, P6 | | `pyproject.toml` | `addopts` gains `--junitxml=metrics/test-results.xml` + `--json-report` (P1, D-120) | P1 | | `docs/presentations/` | Two old decks retired (deleted); unified deck added (P5, D-130) | P5 | ### Telemetry/observability layer architecture (D-120) ``` ┌─────────────────────────────────────────────────────────────────────┐ │ Nova platform components (existing) │ │ run_platform.sh · confidence_signal · checkov_adapter · │ │ hitl_gates · regression_verify · outbox_writer · contract_ingestor │ └────────────────────┬──────────────────────────────────────────────┘ │ CloudEvents 1.0 envelope (new emitters, P1) ▼ ┌─────────────────────────────────────────────────────────────────────┐ │ metrics/events.jsonl (append-only CloudEvents log) │ │ metrics/runs/.json (per-run manifests) │ │ metrics/decision_ledger.db (SQLite hash-chain, D-121) │ │ metrics/test-results.xml (junit, P1) │ └────────────────────┬──────────────────────────────────────────────┘ │ collector reads (P2) ▼ ┌─────────────────────────────────────────────────────────────────────┐ │ metrics/nova_metrics.db (SQLite cold store, D-126) │ │ fact_run · fact_capability · fact_policy_check · fact_confidence │ │ fact_test · fact_decision · fact_cost_estimate │ │ dim_capability · dim_milestone │ │ + 8 empty placeholder views (deferred metrics) │ └────────────────────┬──────────────────────────────────────────────┘ │ powerbi_export (P3) ▼ ┌─────────────────────────────────────────────────────────────────────┐ │ metrics/powerbi/ (CSV/JSON views, folder connector, D-129) │ │ → PowerBI dashboards (external) │ └─────────────────────────────────────────────────────────────────────┘ ``` **Hot path: deferred (D-126).** No live ops dashboard; SQLite is cold-only (batch/historical). The hot path activates when live AWS is re-provisioned (D-096 lift — the v1.26 milestone lifts this for the pilot estate). ### NORTH_STAR integration point (REQ-186) `.ciagent/NORTH_STAR.md` is read by CIAgent in context-loading for all future milestones. The integration mechanism: a reference from `PROJECT.md` + `ARCHITECTURE.md` (this section) + a config entry in `config.json` (`strategic_direction_file: ".ciagent/NORTH_STAR.md"`) that the run workflow reads at SPECIFY. This ensures the strategic direction survives across milestones without being overwritten by status updates. ### §12.7 — Policy Engine Registry (v1.25, REQ-291 — current) The policy-engine abstraction is first-class: a swappable `PolicyEngine` protocol so the engine may change without touching the confidence signal, the pipeline, or the `PolicyCheckResult` schema. This is the **swap boundary** that keeps the platform's compliance posture replaceable (Strategic Objective #2 — provable trust via a replaceable substrate, not a vendor lock-in). ``` contract.yml ─┐ ┌─→ list[PolicyCheckResult] ─┐ stack IR ─────┼─→ PolicyEngine.evaluate ├─→ list[PolicyCheckResult] ─┼─→ confidence_signal plan JSON ────┤ (protocol) └─→ list[PolicyCheckResult] ─┘ (engine-agnostic, PCR list ─────┘ unchanged) │ ▼ ┌─ KyvernoJsonEngine (shells to `kj scan`; engine: "kyverno") └─ OpaEngine (future — same protocol; engine: "opa") checkov/wiz ──→ raw findings ──→ (merged PCR list is the meta-policy payload) ``` **The protocol (`core/policy_engine.py`):** ```python class PolicyEngine(Protocol): @property def name(self) -> str: ... def is_configured(self) -> bool: ... def evaluate(self, payload, policy_dir: Path, contract_id: str) -> list[dict]: ... ``` **The registry** reads `config.json.policy.engine` (default `"kyverno-json"`) and returns the active engine. A `NullEngine` is the fallback when the `policy` key is absent (emits `SKIPPED` PCRs — backward compatibility for tests that don't set the key). The confidence signal is **untouched** — it already consumes `list[PolicyCheckResult]` engine-agnostically (§12.6). v1.25 only changes *who produces* the PCR list, not *what* the list is. **Engine enum reuse (D-116):** kyverno-json PCR records carry `engine: "kyverno"` (no new enum value). The `engine` field records the policy-engine *family*, not the specific binary. The K8s Kyverno adapter and the kyverno-json engine are distinguished by `ruleId` prefix (`KYVERNO_` vs `KJ_`) and `evidence` payload shape (`namespace`/`kind` vs `assertion`/`jmespath`). **Defense-in-depth (D-119):** the declarative meta-policy `block-on-any-critical` (asserts no PCR has `severity: critical` + `result: fail`) is the *source of truth* for "critical = block". The `confidence_signal.py` `PENALTY["critical"]: None` hard-override stays as the *imperative* safety net — the meta-policy runs *before* the confidence signal (produces PCRs that flow in), the hard-override runs *inside* it (the last gate). Removing the hard-override would make the "critical = block" guarantee depend on a single policy file — a regression in provable trust. **Graceful degradation (D-120):** `KyvernoJsonEngine.is_configured()` returns false when `which kj` is absent → `evaluate()` returns a single `SKIPPED` PCR (`ruleId: "KJ_ENGINE_NOT_CONFIGURED"`). The platform functions without the binary (the "platform functions without AI / deterministic scripts" tenet holds — kyverno-json is deterministic, not AI; the `is_configured()` guard ensures the platform runs even when the binary is not installed). ### §12.8 — Pilot Estate (v1.26, live) The first real consumer estate is **`nova-blockchain-exchange`** — a blockchain stock exchange on a homegrown Proof-of-Authority chain, equities only, dev only (D-020/D-200/D-201). The live apply landed on 2026-08-19 against AWS account `581513795199`. This is the estate that activated the Post-Pilot metric denominators (see `docs/METRICS.md`). **The live apply (run id `blkex-pilot-apply-v0.2`):** - Target: account `581513795199`, environment `dev`, autonomous (no HITL — dev is the only autonomous environment, confidence ≥ 0.50). - The microservice L2 composition (ECS Fargate running nginx) + the `dynamodb` L1 (the `nova-blkex-ledger-dev` table) + the `s3` L1 (the `nova-blkex-blocks-dev-581513795199-us-east-1` bucket). - The platform VPC prerequisite (`vpc-0d7c8867e6cc080f1` + 6 subnets + the ECS SG) is read via `terraform_remote_state` — the L2 composition does not own the network boundary (the "restricted from thin-composition" rule from §Layer 2). - Confidence signal: score **0.800**, band **pass**; `human_override` false; `escalation_reason` absent (clean apply). **The Gitea adapter (SPEC §10 Q1):** Gitea Actions does not support cross-repo `uses:`, so the consumer's `deploy.yml` is an **inline adapter** — `actions/checkout@v4` the consumer, `actions/checkout@v4` `acdl/acdl` @ `ref: v1.25` into `platform/`, then `bash platform/scripts/run_platform.sh ...`. The platform's own `.github/workflows/deploy.yml` stays as the GitHub Actions reference impl (the reusable `workflow_call` workflow). See `adapters/README.md` §Consumers for the adapter note. **The Decision Ledger evidence stream** (the apply produces these events in order): ``` nova.confidence.computed (score 0.800, band pass) │ ▼ nova.ai.decision.made (decision_id blkex-pilot-apply-v0.2, chosen_action pass, human_override false) │ ▼ nova.attestation.recorded (dev = no HITL gate; the record exists, the gate is a no-op in the autonomous env) │ ▼ nova.run.completed (apply succeeded) │ ▼ nova.outcome.backfilled (outcome pending → succeeded, REQ-317; backfilled_at 2026-08-19T03:05:04Z) ``` The SQLite hash-chain is valid (0 breaks). S3 Object Lock / JWS (D-083) stays deferred — the SQLite Decision Ledger is the pilot's audit record (D-204). **Live outputs (account 581513795199):** - ALB DNS: `app-254671247.us-east-1.elb.amazonaws.com` - ECS service: `arn:aws:ecs:us-east-1:581513795199:service/nova-cluster/nova-microservice` - DynamoDB table: `nova-blkex-ledger-dev` (PK `block_index`, PAY_PER_REQUEST) - S3 bucket: `nova-blkex-blocks-dev-581513795199-us-east-1` (versioning + SSE) The full evidence (every ARN, the confidence JSON, the Decision Ledger rows, the module-completeness gaps the live apply uncovered) is in `.ciagent/P4-PILOT-RUN-EVIDENCE.md`. ### §12.9 — Secret Rotation (v1.26 P3 W7, SPEC §5.9 — current) The platform-managed scheduled workflow `workflows-src/rotate-aws-key.yml` rotates the `NOVA_AWS_*` static key daily (cron `0 0 * * *`) and on `workflow_dispatch`. v0.2 scope: the mechanism exists (SPEC §5.9 — exists-not-ran); the v0.2 deploy uses the currently-active key. The rotation is idempotent — `scripts/rotate_spike_key.sh` deactivates the old key only after the new one propagates to the consumer's Actions secret store, verified by a post-PUT GET; on upload/verify failure the old key is left Active and the run exits non-zero. The synced workflow file is forge-agnostic (REQ-230): forge base URL / owner / consumer repo come from repository secrets (`NOVA_FORGE_*`, `NOVA_CONSUMER_REPO`), not literals.