RESEARCH.md: kyverno-json CLI (kj scan), ValidatingPolicy structure, assertion trees + ~ modifier + JMESPath, output shape, severity-via- annotation convention, 4 policy targets (contract/stack-IR/plan-JSON/ meta), PolicyEngine protocol + OPA-equivalent swap surface, latency <1s (parallel with checkov), deterministic-not-AI tenet, ECS catalog prior art, 5 logged assumptions (A1..A5). PERSONAS.md: 4 active personas (lead-developer, backend-engineer, new policy-engineer, data-engineer); frontend-engineer deactivated. policy-engineer owns kyverno-json policies + engine translation + STANDARDS.md policy-authoring section. ARCHITECTURE.md §12.7: Policy Engine Registry — protocol, registry, NullEngine fallback, engine enum reuse (D-116), defense-in-depth critical-override (D-119), graceful degradation (D-120). ---ci--- project: acdl phase: 0 milestone: v1.25 status: research ---/ci---
53 KiB
Nova — Architecture (v1.1 target)
Target architecture for the real Agentic Cloud Delivery Platform (rebranded Nova in v1.15). 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 anddocs/vision.mdconflict, the vision wins.
Status
Architecture is at v0.2 upstream (docs/architecture.md). Milestone v1.1
finalizes it to v1.0 in Phase 07 by resolving the 11 open decisions
(see PROJECT.md open-decision resolutions table). This file records the
locked commitments and the v1.1 spike scope.
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 inputsalways optional;profile: agenticfields 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).
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).
Angine execution (§12) — the binding constraint
Target Stack IR (locked): a 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.
Angine adapters are the only engine-specific code. An adapter compiles the IR into a 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.
Terraform adapter (v1): translates IR-typed L1 interface → Terraform
variable/output blocks; IR-typed L2 thin-composition tree → Terraform
root module; IR-typed relationships → module references; emits a
terraform plan from the IR. The adapter is a thin layer; it does not own
L1/L2 content.
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.
Policy result normalization (§12.6): the confidence signal consumes a
normalized PolicyCheckResult schema, not raw engine output.
{
"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.1 spike scope
The spike (Phases 08–10) materializes the minimum that proves the IR commitments hold (no polyglot mess):
- One L1:
l1-s3(IR-typed interface; the only AWS resource in the spike). - One L2 thin-composition:
l2-static-assets(referencesl1-s3only). - Terraform adapter: IR →
terraform planagainst AWS via OIDC. - One contract submission → contract→IR →
terraform plan→ CheckovPolicyCheckResult→ confidence signal → evidence event to the DynamoDB outbox. - State: S3 + DynamoDB (real AWS, single-region).
Out of spike scope: full HITL matrix wiring, Kyverno, OPA, MCP skill catalog, GitOps reconciler, multi-region, prod/dr environments, the 5-skill L3B catalog. Those are post-spike (v1.2+) platform build-out.
Gitea API surface (carried from v1.0, refined)
| Capability | Gitea support | ACDL approach (v1.1) |
|---|---|---|
| Org-scoped repo create | POST /api/v1/orgs/{org}/repos |
Used for any new repos |
| Native Pages | None | Serve acdl-evidence via raw file URLs (unchanged from v1.0) |
| Environments API | None; act_runner ignores environment: |
Model HITL gates via workflow_dispatch approval inputs (v1.0 D-013 pattern) — refined in Phase 07 for the real pre-execution gate model |
repository_dispatch |
Not supported | Cross-repo trigger via workflow_dispatch API (unchanged) |
| Reusable workflows | Supported | acdl/.gitea/workflows/pipeline.yml via uses: ...@<ref> |
id-token: write / OIDC |
Not supported (RESEARCH TARGET 1, conf 0.95). Gitea docs list id-token as an unsupported GitHub-only scope; open proposal go-gitea/gitea#33681; draft PR go-gitea/gitea#36988 unmerged. Even Gitea's own CI uses long-lived AWS keys (issue #37980). |
Spike waiver D-039: per-run-rotated long-lived key (rotated after each run by scripts/rotate_spike_key.sh). Real OIDC deferred to v1.2, blocked on PR #36988. |
actions/configure-aws-credentials |
Unusable without OIDC | Spike uses static AWS creds from a (rotated) Gitea Actions secret via the aws-actions/configure-aws-credentials@v4 access-key-id/secret-access-key inputs, or plain AWS_ACCESS_KEY_ID/AWS_SECRET_ACCESS_KEY env vars. v1.2 switches to role-to-assume when OIDC lands. |
Branch pinning rule (refined for W2.A)
- Dev/qa contracts reference the reusable workflow by tag
(
@v1.1-spike). - Prod-bound workflows reference by SHA; the platform CLI
(
platform/cli/resolve-tag.ts, Phase 07) resolves the current tag to its SHA. (Spike scope: the CLI is a stub; the real CLI lands in v1.2.)
Verification toolchain
ACDL has no package.json. The verification gate substitutes:
- typecheck:
terraform validate,python3 -m py_compile, JSON Schema validation (ajvorpython -m jsonschema) againstschemas/. - test: per-phase
scripts/verify_phaseNN.sh(Phase 06: archive integrity; Phase 07: schema validation + decision-resolution completeness; Phase 08: OIDC assume-role + state backend; Phase 09: IR + L1 + adapterterraform plan; Phase 10: end-to-end contract submission). - build:
terraform init(real build for the spike). - See
PERSONAS.mdverification_toolchain.
Build order (v1.1)
- Phase 06 — archive demo, reorient repo.
- Phase 07 — finalize architecture v1.0; author schemas + designs.
- Phase 08 — AWS OIDC bootstrap (use temp key once, rotate).
- Phase 09 — IR +
l1-s3+ Terraform adapter →terraform plan. - Phase 10 —
l2-static-assets+ contract→IR → end-to-end spike. - COMPLETE gate — review → ship
v1.2.0→ audit. DONE.
v1.2 build-out scope
v1.2 takes the v1.1 spike (dev-only, plan-only, single S3 L1) to a real,
simpler, better-documented platform that delivers a microservice to AWS ECS
Fargate end-to-end. The locked architecture (§1–§12) is unchanged — v1.2
extends the implementation, not the design.
In scope (five axes, user-directed 2026-07-21)
- Re-evaluate the current state. go-gitea/gitea#36988 (OIDC for Gitea
Actions) re-checked 2026-07-21: still open (last updated 2026-05-27,
not merged). Real OIDC remains deferred to v1.3+; v1.2 extends the D-039
per-run-rotated-key waiver as D-047. The waiver continues to satisfy
§12.5's intent (no persistently long-lived key): the spike key is
rotated after each run by
scripts/rotate_spike_key.sh, and Phase 12 tightens the IAM scoping + rotation hygiene. - NFR improvements on the existing spike. Least-privilege IAM audit of
spike_runner_policy.json; idempotentcreate_state_backend.py/create_iam_user.py; proper exit codes / error handling; P1-1 redaction (two AWS access key IDs in.ciagent/VERIFY.mdPhase 09 narrative). - Streamline / simplify the current setup. Consolidate
run_spike_plan.sh+run_spike_e2e.shinto onescripts/run_platform.sh; remove dead code and staleplatform/paths. - README.md fully up to date on how the platform works. Reflect v1.1
complete; document the actual spike flow,
scripts/run_platform.sh, the real repo layout, and the v1.2 objective. - Bootstrap a consumer repo with a basic microservice deployed to ECS
end-to-end. New Gitea repo
acdl-consumer-microservice(orgcontinuous-intelligence); new IR-typed L1s (l1-vpc,l1-ecs-cluster,l1-ecs-service,l1-iam-role,l1-alb,l1-ecr); newl2-microservicethin-composition; one contract submission →terraform apply(dev, autonomous per §10, confidence ≥ 0.50) → a live ECS Fargate service serving HTTP 200 → evidence event to the DynamoDB outbox → acdl-evidence timeline.
Angine extension (ECS Fargate)
The Terraform adapter (§12) remains the only engine-specific code. v1.2
expands the adapter TYPE_MAP to cover the six new ECS-shaped IR resource
types. The L1 interface shape (IR-typed inputs/outputs/NFRs, registered in
modules-ir/registry.json) is unchanged — only the set of registered L1s
grows. The IR commitments (REQ-28) continue to hold: modules-ir/,
schemas/, contracts/, core/confidence_signal.py,
core/contract_resolver.py, core/outbox_writer.py
remain engine-agnostic.
terraform apply (dev only)
v1.2 lifts the engine execution from plan to apply for the dev
environment only. Dev is autonomous per §10 (confidence ≥ 0.50, no HITL).
apply for qa/prod/dr remains HITL-gated and out of scope for v1.2. The
apply result (resources created, plan diff) is captured in the evidence
stream as a terraform.apply event.
Out of scope for v1.2 (deferred to v1.3+)
| Feature | Reason |
|---|---|
| Real OIDC federation | go-gitea/gitea#36988 still open. v1.2 extends D-039 waiver (D-047); real OIDC is v1.3+. |
| Full HITL matrix wiring (qa/prod/dr) | v1.2 is dev-only autonomous apply; HITL wiring is v1.3. |
| Kyverno + OPA policy engines | v1.2 keeps Checkov only; Kyverno/OPA are v1.3. |
| MCP skill catalog + real L3B agent | v1.2 keeps the L3B stub; the 5-skill catalog is v1.3. |
| Audit ledger build-out (S3 Object Lock + JWS + async worker + DLQ + daily checkpoints) | v1.2 keeps the v1.1 outbox; the regulatory ledger is v1.3. |
| Multi-region state / outbox | Single-region in v1 (§9, §12.3); multi-region is v1.3+. |
| Prod/dr environments | v1.2 is dev-only; prod/dr are v1.3. |
| GitOps reconciler (ArgoCD/Flux) | v1.3+. |
Build order (v1.2)
- Phase 11 — re-eval #36988 + NFR audit + simplification findings + README rewrite.
- Phase 12 — NFR harden + simplify (idempotent bootstrap, one
run_platform.sh, IAM audit, redactions). - Phase 13 — six ECS L1s + adapter
TYPE_MAPexpansion. - Phase 14 —
l2-microservice+ contract schema extension. - Phase 15 — consumer repo +
terraform apply(dev) → live ECS service. - Phase 16 — capstone e2e: consumer commit → live HTTP 200 → evidence → timeline.
- COMPLETE gate — review → ship
v1.3.0→ audit.
v1.8 Architecture Addendum
Milestone v1.8 (complete, tag
v1.8.0). Adds encryption-by-default, deletion-protection-by-default, uptime monitoring, decommission alias, engineering standards, and path documentation.
New Primitives
kms-key(aws:kms:key) — Per-stack customer-managed KMS key withenable_key_rotation = true. One key per L2 deployment (no shared keys). Wired into both L2 compositions as a child, with itskms_key_arnoutput connected to all children'skms_key_arninput. Adapter emitsaws_kms_key+enable_key_rotation.uptime(aws:ecs:uptime-service) — Uptime-kuma on ECS Fargate with a feature flag (feature_flag_enabled), monitored endpoints (HTTP/DNS/TCP), alert channels (Teams/email/SMS/GitHub issues). Deployed by default after any L2 module with a separate terraform state. When the feature flag is false, the adapter emits no resources.
Encryption by Default
All 12 L1 primitives have encryption_enabled NFR (default true). Primitives
with at-rest data (s3, rds, ecr, ecs-service, ecs-cluster) have an optional
kms_key_arn input. The adapter emits encryption blocks (SSE-KMS for S3,
storage_encrypted for RDS, encryption_configuration for ECR) referencing the
per-stack CMK when provided. Managed KMS fallback with stderr warning for
standalone L1 deployments.
Deletion Protection by Default
All 12 L1 primitives have deletion_protection NFR (default true). The
adapter emits lifecycle { prevent_destroy = true } when true. L2 modules
expose a features.deletion_protection flag (default true) propagated to
all children via the resolver. Setting inputs.deletion_protection: false
in the contract disables it for the whole stack.
Decommission Alias
A mode: decommission on the deploy pipeline implements a 2-step destroy:
- Disable deletion protection (resolve with
deletion_protection: false, terraform plan/apply, HITL SRE gate via GitHub environment). - Zero counts + destroy (
decommission_transformzeroes all scalable counts, terraform plan/apply, second HITL SRE gate).
CMDB validation via DynamoDB acdl-change-requests table. The Lambda
validate_change_request action queries the table and asserts
status == "approved" + consumerRepo match.
Adapter Expansion
TYPE_MAP grew from 16 to 19 entries (+ aws:kms:key, aws:kms:alias,
aws:ecs:uptime-service). Specialized emission branches added for KMS key
rotation, S3 SSE-KMS configuration, uptime ECS Fargate task, and
prevent_destroy lifecycle on all resources.
Pipeline Stages
The deploy pipeline grew from 8 to 9 stages (+ deploy-uptime after
publish-outputs). The deploy-uptime stage constructs a synthetic uptime
contract from the L2 stack outputs, resolves + adapts it to a separate
terraform state directory, and publishes the uptime URL via PR comment.
Forge-Agnostic API URLs
The platform Lambda (contract_ingestor.py) reads GITHUB_API_BASE env
for forge-agnostic API URLs. GitHub uses /search/issues; Gitea uses
/repos/{owner}/{repo}/issues. Detection via /api/v1 in the base URL.
v1.9 Addendum (2026-07-23)
New Components
core/contract_resolver.pyinterpolation (D-081): the resolver now expands${env.<field>}+${contract.<field>}tokens post-schema-validation, pre-IR-resolution. The env context is the loaded environment onboarding JSON (core/environments/<name>.json, schemaschemas/environment.schema.json). The resolver'schild_input_maproutes L2 wires to the sub-resource that declares the input (P1-1 —desired_count→aws:ecs:service,family→aws:ecs:task_definition).core/environment_check.pyload()(REQ-104): loads + returns the parsed environment JSON; emits a stderr warning for placeholderaccount_idwhen env != dev.core/hitl_gates.py(REQ-108, D-084): the HITL pre-execution attestation gate. Records the approver identity to the DynamoDB outbox (approver_qa/approver_prod/approver_dr), runs the separation-of- duties check on prod, invokes the attestation matrix, returns(ok, reason). Dev skips (autonomous).run_platform.shcallsattestbefore apply for qa/prod/dr.core/attestation_matrix.py(REQ-109, D-084): the 8-concern attestation matrix fromhitl_matrix_design.md§10.4. Offline-testable concerns (contract NFRs, schema validity, policy pass) run for real; operator-supplied concerns accept signed evidence artifacts validated for freshness + schema. Signature verification skips whenACDL_ATTESTATION_SIGNING_KEY_IDis unset (D-089).core/separation_of_duties.pyroute_halt_artifact(REQ-107): real SNS publish (acdl-sod-halttopic, ARN fromACDL_SOD_HALT_TOPIC_ARN) + outbox fallback (SEPARATION_OF_DUTIES_VIOLATIONevent). The SNS topic is defined interraform/platform/main.tf.adapters/wiz/wiz_adapter.pyWizClient(REQ-110): real GraphQL API client (<WIZ_API_URL>/graphql, Bearer auth, pagination viapageInfo.hasNextPage).fetch_and_adapttranslates issues →PolicyCheckResult. Graceful degrade when unconfigured.adapters/kyverno/kyverno_adapter.py(REQ-111): fleshed-outPolicyReport→PolicyCheckResultmapping (pass/fail/skip/warn + severity + skip-with-reason + resource construction). Inactive-for-TF guard preserved.
Per-Environment Promotion (D-082)
The deploy workflow (.github/workflows/deploy.yml +
.gitea/workflows/deploy.yml, byte-identical) declares an environment
workflow_call input. When non-empty, run_platform.sh --environment <name> overrides the contract's environment field before schema
validation (D-088). One CI job per environment; promotion = running the
matching job, no environment: field editing. Per-env contract files
(contracts/<module>.<env>.yaml) use interpolation for env-specific
values.
Adapter Parameterization (P1-1, D-085)
The adapter (adapters/terraform/adapter.py) reads ECS/ALB/VPC defaults
from L1 interface.json inputs (desired_count, launch_type,
family, target_type, load_balancer_type, name). The adapter is a
thin translator; the child_input_map routes wires to the declaring
sub-resource.
Deferred (D-083)
S3 Object Lock + JWS detached signatures + async worker + DLQ + daily checkpoints (audit ledger build-out) — deferred to a future milestone. The hash-chain + DynamoDB-outbox path remains the v1.9 production audit record.
v1.10 Addendum — Regression VERIFY + Local Emulators + Capability Re-Verification
Regression-Class VERIFY (D-091, core/regression_verify.py)
The standard VERIFY stage was diff-scoped (it checked the phase diff
only, never re-ran underlying capability). This let 8 NFR-patch phases
(v1.9.1–v1.9.8) pass while the platform decayed. The regression-class
VERIFY (core/regression_verify.py) re-runs capability checks against
the current codebase and tags each Verified/Decayed/Broken. It fails
closed on any non-Verified capability, blocking milestone completion.
The registry (CAPABILITY_REGISTRY) holds 16 capability checks
(CAP-001..CAP-016): 12 local-tier + 4 live-AWS. Adding a capability is
a single function + one registry entry. The gate runs via
scripts/run_regression.sh and writes .ciagent/REGRESSION_REPORT.md
.json.
Local Emulating Adapters (D-092, core/local_emulators.py)
Four local adapters let the platform run the full headline E2E without cloud credentials:
FlatFileOutbox— flat-file DynamoDB outbox emulator (hash-chained JSONL; resumable across instances; chain verification).LocalEcsEmulator— local ECS Fargate HTTP 200 emulator (binds port 0 on 127.0.0.1; daemon thread; clean destroy).LocalS3StateBackend— rewrites the terraform S3 backend to a local backend (per-stack tfstate in a temp folder).LocalLambdaStub— invokes the contract_ingestor handler in-process (patches_get_dynamodb/_get_secrets_client/urllib.urlopen; DynamoDB writes redirected to the FlatFileOutbox).
run_local_e2e() runs the full pipeline: contract → resolver → adapter
→ local S3 backend → local ECS (HTTP 200) → flat-file outbox (chain
verified) → local Lambda (200). Gated on ACDL_LOCAL_TIER=1.
Capability Re-Verification Sweep (D-093)
.ciagent/CAPABILITY_INVENTORY.md enumerates 16 auto-verified
capabilities + 6 IAM-gated escalated resources. The sweep found and
fixed 7 adapter defects in adapters/terraform/adapter.py (duplicate
outputs, duplicate args, missing required args, deprecated AWS provider
v5 arg names). The headline E2E now passes at both tiers: local
emulator + live-AWS terraform init/validate/plan.
Adapter Defect Fixes (P54)
7 defects fixed in adapters/terraform/adapter.py:
- Duplicate output definitions (per-resource + stack-level both emitted).
- Duplicate
desired_count/launch_typeon ECS service. - Duplicate
target_type/family/load_balancer_type. - Missing
assume_role_policy/role_nameon IAM role (L2 composition gap). - Missing
cidr_block/vpc_id/namedefaults on VPC/subnet/route_table/ ECS cluster/ECR repository. - ECR
kms_key_arnunsupported arg →encryption_configurationblock. - CloudFront OAC + WAF deprecated arg names (AWS provider v5):
signing_behavior,signing_protocol,origin_access_control_id,s3_origin_config.origin_access_identity,origin_id,rule(singular),scope=CLOUDFRONT(uppercase).
v1.11 Addendum — Stateless Adapter + Pipeline-Driven Lifecycle Testing
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.12 Addendum — Presentation Refinement + CAP-013 Fix
CAP-013 adapter dedup fix (REQ-129). Multi-resource L1s (ecs-service,
alb) with stack outputs + cross-module refs now dedup to ONE module block
named by the composition child id, with expanded sub-ids rewritten via
id_remap. terraform validate succeeds for the microservice stack.
CAP-017/018 probe fixes (REQ-130). CAP-017's probe no longer requires
locals.tf for modules that legitimately omit it. CAP-018's probe
instantiates LocalLambdaStub with the required outbox arg.
v1.13 Addendum — Presentation Polish + Config Schema Migration
Config.json schema migration (v1.13.1). Regenerated
.ciagent/config.json to the updated CIAgent v2 config structure (drop
removed fields, migrate gitea→release.gitea, add
secrets/ship/backend/ideation/personas/logging/telemetry
sections).
Presentation polish (v1.13.0, v1.13.2). Action headlines, story-arc restructure, larger fonts, 6 new mermaid diagrams, badge cleanup, platform-architecture diagram. Docs-only NFR patches.
v1.14 Addendum — NFR Refinement (bug fixes, security, stubs, tests, docs)
Bug fixes (Wave 1, P1-P6). Adapter dedup rejects unregistered modules
with ValueError (P1). Static-assets composition wires cloudfront inputs
(P2). L2 lifecycle scripts document remote-state design (P3). Regression
gate adds terraform fmt -check syntax probe (P4). Adapter dedup-merge +
remote-state-key unit tests (P5). ALB target group name_prefix derives
from var.name (P6).
Security (Wave 2, P7-P12). 6 swallowed-error sites narrowed to
specific exceptions (P7). Account ID externalized to
ACDL_AWS_ACCOUNT_ID env (P8). IAM policy scoped to acdl-* ARNs (P9).
Contract ingestor validates contractId/environment/error (P10). Environment
schema adds additionalProperties: false + format validation (P11).
.gitignore credential-pattern catch-all (P12).
Stub/test/CI/hygiene (Wave 3, P13-P17). Kyverno --kube-version flag
removed (P13, G-103). Orphan artifacts + dead config cleaned (P14). 7
untested scripts gain test coverage (P15). Gitea workflow parity
documented + script set flags fixed (P16). Config.json persona +
branching strategy + ollama-cloud aligned (P17).
Standards/docs/VPC (Wave 4, P18-P20). STANDARDS.md reconciled (P18).
Documentation synced: ARCHITECTURE.md addenda, stale @v1.6-1.9 → @v1.13,
GRILL G-005/G-008 resolved, COST.md window extended, D-083 deferral
recorded (P19). Platform VPC CIDR parameterized + data-driven subnet
count (P20).
D-083 deferral (explicit). The audit ledger build-out (S3 Object Lock
- JWS detached signatures + SQS DLQ + async worker + daily checkpoints) remains deferred (D-096, v1.14). The hash-chain + DynamoDB outbox is the v1.14 audit record. JWS per-event authenticity is not implemented; a forged event is only detectable by re-reading the whole chain. The deferral is documented here explicitly per the v1.14 grill (E-001).
v1.15 Addendum — Nova Rebrand (Major/breaking, 2026-07-30)
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. Per the
branch-strategy precedent (breaking/feature milestones tag on their
OWN minor line), 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)
| 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 |
| 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 tonovafor 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.
Migration ordering (binding)
- P1 docs/decks/prose — no runtime impact; ships consumer migration guide announcing the 5 breaking changes.
- P2 code + env vars (dual-read) + consumer path — deployments don't break during the transition window (dual-read fallback).
- P3 SSM path (copy → read → delete) + tag keys (parallel-tag → policy swap → remove old).
- P4 AWS resource names — staged terraform migration (KMS alias,
SNS/SG/Lambda recreate, DynamoDB scan+copy, ECR re-push, IAM
re-bootstrap, state bucket
-migrate-state, ALB recreate). Maintenance window + rollback runbook (docs/NOVA_AWS_MIGRATION.md). - P5 final review + audit + remove dual-read fallback + milestone ship.
Capability gate (binding)
The regression gate (CAP-001..CAP-016, scripts/run_regression.sh) must
stay 16/16 Verified throughout the rebrand. P2/P3/P4 update test
fixtures that reference ACDL/acdl so the gate stays green. No
capability is added, removed, or reclassified in v1.15 — the rebrand is
nomenclature + identifiers, not behavior.
v1.16 Addendum — Nova Simplification (NFR, 2026-07-30)
The v1.16 NFR milestone added 6 new code components + 1 new Terraform module + 1 new schema, all documented here for the architecture record.
New components
| Component | Path | Purpose |
|---|---|---|
| Onboarding request handler | core/onboarding.py |
generate_env_file(request, template_env) — produces a <env>.json from a consumer onboarding request (P19, REQ-183). CLI entry point for self-service env-file generation. |
| Decommission transform | core/decommission_transform.py |
decommission_transform(stack) — zero counts + disable deletion protection (REQ-92). Extracted from contract_resolver (P12, REQ-176). |
| Contract resolver CLI | core/contract_resolver_cli.py |
main() CLI entry point — resolves a contract YAML to a Target Stack JSON. Extracted from contract_resolver (P12, REQ-176). |
| Regression verify CLI | core/regression_verify_cli.py |
main() CLI entry point — runs the regression gate + writes the report. Extracted from regression_verify (P13, REQ-177). |
| Workflow sync generator | scripts/sync_workflows.py |
--check/--write — generates the 3 byte-identical Gitea+GitHub workflow pairs from workflows-src/ (P8, REQ-172). |
| Onboarding Terraform | terraform/onboarding/ |
aws_iam_role.consumer_deploy + aws_iam_role_policy.consumer_invoke (ABAC nova:owner tag). Offline-proven only (P20, REQ-184, D-114). |
Modified components
| Component | Change | Phase |
|---|---|---|
core/contract_resolver.py |
_load_env delegates to environment_check.load() (dedup); is_l2 uses registry kind field; _load_schema caches schemas; decommission_transform + CLI re-export shim (P12). |
P7, P12, P14 |
core/regression_verify.py |
Dedup helpers (_check_resolver, _check_live_terraform_plan, _assert_contracts_resolve); CAP-013..016 Skipped on post-teardown (G-111); passed accepts Skipped; CLI re-export shim (P13). |
P5, P9, P13 |
core/lambda/contract_ingestor.py |
Fail closed on missing IAM identity (P10); env enum from core/environments/ (P10); payload size cap + schema validation (P11); onboard_consumer action (P18); [NOVA-ALERT] rebrand (P2). |
P2, P10, P11, P18 |
core/output_publisher.py |
SAFE_OUTPUT_NAMES schema-driven from interface.json; narrowed excepts; urllib.error import (P4, P14). |
P4, P14 |
core/environment_check.py |
Onboarding message rebranded Nova + self-service request path (P2, P19). | P2, P19 |
core/local_emulators.py |
LocalLambdaStub sets NOVA_LAMBDA_LOCAL_BYPASS; stale dual-read comments + acdl_* prefixes removed (P3, P10). |
P3, P10 |
scripts/run_platform.sh |
--help flag; run_hitl_gate() fn; NOVA_CONTRACT_ID/NOVA_WORK_DIR config; decommission + uptime blocks extracted to sourced helpers (P6, P9, P15). |
P6, P9, P15 |
adapters/terraform/adapter.py |
State bucket nova-tfstate-* (P1); module docstring Nova (P2). |
P1, P2 |
adapters/kyverno/policies/require-resource-labels.yml |
nova:* labels (not acdl:*) (P1). |
P1 |
modules/registry.json |
kind field (l1/l2) on all 14 entries (P7). |
P7 |
New schema
schemas/onboarding.schema.json— the self-service onboarding request (consumerRepo, requestedEnvironment, ownerId, billingTag). P18, REQ-182.
Onboarding request-path architecture (D-113)
The no-humans onboarding flow is a 3-step request path (real AWS provisioning deferred):
Consumer → POST Lambda (onboard_consumer) → pending CMDB row (P18)
→ core/onboarding.py → <env>.json binding file (P19)
→ terraform/onboarding/ → cross-account role + ABAC tag (P20, offline)
The Lambda Function URL (IAM auth) + consumer_invoke_policy.json (ABAC
nova:owner) are the transport; the request is accepted + a binding
generated + the role Terraform proven offline. No AWS resources are
created by the request path (D-113/D-114).
Regression gate (G-111 binding)
The regression gate (D-091) now treats Skipped as acceptable for the
post-v1.11-teardown steady state (D-096): CAP-013..016 (live-AWS tier)
return Skipped when the resources are absent (NoSuchBucket/
ResourceNotFoundException). RegressionReport.passed is
all(r.status in ("Verified", "Skipped")). The gate passes at 18
Verified + 4 Skipped (0 Decayed/Broken).
v1.17 Addendum — Strategic Direction, Leadership Metrics & Unified Story (2026-08-04)
The v1.17 milestone adds 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/<run_id>.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/<kpi>.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/<run_id>.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).
NORTH_STAR integration point (REQ-186)
.ciagent/NORTH_STAR.md is read by CIAgent in context-loading for all
future milestones. The integration mechanism (to be finalized in P4):
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)
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):
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).