a63c85bc51
Relocate completed-milestone history to .ciagent/archive/ (byte-identical
snapshots of PROJECT/REQUIREMENTS/ROADMAP/ARCHITECTURE pre-compression +
verbatim moves of REVIEW/AUDIT/VERIFY/PRE_MORTEM). Slim the in-place files
to retain only active-milestone (v1.26) + immediate-predecessor (v1.25)
context + durable vision/tenets/scope/RACI/capability-status/load-bearing
decisions. REGRESSION_REPORT.{json,md} stay in place (live read/write
targets of core/metrics/collector.py + core/regression_verify.py).
Working context: 11,164 → 4,152 lines (~63% reduction). Archive preserves
8,615 lines. Lossless via relocation + git history. No test regressions
(761 passed; same 3 pre-existing failures as baseline).
---ci---
project: acdl
phase: 2
milestone: v1.26
status: execute
lessons:
- REGRESSION_REPORT.{json,md} are live operational files (read by
core/metrics/collector.py + core/regression_verify.py) — must NOT be
archived. Pre-flight grep for code references to candidate archive
paths before any move.
- test_no_purged_loaded_term scans .ciagent/PROJECT.md + CLARIFY.md +
docs/ for 'penetrat' — slimmed files must not reintroduce it. Historical
description of the purge ('removed the term ...') is safe in ROADMAP.
- Git rename detection (R) works for pure file moves; snapshot-then-slim
shows as A + M. Both preserve history.
---/ci---
946 lines
53 KiB
Markdown
946 lines
53 KiB
Markdown
# 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 and `docs/vision.md` conflict, 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`
|
||
- `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).
|
||
|
||
### 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.
|
||
|
||
```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.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` (references `l1-s3` only).
|
||
- Terraform adapter: IR → `terraform plan` against AWS via OIDC.
|
||
- One contract submission → contract→IR → `terraform plan` → Checkov
|
||
`PolicyCheckResult` → 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 (`ajv` or `python -m jsonschema`) against `schemas/`.
|
||
- **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 + adapter `terraform
|
||
plan`; Phase 10: end-to-end contract submission).
|
||
- **build:** `terraform init` (real build for the spike).
|
||
- See `PERSONAS.md` verification_toolchain.
|
||
|
||
## Build order (v1.1)
|
||
|
||
1. Phase 06 — archive demo, reorient repo.
|
||
2. Phase 07 — finalize architecture v1.0; author schemas + designs.
|
||
3. Phase 08 — AWS OIDC bootstrap (use temp key once, rotate).
|
||
4. Phase 09 — IR + `l1-s3` + Terraform adapter → `terraform plan`.
|
||
5. Phase 10 — `l2-static-assets` + contract→IR → end-to-end spike.
|
||
6. 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)
|
||
|
||
1. **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.
|
||
2. **NFR improvements on the existing spike.** Least-privilege IAM audit of
|
||
`spike_runner_policy.json`; idempotent `create_state_backend.py` /
|
||
`create_iam_user.py`; proper exit codes / error handling; P1-1 redaction
|
||
(two AWS access key IDs in `.ciagent/VERIFY.md` Phase 09 narrative).
|
||
3. **Streamline / simplify the current setup.** Consolidate
|
||
`run_spike_plan.sh` + `run_spike_e2e.sh` into one
|
||
`scripts/run_platform.sh`; remove dead code and stale `platform/` paths.
|
||
4. **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.
|
||
5. **Bootstrap a consumer repo with a basic microservice deployed to ECS
|
||
end-to-end.** New Gitea repo `acdl-consumer-microservice` (org
|
||
`continuous-intelligence`); new IR-typed L1s (`l1-vpc`, `l1-ecs-cluster`,
|
||
`l1-ecs-service`, `l1-iam-role`, `l1-alb`, `l1-ecr`); new
|
||
`l2-microservice` thin-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)
|
||
|
||
1. Phase 11 — re-eval #36988 + NFR audit + simplification findings + README rewrite.
|
||
2. Phase 12 — NFR harden + simplify (idempotent bootstrap, one `run_platform.sh`, IAM audit, redactions).
|
||
3. Phase 13 — six ECS L1s + adapter `TYPE_MAP` expansion.
|
||
4. Phase 14 — `l2-microservice` + contract schema extension.
|
||
5. Phase 15 — consumer repo + `terraform apply` (dev) → live ECS service.
|
||
6. Phase 16 — capstone e2e: consumer commit → live HTTP 200 → evidence → timeline.
|
||
7. 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 with
|
||
`enable_key_rotation = true`. One key per L2 deployment (no shared keys).
|
||
Wired into both L2 compositions as a child, with its `kms_key_arn` output
|
||
connected to all children's `kms_key_arn` input. Adapter emits
|
||
`aws_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:
|
||
1. Disable deletion protection (resolve with `deletion_protection: false`,
|
||
terraform plan/apply, HITL SRE gate via GitHub environment).
|
||
2. Zero counts + destroy (`decommission_transform` zeroes 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.py` interpolation** (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`,
|
||
schema `schemas/environment.schema.json`). The resolver's
|
||
`child_input_map` routes 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.py` `load()`** (REQ-104): loads + returns the
|
||
parsed environment JSON; emits a stderr warning for placeholder
|
||
`account_id` when 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.sh` calls
|
||
`attest` before apply for qa/prod/dr.
|
||
- **`core/attestation_matrix.py`** (REQ-109, D-084): the 8-concern
|
||
attestation matrix from `hitl_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 when
|
||
`ACDL_ATTESTATION_SIGNING_KEY_ID` is unset (D-089).
|
||
- **`core/separation_of_duties.py` `route_halt_artifact`** (REQ-107):
|
||
real SNS publish (`acdl-sod-halt` topic, ARN from
|
||
`ACDL_SOD_HALT_TOPIC_ARN`) + outbox fallback
|
||
(`SEPARATION_OF_DUTIES_VIOLATION` event). The SNS topic is defined in
|
||
`terraform/platform/main.tf`.
|
||
- **`adapters/wiz/wiz_adapter.py` `WizClient`** (REQ-110): real GraphQL
|
||
API client (`<WIZ_API_URL>/graphql`, Bearer auth, pagination via
|
||
`pageInfo.hasNextPage`). `fetch_and_adapt` translates issues →
|
||
`PolicyCheckResult`. Graceful degrade when unconfigured.
|
||
- **`adapters/kyverno/kyverno_adapter.py`** (REQ-111): fleshed-out
|
||
`PolicyReport` → `PolicyCheckResult` mapping (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`:
|
||
1. Duplicate output definitions (per-resource + stack-level both emitted).
|
||
2. Duplicate `desired_count`/`launch_type` on ECS service.
|
||
3. Duplicate `target_type`/`family`/`load_balancer_type`.
|
||
4. Missing `assume_role_policy`/`role_name` on IAM role (L2 composition gap).
|
||
5. Missing `cidr_block`/`vpc_id`/`name` defaults on VPC/subnet/route_table/
|
||
ECS cluster/ECR repository.
|
||
6. ECR `kms_key_arn` unsupported arg → `encryption_configuration` block.
|
||
7. 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|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`.
|
||
|
||
### Migration ordering (binding)
|
||
|
||
1. **P1** docs/decks/prose — no runtime impact; ships consumer migration
|
||
guide announcing the 5 breaking changes.
|
||
2. **P2** code + env vars (dual-read) + consumer path — deployments don't
|
||
break during the transition window (dual-read fallback).
|
||
3. **P3** SSM path (copy → read → delete) + tag keys (parallel-tag →
|
||
policy swap → remove old).
|
||
4. **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`).
|
||
5. **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`):**
|
||
```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).
|