Compare commits
22 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| eef8761658 | |||
| df1b004a46 | |||
| 64b2ba5076 | |||
| 7a4e43d11a | |||
| cda06ab421 | |||
| ec2311a3e0 | |||
| 4d694ba2e9 | |||
| 3d9dd06411 | |||
| b257846981 | |||
| 986171a165 | |||
| 099ed015ac | |||
| cc97a9308d | |||
| c2ca0e4631 | |||
| 225de0f613 | |||
| 69d8496107 | |||
| 1aa525f234 | |||
| 81f111d462 | |||
| 79e7a4a304 | |||
| 8ae307affc | |||
| 6e1a1bd7db | |||
| 040abc0fb7 | |||
| 71bd61ceb1 |
+404
-401
@@ -1,28 +1,16 @@
|
|||||||
# Nova — Architecture
|
# ACDL — Architecture (v1.1 target)
|
||||||
|
|
||||||
> **Compressed.** The full v1.0–v1.24 architecture history (v1.1 spike
|
> Target architecture for the real Agentic Cloud Delivery Platform.
|
||||||
> scope, v1.2 build-out, v1.8–v1.16 addenda) is preserved verbatim at
|
> Source of truth for **how**: `docs/architecture.md` (v0.2) is the upstream
|
||||||
> `.ciagent/archive/ARCHITECTURE-v1.0-v1.24.md`. This file retains the
|
> draft; this file is the ACDL-repo operating copy, refined at phase
|
||||||
> durable target architecture (§1–§12, the four layers + six cross-cutting
|
> boundaries. Where this file and `docs/vision.md` conflict, the vision wins.
|
||||||
> concerns) + the three addenda that describe the **current state**:
|
|
||||||
> v1.11 (stateless adapter), v1.15 (Nova rebrand — current naming), and
|
|
||||||
> v1.17 (telemetry/observability layer + §12.7 Policy Engine Registry).
|
|
||||||
> Intermediate addenda (v1.1 spike scope, v1.2 build-out, v1.8/1.9/1.10/
|
|
||||||
> 1.12/1.13/1.14/1.16) describe evolved or superseded states and are
|
|
||||||
> preserved in the archive snapshot.
|
|
||||||
>
|
|
||||||
> Source of truth for **how**: `docs/architecture.md` (v0.2) is the
|
|
||||||
> upstream draft; this file is the Nova-repo operating copy, refined at
|
|
||||||
> phase boundaries. Where this file and `docs/vision.md` conflict, the
|
|
||||||
> vision wins.
|
|
||||||
|
|
||||||
## Status
|
## Status
|
||||||
|
|
||||||
Architecture is at **v0.2** upstream (`docs/architecture.md`). Milestone
|
Architecture is at **v0.2** upstream (`docs/architecture.md`). Milestone v1.1
|
||||||
v1.1 **finalized it to v1.0** in Phase 07 by resolving the 11 open
|
**finalizes it to v1.0** in Phase 07 by resolving the 11 open decisions
|
||||||
decisions (see `PROJECT.md` open-decision resolutions table). The v1.11
|
(see `PROJECT.md` open-decision resolutions table). This file records the
|
||||||
addendum (stateless adapter) and the v1.17 addendum (telemetry layer +
|
locked commitments and the v1.1 spike scope.
|
||||||
§12.7 Policy Engine Registry) record the current-state refinements.
|
|
||||||
|
|
||||||
## Overview
|
## Overview
|
||||||
|
|
||||||
@@ -67,9 +55,8 @@ the same policy envelope, and the same evidence stream.
|
|||||||
### Layer 1 — Foundational Primitives
|
### Layer 1 — Foundational Primitives
|
||||||
Single-purpose, **engine-agnostic** primitive modules. L1 modules do
|
Single-purpose, **engine-agnostic** primitive modules. L1 modules do
|
||||||
not compose with other L1s; L1 takes its environment as input. The L1
|
not compose with other L1s; L1 takes its environment as input. The L1
|
||||||
interface is defined against the **Target Stack IR**, not against
|
interface is defined against the **Target Stack IR**, not against Terraform
|
||||||
Terraform directly (the IR is shaped to round-trip to Terraform in v1,
|
directly (the IR is shaped to round-trip to Terraform in v1, per §12.1).
|
||||||
per §12.1).
|
|
||||||
|
|
||||||
- No inter-L1 references. L1 may call Terraform data sources.
|
- No inter-L1 references. L1 may call Terraform data sources.
|
||||||
- Semver: interface → MAJOR, behavior → MINOR, lifecycle → PATCH (W3.D).
|
- Semver: interface → MAJOR, behavior → MINOR, lifecycle → PATCH (W3.D).
|
||||||
@@ -81,8 +68,8 @@ Combine L1 primitives into deployable shapes. Each codebase maps to one
|
|||||||
canonical L2 stack (`multiStack: true` only per W1.B). Shape X
|
canonical L2 stack (`multiStack: true` only per W1.B). Shape X
|
||||||
(parameterized module) or Shape Y (thin-composition layer). Hierarchical
|
(parameterized module) or Shape Y (thin-composition layer). Hierarchical
|
||||||
composition, max depth 5, only registered L1s. The thin-composition tree's
|
composition, max depth 5, only registered L1s. The thin-composition tree's
|
||||||
`wires` field is defined against the IR's relationship type, not a
|
`wires` field is defined against the IR's relationship type, not a Terraform
|
||||||
Terraform module block.
|
module block.
|
||||||
|
|
||||||
Pipeline quality checks: secrets-in-plaintext, public ingress, IAM
|
Pipeline quality checks: secrets-in-plaintext, public ingress, IAM
|
||||||
wildcard, KMS key reference, tag compliance, naming convention. Restricted
|
wildcard, KMS key reference, tag compliance, naming convention. Restricted
|
||||||
@@ -162,10 +149,6 @@ before contract submission ack); RTO = async worker's dead-letter recovery.
|
|||||||
Single-region in v1. The outbox also stores per-contract QA and prod
|
Single-region in v1. The outbox also stores per-contract QA and prod
|
||||||
approver identities (the only durable record outside GitHub's audit log).
|
approver identities (the only durable record outside GitHub's audit log).
|
||||||
|
|
||||||
> **v1.17 update:** the Decision Ledger (SQLite hash-chain, D-121) is the
|
|
||||||
> pilot's audit record. S3 Object Lock / JWS (D-083) is deferred — see
|
|
||||||
> the v1.17 addendum below.
|
|
||||||
|
|
||||||
### Human-in-the-Loop mechanics (§10)
|
### Human-in-the-Loop mechanics (§10)
|
||||||
Pre-execution gates. qa, prod, dr are PR-based attestation gates backed by
|
Pre-execution gates. qa, prod, dr are PR-based attestation gates backed by
|
||||||
GitHub Environments with required reviewers. No partial deployment to roll
|
GitHub Environments with required reviewers. No partial deployment to roll
|
||||||
@@ -198,28 +181,28 @@ platform does not run the skill. Stateless agents, all state in the
|
|||||||
platform. Skills are reviewed for sensitive data before release (Infra &
|
platform. Skills are reviewed for sensitive data before release (Infra &
|
||||||
Ops owns the review; it is the mandatory release gate).
|
Ops owns the review; it is the mandatory release gate).
|
||||||
|
|
||||||
### Engine execution (§12) — the binding constraint
|
### Angine execution (§12) — the binding constraint
|
||||||
**Target Stack IR** (locked): an engine-neutral description of resources
|
**Target Stack IR** (locked): a engine-neutral description of resources
|
||||||
(typed inputs/outputs/NFRs), relationships (single parent per child),
|
(typed inputs/outputs/NFRs), relationships (single parent per child),
|
||||||
composition (tree, max depth 5), and policy hooks. The L1 registry, L2
|
composition (tree, max depth 5), and policy hooks. The L1 registry, L2
|
||||||
thin-composition tree, contract YML, and PolicyCheckResult schema are all
|
thin-composition tree, contract YML, and PolicyCheckResult schema are all
|
||||||
defined against the IR — none against any specific engine.
|
defined against the IR — none against any specific engine.
|
||||||
|
|
||||||
**Engine adapters** are the only engine-specific code. An adapter
|
**Angine adapters** are the only engine-specific code. An adapter
|
||||||
compiles the IR into an engine execution plan. **v1 ships exactly one
|
compiles the IR into a engine execution plan. **v1 ships exactly one
|
||||||
adapter: the Terraform adapter.** v2+ may add OpenTofu, Pulumi, K8s CRDs
|
adapter: the Terraform adapter.** v2+ may add OpenTofu, Pulumi, K8s CRDs
|
||||||
without architectural change.
|
without architectural change.
|
||||||
|
|
||||||
v1 reality: the IR is shaped to round-trip cleanly to Terraform (nearly
|
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
|
isomorphic). As more adapters appear, the IR gets more expressive and the
|
||||||
adapters gain translation logic; the L1 content, the YML standard, and
|
adapters gain translation logic; the L1 content, the YML standard, and the
|
||||||
the thin-composition tree do not change.
|
thin-composition tree do not change.
|
||||||
|
|
||||||
> **v1.11 update:** the Terraform adapter is now a **stateless assembler**
|
**Terraform adapter (v1):** translates IR-typed L1 interface → Terraform
|
||||||
> (~80 lines, emits `module "x" { source }` blocks) — see the v1.11
|
`variable`/`output` blocks; IR-typed L2 thin-composition tree → Terraform
|
||||||
> addendum below. The §12 "thin layer that translates IR → Terraform
|
root module; IR-typed relationships → module references; emits a
|
||||||
> variable/output blocks" framing is superseded by the stateless-assembler
|
`terraform plan` from the IR. The adapter is a thin layer; it does not own
|
||||||
> model; the L1-owns-its-shape invariant is the new contract.
|
L1/L2 content.
|
||||||
|
|
||||||
State storage: S3 (state) + DynamoDB (locking), cloud-managed,
|
State storage: S3 (state) + DynamoDB (locking), cloud-managed,
|
||||||
single-region in v1.
|
single-region in v1.
|
||||||
@@ -228,10 +211,6 @@ Policy toolchain: **Checkov** for Terraform plan policy (the L2 checks +
|
|||||||
tag/naming); **Kyverno** for K8s-native/platform-internal policy; **OPA**
|
tag/naming); **Kyverno** for K8s-native/platform-internal policy; **OPA**
|
||||||
reserved for cross-resource cases, explicitly last resort.
|
reserved for cross-resource cases, explicitly last resort.
|
||||||
|
|
||||||
> **v1.25 update:** the policy toolchain is now unified under the
|
|
||||||
> swappable `PolicyEngine` protocol — see §12.7 below. Checkov and Wiz
|
|
||||||
> remain as raw-finding adapters feeding into kyverno-json meta-policies.
|
|
||||||
|
|
||||||
**Policy result normalization (§12.6):** the confidence signal consumes a
|
**Policy result normalization (§12.6):** the confidence signal consumes a
|
||||||
normalized `PolicyCheckResult` schema, not raw engine output.
|
normalized `PolicyCheckResult` schema, not raw engine output.
|
||||||
|
|
||||||
@@ -263,9 +242,337 @@ Contract→IR resolution: the contract declares intent in IR-typed terms;
|
|||||||
the pipeline resolves it to a target stack (list of L1 instances + inputs +
|
the pipeline resolves it to a target stack (list of L1 instances + inputs +
|
||||||
relationships); the Terraform adapter compiles the target stack to a plan.
|
relationships); the Terraform adapter compiles the target stack to a plan.
|
||||||
|
|
||||||
---
|
## v1.1 spike scope
|
||||||
|
|
||||||
## v1.11 Addendum — Stateless Adapter + Pipeline-Driven Lifecycle Testing (current state)
|
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
|
**Stateless adapter (D-098).** `adapters/terraform/adapter.py` rewritten
|
||||||
from a 918-line monolith (3 constant tables `TYPE_MAP`/`INPUT_MAP`/
|
from a 918-line monolith (3 constant tables `TYPE_MAP`/`INPUT_MAP`/
|
||||||
@@ -291,369 +598,65 @@ VPC; the microservice composition references it via
|
|||||||
`terraform_remote_state` (data source). State keys are deterministic and
|
`terraform_remote_state` (data source). State keys are deterministic and
|
||||||
env-aware (`spike/{contract.id}/{contract.environment}/terraform.tfstate`).
|
env-aware (`spike/{contract.id}/{contract.environment}/terraform.tfstate`).
|
||||||
|
|
||||||
**NOVA_LIFECYCLE_MODE (v1.12, REQ-134; renamed ACDL→NOVA in v1.15 P2).**
|
**ACDL_LIFECYCLE_MODE (v1.12, REQ-134).** The lifecycle pipeline defaults
|
||||||
The lifecycle pipeline defaults to plan-only (fast, no AWS mutation, no
|
to plan-only (fast, no AWS mutation, no cost). A CI variable
|
||||||
cost). A CI variable `NOVA_LIFECYCLE_MODE` (default `plan`) overrides to
|
`ACDL_LIFECYCLE_MODE` (default `plan`) overrides to `full` for the real
|
||||||
`full` for the real apply→modify→destroy. (P2–P4 dual-read fallback to
|
apply→modify→destroy.
|
||||||
`ACDL_LIFECYCLE_MODE`; fallback removed in P5 per the v1.15 addendum.)
|
|
||||||
|
|
||||||
---
|
## v1.12 Addendum — Presentation Refinement + CAP-013 Fix
|
||||||
|
|
||||||
## v1.15 Addendum — Nova Rebrand (current naming)
|
**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.
|
||||||
|
|
||||||
**Milestone:** v1.15-Nova. A full rebrand from **ACDL** / "Agentic Cloud
|
**CAP-017/018 probe fixes (REQ-130).** CAP-017's probe no longer requires
|
||||||
Delivery Platform" → **Nova** / "The New Dawn of DevSecOps — security as
|
`locals.tf` for modules that legitimately omit it. CAP-018's probe
|
||||||
a seamless enabler of fast deployments." This is a **Major milestone**
|
instantiates `LocalLambdaStub` with the required `outbox` arg.
|
||||||
(breaking): consumer-facing path, env var prefixes, SSM path, AWS tag
|
|
||||||
keys, and AWS resource names all change. v1.15 tags run on the **v1.15.x
|
|
||||||
minor line**: `v1.15.0` (P0) → `v1.15.4` (P5 final = release). (G-104
|
|
||||||
binding.)
|
|
||||||
|
|
||||||
### Naming conventions (rebranded — current)
|
## v1.13 Addendum — Presentation Polish + Config Schema Migration
|
||||||
|
|
||||||
| Convention | Before (v1.0–v1.14) | After (v1.15+) | Phase |
|
**Config.json schema migration (v1.13.1).** Regenerated
|
||||||
|------------|---------------------|-----------------|-------|
|
`.ciagent/config.json` to the updated CIAgent v2 config structure (drop
|
||||||
| Project name | `ACDL` / "Agentic Cloud Delivery Platform" | `Nova` / "The New Dawn of DevSecOps" | P1 |
|
removed fields, migrate `gitea`→`release.gitea`, add
|
||||||
| 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 |
|
`secrets`/`ship`/`backend`/`ideation`/`personas`/`logging`/`telemetry`
|
||||||
| Schema `$id` URL | `https://acdl.cloudinit.dev/schemas/...` | `https://nova.cloudinit.dev/schemas/...` | P1 |
|
sections).
|
||||||
| 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)
|
**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.
|
||||||
|
|
||||||
- **S&P Global Energy visual theme** (`sp-theme.json`, deck CSS: #D6002A
|
## v1.14 Addendum — NFR Refinement (bug fixes, security, stubs, tests, docs)
|
||||||
red, Akkurat Pro) — client branding, not the Nova product brand (D-107).
|
|
||||||
- **config.json `release.gitea.repo`** = `acdl` — real Gitea repo name
|
|
||||||
unchanged (D-105). Doc URLs updated to `nova` for prose only.
|
|
||||||
- **Git branch/tag naming** — `milestone/v*`, `phase/*`, `v*` semver; no
|
|
||||||
brand name present (D-112: flat-branch convention preserved).
|
|
||||||
- **Past Gitea release titles** — existing releases keep `ACDL vX.Y.Z`.
|
|
||||||
|
|
||||||
> The full migration ordering (P1–P5), capability gate, and rollback
|
**Bug fixes (Wave 1, P1-P6).** Adapter dedup rejects unregistered modules
|
||||||
> runbook are preserved in `.ciagent/archive/ARCHITECTURE-v1.0-v1.24.md`
|
with ValueError (P1). Static-assets composition wires cloudfront inputs
|
||||||
> §v1.15 Addendum.
|
(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).
|
||||||
|
|
||||||
## v1.17 Addendum — Strategic Direction, Leadership Metrics & Unified Story (current telemetry layer)
|
**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).
|
||||||
|
|
||||||
The v1.17 milestone added a telemetry/observability layer, a Decision
|
**Standards/docs/VPC (Wave 4, P18-P20).** STANDARDS.md reconciled (P18).
|
||||||
Ledger, a metrics export pipeline, a unified narrative deck, and a
|
Documentation synced: ARCHITECTURE.md addenda, stale `@v1.6-1.9` → `@v1.13`,
|
||||||
durable strategic-direction artifact. This addendum documents the
|
GRILL G-005/G-008 resolved, COST.md window extended, D-083 deferral
|
||||||
architecture; the full research findings are in RESEARCH.md §v1.17.
|
recorded (P19). Platform VPC CIDR parameterized + data-driven subnet
|
||||||
|
count (P20).
|
||||||
|
|
||||||
### New components
|
**D-083 deferral (explicit).** The audit ledger build-out (S3 Object Lock
|
||||||
|
+ JWS detached signatures + SQS DLQ + async worker + daily checkpoints)
|
||||||
| Component | Path | Purpose |
|
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
|
||||||
| Event envelope | `core/metrics/event_envelope.py` | CloudEvents 1.0 envelope + `platform.*` semantic conventions (P1, REQ-187) |
|
forged event is only detectable by re-reading the whole chain. The
|
||||||
| 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) |
|
deferral is documented here explicitly per the v1.14 grill (E-001).
|
||||||
| 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 — the v1.26 milestone lifts this for the pilot
|
|
||||||
estate).
|
|
||||||
|
|
||||||
### NORTH_STAR integration point (REQ-186)
|
|
||||||
|
|
||||||
`.ciagent/NORTH_STAR.md` is read by CIAgent in context-loading for all
|
|
||||||
future milestones. The integration mechanism: a reference from
|
|
||||||
`PROJECT.md` + `ARCHITECTURE.md` (this section) + a config entry in
|
|
||||||
`config.json` (`strategic_direction_file: ".ciagent/NORTH_STAR.md"`)
|
|
||||||
that the run workflow reads at SPECIFY. This ensures the strategic
|
|
||||||
direction survives across milestones without being overwritten by status
|
|
||||||
updates.
|
|
||||||
|
|
||||||
### §12.7 — Policy Engine Registry (v1.25, REQ-291 — current)
|
|
||||||
|
|
||||||
The policy-engine abstraction is first-class: a swappable `PolicyEngine`
|
|
||||||
protocol so the engine may change without touching the confidence
|
|
||||||
signal, the pipeline, or the `PolicyCheckResult` schema. This is the
|
|
||||||
**swap boundary** that keeps the platform's compliance posture
|
|
||||||
replaceable (Strategic Objective #2 — provable trust via a replaceable
|
|
||||||
substrate, not a vendor lock-in).
|
|
||||||
|
|
||||||
```
|
|
||||||
contract.yml ─┐ ┌─→ list[PolicyCheckResult] ─┐
|
|
||||||
stack IR ─────┼─→ PolicyEngine.evaluate ├─→ list[PolicyCheckResult] ─┼─→ confidence_signal
|
|
||||||
plan JSON ────┤ (protocol) └─→ list[PolicyCheckResult] ─┘ (engine-agnostic,
|
|
||||||
PCR list ─────┘ unchanged)
|
|
||||||
│
|
|
||||||
▼
|
|
||||||
┌─ KyvernoJsonEngine (shells to `kj scan`; engine: "kyverno")
|
|
||||||
└─ OpaEngine (future — same protocol; engine: "opa")
|
|
||||||
|
|
||||||
checkov/wiz ──→ raw findings ──→ (merged PCR list is the meta-policy payload)
|
|
||||||
```
|
|
||||||
|
|
||||||
**The protocol (`core/policy_engine.py`):**
|
|
||||||
```python
|
|
||||||
class PolicyEngine(Protocol):
|
|
||||||
@property
|
|
||||||
def name(self) -> str: ...
|
|
||||||
def is_configured(self) -> bool: ...
|
|
||||||
def evaluate(self, payload, policy_dir: Path, contract_id: str) -> list[dict]: ...
|
|
||||||
```
|
|
||||||
|
|
||||||
**The registry** reads `config.json.policy.engine` (default
|
|
||||||
`"kyverno-json"`) and returns the active engine. A `NullEngine` is the
|
|
||||||
fallback when the `policy` key is absent (emits `SKIPPED` PCRs —
|
|
||||||
backward compatibility for tests that don't set the key). The
|
|
||||||
confidence signal is **untouched** — it already consumes
|
|
||||||
`list[PolicyCheckResult]` engine-agnostically (§12.6). v1.25 only
|
|
||||||
changes *who produces* the PCR list, not *what* the list is.
|
|
||||||
|
|
||||||
**Engine enum reuse (D-116):** kyverno-json PCR records carry
|
|
||||||
`engine: "kyverno"` (no new enum value). The `engine` field records the
|
|
||||||
policy-engine *family*, not the specific binary. The K8s Kyverno adapter
|
|
||||||
and the kyverno-json engine are distinguished by `ruleId` prefix
|
|
||||||
(`KYVERNO_` vs `KJ_`) and `evidence` payload shape (`namespace`/`kind`
|
|
||||||
vs `assertion`/`jmespath`).
|
|
||||||
|
|
||||||
**Defense-in-depth (D-119):** the declarative meta-policy
|
|
||||||
`block-on-any-critical` (asserts no PCR has `severity: critical` +
|
|
||||||
`result: fail`) is the *source of truth* for "critical = block". The
|
|
||||||
`confidence_signal.py` `PENALTY["critical"]: None` hard-override stays
|
|
||||||
as the *imperative* safety net — the meta-policy runs *before* the
|
|
||||||
confidence signal (produces PCRs that flow in), the hard-override runs
|
|
||||||
*inside* it (the last gate). Removing the hard-override would make the
|
|
||||||
"critical = block" guarantee depend on a single policy file — a
|
|
||||||
regression in provable trust.
|
|
||||||
|
|
||||||
**Graceful degradation (D-120):** `KyvernoJsonEngine.is_configured()`
|
|
||||||
returns false when `which kj` is absent → `evaluate()` returns a single
|
|
||||||
`SKIPPED` PCR (`ruleId: "KJ_ENGINE_NOT_CONFIGURED"`). The platform
|
|
||||||
functions without the binary (the "platform functions without AI /
|
|
||||||
deterministic scripts" tenet holds — kyverno-json is deterministic, not
|
|
||||||
AI; the `is_configured()` guard ensures the platform runs even when the
|
|
||||||
binary is not installed).
|
|
||||||
|
|
||||||
### §12.8 — Pilot Estate (v1.26, live)
|
|
||||||
|
|
||||||
The first real consumer estate is **`nova-blockchain-exchange`** — a
|
|
||||||
blockchain stock exchange on a homegrown Proof-of-Authority chain,
|
|
||||||
equities only, dev only (D-020/D-200/D-201). The live apply landed on
|
|
||||||
2026-08-19 against AWS account `581513795199`. This is the estate that
|
|
||||||
activated the Post-Pilot metric denominators (see `docs/METRICS.md`).
|
|
||||||
|
|
||||||
**The live apply (run id `blkex-pilot-apply-v0.2`):**
|
|
||||||
- Target: account `581513795199`, environment `dev`, autonomous (no
|
|
||||||
HITL — dev is the only autonomous environment, confidence ≥ 0.50).
|
|
||||||
- The microservice L2 composition (ECS Fargate running nginx) + the
|
|
||||||
`dynamodb` L1 (the `nova-blkex-ledger-dev` table) + the `s3` L1 (the
|
|
||||||
`nova-blkex-blocks-dev-581513795199-us-east-1` bucket).
|
|
||||||
- The platform VPC prerequisite (`vpc-0d7c8867e6cc080f1` + 6 subnets +
|
|
||||||
the ECS SG) is read via `terraform_remote_state` — the L2 composition
|
|
||||||
does not own the network boundary (the "restricted from
|
|
||||||
thin-composition" rule from §Layer 2).
|
|
||||||
- Confidence signal: score **0.800**, band **pass**; `human_override`
|
|
||||||
false; `escalation_reason` absent (clean apply).
|
|
||||||
|
|
||||||
**The Gitea adapter (SPEC §10 Q1):** Gitea Actions does not support
|
|
||||||
cross-repo `uses:`, so the consumer's `deploy.yml` is an **inline
|
|
||||||
adapter** — `actions/checkout@v4` the consumer, `actions/checkout@v4`
|
|
||||||
`acdl/acdl` @ `ref: v1.25` into `platform/`, then
|
|
||||||
`bash platform/scripts/run_platform.sh ...`. The platform's own
|
|
||||||
`.github/workflows/deploy.yml` stays as the GitHub Actions reference
|
|
||||||
impl (the reusable `workflow_call` workflow). See `adapters/README.md`
|
|
||||||
§Consumers for the adapter note.
|
|
||||||
|
|
||||||
**The Decision Ledger evidence stream** (the apply produces these
|
|
||||||
events in order):
|
|
||||||
```
|
|
||||||
nova.confidence.computed (score 0.800, band pass)
|
|
||||||
│
|
|
||||||
▼
|
|
||||||
nova.ai.decision.made (decision_id blkex-pilot-apply-v0.2,
|
|
||||||
chosen_action pass, human_override false)
|
|
||||||
│
|
|
||||||
▼
|
|
||||||
nova.attestation.recorded (dev = no HITL gate; the record exists,
|
|
||||||
the gate is a no-op in the autonomous env)
|
|
||||||
│
|
|
||||||
▼
|
|
||||||
nova.run.completed (apply succeeded)
|
|
||||||
│
|
|
||||||
▼
|
|
||||||
nova.outcome.backfilled (outcome pending → succeeded, REQ-317;
|
|
||||||
backfilled_at 2026-08-19T03:05:04Z)
|
|
||||||
```
|
|
||||||
The SQLite hash-chain is valid (0 breaks). S3 Object Lock / JWS
|
|
||||||
(D-083) stays deferred — the SQLite Decision Ledger is the pilot's
|
|
||||||
audit record (D-204).
|
|
||||||
|
|
||||||
**Live outputs (account 581513795199):**
|
|
||||||
- ALB DNS: `app-254671247.us-east-1.elb.amazonaws.com`
|
|
||||||
- ECS service: `arn:aws:ecs:us-east-1:581513795199:service/nova-cluster/nova-microservice`
|
|
||||||
- DynamoDB table: `nova-blkex-ledger-dev` (PK `block_index`, PAY_PER_REQUEST)
|
|
||||||
- S3 bucket: `nova-blkex-blocks-dev-581513795199-us-east-1` (versioning + SSE)
|
|
||||||
|
|
||||||
The full evidence (every ARN, the confidence JSON, the Decision Ledger
|
|
||||||
rows, the module-completeness gaps the live apply uncovered) is in
|
|
||||||
`.ciagent/archive/P4-PILOT-RUN-EVIDENCE-v1.26.md` (archived v1.27).
|
|
||||||
|
|
||||||
### §12.9 — Secret Rotation (v1.26 P3 W7, SPEC §5.9 — current)
|
|
||||||
|
|
||||||
The platform-managed scheduled workflow `workflows-src/rotate-aws-key.yml`
|
|
||||||
rotates the `NOVA_AWS_*` static key daily (cron `0 0 * * *`) and on
|
|
||||||
`workflow_dispatch`. v0.2 scope: the mechanism exists (SPEC §5.9 —
|
|
||||||
exists-not-ran); the v0.2 deploy uses the currently-active key. The
|
|
||||||
rotation is idempotent — `scripts/rotate_spike_key.sh` deactivates the old
|
|
||||||
key only after the new one propagates to the consumer's Actions secret
|
|
||||||
store, verified by a post-PUT GET; on upload/verify failure the old key is
|
|
||||||
left Active and the run exits non-zero. The synced workflow file is
|
|
||||||
forge-agnostic (REQ-230): forge base URL / owner / consumer repo come from
|
|
||||||
repository secrets (`NOVA_FORGE_*`, `NOVA_CONSUMER_REPO`), not literals.
|
|
||||||
|
|
||||||
### §12.10 — Nova-idp Identity Layer (v1.28, current)
|
|
||||||
|
|
||||||
Nova owns its identity layer end-to-end. Two (optionally three) Lambda
|
|
||||||
functions + four DynamoDB tables + one KMS asymmetric signing key + one
|
|
||||||
kyverno-json ABAC policy. **No Cognito, no IAM Identity Center (INV-15).**
|
|
||||||
The `nova-cli` Lambda layer carries the Nova wheel + `argon2-cffi` +
|
|
||||||
`cryptography` + `pyjwt` + the `kj` Go binary, making the same code
|
|
||||||
importable in both the CLI and the Lambda (REQ-329 dual-use, NFR-7).
|
|
||||||
|
|
||||||
**Components:**
|
|
||||||
|
|
||||||
- `nova-idp-auth` Lambda — sign-up, sign-in, session creation. Argon2id
|
|
||||||
password hashing (D-228: bundled abi3 wheel; fail-closed on
|
|
||||||
`ImportError`, no pure-Python fallback). DynamoDB: `nova-users`
|
|
||||||
(PK `user_id`, Argon2id `password_hash`), `nova-sessions` (PK
|
|
||||||
`session_id`, TTL `expires_at`), `nova-password-resets` (PK
|
|
||||||
`reset_token`, TTL 15m). Function URL with IAM auth.
|
|
||||||
- `nova-idp-token-vend` Lambda — accepts a PAT (or session token),
|
|
||||||
validates revocation (`nova-pats.GetItem(jti, ConsistentRead=True)` —
|
|
||||||
D-229, 60s SLO), evaluates the kyverno-json ABAC policy at
|
|
||||||
`platform/abac/token-vend.policy` (D-227, INV-17), KMS-signs an
|
|
||||||
ECDSA P-256 JWT (`ES256`), converts DER→raw ECDSA signature (RFC 7515
|
|
||||||
§3.1.3), returns the OIDC token. The `policy_version` (git SHA,
|
|
||||||
D-231) is recorded in every `token.vend.allowed/denied` audit event.
|
|
||||||
- `nova-idp-jwks` Lambda (optional, separation of concerns) — function
|
|
||||||
URL with `AuthType: NONE` (public key only), `Cache-Control: max-age=3600`.
|
|
||||||
`kms.get_public_key` → DER SPKI → JWK via `cryptography`. Custom
|
|
||||||
domain + WAF via CloudFront is OPTIONAL (`--public-jwks-domain` flag
|
|
||||||
on `nova idp setup`, D-230).
|
|
||||||
- `nova-pats` DynamoDB table — PK `jti`, GSI1 `sub` (list PATs for
|
|
||||||
user), GSI2 `pat_hash` (lookup by hash). Only the hash stored (not
|
|
||||||
raw PAT, REQ-343). Revoked PATs retained for audit.
|
|
||||||
|
|
||||||
**CLI surface (`nova` package, greenfield):**
|
|
||||||
|
|
||||||
- Entry point: `[project.scripts] nova = "nova.cli:main"` (argparse-only,
|
|
||||||
no click/typer — repo convention). `nova/cli.py` auto-discovers
|
|
||||||
`nova/<module>.py` subcommands via `pkgutil.iter_modules`, dispatches,
|
|
||||||
emits the `cli.invocation` audit event (INV-12) with `mode`,
|
|
||||||
`selection_reason`, `credential_type`, `command`, `args`.
|
|
||||||
- Each `nova/<module>.py` is ≤50 lines, delegates to `core/` (CAP-034
|
|
||||||
AST scan). Subgroups: `nova auth login/revoke/status`, `nova idp
|
|
||||||
setup --check/--apply/--verify`, `nova init`, `nova apply --local`.
|
|
||||||
- `core/mode_resolver.py` — flag → env (`NOVA_CLIENT_MODE`) → credential
|
|
||||||
type → `sys.stdin.isatty()` (D-226). CLI-only; Lambdas don't resolve
|
|
||||||
modes. Property-tested with `hypothesis` (REQ-349).
|
|
||||||
- `core/env.py:+synthesize_local_env()` — synthesizes a local env dict
|
|
||||||
from a contract + `--local` flag (REQ-330). No cloud provisioning.
|
|
||||||
|
|
||||||
**Packaging (NFR-6, CAP-035):**
|
|
||||||
|
|
||||||
- CI publishes a wheel to CodeArtifact AND a Lambda layer with identical
|
|
||||||
version strings on every merge affecting `core/`/`adapters/`/`nova/`.
|
|
||||||
Version mapping recorded in SSM `/nova/layer/nova-cli/version`.
|
|
||||||
If either publish fails, the merge is blocked (REQ-323).
|
|
||||||
- `nova cli-action` composite action at
|
|
||||||
`.github/actions/nova-cli/action.yml`, referenced by both GitHub +
|
|
||||||
Gitea (`uses: continuous-intelligence/acdl/.github/actions/nova-cli@v1.28`).
|
|
||||||
Python 3.12 pinned. Byte-identical behavior verified by CI matrix
|
|
||||||
(REQ-326, NFR-11).
|
|
||||||
|
|
||||||
**Data flows:**
|
|
||||||
|
|
||||||
1. Sign-up → `nova-idp-auth` → Argon2id → `nova-users` PutItem → session
|
|
||||||
→ `nova-sessions` PutItem → return session token.
|
|
||||||
2. Token vend (hot path) → `nova-idp-token-vend` → `nova-pats` strong
|
|
||||||
read (revocation) → kyverno-json ABAC eval → if allow → KMS sign →
|
|
||||||
DER→raw → return OIDC JWT. Audit at every step.
|
|
||||||
3. JWKS fetch → `nova-idp-jwks` → `kms.get_public_key` → DER→JWK →
|
|
||||||
`{"keys":[...]}`. Cached 1h at CloudFront (if custom domain) / client.
|
|
||||||
4. PAT revoke → `nova auth revoke --pat <jti>` → `nova-pats.UpdateItem(
|
|
||||||
status=revoked)` → audit. Strong read on next vend → 403 (within 60s).
|
|
||||||
|
|
||||||
**`nova idp setup` (REQ-340, NFR-10):** generates a CloudFormation
|
|
||||||
template (raw dict → JSON, no troposphere dep), presents for review
|
|
||||||
(`$PAGER` + resource summary), requires explicit `y/N` approval before
|
|
||||||
`cloudformation deploy --capabilities CAPABILITY_IAM`. `--check` reports
|
|
||||||
prerequisites + IAM policy delta; `--verify` runs the KMS round-trip
|
|
||||||
test. New IAM grants required: `cloudformation:*`, `codeartifact:*`.
|
|
||||||
@@ -0,0 +1,246 @@
|
|||||||
|
# ACDL v1.9 — Audit Report
|
||||||
|
|
||||||
|
> Audit date: 2026-07-23. Auditor: ci-debugger. Milestone: v1.9. Result: PASS.
|
||||||
|
|
||||||
|
## Step 1: Reconstruction Test
|
||||||
|
|
||||||
|
- 16 v1.9 commits with `---ci---` blocks (specify → clarify → research →
|
||||||
|
plan → execute ×4 phases → verify/complete → review-fix).
|
||||||
|
- Reconstructed state: milestone v1.9, phase 43, status complete.
|
||||||
|
- Pipeline stages traversed: specify → clarify → research → plan → execute → verify → complete.
|
||||||
|
- Decisions D-080..D-089 all present in git log + `.ciagent/` files.
|
||||||
|
- config.json (v1.9 complete), PROJECT.md (v1.9 complete), REQUIREMENTS.md
|
||||||
|
(v1.9 complete, 12 reqs), ROADMAP.md (v1.9 complete, phases 39–43),
|
||||||
|
REVIEW.md (READY TO SHIP), PERSONAS.md (v1.9), VERIFY.md, AUDIT.md.
|
||||||
|
**PASS.**
|
||||||
|
|
||||||
|
## Step 2: File Discipline
|
||||||
|
|
||||||
|
- `.ciagent/config.json`: valid JSON; mode, projects[] present. **PASS.**
|
||||||
|
- `.ciagent/PROJECT.md`: Vision/Core Value (≡ "What This Is"), Key
|
||||||
|
Decisions (v1.9 D-080..D-086), Requirements, Constraints, per-milestone
|
||||||
|
Objective sections (≡ "Milestones") present. Section names follow the
|
||||||
|
v1.0 established conventions (not the generic audit template). **PASS.**
|
||||||
|
- `.ciagent/ROADMAP.md`: phases 39–43 present; all marked complete.
|
||||||
|
**PASS.**
|
||||||
|
- `.ciagent/REQUIREMENTS.md`: v1.9 traceability table complete (12/12
|
||||||
|
REQ-100..111 marked `complete (v1.9.0)`). **PASS.**
|
||||||
|
- `.ciagent/ARCHITECTURE.md`: **fixed during audit** — v1.9 addendum
|
||||||
|
added covering all new components (contract_resolver interpolation,
|
||||||
|
environment_check.load, hitl_gates, attestation_matrix,
|
||||||
|
separation_of_duties.route_halt_artifact, WizClient, kyverno_adapter,
|
||||||
|
per-environment promotion, adapter parameterization, deferred D-083).
|
||||||
|
All 9 v1.9 code components now referenced. **PASS (after fix).**
|
||||||
|
|
||||||
|
## Step 3: Branch Hygiene
|
||||||
|
|
||||||
|
- Local: `main` only. Remote: `origin/main` only.
|
||||||
|
- No phase or milestone branches remain (all 5 v1.9 phase branches merged
|
||||||
|
+ pruned during the run/ship workflow).
|
||||||
|
- No orphan branches.
|
||||||
|
**PASS.**
|
||||||
|
|
||||||
|
## Step 4: Commit Discipline
|
||||||
|
|
||||||
|
- 16/16 v1.9 commits have `---ci---` blocks with project/phase/milestone/
|
||||||
|
status fields.
|
||||||
|
- No stale implementation decisions (D-081..D-085, D-087..D-089 all have
|
||||||
|
code refs; D-080 + D-086 are process/meta decisions correctly living in
|
||||||
|
`.ciagent/` files).
|
||||||
|
- No unresolved v1.9 escalations (the 3 `audit(...)` commits in history
|
||||||
|
are from prior milestones v1.0/v1.6/v1.7).
|
||||||
|
**PASS.**
|
||||||
|
|
||||||
|
## Issues fixed during audit
|
||||||
|
|
||||||
|
1. **ARCHITECTURE.md missing v1.9 addendum** — the architecture doc had
|
||||||
|
no coverage of the v1.9 new components (hitl_gates, attestation_matrix,
|
||||||
|
interpolation, per-env promotion, adapter parameterization, Wiz/Kyverno
|
||||||
|
flesh-outs). Fixed: added a v1.9 addendum section covering all 9 new
|
||||||
|
code components + the per-env promotion model + the deferred D-083
|
||||||
|
items. Verified all 9 components now referenced.
|
||||||
|
|
||||||
|
## Audit result: PASS
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# ACDL v1.10 Phase 52 — Audit Addendum
|
||||||
|
|
||||||
|
> Audit date: 2026-07-27. Auditor: ci-debugger. Phase: 52 (pipeline
|
||||||
|
> regression-VERIFY fix). Result: PASS.
|
||||||
|
|
||||||
|
## Process defect recorded (D-091)
|
||||||
|
|
||||||
|
The prior VERIFY stage was diff-scoped: it checked the phase diff only
|
||||||
|
and never re-ran underlying platform capability. This structural defect
|
||||||
|
let 8 NFR-patch phases (v1.9.1→v1.9.8, deck rework) pass VERIFY while the
|
||||||
|
platform they described decayed underneath. The defect is recorded as
|
||||||
|
D-091 and remediated in Phase 52 by `core/regression_verify.py` +
|
||||||
|
`scripts/run_regression.sh`.
|
||||||
|
|
||||||
|
## Phase 52 audit
|
||||||
|
|
||||||
|
- **Reconstruction:** Phase 52 commits present with `---ci---` blocks
|
||||||
|
(plan + execute + verify). Decisions D-090..D-094 recorded in
|
||||||
|
PROJECT.md. Requirements REQ-112..REQ-115 recorded in REQUIREMENTS.md.
|
||||||
|
**PASS.**
|
||||||
|
- **File discipline:** `core/regression_verify.py`,
|
||||||
|
`scripts/run_regression.sh`, `tests/test_verify_regression_mode.py`
|
||||||
|
present. `.ciagent/PLAN.md`, `ROADMAP.md`, `PROJECT.md`,
|
||||||
|
`REQUIREMENTS.md`, `VERIFY.md` updated for v1.10. **PASS.**
|
||||||
|
- **Behavioral:** 502 fast tests pass (was 493; +9 new). 3 slow
|
||||||
|
integration tests pass. `run_regression.sh` runs and reports honestly.
|
||||||
|
**PASS.**
|
||||||
|
- **Commit discipline:** Phase 52 commits carry `---ci---` blocks with
|
||||||
|
project/phase/milestone/status. **PASS.**
|
||||||
|
|
||||||
|
## Note on prior "audit CLEAN" claims
|
||||||
|
|
||||||
|
The v1.1–v1.9 "audit CLEAN" claims were point-in-time true (the
|
||||||
|
capabilities ran at the time of tagging). They do not assert current
|
||||||
|
reproducibility. The capability decay surfaced in the 2026-07-27
|
||||||
|
CLARIFY/RESEARCH stages is being re-verified in Phase 54 (D-093). The
|
||||||
|
v1.10 audit will re-assert current reproducibility after the sweep.
|
||||||
|
|
||||||
|
## Phase 52 audit result: PASS
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# ACDL v1.10 — Milestone Audit
|
||||||
|
|
||||||
|
> Audit date: 2026-07-27. Auditor: ci-debugger. Milestone: v1.10.
|
||||||
|
> Result: PASS.
|
||||||
|
|
||||||
|
## Step 1: Reconstruction Test
|
||||||
|
|
||||||
|
- 5 v1.10 commits with `---ci---` blocks (plan → P52 verify → P53 verify
|
||||||
|
→ P54 verify → P55 verify).
|
||||||
|
- Reconstructed state: milestone v1.10, phase 55, status verify.
|
||||||
|
- Pipeline stages traversed: plan → execute → verify (×4 phases).
|
||||||
|
- Decisions D-090..D-094 all present in git log + `.ciagent/` files.
|
||||||
|
- config.json (v1.10 complete), PROJECT.md (Capability Status section
|
||||||
|
+ decay disclosure), REQUIREMENTS.md (REQ-112..115 complete),
|
||||||
|
ROADMAP.md (v1.10 section, phases 52–55 complete), REVIEW.md (READY
|
||||||
|
TO SHIP), VERIFY.md (Phase 55 PASS), AUDIT.md (this file),
|
||||||
|
CAPABILITY_INVENTORY.md (16 Verified + 6 escalated), REGRESSION_REPORT
|
||||||
|
(16/16 Verified).
|
||||||
|
**PASS.**
|
||||||
|
|
||||||
|
## Step 2: File Discipline
|
||||||
|
|
||||||
|
- `.ciagent/config.json`: valid JSON; mode, projects[] present; milestone
|
||||||
|
v1.10 complete. **PASS.**
|
||||||
|
- `.ciagent/PROJECT.md`: Capability Status section + decay disclosure +
|
||||||
|
D-090..D-094 decision rows present. **PASS.**
|
||||||
|
- `.ciagent/ROADMAP.md`: v1.10 section with phases 52–55 all marked
|
||||||
|
complete; v1.9.8 annotated as last deck-polish before freeze. **PASS.**
|
||||||
|
- `.ciagent/REQUIREMENTS.md`: v1.10 traceability table complete (4/4
|
||||||
|
REQ-112..115 marked `complete (v1.9.9..v1.9.12)`). **PASS.**
|
||||||
|
- `.ciagent/CAPABILITY_INVENTORY.md`: 16 Verified + 6 IAM-gated
|
||||||
|
escalated, with evidence per capability. **PASS.**
|
||||||
|
- `.ciagent/REGRESSION_REPORT.md` + `.json`: 16/16 Verified, gate passes.
|
||||||
|
**PASS.**
|
||||||
|
- `.ciagent/REVIEW.md`: READY TO SHIP (0 P0, 0 P1, 1 P2 post-hoc).
|
||||||
|
**PASS.**
|
||||||
|
|
||||||
|
## Step 3: Branch Hygiene
|
||||||
|
|
||||||
|
- Local: `main` only. Remote: `origin/main` only.
|
||||||
|
- No phase or milestone branches remain (single-project mode, flat
|
||||||
|
`.ciagent/` paths, no phase branches per config.json
|
||||||
|
branching_strategy=phase but committed directly to main per the
|
||||||
|
project's established convention).
|
||||||
|
**PASS.**
|
||||||
|
|
||||||
|
## Step 4: Commit Discipline
|
||||||
|
|
||||||
|
- 5/5 v1.10 commits have `---ci---` blocks with project/phase/milestone/
|
||||||
|
status fields.
|
||||||
|
- Decisions D-090..D-094 all have code/doc refs.
|
||||||
|
- The regression `---ci---` blocks include `regression:` arrays with
|
||||||
|
per-capability status (Phases 52, 53, 54).
|
||||||
|
- No unresolved v1.10 escalations (the 6 IAM-gated resources are
|
||||||
|
documented in CAPABILITY_INVENTORY.md, not unresolved escalations).
|
||||||
|
**PASS.**
|
||||||
|
|
||||||
|
## Audit result: PASS
|
||||||
|
|
||||||
|
The v1.10 milestone is complete. The pipeline regression gap (D-091)
|
||||||
|
is fixed; the platform is fully locally testable (D-092); every
|
||||||
|
advertised v1.1–v1.8 capability is re-verified (D-093, 16/16 Verified);
|
||||||
|
the docs/decks match verified reality (D-094). 0 P0, 0 P1 from review;
|
||||||
|
1 P2 (post-hoc: expand regression registry to uptime-kuma + RDS stacks).
|
||||||
|
513 offline tests pass; the regression gate covers 16 capabilities
|
||||||
|
including 4 live-AWS checks. Ready to tag `v1.10.0`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# ACDL v1.10 — Post-Ship Audit (ciagent-audit workflow)
|
||||||
|
|
||||||
|
> Audit date: 2026-07-27. Auditor: ci-debugger. Milestone: v1.10
|
||||||
|
> (shipped, tag `v1.10.0`). Result: PASS (1 issue fixed during audit).
|
||||||
|
|
||||||
|
## Step 1: Reconstruction Test — PASS
|
||||||
|
|
||||||
|
Parsed all `---ci---` blocks from `v1.9.8..HEAD` (9 commits).
|
||||||
|
Reconstructed state:
|
||||||
|
- Phases: 52, 53, 54, 55 (+ boundary commits 0, 51)
|
||||||
|
- Milestone: v1.10
|
||||||
|
- Final status: complete
|
||||||
|
- Decisions: D-090..D-094
|
||||||
|
- Requirements: REQ-112..REQ-115
|
||||||
|
- Regression caps: CAP-001..CAP-016
|
||||||
|
|
||||||
|
Compared with `.ciagent/` files:
|
||||||
|
- config.json: milestone v1.10, status complete. **MATCH.**
|
||||||
|
- ROADMAP.md: phases 52–55 present, all complete. **MATCH.**
|
||||||
|
- REQUIREMENTS.md: REQ-112..115 all complete. **MATCH.**
|
||||||
|
- PROJECT.md: D-090..D-094 decision rows present. **MATCH.**
|
||||||
|
- CAPABILITY_INVENTORY.md: CAP-001..CAP-016 all Verified. **MATCH.**
|
||||||
|
|
||||||
|
**Reconstruction: PASS** — state fully reconstructable from git log.
|
||||||
|
|
||||||
|
## Step 2: .ciagent/ File Discipline — PASS (1 issue fixed)
|
||||||
|
|
||||||
|
- `config.json`: valid JSON, required fields present. **PASS.**
|
||||||
|
- `PROJECT.md`: all required sections present (Vision, North Star,
|
||||||
|
Capability Status, Requirements, Key Decisions, Constraints,
|
||||||
|
Anti-Goals). **PASS.**
|
||||||
|
- `ROADMAP.md`: phases 52–55 present, v1.10 marked complete. **PASS.**
|
||||||
|
- `REQUIREMENTS.md`: REQ-112..115 all complete in traceability table.
|
||||||
|
**PASS.**
|
||||||
|
- `ARCHITECTURE.md`: **FIXED DURING AUDIT** — had 0 references to
|
||||||
|
v1.10 components (regression_verify, local_emulators,
|
||||||
|
REGRESSION_REPORT, CAPABILITY_INVENTORY). Added a v1.10 addendum
|
||||||
|
section covering the regression-class VERIFY, local emulating
|
||||||
|
adapters, capability re-verification sweep, and the 7 adapter defect
|
||||||
|
fixes. Now references all v1.10 components. **PASS (after fix).**
|
||||||
|
|
||||||
|
## Step 3: Branch Hygiene — PASS
|
||||||
|
|
||||||
|
- Local: `main` only. Remote: `origin/main` only.
|
||||||
|
- No phase or milestone branches (flat workflow per project convention).
|
||||||
|
- No orphan branches.
|
||||||
|
**PASS.**
|
||||||
|
|
||||||
|
## Step 4: Commit Discipline — PASS
|
||||||
|
|
||||||
|
- 9/9 v1.10 commits have `---ci---` blocks with project/phase/milestone/
|
||||||
|
status fields.
|
||||||
|
- Decisions D-090..D-094: D-091/D-092/D-093 have code refs
|
||||||
|
(`core/regression_verify.py`); D-090/D-094 are process/meta decisions
|
||||||
|
with extensive `.ciagent/` doc refs (PLAN, ROADMAP, PROJECT,
|
||||||
|
CAPABILITY_INVENTORY, AUDIT, VERIFY). No stale decisions.
|
||||||
|
- No unresolved v1.10 escalations (the 6 IAM-gated resources are
|
||||||
|
documented in CAPABILITY_INVENTORY.md, not unresolved escalations).
|
||||||
|
**PASS.**
|
||||||
|
|
||||||
|
## Issues fixed during audit
|
||||||
|
|
||||||
|
1. **ARCHITECTURE.md missing v1.10 addendum** — the architecture doc
|
||||||
|
had no coverage of the v1.10 new components (regression_verify,
|
||||||
|
local_emulators, capability inventory, adapter defect fixes). Fixed:
|
||||||
|
added a v1.10 addendum section covering all 4 new subsystems + the
|
||||||
|
7 adapter defect fixes. Verified all v1.10 components now referenced.
|
||||||
|
|
||||||
|
## Audit result: PASS
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
# Nova Capability Inventory — v1.1→v1.8 Re-Verification Sweep
|
# ACDL Capability Inventory — v1.1→v1.8 Re-Verification Sweep
|
||||||
|
|
||||||
> Generated: 2026-07-27. Phase 54 (D-093). Milestone v1.10.
|
> Generated: 2026-07-27. Phase 54 (D-093). Milestone v1.10.
|
||||||
> Source: PROJECT.md + ROADMAP.md v1.1→v1.8 advertised capabilities.
|
> Source: PROJECT.md + ROADMAP.md v1.1→v1.8 advertised capabilities.
|
||||||
@@ -1,18 +1,8 @@
|
|||||||
{
|
{
|
||||||
"phase": 5,
|
"phase": 0,
|
||||||
"stage": "complete",
|
"stage": "complete",
|
||||||
"milestone": "v1.28",
|
"milestone": "v1.14",
|
||||||
"phase_role": "execution",
|
"phase_role": "pre_execution",
|
||||||
"attempts": 0,
|
"attempts": 0,
|
||||||
"updated_at": "2026-08-19T23:45:00Z",
|
"updated_at": "2026-07-29T20:30:00Z"
|
||||||
"project": "acdl",
|
|
||||||
"projects": ["acdl", "nova-blockchain-exchange"],
|
|
||||||
"active_milestone": "v1.28",
|
|
||||||
"milestone_branch": "milestone/v1.28-cli-identity",
|
|
||||||
"phase_branch": "phase/05-docs-integration",
|
|
||||||
"tag_line": "v1.27.x",
|
|
||||||
"phase_name": "docs-integration",
|
|
||||||
"reqs_covered": ["REQ-345", "REQ-346", "REQ-347", "REQ-348", "REQ-349", "REQ-350", "REQ-351"],
|
|
||||||
"tests": {"p5_specific": 17, "total_passing": 1000, "failures": 0},
|
|
||||||
"notes": "v1.28 P5 SHIP. docs-integration complete. Tag v1.27.5. 7 REQs covered (REQ-345..351). Operator guide (C-6.3), developer guide (C-7.3), threat model (C-6.2, C-9.2 INV audit), E2E test (REQ-348). 1000 tests passing. Next: P6 final-review-ship (milestone release)."
|
|
||||||
}
|
}
|
||||||
@@ -1,276 +0,0 @@
|
|||||||
# CLARIFY — v1.28 CLI Canonicalization + Identity Layer
|
|
||||||
|
|
||||||
> **Autonomy:** full. Auto-resolution with assumption logging per
|
|
||||||
> `config.autonomy.level: "full"`. No human escalation unless confidence
|
|
||||||
> < 0.60. The user-approved re-mapping plan (v1.18 spec → v1.28) resolved
|
|
||||||
> the headline discrepancy. This file records the remaining ambiguities
|
|
||||||
> and the grounding gaps surfaced in pre-flight.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Method
|
|
||||||
|
|
||||||
The clarify stage identifies ambiguities in the v1.28 specification and
|
|
||||||
resolves them at full autonomy. The v1.28 spec is the user-provided
|
|
||||||
"Universal Feature Specification — v1.18 CLI Canonicalization + Identity
|
|
||||||
Layer," re-mapped to v1.28 (milestone number, tag line, and all
|
|
||||||
ID namespaces) per the user-approved plan. Each ambiguity gets a
|
|
||||||
decision ID (D-226+, continuing from v1.27's D-214..D-225), a resolution,
|
|
||||||
a confidence score, and a rationale.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Prior-conversation resolutions (already locked, restated for the record)
|
|
||||||
|
|
||||||
These were resolved by the user-approved re-mapping plan in the
|
|
||||||
conversation that spawned v1.28. They are load-bearing for v1.28
|
|
||||||
execution.
|
|
||||||
|
|
||||||
### Q-P1 — The source spec is titled "v1.18" but v1.18 already shipped. What milestone is this?
|
|
||||||
|
|
||||||
**Resolution:** Re-map the spec's *content* (CLI Canonicalization +
|
|
||||||
Identity Layer) to **v1.28**, the next milestone after v1.27 (complete).
|
|
||||||
Tags run on the **v1.27.x** line (P0 = `v1.27.0`). Milestone branch:
|
|
||||||
`milestone/v1.28-cli-identity`.
|
|
||||||
**Confidence:** 1.0 (user-confirmed — "Re-map to v1.28"). **Decision:** n/a (milestone identity, not a D-ID).
|
|
||||||
|
|
||||||
### Q-P2 — The spec's "locked inputs" (D-NEW-26, kj engine, Nova-idp, INV-63/64/65, CAP-025..030, REQ-001..031) don't exist in the repo. How to handle?
|
|
||||||
|
|
||||||
**Resolution:** Author them fresh in this milestone's CLARIFY/RESEARCH as
|
|
||||||
**D-226..D-231, INV-12..17, CAP-033..038, REQ-323..353**. The `kj` engine
|
|
||||||
is mapped to the existing **kyverno-json** engine (INV-4 swappable) — no
|
|
||||||
new engine is built. CAP/INV/REQ IDs are re-allocated to avoid collisions
|
|
||||||
with shipped history (CAP-025..032 and INV-1..11 are blockchain/pilot).
|
|
||||||
**Confidence:** 1.0 (user-confirmed — "Re-map to v1.28"). **Decision:** D-227 (kj→kyverno-json), plus the ID-allocation block in REQUIREMENTS.md.
|
|
||||||
|
|
||||||
### Q-P3 — The spec claims a "Cognito drop." No Cognito exists in the repo. What does NFR-5 mean?
|
|
||||||
|
|
||||||
**Resolution:** NFR-5 (no AWS-managed identity in the path) is a
|
|
||||||
**greenfield constraint**, not a migration. Nova-idp is built fresh; no
|
|
||||||
Cognito/IAM Identity Center is *introduced*. The "drop" framing is
|
|
||||||
aspirational language from the source spec, not a literal removal.
|
|
||||||
**Confidence:** 1.0. **Decision:** D-226 (recorded below; NFR-5 restated
|
|
||||||
as a greenfield constraint in INV-15).
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Open questions from the spec's §7 (auto-resolved at full autonomy)
|
|
||||||
|
|
||||||
### Q1 — Argon2 native dependency in Lambda runtime
|
|
||||||
|
|
||||||
`argon2-cffi` has a C extension that may not build cleanly in the Lambda
|
|
||||||
Python 3.12 runtime.
|
|
||||||
|
|
||||||
**Resolution (D-228):** Use `argon2-cffi` with bundled wheels; if the
|
|
||||||
extension fails to load, fall back to the pure-Python implementation. If
|
|
||||||
both fail, document the Fargate migration path for the auth Lambda.
|
|
||||||
CAP-036 covers end-to-end verification.
|
|
||||||
**Confidence:** 0.85. **Rationale:** Bundled wheels are the standard
|
|
||||||
workaround for Lambda native deps; the pure-Python fallback is a safe
|
|
||||||
degradation. Fargate is the escape hatch if Lambda's runtime is
|
|
||||||
fundamentally incompatible. RESEARCH will validate wheel availability for
|
|
||||||
Python 3.12 + the Lambda execution environment.
|
|
||||||
**Impact if wrong:** Auth Lambda migrates to Fargate, adding ~1 week to P2.
|
|
||||||
|
|
||||||
### Q2 — PAT revocation propagation latency
|
|
||||||
|
|
||||||
The 60-second SLO (NFR-4) depends on whether the token-vend Lambda reads
|
|
||||||
PAT revocation state from DynamoDB on every request (eventually
|
|
||||||
consistent reads) or via a cached/denylist mechanism.
|
|
||||||
|
|
||||||
**Resolution (D-229):** Read-on-every-request with strongly consistent
|
|
||||||
reads on the PAT hash table. Cost is acceptable given expected request
|
|
||||||
volume (token vending is not a hot path — it precedes a deploy, not every
|
|
||||||
request). REV-351 verifies the SLO in CI.
|
|
||||||
**Confidence:** 0.90. **Rationale:** Strongly consistent DynamoDB reads
|
|
||||||
have single-digit-ms latency at expected volume; the 60s SLO has >10x
|
|
||||||
headroom. A cache layer adds invalidation complexity that the SLO does
|
|
||||||
not require.
|
|
||||||
**Impact if wrong:** If read latency exceeds 60s under load, introduce a
|
|
||||||
DynamoDB TTL + cache layer; SLO must be re-verified.
|
|
||||||
|
|
||||||
### Q3 — JWKS endpoint: Lambda function URL vs. API Gateway
|
|
||||||
|
|
||||||
A function URL is simpler and cheaper but lacks throttling, WAF, and
|
|
||||||
custom domains out of the box.
|
|
||||||
|
|
||||||
**Resolution (D-230):** Start with a Lambda function URL behind a custom
|
|
||||||
domain; rate limiting configured at the DNS/CDN layer. API Gateway
|
|
||||||
migration deferred to v1.19+ if throttling requirements grow.
|
|
||||||
**Confidence:** 0.80. **Rationale:** The JWKS endpoint is public-key
|
|
||||||
only (no secrets); the threat surface is low. Function URL + CDN rate-
|
|
||||||
limiting covers the v1.28 volume. API Gateway is over-engineering until
|
|
||||||
traffic patterns are known.
|
|
||||||
**Impact if wrong:** If throttling becomes a requirement, API Gateway
|
|
||||||
migration adds ~3-5 days.
|
|
||||||
|
|
||||||
### Q4 — Mode resolver precedence with invalid `NOVA_CLIENT_MODE` value
|
|
||||||
|
|
||||||
What happens if the env var is set to something other than `agent` or
|
|
||||||
`interactive` (e.g., `NOVA_CLIENT_MODE=auto`)?
|
|
||||||
|
|
||||||
**Resolution (D-226):** Invalid env var values are ignored, falling
|
|
||||||
through to credential type. A warning is logged. Behavior is documented
|
|
||||||
in the `nova-cli` README. This is a sub-clause of the mode-resolution
|
|
||||||
priority decision.
|
|
||||||
**Confidence:** 0.90. **Rationale:** Ignoring + warning is the least
|
|
||||||
surprising behavior for an operator debugging mode issues. Failing hard
|
|
||||||
would block legitimate workflows that set a stale/typo'd env var.
|
|
||||||
**Impact if wrong:** Operators debugging mode issues may be confused;
|
|
||||||
non-blocking.
|
|
||||||
|
|
||||||
### Q5 — Service-account PAT vs. developer PAT in the same session
|
|
||||||
|
|
||||||
What if both credential types are available (e.g., a developer explicitly
|
|
||||||
exports a service-account PAT)?
|
|
||||||
|
|
||||||
**Resolution (D-226):** The most recently acquired credential wins.
|
|
||||||
Documented in `nova auth login` output. The credential type is what
|
|
||||||
drives mode resolution (INV-14), so the operator sees which mode was
|
|
||||||
selected and why.
|
|
||||||
**Confidence:** 0.85. **Rationale:** "Most recent wins" is the simplest
|
|
||||||
deterministic rule that matches operator mental models of "I just logged
|
|
||||||
in as X." The audit event records the winning credential type, so the
|
|
||||||
selection is traceable.
|
|
||||||
**Impact if wrong:** Mode selection may surprise the operator; non-
|
|
||||||
blocking, but `nova auth status` must make the active credential explicit.
|
|
||||||
|
|
||||||
### Q6 — ABAC policy ownership and versioning
|
|
||||||
|
|
||||||
`platform/abac/token-vend.policy` is referenced, but who owns changes?
|
|
||||||
How are policy versions tracked in audit?
|
|
||||||
|
|
||||||
**Resolution (D-231):** Policy changes require PR review; the policy
|
|
||||||
version (git SHA) is recorded in every token-vend audit event. Owner:
|
|
||||||
Platform Security. The policy file lives in the platform repo at
|
|
||||||
`platform/abac/token-vend.policy` and is reviewed like any other
|
|
||||||
production config.
|
|
||||||
**Confidence:** 0.90. **Rationale:** Git SHA is the natural version
|
|
||||||
identifier for a repo-resident policy; recording it in the audit event
|
|
||||||
makes every allow/deny decision reconstructable to the exact policy text.
|
|
||||||
**Impact if wrong:** Untracked policy changes could lead to unexpected
|
|
||||||
allow/deny decisions in production, undermining audit defensibility.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Grounding gaps surfaced in pre-flight (auto-resolved)
|
|
||||||
|
|
||||||
### G1 — The `kj` engine does not exist; the spec treats it as locked.
|
|
||||||
|
|
||||||
**Resolution (D-227):** The token-vend Lambda uses the existing
|
|
||||||
**kyverno-json** engine (INV-4 swappable) as the ABAC evaluator. The
|
|
||||||
policy at `platform/abac/token-vend.policy` is a kyverno-json policy.
|
|
||||||
No new `kj` engine is built in v1.28. If a distinct `kj` engine is
|
|
||||||
desired later, it is a separate research spike (not this milestone).
|
|
||||||
**Confidence:** 0.95. **Rationale:** The repo already has a swappable
|
|
||||||
policy engine (INV-4) implemented as kyverno-json. Building a second
|
|
||||||
engine to do the same job violates the swappable-engine invariant's
|
|
||||||
spirit. kyverno-json's `evaluate` semantics cover the spec's ABAC needs
|
|
||||||
(subject, claims, resource, environment → allow/deny).
|
|
||||||
**Impact if wrong:** If the user actually wants a new `kj` engine, v1.28
|
|
||||||
scope expands significantly (engine design + implementation + migration).
|
|
||||||
This was flagged as caveat #3 in the approved plan; the recommended path
|
|
||||||
(kyverno-json) is locked here.
|
|
||||||
|
|
||||||
### G2 — The spec's INV-18..21, INV-34, INV-63/64/65 don't exist.
|
|
||||||
|
|
||||||
**Resolution:** Re-allocated as **INV-12..INV-17** (see REQUIREMENTS.md
|
|
||||||
§v1.28 Invariants). The 1:1 mapping:
|
|
||||||
- INV-63 (mode observability) → INV-12
|
|
||||||
- INV-64 (mode determinism) → INV-13
|
|
||||||
- INV-65 (credential type encodes role) → INV-14
|
|
||||||
- INV-18..21 (attestation invariants) → INV-15 (no AWS-managed identity),
|
|
||||||
INV-16 (password storage), INV-17 (ABAC discipline). The spec's
|
|
||||||
attestation invariants INV-18..21 are partially covered by existing
|
|
||||||
invariants (INV-6 immutable audit) + INV-17; the JWS-from-PAT behavior
|
|
||||||
(REQ-332) is a requirement, not a separate invariant, in this mapping.
|
|
||||||
- INV-34 (MFA enforcement) → deferred to v1.21+ (out of scope per §2.2);
|
|
||||||
no INV allocated in v1.28.
|
|
||||||
**Confidence:** 0.85. **Rationale:** The mapping preserves the spec's
|
|
||||||
intent without colliding with the repo's INV-1..11. INV-34 (MFA) is
|
|
||||||
explicitly deferred per the spec's own §2.2 out-of-scope table.
|
|
||||||
**Impact if wrong:** If the user wants the exact INV-18..21 semantics as
|
|
||||||
separate invariants, INV-12..17 can be re-numbered; non-blocking.
|
|
||||||
|
|
||||||
### G3 — The spec's CAP-025..030 collide with blockchain/pilot CAPs.
|
|
||||||
|
|
||||||
**Resolution:** Re-allocated as **CAP-033..CAP-038** (see REQUIREMENTS.md
|
|
||||||
§v1.28 + REQ-352). The 1:1 mapping:
|
|
||||||
- CAP-025 (CLI subcommand surface) → CAP-033
|
|
||||||
- CAP-026 (subcommand delegates to core/) → CAP-034
|
|
||||||
- CAP-027 (layer matches wheel) → CAP-035
|
|
||||||
- CAP-028 (Nova-idp auth flow) → CAP-036
|
|
||||||
- CAP-029 (token-vend signs via KMS) → CAP-037
|
|
||||||
- CAP-030 (PAT issuance + revocation) → CAP-038
|
|
||||||
**Confidence:** 1.0. **Rationale:** Existing CAP-025..032 are
|
|
||||||
blockchain/pilot capabilities (STATE.md); re-use would corrupt the
|
|
||||||
capability registry. The re-allocated IDs are the next available.
|
|
||||||
**Impact if wrong:** None — this is a numbering decision, not a semantic
|
|
||||||
one.
|
|
||||||
|
|
||||||
### G4 — The spec's REQ-001..031 collide / don't exist.
|
|
||||||
|
|
||||||
**Resolution:** Re-allocated as **REQ-323..REQ-353** (1:1 with the spec's
|
|
||||||
REQ-001..031). Full text in REQUIREMENTS.md §v1.28. Max existing REQ =
|
|
||||||
REQ-322.
|
|
||||||
**Confidence:** 1.0. **Rationale:** Same as G3 — avoid collision, use
|
|
||||||
next available range.
|
|
||||||
|
|
||||||
### G5 — `platform/abac/`, `nova/` subcommand dir, `nova-idp-*` Lambdas don't exist.
|
|
||||||
|
|
||||||
**Resolution:** These are **greenfield deliverables** of v1.28 execution
|
|
||||||
phases, not pre-existing "locked architectures." RESEARCH will design
|
|
||||||
them; PLAN will sequence them; EXECUTE will build them. The spec's
|
|
||||||
"Operating Principle 1" (incremental delivery) is honored — v1.28 is
|
|
||||||
net-new work.
|
|
||||||
**Confidence:** 1.0. **Rationale:** The spec itself describes these as
|
|
||||||
new ("introducing Nova-idp"). The mis-framing was in calling them
|
|
||||||
"locked" — they are locked in *scope*, not in *prior existence*.
|
|
||||||
**Impact if wrong:** None — this is a framing correction.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Decision ledger (v1.28 — D-226..D-231)
|
|
||||||
|
|
||||||
| ID | Title | Confidence | Load-bearing for |
|
|
||||||
|----|-------|------------|------------------|
|
|
||||||
| D-226 | Mode resolution priority + invalid-env + dual-credential | 0.90 | REQ-327, INV-12, INV-13, INV-14 |
|
|
||||||
| D-227 | ABAC engine = kyverno-json (no `kj` engine built) | 0.95 | REQ-336, REQ-339, INV-17, NFR-9 |
|
|
||||||
| D-228 | Argon2id in Lambda: bundled wheels + pure-Python fallback + Fargate path | 0.85 | REQ-333, REQ-334, INV-16, NFR-8 |
|
|
||||||
| D-229 | PAT revocation: strongly-consistent DDB read-on-every-request, 60s SLO | 0.90 | REQ-342, REQ-343, REQ-351, NFR-4 |
|
|
||||||
| D-230 | JWKS endpoint: Lambda function URL + custom domain + CDN rate-limit | 0.80 | REQ-338, NFR-5 |
|
|
||||||
| D-231 | ABAC policy ownership: Platform Security, git SHA in audit | 0.90 | REQ-339, NFR-9 |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Assumptions logged (full autonomy, no human escalation)
|
|
||||||
|
|
||||||
1. **CodeArtifact is provisionable** in AWS account `581513795199` (the
|
|
||||||
pilot account). RESEARCH will confirm IAM permissions + repository
|
|
||||||
creation. If not, v1.28 falls back to a private PyPI server or a
|
|
||||||
Gitea-hosted wheel index; the CLI subcommand surface (REQ-324) and
|
|
||||||
identity layer (REQ-333+) are unaffected.
|
|
||||||
2. **Python 3.12** is the target runtime for both the CLI wheel and the
|
|
||||||
Lambda functions (spec §4 REQ-004.3). The repo's current Python
|
|
||||||
version will be confirmed in RESEARCH; if it differs, the CLI pins
|
|
||||||
3.12 and Lambda uses the 3.12 runtime regardless.
|
|
||||||
3. **KMS asymmetric signing** (RSA-2048 or ECDSA P-256) is available in
|
|
||||||
the target account. RESEARCH will confirm. If only symmetric KMS is
|
|
||||||
available, the token-vend Lambda uses symmetric signing + a public-key
|
|
||||||
publication step (less ideal, but functional); INV-15 is unaffected.
|
|
||||||
4. **The Forge action** (REQ-326) is the existing `nova cli-action`
|
|
||||||
pattern, extended to both GitHub and Gitea marketplaces. The repo's
|
|
||||||
current Forge/Gitea workflow conventions (`.gitea/workflows/`,
|
|
||||||
`deploy.yml@v1.25`) are the baseline.
|
|
||||||
5. **MFA/TOTP** code path ships in v1.28 (per spec §2.2) but enforcement
|
|
||||||
for prod/dr is deferred to v1.21+. This is a doc/test-only path in
|
|
||||||
v1.28 — no enforcement gate.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## CLARIFY complete
|
|
||||||
|
|
||||||
All material ambiguities resolved at full autonomy (6 open questions +
|
|
||||||
5 grounding gaps → D-226..D-231, confidence ≥ 0.80). No human escalation
|
|
||||||
triggered (all confidences ≥ 0.60 threshold). REQUIREMENTS.md updated
|
|
||||||
with the decision ledger + invariants. Next: RESEARCH.
|
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
# Nova AWS Cost Report (v1.0 → v1.14)
|
# ACDL AWS Cost Report (v1.0 → v1.14)
|
||||||
|
|
||||||
> **Query date:** 2026-07-29 (updated v1.14 P19)
|
> **Query date:** 2026-07-29 (updated v1.14 P19)
|
||||||
> **Source:** AWS Cost Explorer (`ce:GetCostAndUsage`)
|
> **Source:** AWS Cost Explorer (`ce:GetCostAndUsage`)
|
||||||
+300
-98
@@ -1,110 +1,312 @@
|
|||||||
# GRILL — v1.28 CLI Canonicalization + Identity Layer
|
# CIAgent Grill Report
|
||||||
|
|
||||||
> Adversarial review of the v1.28 SPECIFY + CLARIFY + RESEARCH + PLAN.
|
## Run: 2026-07-27 19:30 (mode: interactive, focus: all)
|
||||||
> Griller: ci-griller subagent. Autonomy: full. All 9 axes reviewed;
|
|
||||||
> every claim verified against the live codebase.
|
### Verdict: Proceed with conditions (confidence: 0.72)
|
||||||
|
|
||||||
|
Two escalations must be resolved before the leadership pitch:
|
||||||
|
- **G-005 (risks):** 6 cloud capabilities (CAP-017..022) are deploy-unverified.
|
||||||
|
**RESOLVED (v1.11):** CAP-017..022 are now Verified live-aws via the
|
||||||
|
modules-lifecycle pipeline (apply/modify/destroy exit 0). The IAM-drift
|
||||||
|
framing is removed. See CAPABILITY_INVENTORY.md.
|
||||||
|
- **G-008 (budget):** No cost documentation exists despite live AWS resources.
|
||||||
|
**RESOLVED (v1.11):** COST.md now exists, documenting the v1.0→v1.10 spend
|
||||||
|
window + the v1.11 cost projection. The v1.14 P19 phase extends the
|
||||||
|
window to v1.11–v1.14.
|
||||||
|
|
||||||
|
The project is reclassified as an **OSS reference implementation** (G-003),
|
||||||
|
not a sponsored product. The grill's sponsor/ROI/budget/timeline axes apply
|
||||||
|
in weakened form; the adoption, architecture, and risks axes apply in full.
|
||||||
|
|
||||||
|
### Axis 1 — Business Case
|
||||||
|
- **Q1**: What problem does this actually solve, and is that problem still the top priority?
|
||||||
|
- Evidence: PROJECT.md:3-21 (vision + North Star); G-003 reframing (OSS reference)
|
||||||
|
- Answer: ACDL is an OSS reference implementation showing the shape of an agentic cloud delivery platform. The problem (cognitive load of infra + operational work of safe change) is documented in docs/vision.md.
|
||||||
|
- Confidence: 0.85
|
||||||
|
- Decision: G-003 — reframe as OSS reference implementation; no sponsor/ROI required.
|
||||||
|
- **Q2**: Who is the named executive sponsor, and when did they last make a decision under pressure?
|
||||||
|
- Evidence: MISSING (no named sponsor in any .ciagent/ file)
|
||||||
|
- Answer: Not applicable for an OSS reference implementation (G-003). Senior leadership requesting the pitch is interest, not sponsorship.
|
||||||
|
- Confidence: 0.85
|
||||||
|
- Decision: G-003 (carries forward).
|
||||||
|
- **Q3**: What happens to the business if the project is cancelled?
|
||||||
|
- Evidence: PROJECT.md:487 ("0 consumer adoption"); 10 milestones shipped with no consumers
|
||||||
|
- Answer: If cancelled, no consumer loses a deployed system. The reference value (clonable shape) persists in the repo. Cancellation cost is low — consistent with OSS reference framing.
|
||||||
|
- Confidence: 0.80
|
||||||
|
- Decision: G-003 (carries forward).
|
||||||
|
- **Q4**: Is the ROI calculated against a counterfactual?
|
||||||
|
- Evidence: MISSING (no ROI calculation anywhere)
|
||||||
|
- Answer: Not applicable for an OSS reference implementation. The bar is "is it a credible, demonstrable reference?" not "is there a paying customer?"
|
||||||
|
- Confidence: 0.85
|
||||||
|
- Decision: G-003 (carries forward).
|
||||||
|
|
||||||
|
### Axis 2 — Scope and Requirements
|
||||||
|
- **Q1**: Is the scope expanding, contracting, or genuinely stable?
|
||||||
|
- Evidence: ROADMAP.md (v1.0→v1.10, 55 phases); v1.7 added uptime-kuma + decommission + RDS; v1.9.x added decks; v1.10 added regression-class VERIFY + local emulators
|
||||||
|
- Answer: Expanding. The Out-of-Scope table (REQUIREMENTS.md:61-72) is scoped to v1.1 only; later milestones added scope without boundary updates.
|
||||||
|
- Confidence: 0.70
|
||||||
|
- Decision: G-010 — OSS scope is contributor-bounded; no out-of-scope table needed.
|
||||||
|
- **Q2**: Who owns the requirements, and have they been frozen?
|
||||||
|
- Evidence: REQUIREMENTS.md (115 REQs, REQ-01..REQ-115); config.json autonomy=full
|
||||||
|
- Answer: The user owns requirements via CLARIFY auto-resolution under full autonomy. Not frozen — each milestone adds REQs.
|
||||||
|
- Confidence: 0.70
|
||||||
|
- Decision: G-010 (carries forward).
|
||||||
|
- **Q3**: What is explicitly out of scope?
|
||||||
|
- Evidence: REQUIREMENTS.md:61-72 (v1.1 Out-of-Scope table only); PROJECT.md:42-51 (Domain Boundaries)
|
||||||
|
- Answer: Domain Boundaries section (PROJECT.md:42-51) defines durable out-of-scope: application business logic, IDE workflows, product backlog, node/OS-level compute. No per-milestone out-of-scope updates since v1.1.
|
||||||
|
- Confidence: 0.65
|
||||||
|
- Decision: G-010 — contributor-bounded scope accepted for OSS reference.
|
||||||
|
- **Q4**: Are there hidden requirements only disclosed late in delivery?
|
||||||
|
- Evidence: v1.10 milestone (decay disclosure, PROJECT.md:59-67) — 7 adapter defects undisclosed across 8 phases
|
||||||
|
- Answer: Yes — the v1.10 decay incident is a late-disclosed hidden requirement (reproducibility). D-091 regression gate is the mitigation.
|
||||||
|
- Confidence: 0.72
|
||||||
|
- Decision: G-007 (carries forward — milestone-level regression gate catches late-disclosed decay).
|
||||||
|
|
||||||
|
### Axis 3 — Architecture and Technical Feasibility
|
||||||
|
- **Q1**: Has the proposed architecture been validated by the people who will build and operate it?
|
||||||
|
- Evidence: PERSONAS.md (agent personas only); ARCHITECTURE.md (29KB); no human reviewer sign-off
|
||||||
|
- Answer: Validated by the agent that built it, not by a downstream platform team. Acceptable for an OSS reference (G-002 — Platform Team joins post-clone).
|
||||||
|
- Confidence: 0.72
|
||||||
|
- Decision: G-002 (carries forward).
|
||||||
|
- **Q2**: What is the integration surface?
|
||||||
|
- Evidence: ARCHITECTURE.md; adapters/ (terraform, wiz, kyverno, local emulators); contracts/ schema
|
||||||
|
- Answer: Contract schema (upstream) + engine adapters (downstream). Integration is bounded by the IR + PolicyCheckResult schemas.
|
||||||
|
- Confidence: 0.78
|
||||||
|
- Decision: (resolved by existing architecture; no new binding decision)
|
||||||
|
- **Q3**: Is there an existing system being replaced?
|
||||||
|
- Evidence: PROJECT.md:7-8 (vision: absorb cognitive load + operational work)
|
||||||
|
- Answer: ACDL replaces manual platform engineering + ticket-driven delivery. No existing system in this repo; downstream teams replace their own.
|
||||||
|
- Confidence: 0.75
|
||||||
|
- Decision: (resolved by G-002 white-label framing)
|
||||||
|
- **Q4**: What is the technical debt being inherited, and is it budgeted for?
|
||||||
|
- Evidence: v1.10 decay (7 adapter defects); D-091 regression gate at milestone completion (not per-phase)
|
||||||
|
- Answer: Diff-scoped VERIFY debt was paid down in v1.10. Per-phase regression gap is accepted debt (G-007).
|
||||||
|
- Confidence: 0.70
|
||||||
|
- Decision: G-007 — milestone-level regression gate is correct; inter-milestone decay is an accepted trade-off.
|
||||||
|
|
||||||
|
### Axis 4 — People, Skills, and Organization
|
||||||
|
- **Q1**: Which 2-3 people, if they left, would the project fail?
|
||||||
|
- Evidence: PERSONAS.md (agent personas); all binding decisions made by the user (D-034, D-090, G-001..G-012)
|
||||||
|
- Answer: One person — the user. Bus factor is 1.
|
||||||
|
- Confidence: 0.82
|
||||||
|
- Decision: G-011 — single-maintainer is normal for OSS reference; no action.
|
||||||
|
- **Q2**: Are the assigned resources actually allocated at the percentages claimed?
|
||||||
|
- Evidence: config.json (autonomy=full, max_concurrent_agents=5)
|
||||||
|
- Answer: The agent is the resource; allocation is 100% when invoked, 0% otherwise. No BAU fire-fighting claim to verify.
|
||||||
|
- Confidence: 0.78
|
||||||
|
- Decision: G-011 (carries forward).
|
||||||
|
- **Q3**: Is there a product owner with actual authority to prioritize?
|
||||||
|
- Evidence: config.json (autonomy=full, decision_confidence_threshold=0.6)
|
||||||
|
- Answer: The user is the product owner with absolute authority (full autonomy within user-locked constraints).
|
||||||
|
- Confidence: 0.80
|
||||||
|
- Decision: G-011 (carries forward).
|
||||||
|
- **Q4**: Is the team building capability they don't have?
|
||||||
|
- Evidence: RESEARCH.md (101KB); local emulating adapters (Phase 53) — capability was built and proven
|
||||||
|
- Answer: No — the agent built and verified the capability. Not a prototype-hoping-to-learn scenario.
|
||||||
|
- Confidence: 0.78
|
||||||
|
- Decision: (resolved by existing evidence)
|
||||||
|
|
||||||
|
### Axis 5 — Timeline and Estimates
|
||||||
|
- **Q1**: Was the deadline set before or after the scope was understood?
|
||||||
|
- Evidence: ROADMAP.md (v1.0 07-21 → v1.10 07-27, 6 days); no deadline documented anywhere
|
||||||
|
- Answer: No deadline. Milestones complete when the agent finishes committing.
|
||||||
|
- Confidence: 0.78
|
||||||
|
- Decision: G-006 — autonomous OSS build has no deadline; cadence is fine.
|
||||||
|
- **Q2**: What is the project's critical path?
|
||||||
|
- Evidence: MISSING (no critical path analysis)
|
||||||
|
- Answer: Not applicable — no deadline means no critical path to push.
|
||||||
|
- Confidence: 0.75
|
||||||
|
- Decision: G-006 (carries forward).
|
||||||
|
- **Q3**: Are the estimates evidence-based?
|
||||||
|
- Evidence: MISSING (no estimates; phases complete in agent-time)
|
||||||
|
- Answer: No estimates. The cadence is a function of agent speed, not engineering sizing.
|
||||||
|
- Confidence: 0.72
|
||||||
|
- Decision: G-006 (carries forward — acceptable for autonomous OSS reference).
|
||||||
|
- **Q4**: Is there a working definition of done?
|
||||||
|
- Evidence: VERIFY.md; AUDIT.md; 4-layer verify gate (structural, behavioral, security, quality)
|
||||||
|
- Answer: Yes — the 4-layer verify gate + regression gate (D-091) is the definition of done. "Done" is not "whatever the latest demo shows"; it is a gated, audited state.
|
||||||
|
- Confidence: 0.80
|
||||||
|
- Decision: (resolved by existing verify gate)
|
||||||
|
|
||||||
|
### Axis 6 — Budget and Financial Realism
|
||||||
|
- **Q1**: What percentage of the budget is already spent vs. remaining?
|
||||||
|
- Evidence: MISSING (no budget file in .ciagent/)
|
||||||
|
- Answer: Unresolved — no budget documented.
|
||||||
|
- Confidence: 0.50
|
||||||
|
- Decision: G-008 — ESCALATION.
|
||||||
|
- **Q2**: Are there predictable cost drivers not in the original budget?
|
||||||
|
- Evidence: config.json escalation_hooks (deploy, delete_data); CAP-013..016 verified against live AWS account 581513795199
|
||||||
|
- Answer: Yes — live AWS resources exist (S3 state, DynamoDB outbox, ECS, CloudFront). No cost driver documentation.
|
||||||
|
- Confidence: 0.60
|
||||||
|
- Decision: G-008 (carries forward — escalation).
|
||||||
|
- **Q3**: What's the burn rate, and how long until the money runs out?
|
||||||
|
- Evidence: MISSING
|
||||||
|
- Answer: Unresolved.
|
||||||
|
- Confidence: 0.40
|
||||||
|
- Decision: G-008 (carries forward — escalation).
|
||||||
|
- **Q4**: Is the budget contingent on something that hasn't happened yet?
|
||||||
|
- Evidence: MISSING
|
||||||
|
- Answer: Unresolved — likely contingent on the leadership pitch yielding a pilot platform team (G-001).
|
||||||
|
- Confidence: 0.55
|
||||||
|
- Decision: G-008 (carries forward — escalation).
|
||||||
|
|
||||||
|
### Axis 7 — Risks, Assumptions, and Dependencies
|
||||||
|
- **Q1**: What are the top 3 assumptions the plan rests on?
|
||||||
|
- Evidence: PROJECT.md:79-88 (CAP-017..022 IAM-gated); D-039 (OIDC federation deferred, blocked on go-gitea/gitea#36988); D-090 (no cap on re-verification sweep)
|
||||||
|
- Answer: (1) Terraform plan path proves deployability. (2) Local emulators prove runtime behavior. (3) Gitea OIDC will eventually merge.
|
||||||
|
- Confidence: 0.72
|
||||||
|
- Decision: (resolved by G-005 escalation)
|
||||||
|
- **Q2**: What are you dependent on outside the team?
|
||||||
|
- Evidence: PROJECT.md:79-88 (admin principal needed for IAM re-bootstrap); go-gitea/gitea#36988 (OIDC blocker)
|
||||||
|
- Answer: An admin AWS principal (for CAP-017..022) and the Gitea OIDC PR (for D-039 waiver closure).
|
||||||
|
- Confidence: 0.78
|
||||||
|
- Decision: G-005 (carries forward — escalation).
|
||||||
|
- **Q3**: What is the single risk that, if it materializes, kills the project?
|
||||||
|
- Evidence: CAPABILITY_INVENTORY.md §"Cloud capabilities NOT re-verified" (6 of 22 capabilities, 27%)
|
||||||
|
- Answer: The unverifiable deploy path for CAP-017..022. If the terraform plan path does not translate to a real deploy, 27% of advertised capability is fictional.
|
||||||
|
- Confidence: 0.80
|
||||||
|
- Decision: G-005 — ESCALATION.
|
||||||
|
- **Q4**: Have you done a pre-mortem?
|
||||||
|
- Evidence: MISSING (no pre-mortem document)
|
||||||
|
- Answer: No pre-mortem on file. The v1.10 decay incident is the closest thing to a post-mortem.
|
||||||
|
- Confidence: 0.65
|
||||||
|
- Decision: (flagged; no binding decision — user accepted autonomous governance in G-009)
|
||||||
|
|
||||||
|
### Axis 8 — Governance, Decision-Making, and Communication
|
||||||
|
- **Q1**: Who is the decision-maker when two executives disagree?
|
||||||
|
- Evidence: config.json (autonomy=full); no human governance body documented
|
||||||
|
- Answer: The user is the single decision-maker. No executive disagreement is possible because there is no executive body.
|
||||||
|
- Confidence: 0.78
|
||||||
|
- Decision: G-009 — autonomous CI is the governance.
|
||||||
|
- **Q2**: How often does governance meet, and what's the escalation pattern?
|
||||||
|
- Evidence: config.json (escalation_hooks: deploy, delete_data, merge_to_main; escalation_timeout_ms: 300000)
|
||||||
|
- Answer: Governance is event-driven (escalation hooks), not cadence-driven. 5-minute timeout.
|
||||||
|
- Confidence: 0.72
|
||||||
|
- Decision: G-009 (carries forward).
|
||||||
|
- **Q3**: What is being omitted from the status reports?
|
||||||
|
- Evidence: v1.10 decay disclosure (PROJECT.md:59-67) — 8 phases omitted the decay from status
|
||||||
|
- Answer: The v1.10 incident is direct evidence that status reports (decks) omitted material decay. D-094 (rewrite to verified reality) is the correction.
|
||||||
|
- Confidence: 0.75
|
||||||
|
- Decision: (resolved by D-094 + G-007 regression gate)
|
||||||
|
- **Q4**: Is there a "stop the project" trigger?
|
||||||
|
- Evidence: MISSING (no stop-trigger documented)
|
||||||
|
- Answer: No formal stop-trigger. The user is the single point of cancellation authority.
|
||||||
|
- Confidence: 0.68
|
||||||
|
- Decision: G-009 — autonomous CI is the governance; no human stop-trigger needed.
|
||||||
|
|
||||||
|
### Axis 9 — Change, Adoption, and Operational Readiness
|
||||||
|
- **Q1**: Who will use this, and what is in it for them?
|
||||||
|
- Evidence: PROJECT.md:487 ("0 consumer adoption"); G-001 (MVP for leadership pitch + pilot consumers)
|
||||||
|
- Answer: Pilot platform teams (post-pitch) will clone, customize, and deploy for their internal consumers. The value to them is a working reference shape.
|
||||||
|
- Confidence: 0.65
|
||||||
|
- Decision: G-001 — feature-complete MVP for pitch + pilot consumers in parallel.
|
||||||
|
- **Q2**: Is the operations/support team involved now or being handed a finished product?
|
||||||
|
- Evidence: MISSING (no Platform Team involvement in 55 phases); G-002 (white-label, out-of-repo)
|
||||||
|
- Answer: Intentionally out-of-scope — ACDL is white-label; Platform Team customization happens outside this repo.
|
||||||
|
- Confidence: 0.78
|
||||||
|
- Decision: G-002 — white-label; Platform Team customization is out-of-repo.
|
||||||
|
- **Q3**: What is the rollback plan if it goes wrong?
|
||||||
|
- Evidence: D-070 (decommission mode, 2-step pipeline with HITL SRE gates)
|
||||||
|
- Answer: Decommission mode exists for deployed stacks. For the reference repo itself, rollback = git revert (no production state to roll back).
|
||||||
|
- Confidence: 0.75
|
||||||
|
- Decision: (resolved by existing D-070 decommission mode)
|
||||||
|
- **Q4**: Has anyone validated the success criteria with the people who will judge success?
|
||||||
|
- Evidence: PROJECT.md (leadership pitch requested); no documented success-criteria validation with leadership
|
||||||
|
- Answer: The leadership pitch IS the validation moment. Success criteria for an OSS reference = "leadership says this is a credible shape."
|
||||||
|
- Confidence: 0.68
|
||||||
|
- Decision: G-001 (carries forward — pitch is the validation).
|
||||||
|
|
||||||
|
### Meta — Closing Review
|
||||||
|
- **Q1**: If you were the auditor, what would you flag?
|
||||||
|
- Evidence: This grill run
|
||||||
|
- Answer: (1) 6 unverifiable cloud capabilities (G-005). (2) No cost documentation (G-008). (3) Vision doc vs. OSS-reference framing tension (G-004 — resolved by keeping vision as target-state description).
|
||||||
|
- Confidence: 0.78
|
||||||
|
- Decision: (aggregated; G-005 + G-008 are the actionable flags)
|
||||||
|
- **Q2**: What is the project not doing that it should?
|
||||||
|
- Evidence: MISSING (no pre-mortem, no cost doc, no Platform Team engagement, no stop-trigger)
|
||||||
|
- Answer: Documenting the operating model (cost, deploy verification, governance) for a downstream team. The grill surfaced this across G-005, G-008, G-009.
|
||||||
|
- Confidence: 0.75
|
||||||
|
- Decision: (aggregated; G-005 + G-008 are the actionable items)
|
||||||
|
- **Q3**: What is the simplest possible version that could deliver 80% of the value?
|
||||||
|
- Evidence: ROADMAP.md (v1.1 spike, Phase 10, REQ-27 — core E2E proven); v1.2-v1.10 (45 phases of expansion)
|
||||||
|
- Answer: The v1.1 spike (contract → IR → terraform plan → Checkov → confidence → outbox) is the 80%-value version. The full 115-requirement build is accepted as the reference value (G-012).
|
||||||
|
- Confidence: 0.68
|
||||||
|
- Decision: G-012 — full catalog is the value; no minimal release needed.
|
||||||
|
- **Q4**: What would have to be true for this to succeed in the next 90 days, and is it true today?
|
||||||
|
- Evidence: G-001 (pitch + pilot); G-005 (IAM re-bootstrap); G-008 (cost doc)
|
||||||
|
- Answer: (1) Leadership pitch yields a pilot platform team — NOT TRUE today (pitch not yet delivered). (2) CAP-017..022 deploy path is verifiable — NOT TRUE today (G-005 escalation). (3) Cost operating model is documented — NOT TRUE today (G-008 escalation).
|
||||||
|
- Confidence: 0.72
|
||||||
|
- Decision: (aggregated; G-005 + G-008 + G-001 pitch are the 90-day conditions)
|
||||||
|
|
||||||
|
### Binding Decisions
|
||||||
|
| ID | Axis | Decision | Confidence |
|
||||||
|
|----|------|----------|-----------|
|
||||||
|
| G-001 | adoption | Feature-complete MVP for leadership pitch + pilot consumers in parallel; CIAgent builds, Platform Team deploys | 0.65 |
|
||||||
|
| G-002 | adoption | ACDL is white-label; Platform Team customization is out-of-repo; resolves ops-handoff concern | 0.78 |
|
||||||
|
| G-003 | business | Reframe as OSS reference implementation; no sponsor/ROI required | 0.85 |
|
||||||
|
| G-004 | business | Keep production-deployment vision; reference describes target state | 0.75 |
|
||||||
|
| G-005 | risks | ESCALATION — re-bootstrap IAM or mark CAP-017..022 deploy-unverified in decks | 0.80 |
|
||||||
|
| G-006 | timeline | Autonomous OSS build has no deadline; cadence acceptable | 0.72 |
|
||||||
|
| G-007 | architecture | Milestone-level regression gate is correct; system worked as designed | 0.70 |
|
||||||
|
| G-008 | budget | ESCALATION — add COST.md or document zero-cloud-cost operating model | 0.74 |
|
||||||
|
| G-009 | governance | Autonomous CI is the governance; no human stop-trigger needed | 0.68 |
|
||||||
|
| G-010 | scope | OSS scope is contributor-bounded; no out-of-scope table needed | 0.65 |
|
||||||
|
| G-011 | people | Single-maintainer is normal for OSS reference; no action | 0.70 |
|
||||||
|
| G-012 | meta | Full catalog is the value; no minimal release needed | 0.68 |
|
||||||
|
|
||||||
|
### Escalations
|
||||||
|
- **[G-005] risks** — 6 cloud capabilities (CAP-017..022: DynamoDB contracts table, Lambda contract-ingestor, ECS service live, CloudFront production stack, uptime-kuma, OIDC role) are deploy-unverified. The `acdl-spike-runner` IAM user cannot fix its own IAM (chicken-and-egg). Either re-bootstrap IAM with an admin principal to re-verify, or explicitly mark these 6 as "design-verified, deploy-unverified" in every leadership deck before the pitch. Resolves: project-killing risk (Axis 7 Q3).
|
||||||
|
- **[G-008] budget** — No cost documentation exists in `.ciagent/` despite live AWS resources (account 581513795199, CAP-013..016 verified). Either add a `COST.md` documenting monthly AWS spend, or explicitly document that ACDL runs at zero cloud cost (local emulators are the primary tier; live-AWS is a one-off spike per milestone). Resolves: financial-control gap (Axis 6 Q1-Q4).
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Overall verdict: **PROCEED-WITH-CONDITIONS** · Confidence 0.76
|
## Run: 2026-07-29 20:25 (mode: adversarial, focus: v1.14 NFR plan)
|
||||||
|
|
||||||
The plan is fundamentally sound — architecture correct, re-mapping
|
### Verdict: FEASIBLE WITH BINDING DECISIONS (confidence: 0.72)
|
||||||
clean (no ID collisions), technical depth accurate (DER→raw, strong-
|
|
||||||
read revocation, stdin TTY), highest-risk item (kj binary) has a
|
|
||||||
Fargate fallback. Not unfeasible, not over-scoped beyond an agent-driven
|
|
||||||
repo's capacity, not security-broken by design.
|
|
||||||
|
|
||||||
**3 critical conditions (must-fix before P1) + 16 tracked conditions.**
|
The v1.14 milestone is a sound, well-evidenced NFR sweep with a genuine,
|
||||||
No escalations (all axes ≥ 0.70 confidence).
|
traceable backlog. Not fundamentally infeasible. Four binding decisions
|
||||||
|
close plan defects + unverified assumptions that would otherwise re-expose
|
||||||
|
the v1.11 4-VPC failure mode. One escalation (E-001) auto-resolved at full
|
||||||
|
autonomy with assumption logging.
|
||||||
|
|
||||||
---
|
### 9-Axis scores
|
||||||
|
|
||||||
## Axis verdicts
|
| Axis | Confidence | Forcing question (short) |
|
||||||
|
|------|-----------|---------------------------|
|
||||||
|
| 1 Business | 0.80 | Real backlog (5 P1 + 4 P2 + 6 swallowed errors + 15+ hardcoded IDs); cancellation survivable but inherits decay risk |
|
||||||
|
| 2 Scope | 0.70 | User-directed + frozen; P13 has a hidden feature door (implement vs remove); P2 conditional-child edges past wiring |
|
||||||
|
| 3 Architecture | 0.62 | P8 grep unsatisfiable for backend blocks; P8 state-bucket continuity unguarded; P9 IAM naming unverified; P4/P8 file overlap |
|
||||||
|
| 4 People | 0.85 | Agentic single-operator; runtime availability is the key-person risk |
|
||||||
|
| 5 Timeline | 0.68 | No deadline; 20-phase unverified span is the longest since G-007; P8 is the latent multi-phase-rework risk |
|
||||||
|
| 6 Budget | 0.85 | NFR-only, no new AWS resources; P8 re-creation is a one-shot accident not structural cost |
|
||||||
|
| 7 Risks | 0.60 | A1 (acdl-* naming unverified), A2 (fallback constant unbound), A3 (P4 gate hardening); kill-risk = P8 orphans state |
|
||||||
|
| 8 Governance | 0.72 | Full autonomy; no mid-milestone stop trigger; per-phase "green" ≠ "capabilities Verified" |
|
||||||
|
| 9 Adoption | 0.70 | No external users; rollback is git-level for code, AWS-state rollback unaddressed if P8 misfires pre-detection |
|
||||||
|
|
||||||
| Axis | Verdict | Confidence | Critical condition |
|
### Binding Decisions
|
||||||
|------|---------|-----------|-------------------|
|
|
||||||
| §1 Feasibility | PROCEED-WITH-CONDITIONS | 0.82 | C-1.1 KMS asym verify; C-1.2 Argon2 fail-closed test |
|
|
||||||
| §2 Scope | PROCEED-WITH-CONDITIONS | 0.74 | C-2.1 fold P5 into P4; C-2.2 P4 overload |
|
|
||||||
| §3 Cost | PROCEED-WITH-CONDITIONS | 0.70 | C-3.1 cost estimate; C-3.2 CodeArtifact P1 task |
|
|
||||||
| §4 Schedule | PROCEED-WITH-CONDITIONS | 0.76 | C-4.1 P4 critical path; C-4.2 per-phase exit |
|
|
||||||
| §5 Technical Depth | PROCEED-WITH-CONDITIONS | 0.80 | C-5.1 ABAC shape; **C-5.2 JWS KDF** |
|
|
||||||
| §6 Operational Readiness | PROCEED-WITH-CONDITIONS | 0.72 | **C-6.1 ABAC fail-closed**; C-6.2 threat model; C-6.3 ops guide |
|
|
||||||
| §7 Security Posture | PROCEED-WITH-CONDITIONS | 0.73 | **C-7.1 ABAC fail-closed**; C-7.2 Argon2 params; C-7.3 cred file |
|
|
||||||
| §8 Dependency Risk | PROCEED-WITH-CONDITIONS | 0.83 | C-8.1 CodeArtifact P1; C-8.2 pin kj version |
|
|
||||||
| §9 Re-mapping Integrity | PROCEED-WITH-CONDITIONS | 0.84 | **C-9.1 traceability fix**; C-9.2 INV audit |
|
|
||||||
|
|
||||||
---
|
| ID | Axis | Decision | Confidence |
|
||||||
|
|----|------|----------|-----------|
|
||||||
|
| G-101 | architecture | P8 grep scope amended to exclude terraform `backend "s3"` blocks (bucket arg is static-config-only, evaluated pre-init; cannot reference `data.aws_caller_identity`). Resource ARNs in policy/code ARE externalized; backend blocks stay literal or move to `-backend-config` (separate change). | 0.80 |
|
||||||
|
| G-102 | risks | P8 must bind `ACDL_AWS_ACCOUNT_ID` fallback to the live account ID (not a placeholder) AND the lifecycle workflow (full-mode jobs) must set `ACDL_AWS_ACCOUNT_ID` from `aws sts get-caller-identity` before any lifecycle invocation. No full-mode run proceeds with the env unset. | 0.78 |
|
||||||
|
| G-103 | scope | P13 must take the removal+documentation path (remove `--kube-version` + document deferral to GitOps reconciler roadmap), NOT the implementation path. Implementing version-aware policy selection is a new feature, violating D-095. | 0.85 |
|
||||||
|
| G-104 | architecture | P9 must verify (grep/audit of `modules/l1/*/terraform/main.tf` + `modules/l2/*/composition.json`) that every IAM role + KMS key created by the lifecycle pipeline matches `acdl-*` prefix before merge. CloudFront + WAFv2 (CloudFront scope) remain `Resource: "*"` with a documented global-ARN constraint. | 0.70 |
|
||||||
|
| G-105 | governance | P4's regression-gate hardening must be validated by running the full regression gate immediately after P4 lands (not deferred to P21). Gate must pass clean post-P4 before W2 begins. | 0.70 |
|
||||||
|
| G-106 | governance | A mid-milestone regression-gate checkpoint is added after W2 (P12), before W3 begins. Gate runs offline (D-091); a non-Verified result halts W3 until fixed. Not a re-litigation of G-007 (per-phase stays deferred) — a single checkpoint at the natural seam after the security wave. | 0.65 |
|
||||||
|
|
||||||
## Critical conditions (the 3 must-fix-before-P1)
|
### Escalations
|
||||||
|
|
||||||
### 🔴 C-6.1 / C-7.1 — ABAC fail-closed
|
- **[E-001] risks** — P8 state-bucket continuity re-exposes the v1.11 4-VPC
|
||||||
The token-vend Lambda's behavior on `kj` absence/error is unspecified.
|
root cause. G-102 proposes a binding mitigation (bind fallback + wire env
|
||||||
Without fail-closed, INV-17 is documentation, not a runtime guarantee —
|
into workflow), but the residual risk (a future full-mode lifecycle run
|
||||||
a `kj` load failure would bypass the ABAC gate (every PAT gets a token).
|
with a misconfigured env orphans live state and re-creates resources)
|
||||||
**Fix applied to PLAN.md P4 Wave 4 Task 4.1:** "If
|
cannot be reduced below 0.20 by plan-level decisions alone. **Auto-
|
||||||
`KyvernoJsonEngine.is_configured()` returns false or `evaluate()`
|
resolved at full autonomy (D-101):** accept the residual risk; G-102's
|
||||||
raises, return 403 + audit `token.vend.denied` (reason:
|
binding mitigation (fallback bound to live account ID + workflow env
|
||||||
`abac_eval_failed`). Never fail open. Test: `tests/test_abac_fail_closed.py`."
|
wiring) is the control. The lifecycle pipeline defaults to plan-only
|
||||||
|
(REQ-134) — full-mode runs are workflow_dispatch only, reducing the
|
||||||
### 🔴 C-5.2 — JWS-from-PAT key derivation
|
accident surface. If the user prefers zero residual risk, direct that
|
||||||
REQ-332's AC ("public key derivable from the PAT") is unimplementable
|
P8 exclude the state-bucket name from externalization entirely
|
||||||
without a specified KDF. A PAT is a JWT, not a keypair.
|
(externalize only resource ARNs, leave the backend `bucket` literal).
|
||||||
**Fix applied to PLAN.md P2 Wave 2 Task 2.3 + REQ-332 AC:** the JWS
|
Confidence 0.55; auto-resolved per `config.autonomy.level=full`.
|
||||||
uses HMAC-SHA256 with a key derived via
|
|
||||||
`HKDF-SHA256(PAT_bytes, salt='nova-local-attestation', info='jws-signing-key')`
|
|
||||||
→ 32-byte symmetric key. The "public key derivable" AC is re-interpreted:
|
|
||||||
the *verification key* is derived from the PAT via the same KDF (the
|
|
||||||
PAT is the shared secret). This is a symmetric scheme, not asymmetric.
|
|
||||||
|
|
||||||
### 🔴 C-9.1 — Traceability drift
|
|
||||||
REQUIREMENTS.md §v1.28 traceability table mapped 16 REQs to P2;
|
|
||||||
PLAN.md splits them across P2/P3/P4/P5/P6. **Fix applied to
|
|
||||||
REQUIREMENTS.md** — traceability table updated to match PLAN.md phase
|
|
||||||
structure.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Tracked conditions (16 — applied to PLAN.md as amendments)
|
|
||||||
|
|
||||||
- **C-1.1** KMS asymmetric key verification before P4 Wave 3 (one
|
|
||||||
`aws kms create-key --key-spec ECC_NIST_P256` call).
|
|
||||||
- **C-1.2** Argon2 fail-closed test in P3 Wave 2 (Lambda returns 503
|
|
||||||
on `ImportError`, not a crash or pure-Python hash).
|
|
||||||
- **C-2.1** Fold P5 (idp-setup) into P4 as P4 Wave 8 → **reduces to 6
|
|
||||||
execution phases** (P1..P6, P7 = final). Applied.
|
|
||||||
- **C-2.2** P4 is a double-length phase; acknowledged in P4 header.
|
|
||||||
- **C-3.1** Cost envelope subsection added to PLAN.md.
|
|
||||||
- **C-3.2 / C-8.1** CodeArtifact provisioning = P1 Wave 0 task with
|
|
||||||
binary go/no-go gate; Gitea wheel index fallback documented.
|
|
||||||
- **C-4.1** P4 flagged as critical-path phase (kj spike = highest-
|
|
||||||
probability schedule slip; Fargate = +1 week).
|
|
||||||
- **C-4.2** Per-phase exit criteria added to PLAN.md.
|
|
||||||
- **C-5.1** `requested_claims` = list of claim names (the policy
|
|
||||||
asserts the subject is *allowed* to request those claims).
|
|
||||||
- **C-6.2** Threat model (REQ-347) adds: JWKS DDoS surface, PAT theft
|
|
||||||
+ max TTL (≤24h dev, ≤1h service-account), ABAC fail-closed,
|
|
||||||
INV-18..21 compression audit.
|
|
||||||
- **C-6.3** Operator guide (REQ-345) adds: KMS rotation, layer update,
|
|
||||||
PITR restore, emergency PAT revocation.
|
|
||||||
- **C-7.2** Argon2id parameters: t=3, m=65536 KiB, p=1 (OWASP min).
|
|
||||||
- **C-7.3** `~/.nova/credentials.json` stores OIDC token + PAT metadata
|
|
||||||
(jti, exp, type), NOT the raw PAT.
|
|
||||||
- **C-8.2** `kj` pinned to a specific release + SHA256 recorded.
|
|
||||||
- **C-9.2** Threat model includes INV-18..21 compression audit
|
|
||||||
(verify spec's attestation invariant semantics are captured by
|
|
||||||
INV-15/16/17 + REQ-332).
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Escalations
|
|
||||||
|
|
||||||
None. All 9 axes resolved at confidence ≥ 0.70. No human escalation
|
|
||||||
required (full autonomy).
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Grill complete
|
|
||||||
|
|
||||||
The plan proceeds with the 3 critical fixes and 16 tracked conditions
|
|
||||||
applied to PLAN.md + REQUIREMENTS.md. The binding decisions above are
|
|
||||||
the authoritative grill record. Next: MVP/UX CHECK → SHIP phase 0.
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
# Nova — IAM Policy Baseline (v1.11, REQ-116)
|
# ACDL — IAM Policy Baseline (v1.11, REQ-116)
|
||||||
|
|
||||||
> Source of truth: `terraform/bootstrap/spike_runner_policy.json`.
|
> Source of truth: `terraform/bootstrap/spike_runner_policy.json`.
|
||||||
> Applied as: customer-managed policy `acdl-spike-runner-policy`
|
> Applied as: customer-managed policy `acdl-spike-runner-policy`
|
||||||
@@ -56,8 +56,7 @@ and covered by the baseline test.
|
|||||||
## OIDC act_runner role (CAP-022, Phase 56)
|
## OIDC act_runner role (CAP-022, Phase 56)
|
||||||
|
|
||||||
The OIDC role for the Gitea `act_runner` was created in Phase 08 and
|
The OIDC role for the Gitea `act_runner` was created in Phase 08 and
|
||||||
gone since (`archive/CAPABILITY_INVENTORY-v1.10.md` CAP-022, archived
|
gone since (CAPABILITY_INVENTORY.md CAP-022). Phase 56 re-creates it
|
||||||
v1.27). Phase 56 re-creates it
|
|
||||||
with a trust policy for the Gitea runner ARN. The role grants the
|
with a trust policy for the Gitea runner ARN. The role grants the
|
||||||
spike-runner-equivalent permissions to the runner via `sts:AssumeRole`,
|
spike-runner-equivalent permissions to the runner via `sts:AssumeRole`,
|
||||||
so the runner does not need a long-lived access key. This closes the
|
so the runner does not need a long-lived access key. This closes the
|
||||||
@@ -74,7 +73,7 @@ bootstrap root key; the runner then assumes the role.
|
|||||||
|
|
||||||
The OIDC role for the Gitea `act_runner` was planned in Phase 08 but
|
The OIDC role for the Gitea `act_runner` was planned in Phase 08 but
|
||||||
never created (the spike used a long-lived key per D-039 waiver).
|
never created (the spike used a long-lived key per D-039 waiver).
|
||||||
`archive/CAPABILITY_INVENTORY-v1.10.md` CAP-022 recorded "iam:ListRoles shows no acdl*
|
CAPABILITY_INVENTORY.md CAP-022 recorded "iam:ListRoles shows no acdl*
|
||||||
roles." Phase 56 re-created the role:
|
roles." Phase 56 re-created the role:
|
||||||
|
|
||||||
- **Role name:** `acdl-act-runner-role`
|
- **Role name:** `acdl-act-runner-role`
|
||||||
|
|||||||
@@ -1,194 +0,0 @@
|
|||||||
# IDEATE — v1.26 Live Pilot Estate Activation
|
|
||||||
|
|
||||||
> **Autonomy:** full. 3-tier ideation per `config.json ideation.enabled:
|
|
||||||
> true`. `cross_project.enabled: false` → cross-project tier scoped to
|
|
||||||
> multi-project (deferred ideas only, no cross-project candidates
|
|
||||||
> accepted). `confidence_threshold: 0.6`, `max_ideas: 20`.
|
|
||||||
> Categories: security, quality, architecture, coverage, improvement.
|
|
||||||
|
|
||||||
## Tier 1 — Mechanical (pattern-driven, codebase-grounded)
|
|
||||||
|
|
||||||
### I1 — Outcome-backfill emitter ✅ ACCEPTED (REQ-317)
|
|
||||||
|
|
||||||
**Category:** quality, coverage
|
|
||||||
**Confidence:** 0.92
|
|
||||||
**Pattern:** stuck `pending` status → backfilled from a later event
|
|
||||||
(the most direct metric-grounding pattern).
|
|
||||||
**Source:** `core/metrics/decision_ledger.py:210-211` documents the
|
|
||||||
event chain `confidence.computed → ai.decision.made →
|
|
||||||
attestation.recorded → run.completed/failed`. `collector.py:262`
|
|
||||||
inserts `fact_decision.outcome` as `"pending"` — no backfill step
|
|
||||||
wires `run.completed/failed` back into the decision's outcome. The AI
|
|
||||||
Decision Accuracy metric (`trust_snapshot.py:70-85`) reads
|
|
||||||
`decisions WHERE outcome='succeeded' ÷ total` → 0% today (all pending).
|
|
||||||
**Idea:** `core/metrics/outcome_backfill.py` reads run-manifest
|
|
||||||
`completed`/`failed` events and updates `fact_decision.outcome` +
|
|
||||||
`fact_decision.backfilled_at`. The collector invokes backfill after run
|
|
||||||
completion. Grounds AI Decision Accuracy (Post-Pilot target).
|
|
||||||
**Accepted into:** REQ-317. Phase P3.
|
|
||||||
|
|
||||||
### I2 — `reason='confidence'` escalation tag ✅ ACCEPTED (REQ-318)
|
|
||||||
|
|
||||||
**Category:** quality, coverage
|
|
||||||
**Confidence:** 0.90
|
|
||||||
**Pattern:** boolean field → discriminated field (the metric-numerator
|
|
||||||
precision pattern).
|
|
||||||
**Source:** `core/confidence_signal.py:184` — a `block` band sets
|
|
||||||
`human_override=True`. The Human Escalation Frequency metric
|
|
||||||
(`docs/metrics/human_escalation_frequency.md:11-12`) is defined as
|
|
||||||
`count(runs WHERE hitl_block=1 AND reason='confidence') ÷ total runs`.
|
|
||||||
The `reason='confidence'` discriminator is not stored today.
|
|
||||||
**Idea:** `ai.decision.made` gains `escalation_reason: 'confidence'`
|
|
||||||
when `band == 'block'`. The collector persists it into `fact_run`.
|
|
||||||
Grounds Human Escalation Frequency numerator.
|
|
||||||
**Accepted into:** REQ-318. Phase P3.
|
|
||||||
|
|
||||||
### I3 — Env-JSON `state_backend` wiring reconciliation ✅ ACCEPTED (REQ-319)
|
|
||||||
|
|
||||||
**Category:** architecture, improvement
|
|
||||||
**Confidence:** 0.88
|
|
||||||
**Pattern:** unused config field → wired config field (the
|
|
||||||
single-source-of-truth pattern).
|
|
||||||
**Source:** `adapters/terraform/adapter.py:116-117` computes the state
|
|
||||||
bucket as `nova-tfstate-<AWS_ACCOUNT_ID>-us-east-1` from the
|
|
||||||
`AWS_ACCOUNT_ID` env var — **not** from the env JSON's
|
|
||||||
`state_backend.bucket`. The env JSON's `state_backend` field is
|
|
||||||
currently unused by the live apply path.
|
|
||||||
**Idea:** The adapter reads `env.state_backend.bucket` when present
|
|
||||||
(falling back to the computed name for backwards compat). `dev.json`
|
|
||||||
gets the real bucket name. Closes the wiring gap so the pilot's env
|
|
||||||
JSON is the single source of truth.
|
|
||||||
**Accepted into:** REQ-319. Phase P3.
|
|
||||||
|
|
||||||
### I4 — Pilot-readiness kyverno-json policy ✅ ACCEPTED (REQ-320)
|
|
||||||
|
|
||||||
**Category:** security, architecture
|
|
||||||
**Confidence:** 0.85
|
|
||||||
**Pattern:** runtime guard → declarative policy (the v1.25 thesis
|
|
||||||
applied to pilot onboarding).
|
|
||||||
**Source:** `core/environment_check.py:48-53` emits a stderr warning
|
|
||||||
(non-fatal) when `account_id == "000000000000"` and env != dev. A
|
|
||||||
warning is not a gate. The pilot should fail-closed if someone tries
|
|
||||||
to apply against a placeholder account.
|
|
||||||
**Idea:** A kyverno-json policy over the env JSON asserting
|
|
||||||
`account_id != "000000000000"` before any apply. Declarative
|
|
||||||
fail-closed gate. Extends v1.25's policy engine to the pilot-onboarding
|
|
||||||
domain.
|
|
||||||
**Accepted into:** REQ-320. Phase P3.
|
|
||||||
|
|
||||||
## Tier 2 — Backend-enriched (signal-driven)
|
|
||||||
|
|
||||||
### I5 — Settlement-finality kyverno-json policy ✅ ACCEPTED (REQ-315)
|
|
||||||
|
|
||||||
**Category:** security, coverage
|
|
||||||
**Confidence:** 0.82
|
|
||||||
**Pattern:** domain invariant → declarative policy (the v1.25 thesis
|
|
||||||
applied to the securities domain — the most novel use of kyverno-json
|
|
||||||
in v1.26).
|
|
||||||
**Source:** The pilot's settlement service records matches as
|
|
||||||
transactions on the chain; settlement finality = block commit. The
|
|
||||||
NORTH_STAR Objective #2 (provable trust) says trust should be a policy
|
|
||||||
artifact, not a promise. Today settlement finality is a runtime
|
|
||||||
property of the chain; making it a declarative policy turns it into an
|
|
||||||
auditable gate.
|
|
||||||
**Idea:** A kyverno-json policy over the settlement-service status JSON
|
|
||||||
asserting `all_committed: true` before any promotion (qa→prod). The
|
|
||||||
securities-specific extension of v1.25's policy engine. The policy is
|
|
||||||
skip-when-kj-absent (graceful).
|
|
||||||
**Accepted into:** REQ-315. Phase P3.
|
|
||||||
|
|
||||||
### I6 — Pilot-estate regression capability (CAP-025) ✅ ACCEPTED (REQ-316)
|
|
||||||
|
|
||||||
**Category:** quality, coverage
|
|
||||||
**Confidence:** 0.88
|
|
||||||
**Pattern:** manual e2e → regression-gated capability (the v1.0 CAP
|
|
||||||
pattern applied to the pilot).
|
|
||||||
**Source:** `core/regression_verify.py` has CAP-013..024 (live-AWS +
|
|
||||||
local tiers). The pilot estate is a new live-AWS capability —
|
|
||||||
"contract resolve → adapter compile → terraform plan → policy scan →
|
|
||||||
confidence signal → attestation → outbox record" against
|
|
||||||
`581513795199`. Without a regression CAP, the pilot could silently
|
|
||||||
decay.
|
|
||||||
**Idea:** CAP-025 (live-pilot-apply) in the regression gate. The
|
|
||||||
round-trip assertion. Grounds the pilot as a maintained capability,
|
|
||||||
not a one-shot demo.
|
|
||||||
**Accepted into:** REQ-316. Phase P3.
|
|
||||||
|
|
||||||
### I7 — DynamoDB L1 primitive ✅ ACCEPTED (REQ-322)
|
|
||||||
|
|
||||||
**Category:** architecture, coverage
|
|
||||||
**Confidence:** 0.95
|
|
||||||
**Pattern:** missing primitive → authored module (the v1.7 + v1.8
|
|
||||||
module-build-out pattern).
|
|
||||||
**Source:** RESEARCH §3.4 — no `modules/l1/dynamodb/` exists. The
|
|
||||||
blockchain exchange's ledger table needs it. The adapter is
|
|
||||||
stateless/registry-driven (no `TYPE_MAP`); a new stack type requires a
|
|
||||||
new L1 module, not an adapter change.
|
|
||||||
**Idea:** Author `modules/l1/dynamodb/` (interface.json +
|
|
||||||
terraform/main.tf + README.md + instance.json + registry.json entry).
|
|
||||||
The single platform-side module build-out for the milestone. Follows
|
|
||||||
the `s3`/`rds` primitive template. Encryption + PITR enabled per v1.8
|
|
||||||
NFR defaults.
|
|
||||||
**Accepted into:** REQ-322. Phase P3.
|
|
||||||
|
|
||||||
### I8 — Stale `adapters/README.md` TYPE_MAP references ❌ DEFERRED (scope)
|
|
||||||
|
|
||||||
**Category:** improvement
|
|
||||||
**Confidence:** 0.70 (above threshold, but scoped into REQ-321)
|
|
||||||
**Pattern:** stale doc → corrected doc.
|
|
||||||
**Source:** `adapters/README.md:49-54` references the deleted
|
|
||||||
`TYPE_MAP`/`INPUT_MAP`/`OUTPUT_MAP` — contradicts `adapter.py:1-11` +
|
|
||||||
`modules/STANDARDS.md:212-214`.
|
|
||||||
**Idea:** Fix the stale references as part of the docs phase.
|
|
||||||
**Reason deferred as a standalone idea:** Already captured in REQ-321
|
|
||||||
(docs + adapter README). No new requirement needed — the fix lands in
|
|
||||||
P4 docs.
|
|
||||||
|
|
||||||
## Tier 3 — Cross-project (deferred — multi-project, but cross-project sharing disabled)
|
|
||||||
|
|
||||||
### I9 — Cross-project policy sharing ❌ DEFERRED (config)
|
|
||||||
|
|
||||||
**Category:** improvement
|
|
||||||
**Confidence:** N/A
|
|
||||||
**Pattern:** policies shared across projects in a multi-project org.
|
|
||||||
**Source:** `config.json ideation.cross_project.enabled: false`.
|
|
||||||
**Idea:** In a multi-project org, kyverno-json policies could be shared
|
|
||||||
across projects (a tagging standard policy applies to all projects).
|
|
||||||
**Reason deferred:** `cross_project.enabled: false`. Even though
|
|
||||||
v1.26 is multi-project (acdl + nova-blockchain-exchange),
|
|
||||||
cross-project *ideation* is disabled in config. Recorded for when the
|
|
||||||
org grows + the flag is enabled.
|
|
||||||
|
|
||||||
### I10 — Consumer-repo CI scaffolding as a reusable template ❌ DEFERRED
|
|
||||||
|
|
||||||
**Category:** improvement
|
|
||||||
**Confidence:** 0.55 (below threshold — deferred, not rejected)
|
|
||||||
**Pattern:** one-off CI → reusable template.
|
|
||||||
**Source:** The consumer repo (`nova-blockchain-exchange`) needs its
|
|
||||||
own CI (`ci.yml` — lint + pytest). If Nova expects many consumers, a
|
|
||||||
reusable consumer-CI template would reduce onboarding friction.
|
|
||||||
**Idea:** A `nova-consumer-template` repo (or a
|
|
||||||
`.github/workflow-templates/` dir) that new consumers instantiate.
|
|
||||||
**Reason deferred:** Nova has 1 consumer today (the pilot). A template
|
|
||||||
is premature abstraction until the 2nd consumer arrives. The pilot's
|
|
||||||
CI is authored directly (REQ-310..312 tests). Recorded for when the
|
|
||||||
3rd consumer onboards.
|
|
||||||
|
|
||||||
## Summary
|
|
||||||
|
|
||||||
- 7 ideas accepted (I1..I7) → already captured as REQ-315, REQ-316,
|
|
||||||
REQ-317, REQ-318, REQ-319, REQ-320, REQ-322.
|
|
||||||
- 3 ideas deferred (I8 scoped into REQ-321; I9 config-disabled; I10
|
|
||||||
below threshold) with documented blocking reasons.
|
|
||||||
- 0 ideas rejected (below-threshold ideas are deferred, not rejected —
|
|
||||||
they may activate when their blockers lift).
|
|
||||||
- The accepted ideas are the **quality improvement** the `--ideate` flag
|
|
||||||
drives: I1 + I2 ground the Post-Pilot metrics (outcome backfill +
|
|
||||||
escalation reason); I3 closes the env-JSON wiring gap; I4 + I5 extend
|
|
||||||
v1.25's policy engine to the pilot domain (pilot-readiness +
|
|
||||||
settlement-finality); I6 gates the pilot as a maintained capability;
|
|
||||||
I7 is the single platform-side module build-out.
|
|
||||||
- No new requirements added beyond REQ-310..322 (the accepted ideas are
|
|
||||||
already scoped into the existing requirements). The IDEATE pass
|
|
||||||
validated the requirement set rather than expanding it — the ideas
|
|
||||||
were anticipated in the SPECIFY + RESEARCH stages.
|
|
||||||
@@ -1,256 +0,0 @@
|
|||||||
# NORTH_STAR — Nova
|
|
||||||
|
|
||||||
> **Status:** Draft (pending interactive GRILL → final)
|
|
||||||
> **Milestone:** v1.21 — Nova Deck Refinement & Pipeline Hardening
|
|
||||||
> **Owner:** Product Owner
|
|
||||||
> **Purpose:** Durable strategic intent. Read by CIAgent in every future
|
|
||||||
> `/ci-run` so the platform's direction survives across milestones. This
|
|
||||||
> is NOT a status document (that's PROJECT.md) and NOT an engineering
|
|
||||||
> architecture (that's the telemetry reference in RESEARCH.md/
|
|
||||||
> ARCHITECTURE.md). It is the PO's committed direction: what we're
|
|
||||||
> building toward, what we refuse to build, and how we'll know we won.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Vision
|
|
||||||
|
|
||||||
> **Infrastructure operations become visible. Every environment
|
|
||||||
> provisioned, every incident healed, every risk remediated — by an
|
|
||||||
> autonomous system whose trustworthiness is provable, not promised.
|
|
||||||
> Human attestation remains required at stage gates — QA signs off for
|
|
||||||
> production, SRE greenlights based on operational readiness — but the
|
|
||||||
> operator is never in the loop of normal operations.**
|
|
||||||
|
|
||||||
Nova is the autonomous infrastructure layer that lets product teams ship
|
|
||||||
without engaging an operator, and lets executives trust the platform not
|
|
||||||
because it never fails but because every decision is captured, scored,
|
|
||||||
and accountable. The recurring theme across the platform is that
|
|
||||||
**infrastructure operations become visible** — security posture,
|
|
||||||
remediation velocity, reliability, and lead time are surfaced as
|
|
||||||
queryable signals rather than hidden in tribal knowledge.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Strategic Objectives (4)
|
|
||||||
|
|
||||||
**1. Demonstrate production-grade zero-touch operations.**
|
|
||||||
Nova must run real customer estates with no human in the loop of normal
|
|
||||||
operations — autonomy as the default, not the demo. Stage-gate
|
|
||||||
attestation (QA for production, SRE for operational readiness) remains
|
|
||||||
human by design; operational escalations (AI confidence too low to
|
|
||||||
proceed) are the failure mode we drive toward zero. Everything else
|
|
||||||
collapses if autonomy isn't real.
|
|
||||||
|
|
||||||
**2. Establish provable trust in automated decisions.**
|
|
||||||
Trust is established by deterministic scripts that calculate a score and
|
|
||||||
a band outcome that gates the action — the platform functions without AI.
|
|
||||||
"AI decisions" are really automated decisions. The audit substrate —
|
|
||||||
Decision Ledger, confidence scoring, circuit breakers, blast-radius
|
|
||||||
controls — turns "autonomous" from a marketing claim into a defensible
|
|
||||||
one. Trust is the moat. Features can be copied; an immutable, queryable
|
|
||||||
decision history cannot.
|
|
||||||
|
|
||||||
**3. Deliver compounding, quantifiable ROI for customers.**
|
|
||||||
Each quarter on Nova must show measurable improvement on four CTO-grade
|
|
||||||
metrics, all of which flow into PowerBI views and are captured by the
|
|
||||||
telemetry pipeline:
|
|
||||||
|
|
||||||
- **Lead Time** — from PR merge to production deployment (downward trend).
|
|
||||||
- **Infrastructure Vulnerability Count** — open findings on deployed
|
|
||||||
resources (downward trend, demonstrating that proactive scanning +
|
|
||||||
remediation keeps up with the AI-era 0-day pace).
|
|
||||||
- **MTTR** — for platform-detected and platform-remediated incidents.
|
|
||||||
- **Cloud Spend Reduction** — on pilot estates vs. the pre-Nova
|
|
||||||
baseline.
|
|
||||||
|
|
||||||
If leadership cannot point to a number that improves quarter-over-quarter
|
|
||||||
on these four axes, Nova fails its commercial test, regardless of how
|
|
||||||
clever the automation is.
|
|
||||||
|
|
||||||
**4. Integrate with externally owned development platforms — regardless of source.**
|
|
||||||
Nova integrates with externally owned PDLC, SDLC, Agentic, and Citizen
|
|
||||||
Developer platforms with no regard for the source of the intent. Nova
|
|
||||||
provides a set of skills and MCP endpoints that help the developer or AI
|
|
||||||
agent make their application production-grade. Regardless of the source,
|
|
||||||
all intents to deploy to production go through the same rigorous
|
|
||||||
controls, quality gates, attestation, and evidence stream. Nova is the
|
|
||||||
layer any of those platforms reach for first when an agent needs to
|
|
||||||
deploy — not a vendor arriving late to that market.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Anti-Goals (4 — what Nova is fundamentally NOT)
|
|
||||||
|
|
||||||
1. **Not a general-purpose AI agent platform.** We are purpose-built for
|
|
||||||
infrastructure operations. Breadth here produces shallow tools; depth
|
|
||||||
here wins the category.
|
|
||||||
2. **Not a system that removes humans from accountability.** Only from
|
|
||||||
normal operations. Every automated decision lands in an immutable
|
|
||||||
ledger. Every stage-gate promotion (qa/prod/dr) requires a human
|
|
||||||
attestation recorded with approver identity, separation-of-duties
|
|
||||||
check, and the evidence matrix. The absence of an operator in the
|
|
||||||
loop is never the absence of a record.
|
|
||||||
3. **Not an upstream development platform.** Nova does not own the
|
|
||||||
product backlog, IDE workflows, code authorship, or application
|
|
||||||
business logic. The PDLC is upstream; Nova integrates with it through
|
|
||||||
a validated contract boundary — Nova never reaches into it.
|
|
||||||
4. **Not a replacement for the Product Development Lifecycle (PDLC).**
|
|
||||||
Nova governs infrastructure + delivery only. Product lifecycle
|
|
||||||
decisions (what to build, when to ship, for whom) remain with the
|
|
||||||
product team. Nova makes their intent production-grade; it does not
|
|
||||||
own the intent.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Non-Goals (v1.17 milestone scope — deferred work, not permanent boundaries)
|
|
||||||
|
|
||||||
> Anti-Goals are what Nova *fundamentally is not*. Non-Goals are what we
|
|
||||||
> *will not do this milestone* — deferred work, not permanent boundaries.
|
|
||||||
> Each Non-Goal cites the controlling decision ID.
|
|
||||||
|
|
||||||
1. **Live AWS re-provisioning** (deferred — D-096). Metrics that require
|
|
||||||
live infrastructure ship as placeholder PowerBI views with documented
|
|
||||||
schemas.
|
|
||||||
2. **Onboarding auto-grant** (deferred — D-113/D-114/D-119). Only the
|
|
||||||
request-path metric is grounded; the requested→granted funnel is a
|
|
||||||
placeholder.
|
|
||||||
3. **ML anomaly-forecasting / predictive remediation** (no emitter today).
|
|
||||||
The Predictive-vs-Reactive metric ships as a placeholder.
|
|
||||||
4. **Drift detection scheduled job** (deferred — D-096 + no scheduler).
|
|
||||||
Drift metrics ship as placeholders.
|
|
||||||
5. **Live cost CUR reconciliation** (deferred — D-096). Pre-apply Infracost
|
|
||||||
estimates are grounded; actual-spend reconciliation is a placeholder.
|
|
||||||
6. **S3 Object Lock / JWS tamper-evident ledger** (deferred — D-083). The
|
|
||||||
Decision Ledger uses a local SQLite hash-chain this milestone; the
|
|
||||||
Object-Lock/JWS build-out is a future milestone.
|
|
||||||
7. **Multi-cloud support** (Azure/GCP/K8s). Nova is AWS-only this milestone.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 12–18 Month Targets
|
|
||||||
|
|
||||||
Targets are committed, not aspirational. Each is a number a board member
|
|
||||||
can repeat back to us. The grounding column records whether the metric is
|
|
||||||
measurable this milestone, and if not, what blocks it.
|
|
||||||
|
|
||||||
> **Honesty note (GRILL G-Q6 binding):** Nova has 0 consumer adoption
|
|
||||||
> today (`PROJECT.md:495`). Three targets (Touchless Resolution, Human
|
|
||||||
> Escalation, AI Decision Accuracy) are scoped "across production
|
|
||||||
> estates" — the measurement *pipeline* is grounded this milestone, but
|
|
||||||
> the *denominator* is zero until a pilot estate activates. These
|
|
||||||
> targets are reclassified as **Post-Pilot** (the pipeline works; the
|
|
||||||
> numbers fill when consumers exist). This is the same honesty model as
|
|
||||||
> Cloud Spend Reduction (partial: pipeline grounded, actuals deferred).
|
|
||||||
|
|
||||||
### Current-milestone targets (grounded or derived this milestone)
|
|
||||||
|
|
||||||
| Domain | Target | Grounding (v1.17) | Note |
|
|
||||||
|---|---|---|---|
|
|
||||||
| **MTTR (p95)** | < 60 seconds | grounded (platform-run MTTR) | apply.failed → successful retry; infra-incident MTTR deferred (no incident detection) |
|
|
||||||
| **Cloud Spend Reduction** | ≥ 25% on pilot estates vs. 12-month pre-Nova baseline | partial | pre-apply estimate grounded (Infracost); actual-spend deferred (D-096 CUR) |
|
|
||||||
| **L1 / L2 Ops Hours Avoided** | ≥ 70% of pre-Nova FTE allocation | derived | formula over run count × manual baseline (computed on N internal runs; production-denominator activates post-pilot) |
|
|
||||||
| **Platform ROI** | ≥ 250% measured annually | derived | formula (labor savings + cloud savings + avoided downtime) ÷ platform op cost (computed on N internal runs; production-denominator activates post-pilot) |
|
|
||||||
| **Decision Ledger Coverage** | 100% of AI actions with backfilled outcome | grounded (this milestone builds it) | outbox_writer.py → SQLite hash-chain |
|
|
||||||
| **Attestation Coverage** | 100% of prod/dr promotions attested by a human | grounded | hitl_gates.py + outbox approver_* attributes; separation-of-duties on prod |
|
|
||||||
|
|
||||||
### Post-Pilot targets (pipeline grounded this milestone; denominator activates when a pilot estate runs)
|
|
||||||
|
|
||||||
| Domain | Target | Grounding (v1.17) | Note |
|
|
||||||
|---|---|---|---|
|
|
||||||
| **Touchless Resolution Rate** | ≥ 99% across production estates | partial (pipeline grounded; denominator = 0 today) | runs completing without *operational* HITL block ÷ total runs (attestation gates excluded); activates post-pilot |
|
|
||||||
| **Human Escalation Frequency** | < 0.1% of platform actions | partial (pipeline grounded; denominator = 0 today) | *operational* HITL blocks only (confidence-driven); attestation sign-offs excluded; activates post-pilot |
|
|
||||||
| **AI Decision Accuracy** | ≥ 99.5% (no rollback, no follow-up incident within 5 min of action) | partial (pipeline grounded; denominator = 0 today) | decisions not followed by apply.failed/incident within 5min; activates post-pilot |
|
|
||||||
|
|
||||||
### Deferred targets (measurement requires future systems)
|
|
||||||
|
|
||||||
| Domain | Target | Grounding (v1.17) | Note |
|
|
||||||
|---|---|---|---|
|
|
||||||
| **Predictive vs. Reactive Ratio** | ≥ 3 : 1 (prevention dominates reaction) | deferred | requires ML forecasting service (future emitter) |
|
|
||||||
| **Drift Auto-Reversal Rate** | ≥ 95% within one detection cycle | deferred | requires drift detection (D-096 + scheduler) |
|
|
||||||
|
|
||||||
> Committed targets whose measurement is deferred remain committed — the
|
|
||||||
> target is the destination; the metric is the odometer, and some
|
|
||||||
> odometers aren't built yet. Each deferred metric ships as a placeholder
|
|
||||||
> PowerBI view + a definition-of-success doc recording the dependency.
|
|
||||||
> Post-Pilot targets are committed targets whose measurement pipeline is
|
|
||||||
> grounded this milestone; the numbers activate when a pilot estate runs.
|
|
||||||
|
|
||||||
### Future Horizons (strategic direction, not committed targets)
|
|
||||||
|
|
||||||
| Domain | Aspiration | Note |
|
|
||||||
|---|---|---|
|
|
||||||
| **AI-Agent Intent Share** | ≥ 40% of total intent volume originated by non-human consumers | Strategic Objective #4 direction. No backing requirement, no placeholder view, no emitter today. Moves to a committed target when agentic consumption is real. |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Success Criteria (v1.17 — what constitutes success for THIS milestone)
|
|
||||||
|
|
||||||
> Distinct from the 12–18mo targets: those are the destination. These are
|
|
||||||
> the milestone's exit criteria.
|
|
||||||
|
|
||||||
v1.17 is a success if:
|
|
||||||
|
|
||||||
1. **Decision Ledger emits `ai.decision.made` for 100% of platform runs**
|
|
||||||
with outcome backfill, AND **`attestation.recorded` events for 100%
|
|
||||||
of qa/prod/dr promotions** (event completeness — all 3 gates captured;
|
|
||||||
grounded in `outbox_writer.py` → SQLite hash-chain; honors D-083).
|
|
||||||
The **Attestation Coverage metric** (target 100%) measures prod/dr
|
|
||||||
promotions specifically — see REQ-194.
|
|
||||||
2. **`docs/METRICS.md` catalogs every executive KPI** with a `grounded` /
|
|
||||||
`derived` / `deferred` status, a source file or decision ID, and a
|
|
||||||
per-KPI definition-of-success doc in `docs/metrics/`.
|
|
||||||
3. **The PowerBI export produces all fact/dimension views** + 8 empty
|
|
||||||
placeholder views for deferred metrics (with documented schemas ready
|
|
||||||
to fill when their blocking decisions lift).
|
|
||||||
4. **The unified narrative deck ships** with the x3 arc
|
|
||||||
(Problem→Vision→How→Proof→Roadmap) at deck + slide level, per-slide
|
|
||||||
benefit callouts, and fluid transitions; both old decks retired.
|
|
||||||
5. **`NORTH_STAR.md` is wired into CIAgent context-loading** so every
|
|
||||||
future `/ci-run` reads it.
|
|
||||||
6. **CAP-023 (metrics collector) + CAP-024 (deck structure) pass** in the
|
|
||||||
regression gate.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## What "won" looks like
|
|
||||||
|
|
||||||
By month 18, Nova is the layer enterprise leadership points to when they
|
|
||||||
say *"we don't have an infrastructure ops team anymore, and the audit
|
|
||||||
trail is stronger than it ever was"* — and it is the default substrate
|
|
||||||
their AI engineering teams reach for first when an agent needs to deploy.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Relationship to v1.17 engineering
|
|
||||||
|
|
||||||
- **Pillar A (this file):** strategic direction — durable, PO-authored.
|
|
||||||
- **Pillar B (engineering):** the telemetry reference architecture
|
|
||||||
(adapted from the PO's technical-direction input) lives in
|
|
||||||
RESEARCH.md/ARCHITECTURE.md. It is the *how*; this file is the *why*.
|
|
||||||
- **Pillar C (story):** the unified narrative deck proves Pillars A+B to
|
|
||||||
leadership. The deck's Proof section cites grounded metrics; its
|
|
||||||
Roadmap section cites deferred targets honestly.
|
|
||||||
|
|
||||||
## Relationship to engineering files (v1.27 update)
|
|
||||||
|
|
||||||
- **NORTH_STAR.md** (this file) = the *why* — PO-authored strategic
|
|
||||||
direction, loaded every ci-run via `config.strategic_direction_file`.
|
|
||||||
- **STATE.md** = the *what exists* — PO-owned capability catalog,
|
|
||||||
additive, updated at every milestone ship (P-final Wave 3). The PO
|
|
||||||
reads STATE.md before writing new REQ-NNN specs to avoid re-spec'ing
|
|
||||||
existing capability and to respect the invariants.
|
|
||||||
- **ARCHITECTURE.md** = the *how* — the durable target architecture.
|
|
||||||
- **CHECKPOINT.json** = the *now* — authoritative live phase/ship
|
|
||||||
state.
|
|
||||||
|
|
||||||
## v1.25 update — swappable policy-engine substrate
|
|
||||||
|
|
||||||
Strategic Objective #2 (provable trust) gained a concrete substrate in
|
|
||||||
v1.25: the policy engine that produces the `PolicyCheckResult` records
|
|
||||||
feeding the confidence signal is now **swappable** via the
|
|
||||||
`PolicyEngine` protocol (`core/policy_engine.py`). `kyverno-json` is
|
|
||||||
the v1.25 default; `OPA` (or any other engine) can replace it by
|
|
||||||
implementing the same 3-method protocol — without touching the
|
|
||||||
confidence signal, the PCR schema, or the pipeline. See
|
|
||||||
ARCHITECTURE.md §12.7. The trust moat is a *replaceable* engine, not a
|
|
||||||
vendor lock-in.
|
|
||||||
+133
-101
@@ -1,119 +1,151 @@
|
|||||||
---
|
---
|
||||||
project: acdl
|
project: acdl
|
||||||
milestone: v1.28
|
milestone: v1.14
|
||||||
generated_at: 2026-08-19
|
generated_at: 2026-07-29
|
||||||
generator: lead-developer
|
generator: lead-developer
|
||||||
verification_toolchain:
|
verification_toolchain:
|
||||||
typecheck: "python3 -m py_compile core/mode_resolver.py nova/cli.py 2>&1 | head -5 || true"
|
typecheck: "terraform validate && python3 -m py_compile core/**/*.py && python3 -m jsonschema schemas/*.schema.json"
|
||||||
test: "pytest tests/test_mode_resolver.py tests/test_cli_subcommands.py -q 2>&1 | tail -15 || true"
|
test: "bash scripts/run_primitive_plan.sh --check-only <primitive> # pipeline-driven (D-102); no per-module pytest"
|
||||||
lint: "ruff check nova/ core/lambda/nova_idp_*.py 2>/dev/null || true"
|
build: "terraform init && terraform plan"
|
||||||
note: |
|
note: |
|
||||||
v1.28 is a feature milestone (CLI Canonicalization + Identity Layer).
|
ACDL has no package.json. The execute/verify/ship workflows substitute
|
||||||
Four active personas: backend-engineer (Lambda/DynamoDB/KMS/CodeArtifact),
|
`terraform validate` + `python -m py_compile` + JSON Schema validation
|
||||||
security-engineer (Argon2id/KMS/ABAC/threat model), cli-engineer
|
for npm run typecheck, a per-phase verify script (or the
|
||||||
(subcommand surface/mode_resolver/argparse/CAP-034), lead-developer
|
modules-lifecycle pipeline cell) for npm test, and `terraform init` +
|
||||||
(plan/review/ship/capability gate). frontend-engineer + data-engineer
|
`terraform plan` for npm run build. v1.11 testing is pipeline-driven
|
||||||
deactivated (no UI, no data pipelines). The kj-binary-in-Lambda-layer
|
(D-102): the modules-lifecycle pipeline matrix-runs each L1 module's
|
||||||
risk (D-227, RESEARCH §7) is the highest-risk item; P2 spike confirms.
|
examples/{simple,complex}.yml contracts through apply→modify→destroy
|
||||||
|
against live AWS. No per-module Python/pytest. This override is
|
||||||
|
documented here as the single source of truth; the ci-* agents read
|
||||||
|
PERSONAS.md before running verification commands.
|
||||||
|
v1.14 note: NFR-only milestone (bug fixes, security, tests, docs).
|
||||||
|
Roster carries forward from v1.11 unchanged. frontend-engineer stays
|
||||||
|
inactive (no frontend; decks are markdown = lead-developer
|
||||||
|
territory). No custom personas needed (no new domains).
|
||||||
---
|
---
|
||||||
|
|
||||||
# Personas — v1.28 CLI Canonicalization + Identity Layer
|
# ACDL — Persona Roster (project-level, v1.11 RESTART)
|
||||||
|
|
||||||
## Roster
|
> v1.11 is a restart (D-097). The v1.9 roster is superseded. Three
|
||||||
|
> structural corrections: (1) stateless adapter (D-098), (2) terraform
|
||||||
|
> owns lifecycle (D-101), (3) pipeline-driven testing (D-102). The roster
|
||||||
|
> is simplified to the three active domains: data (terraform foundation),
|
||||||
|
> backend (adapter/resolver), general (pipelines/workflows).
|
||||||
|
|
||||||
### backend-engineer
|
## Active personas
|
||||||
```yaml
|
|
||||||
active: true
|
|
||||||
domain: "Lambda functions, DynamoDB, KMS integration, dual-use packaging, CodeArtifact publish, CloudFormation generation"
|
|
||||||
frameworks: ["Python 3.12", "boto3", "argparse", "pytest", "moto[dynamodb]", "CloudFormation"]
|
|
||||||
constraints: ["INV-15", "INV-16", "INV-17", "D-228", "D-229", "D-230", "NFR-5", "NFR-6", "NFR-7", "NFR-8"]
|
|
||||||
territory:
|
|
||||||
- "core/lambda/**"
|
|
||||||
- "core/metrics/**"
|
|
||||||
- "core/env.py"
|
|
||||||
- "core/outbox_writer.py"
|
|
||||||
- "terraform/bootstrap/**"
|
|
||||||
- ".gitea/workflows/publish.yml"
|
|
||||||
- ".github/workflows/publish.yml"
|
|
||||||
- ".github/actions/nova-cli/**"
|
|
||||||
```
|
|
||||||
|
|
||||||
### security-engineer
|
|
||||||
```yaml
|
|
||||||
active: true
|
|
||||||
domain: "Argon2id hashing, KMS asymmetric signing (ECDSA P-256 / ES256), ABAC policy, JWKS exposure, PAT lifecycle, threat model, DER→raw ECDSA conversion"
|
|
||||||
frameworks: ["argon2-cffi", "cryptography", "pyjwt", "kyverno-json", "JMESPath", "KMS Sign/Verify/GetPublicKey"]
|
|
||||||
constraints: ["INV-15", "INV-16", "INV-17", "NFR-5", "NFR-8", "NFR-9", "D-227", "D-231"]
|
|
||||||
territory:
|
|
||||||
- "platform/abac/**"
|
|
||||||
- "core/policy_engine.py"
|
|
||||||
- "adapters/kyverno-json/**"
|
|
||||||
- "core/lambda/nova_idp_auth.py"
|
|
||||||
- "core/lambda/nova_idp_token_vend.py"
|
|
||||||
- "core/lambda/nova_idp_jwks.py"
|
|
||||||
- "docs/threat-model.md"
|
|
||||||
```
|
|
||||||
|
|
||||||
### cli-engineer
|
|
||||||
```yaml
|
|
||||||
active: true
|
|
||||||
domain: "CLI subcommand surface, mode_resolver, argparse, [project.scripts] entry-point, CAP-034 AST scan, nova auth/idp subgroups, property tests"
|
|
||||||
frameworks: ["Python 3.12", "argparse", "setuptools [project.scripts]", "hypothesis", "pkgutil"]
|
|
||||||
constraints: ["INV-12", "INV-13", "INV-14", "D-226", "NFR-1", "NFR-2", "NFR-3"]
|
|
||||||
territory:
|
|
||||||
- "nova/**"
|
|
||||||
- "core/mode_resolver.py"
|
|
||||||
- "pyproject.toml"
|
|
||||||
- "tests/test_mode_resolver.py"
|
|
||||||
- "tests/test_cli_subcommands.py"
|
|
||||||
```
|
|
||||||
|
|
||||||
### lead-developer
|
### lead-developer
|
||||||
```yaml
|
- **Domain:** coordination
|
||||||
active: true
|
- **Active:** true
|
||||||
domain: "Phase plan, persona roster, review gates, milestone ship, capability gate (CAP-033..038), ROADMAP/STATE/PROJECT wiring"
|
- **Phase-specific:** false
|
||||||
frameworks: ["git", "Gitea Actions", "semver tagging", ".ciagent/ discipline"]
|
- **Reason:** Owns CIAgent metadata, cross-phase verification scripts, the v1.11 phase orchestration (D-107: P56a + P56b split), and arbitrates persona conflicts. Resolves the milestone decomposition and the STANDARDS.md §8 rewrite (the adapter extension pattern is replaced by the per-module terraform subdir pattern).
|
||||||
constraints: ["INV-1..17 (cross-cutting)", "v1.28 hard constraints", "NFR-6", "NFR-11"]
|
|
||||||
territory:
|
|
||||||
- ".ciagent/**"
|
|
||||||
- "PLAN.md"
|
|
||||||
- "CHECKPOINT.json"
|
|
||||||
- "STATE.md"
|
|
||||||
- "REQUIREMENTS.md"
|
|
||||||
- "ROADMAP.md"
|
|
||||||
```
|
|
||||||
|
|
||||||
### frontend-engineer
|
### backend-engineer
|
||||||
```yaml
|
- **Domain:** backend
|
||||||
active: false
|
- **Active:** true
|
||||||
phase_specific: false
|
- **Phase-specific:** false
|
||||||
reason: "No UI in v1.28 (CLI + JSON endpoints only). JWKS serves application/json; no HTML/CSS/JS surface."
|
- **Reason:** Owns the adapter rewrite (D-098: stateless assembler — deletes TYPE_MAP/INPUT_MAP/OUTPUT_MAP + 39 type-specific branches, becomes a ~80-line assembler that emits `module "x" { source = "..." ... }` blocks) and the contract resolver env-aware state keys (D-106: `spike/{id}/{env}/terraform.tfstate`). The adapter holds no module content; the engine binding lives in the per-module `terraform/` subdir. Co-authoring expected on the adapter + `run_platform.sh` boundary (general adds `--apply`/`--destroy` modes that invoke the adapter).
|
||||||
```
|
- **Territory:** `adapters/terraform/adapter.py` (rewrite to stateless assembler), `core/contract_resolver.py` (env-aware state keys, deterministic composition), `schemas/stack.schema.json` (if the stack instance shape changes), `tests/test_adapter*.py` (regression baseline — the s3 instance.json round-trip must still pass).
|
||||||
|
|
||||||
### data-engineer
|
### data-engineer
|
||||||
```yaml
|
- **Domain:** data
|
||||||
active: false
|
- **Active:** true
|
||||||
phase_specific: false
|
- **Phase-specific:** false
|
||||||
reason: "No data pipelines / metrics / PowerBI work in v1.28. The metrics layer is v1.17-complete; v1.28 adds audit events but no new fact/dim tables."
|
- **Reason:** Reactivated for v1.11. Owns the heaviest territory: the per-module `terraform/` subdirs (D-098/D-099/D-100 — the engine binding) for all 12 L1 modules, plus the single platform VPC (D-105: `terraform/platform` owns ONE VPC; the microservice composition drops its `vpc` child and references the platform VPC via data source). Each L1 module ships a real terraform module dir (versions/variables/locals/main/outputs.tf) owning its resource shape, nested blocks, and defaults. `locals.tf` is used heavily to centralize default interpolation (D-099). Multi-resource modules get the full 5-file split; trivial single-resource modules may inline locals in main.tf. This is the binding constraint — the stateless adapter cannot be written until the reference s3 module exists (D-107: P56a proves the design with s3 first).
|
||||||
```
|
- **Territory:** `terraform/` (platform VPC, D-105), `modules/l1/*/terraform/` (per-module terraform subdirs — the engine binding), `modules/l1/*/interface.json` (defaults move from adapter to interface inputs), `modules/registry.json` (terraform_dir field), `modules/l2/microservice/composition.json` (drop the vpc child, D-105), `modules/STANDARDS.md` §8 (rewrite the adapter extension pattern → per-module terraform subdir pattern).
|
||||||
|
|
||||||
## Territory overlap notes
|
### general (lead-developer + backend-engineer pipeline work)
|
||||||
|
- **Domain:** coordination + pipelines
|
||||||
|
- **Active:** true
|
||||||
|
- **Phase-specific:** false
|
||||||
|
- **Reason:** Owns the pipeline-driven testing (D-102/D-103/D-104) and the terraform lifecycle modes (D-101). The 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. `run_platform.sh` gains `--apply` and `--destroy` modes; Python never runs terraform. `verify_deploy_microservice.py` is deleted (D-101). Co-authoring expected on the `run_platform.sh` boundary (backend-engineer rewrites the adapter that `run_platform.sh` invokes).
|
||||||
|
- **Territory:** `pipelines/modules-lifecycle.yml`, `.gitea/workflows/modules-lifecycle.yml` + `.github/workflows/modules-lifecycle.yml` (byte-identical, D-102), `scripts/run_platform.sh` (`--apply`/`--destroy` modes, D-101), `scripts/run_primitive_plan.sh` (if extended for lifecycle), `scripts/run_pattern_plan.sh` (if extended), `pipelines/README.md` (document the new pipeline), `schemas/deploy-pipeline.schema.json` (if the lifecycle stages are added to the contract).
|
||||||
|
|
||||||
- `core/lambda/contract_ingestor.py` (dual-use refactor, REQ-329) =
|
## Deactivated personas
|
||||||
backend-engineer territory. `core/lambda/nova_idp_auth.py` +
|
|
||||||
`nova_idp_token_vend.py` are **co-owned** by backend-engineer (Lambda
|
|
||||||
plumbing, DynamoDB, function URLs) + security-engineer (crypto, ABAC,
|
|
||||||
Argon2id logic inside).
|
|
||||||
- `core/mode_resolver.py` = cli-engineer. `core/policy_engine.py` =
|
|
||||||
security-engineer (the ABAC evaluation path).
|
|
||||||
- `nova/idp/setup.py` = cli-engineer (the subcommand + arg parsing) +
|
|
||||||
backend-engineer (the CloudFormation generation + deploy).
|
|
||||||
- `nova/auth/*` = cli-engineer (subcommands) + security-engineer (the
|
|
||||||
token exchange + credential storage logic).
|
|
||||||
|
|
||||||
## Phase-specific personas
|
### lambda-engineer (custom, v1.9 — deactivated for v1.11)
|
||||||
|
- **Domain:** serverless
|
||||||
|
- **Active:** false
|
||||||
|
- **Phase-specific:** false
|
||||||
|
- **Reason:** No per-module Python this milestone (D-102: testing is pipeline-driven, not pytest). The v1.9 Lambda (`core/lambda/contract_ingestor.py`) and the `terraform/platform/main.tf` Lambda/DynamoDB/KMS/Secrets definitions persist from v1.9 but are not touched in v1.11. The `acdl-sod-halt` SNS topic and the attestation matrix are out of scope. Removed from the roster for v1.11; reactivates if a future milestone touches the Lambda.
|
||||||
|
|
||||||
None. All four active personas span the full milestone. The
|
### platform-engineer (custom, v1.9 — folded into data-engineer for v1.11)
|
||||||
security-engineer is heaviest in P2 (identity layer) + P3 (threat model);
|
- **Domain:** infra
|
||||||
the cli-engineer is heaviest in P1 (CLI substrate); the backend-engineer
|
- **Active:** false
|
||||||
spans P1 (CodeArtifact/layer) + P2 (Lambdas/DynamoDB).
|
- **Phase-specific:** false
|
||||||
|
- **Reason:** The v1.11 scope (D-097..D-107) is terraform module authoring + adapter rewrite + pipelines — not the v1.9-era L1/L2 IR-typed module authoring or the AWS OIDC bootstrap. The platform-engineer's v1.9 territory (`adapters/terraform/**`, `modules/**`, `terraform/**`) is split: the adapter goes to backend-engineer (rewrite), the per-module terraform subdirs + platform VPC go to data-engineer (the heaviest v1.11 work). Folded into data-engineer for v1.11; reactivates if a future milestone does IR-shaped module authoring or OIDC bootstrap work.
|
||||||
|
|
||||||
|
### security-engineer (custom, v1.9 — deactivated for v1.11)
|
||||||
|
- **Domain:** security
|
||||||
|
- **Active:** false
|
||||||
|
- **Phase-specific:** false
|
||||||
|
- **Reason:** The v1.11 scope does not touch Wiz/Kyverno/Checkov adapters, the HITL matrix, separation-of-duties, or the audit ledger. The security-engineer's v1.9 territory persists but is not touched. Removed from the roster for v1.11; reactivates if a future milestone touches security adapters or HITL gates.
|
||||||
|
|
||||||
|
### frontend-engineer
|
||||||
|
- **Domain:** frontend
|
||||||
|
- **Active:** false
|
||||||
|
- **Phase-specific:** false
|
||||||
|
- **Reason:** The evidence timeline UI (`evidence-ui/**`) is unchanged from v1.0 and not touched in v1.11. Removed from the active roster; reactivates if a future milestone touches the timeline UI.
|
||||||
|
|
||||||
|
### data-engineer (v1.9 — was deactivated, reactivated for v1.11)
|
||||||
|
- **Domain:** data
|
||||||
|
- **Active:** true (reactivated)
|
||||||
|
- **Phase-specific:** false
|
||||||
|
- **Reason:** See the active `data-engineer` entry above. The v1.9 deactivation rationale ("No ORM/persistence framework") no longer applies — v1.11's data-engineer owns terraform module authoring, not a data persistence layer.
|
||||||
|
|
||||||
|
### infra-stub-engineer (custom, v1.0 only)
|
||||||
|
- **Domain:** backend
|
||||||
|
- **Active:** false
|
||||||
|
- **Reason:** Owned L1 stub modules in the v1.0 demo. The demo is archived to `demo/`; real L1 modules are owned by data-engineer (v1.11). Not reactivated.
|
||||||
|
|
||||||
|
## Phase-specific overrides
|
||||||
|
|
||||||
|
| Phase | Personas active | Notes |
|
||||||
|
|-------|------------------|-------|
|
||||||
|
| 56a adapter-rewrite-and-s3-reference-module | data-engineer (lead: s3 reference terraform module — proves the design), backend-engineer (lead: stateless adapter rewrite — emits module blocks for s3), general (run_platform.sh --apply/--destroy skeleton) | security/lambda/frontend idle |
|
||||||
|
| 56b remaining-11-l1-module-terraform-subdirs | data-engineer (lead: author 11 L1 module terraform subdirs — vpc, ecs-cluster, ecs-service, iam-role, alb, ecr, cloudfront, waf, rds, kms-key, uptime), backend-engineer (adapter: confirm each module round-trips through the assembler), general (modules-lifecycle pipeline wiring) | security/lambda/frontend idle |
|
||||||
|
| (modules-lifecycle pipeline) | general (lead: byte-identical Gitea+GitHub workflow + matrix apply→modify→destroy), data-engineer (examples/{simple,complex}.yml contracts as the modify variants), backend-engineer (adapter confirms the lifecycle cells resolve) | security/lambda/frontend idle |
|
||||||
|
| (platform VPC + composition drop) | data-engineer (lead: terraform/platform VPC + microservice composition drops vpc child, D-105), backend-engineer (resolver: env-aware state keys, D-106) | general/security/lambda/frontend idle |
|
||||||
|
| verify | lead-developer (lead: 4-layer verification), all active personas (review their territory) | — |
|
||||||
|
| review-audit-complete | lead-developer (lead: review + audit + milestone completion), all active personas (review participation) | — |
|
||||||
|
|
||||||
|
## Domain priority (used by TaskDecomposer)
|
||||||
|
|
||||||
|
`data → backend → general`
|
||||||
|
|
||||||
|
Rationale: in v1.11, the terraform foundation (per-module `terraform/`
|
||||||
|
subdirs + platform VPC) is the binding constraint — the stateless adapter
|
||||||
|
cannot be written until the reference s3 module exists (D-107: P56a
|
||||||
|
proves the design with s3 first). Backend (adapter/resolver) follows once
|
||||||
|
the module shape is proven. General (pipelines/workflows) wires the
|
||||||
|
lifecycle modes last, once the adapter + modules produce valid terraform.
|
||||||
|
|
||||||
|
## Conflict resolutions (lead-developer arbitration)
|
||||||
|
|
||||||
|
- `backend-engineer` vs `data-engineer` over `modules/l1/*/interface.json`:
|
||||||
|
data-engineer owns the interface defaults (defaults move from the
|
||||||
|
adapter to the interface inputs, D-100); backend-engineer owns the
|
||||||
|
adapter that reads them. Co-authoring is expected; conflict goes to
|
||||||
|
lead-developer.
|
||||||
|
- `backend-engineer` vs `general` over `scripts/run_platform.sh`:
|
||||||
|
backend-engineer rewrites the adapter that `run_platform.sh` invokes;
|
||||||
|
general adds the `--apply`/`--destroy` modes. The interface (the CLI
|
||||||
|
flags + the adapter invocation) is co-authored; conflicts go to
|
||||||
|
lead-developer.
|
||||||
|
- `data-engineer` vs `general` over `modules/l1/*/examples/`:
|
||||||
|
data-engineer owns the example contracts (the modify variants,
|
||||||
|
D-103); general owns the pipeline that matrix-runs them. Co-authoring
|
||||||
|
is expected; conflicts go to lead-developer.
|
||||||
|
- `lead-developer` vs any: lead-developer owns `.ciagent/**` + `docs/**`
|
||||||
|
meta + verification scripts + `modules/STANDARDS.md` §8 rewrite; persona
|
||||||
|
engineers do not edit CIAgent metadata or the vision/architecture
|
||||||
|
source docs.
|
||||||
|
|
||||||
|
## Territory enforcement mode
|
||||||
|
|
||||||
|
`warn` — config.json has no `personas.territory_enforcement` field, so the
|
||||||
|
default per execute.md is `warn`. Cross-territory edits are logged in the
|
||||||
|
commit message but do not fail the task. v1.11's scope means co-authoring
|
||||||
|
across territories is likely (e.g. backend + general on the adapter +
|
||||||
|
`run_platform.sh` boundary; data + general on the examples + pipeline
|
||||||
|
boundary); `warn` keeps it frictionless.
|
||||||
+360
-456
@@ -1,489 +1,393 @@
|
|||||||
# PLAN — v1.28 CLI Canonicalization + Identity Layer
|
---
|
||||||
|
phase: P0
|
||||||
|
name: pre-execution
|
||||||
|
milestone: v1.14
|
||||||
|
requirements: [REQ-135, REQ-136, REQ-137, REQ-138, REQ-139, REQ-140, REQ-141, REQ-142, REQ-143, REQ-144, REQ-145, REQ-146, REQ-147, REQ-148, REQ-149, REQ-150, REQ-151, REQ-152, REQ-153, REQ-154]
|
||||||
|
wave: 0
|
||||||
|
depends_on: []
|
||||||
|
---
|
||||||
|
|
||||||
> **Milestone:** v1.28 (feature — CLI substrate + Nova-idp identity
|
# v1.14 — NFR Refinement Plan (20 execution phases + 1 final)
|
||||||
> layer). Tags on the **v1.27.x** line: `v1.27.0` (P0) →
|
|
||||||
> `v1.27.1..v1.27.6` (P1..P6) → `v1.27.7` (P7 final = milestone
|
|
||||||
> release). The final phase's patch IS the milestone release.
|
|
||||||
> **Branch:** `milestone/v1.28-cli-identity`. Phase branches:
|
|
||||||
> `phase/00-pre-execution`, `phase/01-cli-substrate`,
|
|
||||||
> `phase/02-lambda-packaging`, `phase/03-idp-auth`,
|
|
||||||
> `phase/04-token-vend-pat`, `phase/05-docs-integration`,
|
|
||||||
> `phase/06-final-review-ship`.
|
|
||||||
>
|
|
||||||
> **Tags:** `v1.27.0` (P0) → `v1.27.1..v1.27.5` (P1..P5) →
|
|
||||||
> `v1.27.6` (P6 final = milestone release). 6 execution phases
|
|
||||||
> (P5 idp-setup folded into P4 Wave 8 per grill C-2.1).
|
|
||||||
|
|
||||||
## Milestone goal
|
**Milestone:** v1.14 (NFR — bug fixes, security, stubs, tests, docs)
|
||||||
|
**Type:** NFR (all phases fix/test/docs/chore/refactor). Final patch IS
|
||||||
|
the release. Tags: `v1.13.3` (P0) → `v1.13.4..v1.13.23` (P1–P20) →
|
||||||
|
`v1.13.24` (P21 = milestone release).
|
||||||
|
**Branch:** `milestone/v1.14-refinement` → `phase/NN-<slug>`
|
||||||
|
|
||||||
The Nova CLI is installable from internal PyPI (CodeArtifact); every
|
## Wave ordering (D-098)
|
||||||
`core/` module is reachable as a `nova <subcommand>`; the CLI and
|
|
||||||
Lambda functions share a single `core/` source tree; and Nova owns its
|
|
||||||
identity layer end-to-end (Nova-idp: `nova-idp-auth` +
|
|
||||||
`nova-idp-token-vend` Lambdas, KMS-signed OIDC tokens, kyverno-json
|
|
||||||
ABAC token vending, PAT lifecycle). No AWS-managed identity services
|
|
||||||
in the path (INV-15).
|
|
||||||
|
|
||||||
## Requirements
|
- **Wave 1 (P1–P6):** bug fixes. P1→P2 sequential (composition depends
|
||||||
|
on dedup correctness); P3–P6 independent. **G-105: full regression
|
||||||
|
gate run after P4** (validates the hardened gate before W2).
|
||||||
|
- **Wave 2 (P7–P12):** security. P8→P9 sequential (IAM ARNs reference
|
||||||
|
externalized account ID); rest independent. **G-106: mid-milestone
|
||||||
|
regression-gate checkpoint after P12** (offline gate run; non-Verified
|
||||||
|
halts W3 until fixed).
|
||||||
|
- **Wave 3 (P13–P17):** stub/test/CI/hygiene. P15 depends on P7
|
||||||
|
(hardened errors before script tests); P17 depends on P14 (both touch
|
||||||
|
config.json); P13 independent.
|
||||||
|
- **Wave 4 (P18–P20):** standards/docs/VPC. P19 depends on P1–P18
|
||||||
|
(reflects all prior phases); P18 + P20 independent.
|
||||||
|
|
||||||
31 requirements: REQ-323..REQ-353 (full text in
|
## Execution approach
|
||||||
`.ciagent/REQUIREMENTS.md` §v1.28). 6 capabilities: CAP-033..CAP-038.
|
|
||||||
6 invariants: INV-12..INV-17. 6 decisions: D-226..D-231 (CLARIFY) +
|
|
||||||
RESEARCH amendments (D-228 fail-closed, D-229 strong-read-on-PK).
|
|
||||||
|
|
||||||
## Phase breakdown
|
Each phase: EXECUTE (persona-assigned task groups) → VERIFY (4 layers +
|
||||||
|
regression gate at milestone complete) → SHIP (patch tag). Phase
|
||||||
### Phase P1 — cli-substrate (REQ-323..REQ-328)
|
boundary checkpoint resets context. The execute workflow reads this
|
||||||
|
PLAN.md + ROADMAP.md §v1.14 + PERSONAS.md for task decomposition.
|
||||||
**Goal:** CodeArtifact wheel + Lambda layer pipeline; `nova/` CLI
|
|
||||||
package with a subcommand per `core/` module; `nova init`; `nova
|
|
||||||
cli-action` composite action; `core/mode_resolver.py`; audit emission
|
|
||||||
with `mode` + `selection_reason`. The CLI is installable and every
|
|
||||||
`core/` module is reachable.
|
|
||||||
|
|
||||||
**Exit criterion:** CAP-033 + CAP-034 + CAP-035 Verified + all REQ-323..328
|
|
||||||
tests pass. CodeArtifact provisioned (Wave 0 gate).
|
|
||||||
|
|
||||||
#### Wave 0 — CodeArtifact provisioning (backend-engineer) [C-3.2/C-8.1]
|
|
||||||
- **Task 0.1** (backend-engineer): provision CodeArtifact domain
|
|
||||||
(`nova`) + repository (`nova-pypi`) in `581513795199`. Verify
|
|
||||||
`codeartifact:*` IAM grant on `nova-spike-runner`. **Binary go/no-go
|
|
||||||
gate for Wave 4.** If fail: activate Gitea wheel index fallback
|
|
||||||
(CLARIFY assumption #1) and document in PLAN.md.
|
|
||||||
|
|
||||||
#### Wave 1 — pyproject + entry point (cli-engineer)
|
|
||||||
- **Task 1.1** (cli-engineer): `pyproject.toml` — add
|
|
||||||
`[project.scripts] nova = "nova.cli:main"`; add
|
|
||||||
`[tool.setuptools.packages.find]` including `nova`, `nova.*`, `core`,
|
|
||||||
`core.*`, `adapters.*`; bump `requires-python` to `>=3.12`; add
|
|
||||||
`argon2-cffi`, `cryptography`, `pyjwt`, `hypothesis` to deps/test-deps.
|
|
||||||
Verify `pip install -e .` produces a `nova` executable.
|
|
||||||
|
|
||||||
#### Wave 2 — CLI dispatch + subcommands (cli-engineer)
|
|
||||||
- **Task 2.1** (cli-engineer): `nova/__init__.py` + `nova/cli.py`
|
|
||||||
(~80 lines, auto-discovers `nova/<module>.py` via `pkgutil.iter_modules`,
|
|
||||||
dispatches, emits `cli.invocation` audit event stub with INV-12 fields).
|
|
||||||
- **Task 2.2** (cli-engineer): `nova/<module>.py` for each `core/`
|
|
||||||
module (≤50 lines, `add_parser` + `run` delegates to `core/`). Cover:
|
|
||||||
`resolve`, `decommission`, `env-transition`, `env-check`, `hitl`,
|
|
||||||
`onboard`, `outbox`, `publish-outputs`, `policy`, `regression`, `sod`,
|
|
||||||
`readiness`, `attestation-matrix`, `confidence`. Skip internal-only
|
|
||||||
(`env`, `local_emulators`, `output_publisher` if not user-facing).
|
|
||||||
- **Task 2.3** (cli-engineer): `nova/init.py` (REQ-325) — scaffolds
|
|
||||||
`.nova/`, `.nova/contract.yml.attestations/`, `.gitignore` (excludes
|
|
||||||
secrets, `~/.nova/credentials.json`).
|
|
||||||
|
|
||||||
#### Wave 3 — mode_resolver + audit (cli-engineer)
|
|
||||||
- **Task 3.1** (cli-engineer): `core/mode_resolver.py` —
|
|
||||||
`resolve_mode(flag, env_var, credential_type, stdin_isatty)` per D-226.
|
|
||||||
`sys.stdin.isatty()` is the TTY check (RESEARCH §11). Invalid env →
|
|
||||||
warn + fall through. Returns `(mode, selection_reason)`.
|
|
||||||
- **Task 3.2** (cli-engineer): wire `mode_resolver` into `nova/cli.py`
|
|
||||||
— resolve mode before dispatch, emit `cli.invocation` with `mode`,
|
|
||||||
`selection_reason`, `credential_type`, `command`, `args` (INV-12,
|
|
||||||
REQ-328).
|
|
||||||
- **Task 3.3** (cli-engineer): `tests/test_mode_resolver.py` —
|
|
||||||
`hypothesis` property tests (REQ-349): deterministic, flag-wins,
|
|
||||||
invalid-env-ignored, no-silent-fallback. Edge cases: TTY + piped
|
|
||||||
stdout, missing credential, conflicting flag/env, invalid env value.
|
|
||||||
|
|
||||||
#### Wave 4 — CodeArtifact + layer pipeline (backend-engineer)
|
|
||||||
- **Task 4.1** (backend-engineer): `.gitea/workflows/publish.yml` +
|
|
||||||
`.github/workflows/publish.yml` (byte-identical) — build wheel →
|
|
||||||
CodeArtifact `twine upload` → build layer (`pip install --target
|
|
||||||
layer/python/` + `argon2-cffi` + `cryptography` + `pyjwt`) →
|
|
||||||
`lambda publish-layer-version` → SSM `/nova/layer/nova-cli/version`
|
|
||||||
mapping (CAP-035). Fail either → job fails (merge blocked, REQ-323).
|
|
||||||
Pin version to `<semver>+<sha7>` for idempotent re-runs.
|
|
||||||
|
|
||||||
#### Wave 5 — composite action (cli-engineer + backend-engineer)
|
|
||||||
- **Task 5.1** (cli-engineer): `.github/actions/nova-cli/action.yml` —
|
|
||||||
composite action, `setup-python@v5` (3.12), CodeArtifact login +
|
|
||||||
`pip install nova`, `nova ${{ inputs.command }}`. `NOVA_CLIENT_MODE`
|
|
||||||
from input.
|
|
||||||
- **Task 5.2** (backend-engineer): byte-identical integration test —
|
|
||||||
CI matrix runs the action on GitHub `ubuntu-latest` + Gitea
|
|
||||||
`act_runner`; assert same stdout/exit code (REQ-326 AC2, NFR-11).
|
|
||||||
|
|
||||||
#### Wave 6 — CAP-033/034 gate (cli-engineer)
|
|
||||||
- **Task 6.1** (cli-engineer): `tests/test_cli_subcommands.py` —
|
|
||||||
CAP-033 (`nova --help` lists a subcommand for every `core/` module)
|
|
||||||
+ CAP-034 (AST scan: ≤50 lines, ≤3 defs, all calls resolve to `core.`,
|
|
||||||
no conditionals beyond `if __name__`). Wire into CI merge gate.
|
|
||||||
|
|
||||||
### Phase P2 — lambda-packaging (REQ-329, REQ-330, REQ-331)
|
|
||||||
|
|
||||||
**Goal:** Dual-use `core/lambda/contract_ingestor.py` (Lambda + CLI
|
|
||||||
paths share ≥80% code); `core/env.py:+synthesize_local_env()` for
|
|
||||||
`nova apply --local`; `.nova/contract.yml.attestations/` scaffolded;
|
|
||||||
JWS-from-PAT key derivation (C-5.2).
|
|
||||||
|
|
||||||
**Exit criterion:** all REQ-329..331 tests pass + JWS-from-PAT KDF
|
|
||||||
specified.
|
|
||||||
|
|
||||||
#### Wave 1 — dual-use refactor (backend-engineer)
|
|
||||||
- **Task 1.1** (backend-engineer): refactor
|
|
||||||
`core/lambda/contract_ingestor.py` — extract the shared logic into
|
|
||||||
importable functions; the Lambda handler + the CLI `__main__` block
|
|
||||||
both call them. The `__main__` block already exists (the dual-use
|
|
||||||
precedent per RESEARCH §1.2). Verify ≥80% code share (CAP-034 / code
|
|
||||||
review). Local path via `core/local_emulators.py:LocalLambdaStub`.
|
|
||||||
|
|
||||||
#### Wave 2 — local env synthesizer + JWS KDF (backend-engineer)
|
|
||||||
- **Task 2.1** (backend-engineer): `core/env.py:+synthesize_local_env()
|
|
||||||
` — produces a local env dict (account_id placeholder, region local,
|
|
||||||
no real AWS) from a contract + `--local` flag. Mirrors
|
|
||||||
`core/onboarding.py:generate_env_file()`.
|
|
||||||
- **Task 2.2** (cli-engineer): `nova/apply.py` (≤50 lines) — `nova
|
|
||||||
apply --local` delegates to `core.env.synthesize_local_env()` +
|
|
||||||
`core.contract_resolver.resolve()`.
|
|
||||||
- **Task 2.3** (security-engineer): JWS-from-PAT key derivation
|
|
||||||
(C-5.2). HKDF-SHA256(PAT_bytes, salt='nova-local-attestation',
|
|
||||||
info='jws-signing-key') → 32-byte symmetric key. The JWS is
|
|
||||||
HMAC-SHA256 (symmetric, not asymmetric). The "public key derivable
|
|
||||||
from the PAT" AC (REQ-332) is re-interpreted: the *verification key*
|
|
||||||
is derived from the PAT via the same KDF (the PAT is the shared
|
|
||||||
secret). Document in `docs/developer-guide-auth.md`. Update REQ-332
|
|
||||||
AC accordingly.
|
|
||||||
|
|
||||||
#### Wave 3 — attestations dir (cli-engineer)
|
|
||||||
- **Task 3.1** (cli-engineer): verify `nova init` (P1 Wave 2 Task 2.3)
|
|
||||||
creates `.nova/contract.yml.attestations/` (empty). REQ-331 test.
|
|
||||||
|
|
||||||
### Phase P3 — idp-auth (REQ-333, REQ-334, REQ-335)
|
|
||||||
|
|
||||||
**Goal:** `nova-idp-auth` Lambda (sign-up, sign-in, session) with
|
|
||||||
Argon2id hashing + DynamoDB tables. CAP-036 target.
|
|
||||||
|
|
||||||
**Exit criterion:** CAP-036 Verified (E2E sign-up → sign-in → session
|
|
||||||
passes in CI).
|
|
||||||
|
|
||||||
#### Wave 1 — DynamoDB schema (backend-engineer)
|
|
||||||
- **Task 1.1** (backend-engineer): define the 4 DynamoDB table schemas
|
|
||||||
(`nova-users`, `nova-sessions`, `nova-password-resets`, `nova-pats`)
|
|
||||||
in a CloudFormation snippet (reused by P4 Wave 8 `nova idp setup`).
|
|
||||||
PITR enabled on each (REQ-335).
|
|
||||||
|
|
||||||
#### Wave 2 — Argon2id (security-engineer) [C-1.2/C-7.2]
|
|
||||||
- **Task 2.1** (security-engineer): `core/lambda/nova_idp_auth.py` —
|
|
||||||
Argon2id password hashing via `argon2-cffi` (D-228: bundled abi3
|
|
||||||
wheel; **fail-closed on `ImportError` → 503, no pure-Python
|
|
||||||
fallback**). **Parameters: t=3, m=65536 KiB, p=1** (OWASP-recommended
|
|
||||||
minimum). Lambda memory ≥512 MB (m=64 MiB + Python overhead fits).
|
|
||||||
Raw passwords never in logs/traces/env/DDB (INV-16, REQ-334).
|
|
||||||
- **Task 2.2** (security-engineer): `tests/test_argon2_fail_closed.py`
|
|
||||||
— mock `argon2.low_level` import failure → assert auth Lambda
|
|
||||||
returns 503 (not a crash, not a weak hash). C-1.2.
|
|
||||||
|
|
||||||
#### Wave 3 — auth Lambda (backend-engineer + security-engineer)
|
|
||||||
- **Task 3.1** (backend-engineer): `nova-idp-auth` Lambda handler —
|
|
||||||
sign-up, sign-in, session creation endpoints. Function URL + IAM
|
|
||||||
auth. DynamoDB via lazy `boto3.resource` (the existing pattern).
|
|
||||||
- **Task 3.2** (security-engineer): session token issuance + session
|
|
||||||
storage in `nova-sessions` (TTL `expires_at`). Password reset flow
|
|
||||||
in `nova-password-resets` (TTL 15m).
|
|
||||||
|
|
||||||
#### Wave 4 — CAP-036 E2E (backend-engineer)
|
|
||||||
- **Task 4.1** (backend-engineer): `tests/test_idp_auth.py` — sign-up
|
|
||||||
→ sign-in → session round-trip (moto[dynamodb] for local; deployed
|
|
||||||
for CI). CAP-036 verification.
|
|
||||||
|
|
||||||
### Phase P4 — token-vend-pat (REQ-336..REQ-344, REQ-340, REQ-341) [was P4+P5]
|
|
||||||
|
|
||||||
**Goal:** `nova-idp-token-vend` Lambda (KMS-signed OIDC, kyverno-json
|
|
||||||
ABAC), JWKS endpoint, PAT lifecycle, `nova auth` commands, **and**
|
|
||||||
`nova idp setup` (folded from P5 per C-2.1). CAP-037 + CAP-038 target.
|
|
||||||
**Highest-risk phase — critical-path.** The kj-binary spike (Wave 1)
|
|
||||||
is the single highest-probability schedule slip; Fargate fallback adds
|
|
||||||
~1 week (D-227). This is a **double-length phase** (8 waves).
|
|
||||||
|
|
||||||
**Exit criterion:** CAP-037 + CAP-038 Verified + `nova idp setup
|
|
||||||
--check/--apply/--verify` works against a fresh AWS account.
|
|
||||||
|
|
||||||
#### Wave 1 — kj-binary spike (backend-engineer + security-engineer) [C-8.2]
|
|
||||||
- **Task 1.1** (backend-engineer): confirm the `kj` Go binary
|
|
||||||
(~40 MB Linux amd64) runs in the Lambda Python 3.12 runtime on
|
|
||||||
AL2023. Bundle it in the `nova-cli` layer (`wget` a **pinned
|
|
||||||
release** (e.g. `kj@v1.x.y`) + record SHA256 into `layer/kj.sha256`
|
|
||||||
— C-8.2, supply-chain safety) into `layer/bin/kj`, `chmod +x`.
|
|
||||||
Verify `KyvernoJsonEngine.is_configured()` finds `/opt/bin/kj`.
|
|
||||||
**If this fails:** fall back to Fargate for the token-vend Lambda
|
|
||||||
(D-227 risk, RESEARCH §7). Escalate to user only if both fail (full
|
|
||||||
autonomy: log assumption + proceed with Fargate).
|
|
||||||
|
|
||||||
#### Wave 2 — ABAC policy (security-engineer) [C-5.1]
|
|
||||||
- **Task 2.1** (security-engineer): `platform/abac/token-vend.policy`
|
|
||||||
— kyverno-json `ValidatingPolicy` (D-227). Payload:
|
|
||||||
`{subject, requested_claims, target_resource, environment, pat_jti,
|
|
||||||
policy_version}`. **`requested_claims` = list of claim names** (the
|
|
||||||
policy asserts the subject is *allowed* to request those claims; the
|
|
||||||
values are assigned by the Lambda, not the requestor — C-5.1).
|
|
||||||
JMESPath checks for role/scope/env/owner. Severity `critical` = deny
|
|
||||||
on fail.
|
|
||||||
- **Task 2.2** (security-engineer): `policy_version` = git SHA of the
|
|
||||||
policy file, baked into the Lambda layer (D-231). Recorded in every
|
|
||||||
`token.vend.allowed/denied` audit event.
|
|
||||||
|
|
||||||
#### Wave 3 — KMS signing (security-engineer) [C-1.1]
|
|
||||||
- **Task 3.1** (security-engineer): **verify KMS asymmetric key
|
|
||||||
support** before implementation: `aws kms create-key --key-spec
|
|
||||||
ECC_NIST_P256 --key-usage SIGN_VERIFY` in the target account
|
|
||||||
(C-1.1). If fail: fall back to RSA-2048 (also supported, larger
|
|
||||||
tokens) or escalate. Do not discover this mid-Wave.
|
|
||||||
- **Task 3.2** (security-engineer): KMS key `alias/nova-oidc-signing`
|
|
||||||
(ECC_NIST_P256, SIGN_VERIFY). Token-vend Lambda signs via
|
|
||||||
`kms.sign(SigningAlgorithm="ECDSA_SHA_256")` → DER→raw ECDSA
|
|
||||||
conversion (`decode_dss_signature` → `r.to_bytes(32) + s.to_bytes(32)`,
|
|
||||||
RESEARCH §5). JWT header `{"alg":"ES256","typ":"JWT","kid":"..."}`.
|
|
||||||
|
|
||||||
#### Wave 4 — token-vend Lambda (backend-engineer + security-engineer) [C-6.1/C-7.1 ABAC FAIL-CLOSED]
|
|
||||||
- **Task 4.1** (backend-engineer): `core/lambda/nova_idp_token_vend.py`
|
|
||||||
— accepts PAT/session, validates revocation (`nova-pats.GetItem(jti,
|
|
||||||
ConsistentRead=True)` — D-229), evaluates ABAC (Wave 2), signs (Wave
|
|
||||||
3), returns OIDC JWT. Audit at every step.
|
|
||||||
- **Task 4.2** (security-engineer): token claims `sub, aud, iss, exp,
|
|
||||||
iat, jti, roles` (REQ-336).
|
|
||||||
- **Task 4.3** (security-engineer) 🔴: **ABAC fail-closed.** If
|
|
||||||
`KyvernoJsonEngine.is_configured()` returns false or `evaluate()`
|
|
||||||
raises, return 403 + audit `token.vend.denied` (reason:
|
|
||||||
`abac_eval_failed`). **Never fail open.** This is INV-17's runtime
|
|
||||||
enforcement (C-6.1/C-7.1 — the grill's #1 finding). Test:
|
|
||||||
`tests/test_abac_fail_closed.py` — mock `kj` absent → assert 403 +
|
|
||||||
audit event.
|
|
||||||
|
|
||||||
#### Wave 5 — JWKS endpoint (backend-engineer)
|
|
||||||
- **Task 5.1** (backend-engineer): `core/lambda/nova_idp_jwks.py` —
|
|
||||||
function URL `AuthType: NONE`, `Cache-Control: max-age=3600`.
|
|
||||||
`kms.get_public_key` → DER SPKI → JWK via `cryptography`. Returns
|
|
||||||
`{"keys":[...]}`. Custom domain + WAF = optional (D-230).
|
|
||||||
|
|
||||||
#### Wave 6 — PAT lifecycle (security-engineer + cli-engineer) [C-7.3]
|
|
||||||
- **Task 6.1** (security-engineer): PAT issuance — signed JWT
|
|
||||||
(`typ: "developer_pat"`, INV-14), `nova-pats` PutItem (jti, pat_hash,
|
|
||||||
status=active). Only hash stored (REQ-343). Revoked PATs retained.
|
|
||||||
**Max TTL: ≤24h for developer PATs, ≤1h for service-account PATs**
|
|
||||||
(C-6.2 threat model).
|
|
||||||
- **Task 6.2** (cli-engineer): `nova/auth/{login,revoke,status}.py` —
|
|
||||||
`nova auth login` (session→OIDC token, store in
|
|
||||||
`~/.nova/credentials.json` 0600), `nova auth revoke --pat <jti>`,
|
|
||||||
`nova auth status` (active credential, mode, selection_reason).
|
|
||||||
All emit audit events (REQ-344). **C-7.3: `~/.nova/credentials.json`
|
|
||||||
stores the OIDC token + PAT metadata (jti, exp, type) ONLY — NOT the
|
|
||||||
raw PAT.** The raw PAT is entered once at `nova auth login` and not
|
|
||||||
persisted (reduces filesystem-compromise blast radius).
|
|
||||||
|
|
||||||
#### Wave 7 — CAP-037/038 (security-engineer)
|
|
||||||
- **Task 7.1** (security-engineer): `tests/test_kms_roundtrip.py`
|
|
||||||
(REQ-350, CAP-037) — sign test JWT via token-vend, fetch JWKS,
|
|
||||||
verify with `pyjwt`. `tests/test_pat_revocation.py` (REQ-351,
|
|
||||||
CAP-038) — issue → vend → revoke → assert 403 within 60s P95.
|
|
||||||
|
|
||||||
#### Wave 8 — nova idp setup (cli-engineer + backend-engineer) [folded from P5 per C-2.1]
|
|
||||||
|
|
||||||
**Goal:** `nova idp setup` command with `--check/--apply/--verify`
|
|
||||||
modes; CloudFormation template generation + review (REQ-340, REQ-341).
|
|
||||||
|
|
||||||
- **Task 8.1** (backend-engineer): `nova/idp/setup.py` (+ backend
|
|
||||||
helper) — generates the Nova-idp CloudFormation template (raw dict →
|
|
||||||
JSON): 2-3 Lambdas, 4 DDB tables, KMS key, function URLs, IAM roles,
|
|
||||||
optional CloudFront/WAF/ACM (`--public-jwks-domain` flag).
|
|
||||||
- **Task 8.2** (cli-engineer): `--check` (prerequisites + IAM policy
|
|
||||||
delta), `--apply` (generate → `$PAGER` → `y/N` → `cloudformation
|
|
||||||
deploy --capabilities CAPABILITY_IAM`, NFR-10), `--dry-run` (resource
|
|
||||||
list only), `--verify` (KMS round-trip, delegates to REQ-350 test).
|
|
||||||
- **Task 8.3** (backend-engineer): IAM policy delta computation —
|
|
||||||
compares current `nova-spike-runner` grants to required
|
|
||||||
`cloudformation:*` + `codeartifact:*` + `kms:*` + `lambda:*` +
|
|
||||||
`dynamodb:*` + `ssm:*`.
|
|
||||||
|
|
||||||
### Phase P5 — docs-integration (REQ-345..REQ-351)
|
|
||||||
|
|
||||||
**Goal:** Operator guide, developer guide, threat model; E2E
|
|
||||||
integration test; property tests; KMS round-trip; PAT revocation SLO.
|
|
||||||
|
|
||||||
**Exit criterion:** all REQ-345..351 tests pass + docs published +
|
|
||||||
threat model reviewed.
|
|
||||||
|
|
||||||
#### Wave 1 — docs (lead-developer + security-engineer) [C-6.2/C-6.3/C-9.2]
|
|
||||||
- **Task 1.1** (lead-developer): `docs/operator-guide-idp.md` (REQ-345)
|
|
||||||
— `nova idp setup --check/--apply/--verify`, prerequisite IAM policy,
|
|
||||||
CloudFormation review flow. **C-6.3 additions:** KMS key rotation
|
|
||||||
procedure (90 days), Lambda layer update procedure, DDB PITR restore
|
|
||||||
procedure, emergency PAT revocation (DDB-level, not CLI).
|
|
||||||
- **Task 1.2** (lead-developer): `docs/developer-guide-auth.md`
|
|
||||||
(REQ-346) — signup, signin, login, mode resolution, TTY vs piped
|
|
||||||
stdout behavior, JWS-from-PAT KDF (P2 Wave 2 Task 2.3).
|
|
||||||
- **Task 1.3** (security-engineer): `docs/threat-model.md` (REQ-347) —
|
|
||||||
Argon2id storage, KMS signing, JWKS exposure, PAT revocation SLO,
|
|
||||||
ABAC token vending, no-AWS-managed-identity (INV-15), DER→raw ECDSA
|
|
||||||
gotcha. **C-6.2 additions:** (a) JWKS unauthenticated endpoint DDoS
|
|
||||||
surface + reserved-concurrency mitigation; (b) PAT theft + max TTL
|
|
||||||
(≤24h dev, ≤1h service-account); (c) ABAC fail-closed guarantee
|
|
||||||
(C-6.1). **C-9.2 addition:** INV-18..21 compression audit — verify
|
|
||||||
the spec's attestation invariant semantics are fully captured by
|
|
||||||
INV-15/16/17 + REQ-332.
|
|
||||||
|
|
||||||
#### Wave 2 — integration tests (backend-engineer + security-engineer)
|
|
||||||
- **Task 2.1** (backend-engineer): `tests/test_e2e_idp.py` (REQ-348) —
|
|
||||||
sign-up → sign-in → token-vend → apply → audit. Verifiable audit
|
|
||||||
chain. Runs in CI against deployed Nova-idp.
|
|
||||||
- **Task 2.2** (security-engineer): verify REQ-349 (mode_resolver
|
|
||||||
property tests, P1 Wave 3 Task 3.3) + REQ-350 (KMS round-trip, P4
|
|
||||||
Wave 7 Task 7.1) + REQ-351 (PAT revocation SLO, P4 Wave 7 Task 7.1)
|
|
||||||
pass in CI.
|
|
||||||
|
|
||||||
### Phase P6 — final-review-ship (Final Phase)
|
|
||||||
|
|
||||||
**Goal:** Multi-persona code review across P1..P5; project-health
|
|
||||||
audit; milestone ship to main; CAP-033..038 Verified.
|
|
||||||
|
|
||||||
#### Wave 1 — review (lead-developer)
|
|
||||||
- **Task 1.1** (lead-developer): `ciagent-review` across all phases.
|
|
||||||
Auto-fix P0; flag P1+ for post-hoc. If P1+ found, fix in this phase.
|
|
||||||
|
|
||||||
#### Wave 2 — audit (lead-developer)
|
|
||||||
- **Task 2.1** (lead-developer): `ciagent-audit` — reconstruction test
|
|
||||||
(git log ↔ `.ciagent/`), file/branch/commit discipline. Fix critical
|
|
||||||
issues in this phase.
|
|
||||||
|
|
||||||
#### Wave 3 — milestone ship (lead-developer)
|
|
||||||
- **Task 3.1** (lead-developer): `ciagent-ship` — merge `phase/06` →
|
|
||||||
`milestone/v1.28-cli-identity` → `main`; tag `v1.27.6` (= the v1.28
|
|
||||||
release); Gitea release with full milestone summary; delete all
|
|
||||||
milestone branches. Update REQUIREMENTS.md (mark REQ-323..353
|
|
||||||
complete), ROADMAP.md (mark v1.28 complete), STATE.md (append
|
|
||||||
CAP-033..038 + INV-12..17), NORTH_STAR.md.
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## User-Facing Surface
|
## Wave 1 — Bug Fixes (P1–P6)
|
||||||
|
|
||||||
> MVP/UX CHECK §1 (REQ-MVP-UX-001).
|
### P1 — adapter-dedup-diagnostic (REQ-135)
|
||||||
|
**Persona:** backend-engineer
|
||||||
|
**Territory:** `adapters/terraform/adapter.py`
|
||||||
|
**Tasks:**
|
||||||
|
1. In the dedup loop (`adapter.py:159-170`), when `tf_dir` is `None`,
|
||||||
|
raise `ValueError(f"no terraform_dir in registry for module
|
||||||
|
{module}")` instead of silently skipping.
|
||||||
|
2. Verify registered-module dedup behavior preserved (multi-resource L1s
|
||||||
|
still merge into one `module "x" { ... }` block).
|
||||||
|
3. Run `pytest tests/test_adapter.py` + `run_ci.sh`.
|
||||||
|
|
||||||
1. **CLI flag:** `nova --help` lists every subcommand; `nova init`
|
### P2 — static-assets-wiring-fix (REQ-136)
|
||||||
scaffolds a project; `nova auth login` authenticates; `nova apply
|
**Persona:** data-engineer
|
||||||
--local` runs locally; `nova idp setup` deploys the identity stack.
|
**Territory:** `modules/l2/static-assets/`
|
||||||
2. **README quickstart:** `docs/developer-guide-auth.md` (REQ-346)
|
**Tasks:**
|
||||||
documents signup → signin → login → `nova apply` in a quickstart.
|
1. Wire `default_ttl`/`max_ttl`/`price_class`/`viewer_protocol_policy`
|
||||||
3. **`.feature` Scenario:** `tests/test_e2e_idp.py` (REQ-348) is the
|
in `composition.json` to the cloudfront child's inputs.
|
||||||
E2E happy path (sign-up → sign-in → token-vend → apply → audit).
|
2. Add a `waf_enabled` feature flag (default true) to the
|
||||||
|
static-assets composition; make the WAF child conditional on it.
|
||||||
|
3. Update `examples/complex.yml` to set `waf_enabled: true` + non-default
|
||||||
|
TTLs so it resolves to a different resource set than `simple.yml`.
|
||||||
|
4. Run `pytest` + `run_ci.sh`.
|
||||||
|
|
||||||
## Happy Path
|
### P3 — lifecycle-script-arg-cleanup (REQ-137)
|
||||||
|
**Persona:** backend-engineer
|
||||||
|
**Territory:** `scripts/run_l2_lifecycle_*.sh`
|
||||||
|
**Tasks:**
|
||||||
|
1. Remove the `[ci-vpc-outputs.json]` token from the usage strings of
|
||||||
|
`run_l2_lifecycle_test.sh` + `run_l2_lifecycle_destroy.sh`, OR add a
|
||||||
|
comment documenting the L2-uses-remote-state design + parity reason.
|
||||||
|
2. Run `pytest` + `run_ci.sh`.
|
||||||
|
|
||||||
> MVP/UX CHECK §2 (REQ-MVP-UX-001). End-to-end scenario written BEFORE
|
### P4 — regression-gate-evidence-hardening (REQ-138)
|
||||||
> execute.
|
**Persona:** backend-engineer
|
||||||
|
**Territory:** `core/regression_verify.py`, `.ciagent/CAPABILITY_INVENTORY.md`
|
||||||
|
**Binding decisions:** G-105 (gate must pass clean post-P4 before W2)
|
||||||
|
**Tasks:**
|
||||||
|
1. Add a `terraform validate` step to
|
||||||
|
`_check_lifecycle_module_terraform` (or document why it's too slow +
|
||||||
|
fall back to a `terraform fmt -check` syntax probe).
|
||||||
|
2. Tighten CAPABILITY_INVENTORY + docstrings to "offline proxy; live
|
||||||
|
apply/modify/destroy verified by the modules-lifecycle workflow run,
|
||||||
|
not by this gate."
|
||||||
|
3. **Run the full regression gate immediately after P4 lands** (G-105).
|
||||||
|
Gate must pass clean before W2 begins.
|
||||||
|
4. Run `pytest` + `run_ci.sh`.
|
||||||
|
|
||||||
**Journey 2 — Dev authenticates and deploys locally:**
|
### P5 — adapter-behavior-tests (REQ-139)
|
||||||
1. `nova auth signup` → `nova-idp-auth` Lambda → Argon2id hash →
|
**Persona:** backend-engineer
|
||||||
`nova-users` PutItem → session token.
|
**Territory:** `tests/test_adapter.py`
|
||||||
2. `nova auth signin` → `nova-idp-auth` → Argon2id verify → session.
|
**Tasks:**
|
||||||
3. `nova auth login` → `nova-idp-token-vend` (exchanges session for
|
1. Add `test_adapter_dedup_merges_same_module` — two resources with the
|
||||||
Nova OIDC token; stores in `~/.nova/credentials.json` 0600).
|
same `module` collapse to one `module "<first_id>" { ... }` block with
|
||||||
4. `nova init` in a project dir → `.nova/`, `.gitignore`,
|
merged inputs.
|
||||||
`.nova/contract.yml.attestations/`.
|
2. Add `test_adapter_remote_state_key_override` — `ACDL_REMOTE_STATE_KEY`
|
||||||
5. `nova apply --local --sign-local-review` →
|
overrides the default `platform/terraform.tfstate` key in the emitted
|
||||||
`core.env.synthesize_local_env()` → `core.contract_resolver.resolve()`
|
`data terraform_remote_state` block.
|
||||||
→ JWS attestation signed with a key derived from the PAT → local
|
3. Run `pytest` + `run_ci.sh`.
|
||||||
ledger entry.
|
|
||||||
|
|
||||||
The E2E test (`tests/test_e2e_idp.py`, REQ-348) verifies this chain +
|
### P6 — alb-name-prefix-fix (REQ-140)
|
||||||
the audit event chain in CI against a deployed Nova-idp.
|
**Persona:** data-engineer
|
||||||
|
**Territory:** `modules/l1/alb/terraform/main.tf`
|
||||||
## UX Acceptance Criteria
|
**Tasks:**
|
||||||
|
1. Change `name_prefix = "tg-ci-"` to `name_prefix = "${var.name}-"` so
|
||||||
> MVP/UX CHECK §3 (REQ-MVP-UX-001).
|
the consumer's name prefixes the target group.
|
||||||
|
2. Run `terraform validate` in the alb module dir standalone.
|
||||||
1. `nova --help` exits 0 and lists a subcommand for every `core/`
|
3. Run `pytest` + `run_ci.sh`.
|
||||||
module (CAP-033).
|
|
||||||
2. `nova init` in an empty dir creates `.nova/`,
|
|
||||||
`.nova/contract.yml.attestations/`, `.gitignore` (secrets excluded).
|
|
||||||
3. `nova auth login` at a TTY resolves `mode=interactive,
|
|
||||||
selection_reason=credential:developer_pat` (INV-12, INV-14).
|
|
||||||
4. `nova apply --local` produces a JWS attestation verifiable with the
|
|
||||||
public key derived from the PAT (REQ-332).
|
|
||||||
5. `nova idp setup --check` reports prerequisites + IAM policy delta;
|
|
||||||
`--apply` presents the CloudFormation template for review before any
|
|
||||||
resource is created (NFR-10); `--verify` confirms the KMS round-trip.
|
|
||||||
6. The Forge action (`nova cli-action`) runs `nova apply` in
|
|
||||||
`mode=agent, selection_reason=credential:service_account_pat` with
|
|
||||||
no TTY dependency (Journey 3, INV-12).
|
|
||||||
7. PAT revocation takes effect within 60s P95 (NFR-4, CAP-038).
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Capability gate (CAP-033..CAP-038)
|
## Wave 2 — Security (P7–P12)
|
||||||
|
|
||||||
| CAP | Name | Phase | Gate rule |
|
### P7 — swallowed-error-hardening (REQ-141)
|
||||||
|-----|------|-------|-----------|
|
**Persona:** backend-engineer
|
||||||
| CAP-033 | CLI subcommand surface exists | P1 | `nova --help` lists a subcommand for every `core/` module |
|
**Territory:** `core/local_emulators.py`, `core/lambda/contract_ingestor.py`,
|
||||||
| CAP-034 | Subcommand delegates to `core/` | P1 | Every `nova/<module>.py` ≤50 lines, no business logic, AST scan |
|
`terraform/bootstrap/create_state_backend.py`, `core/output_publisher.py`,
|
||||||
| CAP-035 | Layer matches wheel | P1 | Lambda layer ARN version matches `nova-cli` wheel version (SSM mapping) |
|
`terraform/bootstrap/apply_iam_baseline.py`
|
||||||
| CAP-036 | Nova-idp auth flow works | P3 | E2E test (sign-up → sign-in → session) passes in CI |
|
**Tasks:**
|
||||||
| CAP-037 | Token-vend signs via KMS | P4 | KMS round-trip test (REQ-350) passes in CI |
|
1. `local_emulators.py:374` — narrow `except Exception: pass` to catch
|
||||||
| CAP-038 | PAT issuance + revocation | P4 | Issue → vend → revoke → 403 within 60s P95 (REQ-351) passes in CI |
|
`AttributeError`/`TypeError` (monkeypatch setup); log + re-raise if
|
||||||
**Release gate (§6 of the spec):** CAP-001..CAP-032 remain Verified;
|
patching fails (prevents network egress).
|
||||||
CAP-033..CAP-038 are Verified; all v1.28 release-gate criteria met.
|
2. `contract_ingestor.py:157` — catch `urllib.error.URLError`/
|
||||||
|
`HTTPError` specifically; log the search failure; keep `existing = []`
|
||||||
|
only on `404`/network, re-raise on auth errors.
|
||||||
|
3. `create_state_backend.py:51` — catch `ClientError` with
|
||||||
|
`NoSuchBucket`/`404` error code; re-raise on permissions/network.
|
||||||
|
4. `output_publisher.py:100,168` — catch `ClientError`/`HTTPError`
|
||||||
|
specifically; log with context.
|
||||||
|
5. `apply_iam_baseline.py:78` — catch `NoSuchEntityException` on
|
||||||
|
old-version delete; re-raise on other errors.
|
||||||
|
6. Run `pytest` + `run_ci.sh`.
|
||||||
|
|
||||||
|
### P8 — account-id-externalization (REQ-142)
|
||||||
|
**Persona:** backend-engineer + data-engineer
|
||||||
|
**Territory:** `adapters/terraform/adapter.py`, `terraform/bootstrap/`,
|
||||||
|
`scripts/push_consumer_image.py`, terraform resource ARNs
|
||||||
|
**Binding decisions:** G-101 (grep excludes backend blocks), G-102
|
||||||
|
(fallback bound to live account ID + workflow env wiring)
|
||||||
|
**Tasks:**
|
||||||
|
1. `adapter.py:125,140` — read `ACDL_AWS_ACCOUNT_ID` env; build the
|
||||||
|
state-bucket name dynamically. **Fallback constant = `581513795199`**
|
||||||
|
(the live account ID, NOT a placeholder — G-102). Documented for
|
||||||
|
offline tests.
|
||||||
|
2. `apply_iam_baseline.py:33`, `create_state_backend.py:33,35` — read
|
||||||
|
from env (same fallback).
|
||||||
|
3. `push_consumer_image.py:32` — read from env.
|
||||||
|
4. Terraform: use `data.aws_caller_identity.current.account_id` for
|
||||||
|
**resource ARNs** in `spike_runner_policy.json` + resource names.
|
||||||
|
**Exclude terraform `backend "s3"` blocks** (`terraform/*/terraform.tf`,
|
||||||
|
`terraform/ci-vpc/main.tf`, `terraform/platform/main.tf`,
|
||||||
|
`terraform/microservice/terraform.tf`) — backend `bucket` args are
|
||||||
|
static-config-only, evaluated pre-init (G-101). Leave backend blocks
|
||||||
|
literal or move to `terraform init -backend-config` (separate change,
|
||||||
|
not in P8 scope).
|
||||||
|
5. **Lifecycle workflow env wiring (G-102):** the `modules-lifecycle.yml`
|
||||||
|
full-mode jobs must set `ACDL_AWS_ACCOUNT_ID` from
|
||||||
|
`aws sts get-caller-identity --query Account --output text` before
|
||||||
|
any `run_platform.sh`/lifecycle invocation. No full-mode run proceeds
|
||||||
|
with the env unset.
|
||||||
|
6. Run `pytest` + `run_ci.sh`; verify
|
||||||
|
`grep -rn "581513795199" adapters/ scripts/ terraform/bootstrap/ core/`
|
||||||
|
returns 0 hits (excluding tests + docs + terraform backend blocks).
|
||||||
|
|
||||||
|
### P9 — iam-policy-least-privilege (REQ-143)
|
||||||
|
**Persona:** data-engineer
|
||||||
|
**Territory:** `terraform/bootstrap/spike_runner_policy.json`,
|
||||||
|
`tests/test_iam_policy_baseline.py`, `modules/l1/*/terraform/main.tf`,
|
||||||
|
`modules/l2/*/composition.json`
|
||||||
|
**Binding decisions:** G-104 (verify acdl-* naming before merge)
|
||||||
|
**Tasks:**
|
||||||
|
1. Scope `iam:CreateRole` etc. (line 236) to
|
||||||
|
`arn:aws:iam::*:role/acdl-*`.
|
||||||
|
2. Scope KMS (line 218) to `arn:aws:kms::*:key/acdl-*` (or
|
||||||
|
`alias/acdl-*`).
|
||||||
|
3. CloudFront (line 117) + WAFv2 (line 129) remain `Resource: "*"` with
|
||||||
|
a documented global-ARN constraint (CloudFront ARNs are global;
|
||||||
|
cannot be account-scoped — G-104).
|
||||||
|
4. **Verify acdl-* naming (G-104):** grep/audit
|
||||||
|
`modules/l1/*/terraform/main.tf` + `modules/l2/*/composition.json`
|
||||||
|
for every IAM role + KMS key name created by the lifecycle pipeline.
|
||||||
|
If any non-`acdl-*` name is found, rename the resource or widen that
|
||||||
|
one statement (documented).
|
||||||
|
5. Add a regression test in `test_iam_policy_baseline.py` asserting no
|
||||||
|
new `Resource: "*"` on non-global actions.
|
||||||
|
6. Run `pytest` + `run_ci.sh`.
|
||||||
|
|
||||||
|
### P10 — contract-ingestor-identity-validation (REQ-144)
|
||||||
|
**Persona:** backend-engineer
|
||||||
|
**Territory:** `core/lambda/contract_ingestor.py`, `tests/test_contract_ingestor.py`
|
||||||
|
**Tasks:**
|
||||||
|
1. Add `contractId` format validation (regex, ≤64 chars).
|
||||||
|
2. Add `environment` enum validation (dev/qa/prod/dr).
|
||||||
|
3. Add `error` length cap (truncate `stackTrace` at a reasonable limit).
|
||||||
|
4. Document the ABAC reliance in the `_validate_caller_identity`
|
||||||
|
docstring + add a note to ARCHITECTURE.md (P19 will land it).
|
||||||
|
5. Add a spoofing-resistance test (caller submits a `consumerRepo` they
|
||||||
|
don't own → rejected if ABAC misconfigured; documented best-effort).
|
||||||
|
6. Run `pytest` + `run_ci.sh`.
|
||||||
|
|
||||||
|
### P11 — schema-input-validation-hardening (REQ-145)
|
||||||
|
**Persona:** backend-engineer
|
||||||
|
**Territory:** `schemas/contract.schema.json`, `schemas/environment.schema.json`,
|
||||||
|
`tests/test_environment_schema.py`, `tests/test_contract_schema.py`
|
||||||
|
**Tasks:**
|
||||||
|
1. Add `"additionalProperties": false` to both schemas' top-level
|
||||||
|
objects.
|
||||||
|
2. Add `maxItems`/`maxProperties` bounds to `infrastructure` map +
|
||||||
|
`monitored_endpoints` array.
|
||||||
|
3. Add `pattern` validation for `state_backend.bucket` (S3 naming
|
||||||
|
rules: lowercase, 3-63 chars, no underscores).
|
||||||
|
4. Add `pattern` validation for `runner_role_arn` (ARN format).
|
||||||
|
5. Add `pattern` validation for `vpc_cidr` (CIDR format).
|
||||||
|
6. Add tests asserting rejection of undocumented fields + malformed
|
||||||
|
values.
|
||||||
|
7. Run `pytest` + `run_ci.sh`.
|
||||||
|
|
||||||
|
### P12 — gitignore-credential-hygiene (REQ-146)
|
||||||
|
**Persona:** lead-developer
|
||||||
|
**Territory:** `.gitignore`, `tests/test_no_secrets_tracked.py`
|
||||||
|
**Tasks:**
|
||||||
|
1. Add credential-pattern catch-all to `.gitignore`:
|
||||||
|
`*.pem`, `*.key`, `*.p12`, `*.pfx`, `*.cer`, `*.crt`, `*.jks`.
|
||||||
|
2. Create `tests/test_no_secrets_tracked.py` — runs
|
||||||
|
`git ls-files | grep -E '\.(pem|key|p12|pfx|cer|crt|jks)$'` and
|
||||||
|
asserts 0 hits.
|
||||||
|
3. Run `pytest` + `run_ci.sh`.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Test evidence required for v1.28 release
|
## Wave 3 — Stub / Test / CI / Hygiene (P13–P17)
|
||||||
|
|
||||||
- [ ] Code coverage ≥ 80% on new modules (`mode_resolver.py`,
|
### P13 — kyverno-kube-version-resolution (REQ-147)
|
||||||
`nova-idp-auth`, `nova-idp-token-vend`, PAT lifecycle).
|
**Persona:** backend-engineer
|
||||||
- [ ] CI/CD pipeline GREEN: wheel + Lambda layer publish on every merge
|
**Territory:** `adapters/kyverno/kyverno_adapter.py`, `tests/test_kyverno_adapter.py`
|
||||||
(REQ-323, CAP-035).
|
**Binding decisions:** G-103 (removal+documentation path, NOT implementation)
|
||||||
- [ ] QA sign-off: all four happy-path journeys (J1–J4) pass integration
|
**Tasks:**
|
||||||
tests in CI.
|
1. **Remove the `--kube-version` flag** from
|
||||||
- [ ] Security/compliance review: threat model published, Argon2id
|
`kyverno_adapter.py:11,115-116` (G-103 — implementing version-aware
|
||||||
verified, ABAC policy reviewed.
|
policy selection would be a new feature, violating D-095).
|
||||||
- [ ] Capability gate GREEN: CAP-001..032 remain Verified; CAP-033..038
|
2. Add a docstring documenting the deferral to the GitOps reconciler
|
||||||
Verified.
|
roadmap (D-053): the Kyverno adapter is inactive for Terraform-only
|
||||||
- [ ] Mode resolver property tests pass (all four priority levels + edge
|
stacks; `--kube-version` will be relevant when the GitOps reconciler
|
||||||
cases; REQ-349).
|
emits K8s manifests.
|
||||||
- [ ] KMS round-trip test passes against deployed JWKS (REQ-350).
|
3. Update `test_kyverno_adapter.py` to remove the `--kube-version` test
|
||||||
- [ ] PAT revocation SLO verified: ≤60s P95 in CI (REQ-351, NFR-4).
|
cases + assert the flag is absent.
|
||||||
- [ ] Operator + developer guides published.
|
4. Run `pytest` + `run_ci.sh`.
|
||||||
- [ ] `nova idp setup` succeeds in a fresh AWS account.
|
|
||||||
- [ ] Byte-identical Forge action on GitHub + Gitea (REQ-326, NFR-11).
|
### P14 — orphan-artifact-and-dead-config-cleanup (REQ-148)
|
||||||
|
**Persona:** lead-developer
|
||||||
|
**Territory:** `scripts/__pycache__/`, `pyproject.toml`, `.ciagent/config.json`
|
||||||
|
**Tasks:**
|
||||||
|
1. Delete the orphan
|
||||||
|
`scripts/__pycache__/verify_deploy_microservice.cpython-312.pyc`.
|
||||||
|
2. Fix `pyproject.toml` coverage source: `acdl_platform` → `core`.
|
||||||
|
3. Bump `pyproject.toml` version `1.3.0` → current (v1.14).
|
||||||
|
4. Remove dead JS allowlist entries from `config.json`
|
||||||
|
`bash_allowlist.allowed_commands` (npm/node/npx/pnpm/yarn/jest/eslint/
|
||||||
|
tsc/prettier — no package.json).
|
||||||
|
5. Run `pytest` + `run_ci.sh`.
|
||||||
|
|
||||||
|
### P15 — untested-scripts-coverage (REQ-149)
|
||||||
|
**Persona:** backend-engineer
|
||||||
|
**Territory:** `tests/` (new test files for 7 scripts)
|
||||||
|
**Tasks:**
|
||||||
|
1. `tests/test_seed_uptime_monitors.py` — mock the uptime-kuma API;
|
||||||
|
assert monitor creation from a JSON file.
|
||||||
|
2. `tests/test_push_consumer_image.py` — mock `subprocess.run` (docker
|
||||||
|
login/build/push) + boto3 ECR; assert the flow.
|
||||||
|
3. `tests/test_sync_to_gl.sh` (shell test) — dry-run mode; assert the
|
||||||
|
copy + push commands are constructed correctly.
|
||||||
|
4. `tests/test_post_stage_comment.sh` (shell test) — no-op when not in
|
||||||
|
a PR context; assert the `gh api` call structure when in PR.
|
||||||
|
5. `tests/test_rotate_spike_key.sh` (shell test) — mock `aws iam`;
|
||||||
|
assert deactivate/create/update-secret flow.
|
||||||
|
6. `tests/test_create_state_backend.py` — mock boto3 S3/DynamoDB;
|
||||||
|
assert idempotent creation.
|
||||||
|
7. `tests/test_create_iam_user.py` — mock boto3 IAM; assert idempotent
|
||||||
|
user/policy/key creation.
|
||||||
|
8. Run `pytest` + `run_ci.sh`.
|
||||||
|
|
||||||
|
### P16 — workflow-parity-and-script-flags (REQ-150)
|
||||||
|
**Persona:** backend-engineer
|
||||||
|
**Territory:** `.gitea/workflows/`, `scripts/rotate_spike_key.sh`,
|
||||||
|
`scripts/sync_to_gl.sh`
|
||||||
|
**Tasks:**
|
||||||
|
1. Either mirror the 4 GitHub-only workflows (patterns-plan,
|
||||||
|
platform-test, primitives-plan, release) to `.gitea/workflows/`, or
|
||||||
|
add a README documenting the Gitea limitation (Gitea runners don't
|
||||||
|
use release/primitives-plan/patterns-plan; release is GitHub-only by
|
||||||
|
design).
|
||||||
|
2. Add `set -euo pipefail` to `rotate_spike_key.sh` (currently only
|
||||||
|
`set -u`).
|
||||||
|
3. Add `set -euo pipefail` to `sync_to_gl.sh` (currently no `set`
|
||||||
|
flags).
|
||||||
|
4. Run `pytest` + `run_ci.sh`.
|
||||||
|
|
||||||
|
### P17 — config-and-persona-hygiene (REQ-151)
|
||||||
|
**Persona:** lead-developer
|
||||||
|
**Territory:** `.ciagent/config.json`, `.ciagent/PERSONAS.md`
|
||||||
|
**Tasks:**
|
||||||
|
1. Mark `frontend-engineer` persona `active: false` in `config.json`
|
||||||
|
`personas.personas[]` (PERSONAS.md:80 already says inactive).
|
||||||
|
2. Fix `branching_strategy: "phase"` — either change to `"flat"` or
|
||||||
|
document that the field is advisory + the project uses flat workflow
|
||||||
|
(committed directly to main per established convention).
|
||||||
|
3. Configure `ollama-cloud` backend: set `base_url` to the actual
|
||||||
|
endpoint OR add a comment documenting why it's intentionally unset
|
||||||
|
(the runtime uses the `glm-5.2` model via the opencode backend, not
|
||||||
|
the `llm_backends` config).
|
||||||
|
4. Run `pytest` + `run_ci.sh`.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Plan completeness checklist
|
## Wave 4 — Standards / Docs / VPC (P18–P20)
|
||||||
|
|
||||||
- [x] Every REQ-323..353 mapped to a phase + wave + task.
|
### P18 — module-standards-consistency (REQ-152)
|
||||||
- [x] Every CAP-033..038 mapped to a phase + gate rule.
|
**Persona:** data-engineer
|
||||||
- [x] Every INV-12..17 referenced in persona constraints.
|
**Territory:** `modules/STANDARDS.md`, `modules/l1/{ecr,ecs-cluster,rds}/terraform/`
|
||||||
- [x] Every D-226..231 referenced in task rationale.
|
**Tasks:**
|
||||||
- [x] Vertical slices: each phase ships independently (P1 CLI substrate
|
1. Either add `locals.tf` to `ecr`, `ecs-cluster`, `rds` (extract
|
||||||
is useful before P2 packaging; P2 before P3 auth; etc.).
|
inlined locals from `main.tf`), OR reconcile STANDARDS §9.4 to
|
||||||
- [x] Wave ordering within phases (no wave N+1 depends on wave N work
|
explicitly allow inlining for trivial single-resource modules.
|
||||||
in the same phase).
|
2. Remove the stale `TYPE_MAP` reference in STANDARDS §8 (deleted in
|
||||||
- [x] Persona assignments per task (4 active personas).
|
the v1.11 stateless rewrite).
|
||||||
- [x] MVP/UX CHECK: 3 sections present (User-Facing Surface, Happy Path,
|
3. Run `pytest` + `run_ci.sh`.
|
||||||
UX Acceptance Criteria).
|
|
||||||
- [x] Highest-risk item flagged (P4 Wave 1 kj-binary spike).
|
### P19 — documentation-sync-v1.14 (REQ-153)
|
||||||
- [x] Grill conditions applied (3 critical + 16 tracked; see GRILL.md).
|
**Persona:** lead-developer
|
||||||
|
**Territory:** `.ciagent/ARCHITECTURE.md`, `docs/`, `README.md`,
|
||||||
|
`.ciagent/COST.md`, `.ciagent/GRILL.md`, `.ciagent/IAM_POLICY.md`,
|
||||||
|
`docs/presentations/`
|
||||||
|
**Tasks:**
|
||||||
|
1. ARCHITECTURE.md: add v1.11 addendum (stateless adapter, platform VPC,
|
||||||
|
ACDL_LIFECYCLE_MODE), v1.12 addendum (CAP-013 fix, plan-only
|
||||||
|
default), v1.13 addendum (config.json schema migration, badge
|
||||||
|
cleanup, platform-architecture diagram), v1.14 addendum (all 20
|
||||||
|
phases). Record D-083 deferral explicitly.
|
||||||
|
2. Bump stale `@v1.6`–`@v1.9` → `@v1.13` across `README.md:225`,
|
||||||
|
`docs/consumer-guide.md` (12 sites), `docs/architecture.md:233`,
|
||||||
|
`docs/pipeline/versioning.md:29`, `docs/pipeline/index.md:42`.
|
||||||
|
3. Sync decks to v1.13.2 reality (version refs, capability claims).
|
||||||
|
4. Update COST.md window to v1.11–v1.14 (lifecycle pipeline live-runs +
|
||||||
|
teardown).
|
||||||
|
5. Resolve G-005/G-008 in GRILL.md (CAP-017..022 now Verified via
|
||||||
|
lifecycle pipeline; COST.md now exists + covers v1.11+).
|
||||||
|
6. Update IAM_POLICY.md for v1.12/v1.13/v1.14 (plan-only default,
|
||||||
|
config.json schema, v1.14 IAM scoping from P9).
|
||||||
|
7. Run `pytest` + `run_ci.sh`; verify
|
||||||
|
`grep -rn "@v1\.[6-9]" docs/ README.md` returns 0 hits.
|
||||||
|
|
||||||
|
### P20 — platform-vpc-parameterization (REQ-154)
|
||||||
|
**Persona:** data-engineer
|
||||||
|
**Territory:** `terraform/platform/main.tf`
|
||||||
|
**Tasks:**
|
||||||
|
1. Add a `vpc_cidr` variable (default `10.0.0.0/16`); replace the
|
||||||
|
hardcoded `cidr_block`.
|
||||||
|
2. Replace `count = 2` subnets with
|
||||||
|
`count = length(data.aws_availability_zones.available.names)`.
|
||||||
|
3. Add a `data "aws_availability_zones" "available" {}` block.
|
||||||
|
4. Document the `0.0.0.0/0` ingress on port 80 (ALB-fronted, acceptable
|
||||||
|
for a public-facing service; add a comment).
|
||||||
|
5. Run `terraform validate` + `pytest` + `run_ci.sh`.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Cost envelope (C-3.1)
|
## Final Phase — P21 (review + audit + ship)
|
||||||
|
|
||||||
Monthly estimate for the default (no CloudFront) Nova-idp deployment in
|
**Persona:** lead-developer (review coordination) + ci-code-reviewer +
|
||||||
account `581513795199`:
|
ci-debugger (audit)
|
||||||
|
**Tasks:**
|
||||||
|
1. Multi-persona code review across all v1.14 phases (P1–P20). Auto-apply
|
||||||
|
P0 fixes; flag P1+ for post-hoc review. If P1+ found, fix in-phase.
|
||||||
|
2. Audit: reconstruction test (git log vs `.ciagent/` files), file
|
||||||
|
discipline, branch hygiene, commit discipline. Fix critical issues
|
||||||
|
in-phase.
|
||||||
|
3. Complete: update REQUIREMENTS.md (REQ-135..154 → complete),
|
||||||
|
ROADMAP.md (v1.14 complete), PROJECT.md.
|
||||||
|
4. Tag `v1.13.24` (IS the milestone release). Merge
|
||||||
|
`milestone/v1.14-refinement` → `main`. Create Gitea release with full
|
||||||
|
milestone summary.
|
||||||
|
|
||||||
| Resource | Quantity | Pricing | Est. monthly |
|
## Success Criteria (milestone gate)
|
||||||
|----------|----------|---------|-------------|
|
|
||||||
| DynamoDB (on-demand) | 4 tables | $1.25/1M write, $0.25/1M read | ~$1 (pilot volume) |
|
|
||||||
| DynamoDB PITR | 4 tables | $0.20/GB-month | ~$1 (small tables) |
|
|
||||||
| KMS asymmetric key | 1 key | $1/key-month + $0.03/10k signs | ~$1 |
|
|
||||||
| Lambda invocations | 3 Lambdas | $0.20/1M req + $0.000016/GB-s | ~$2 (low volume) |
|
|
||||||
| Lambda layer storage | ~50 MB | $0.02/GB-month | <$1 |
|
|
||||||
| CodeArtifact | 1 domain + 1 repo | $1/domain + $1/repo | $2 |
|
|
||||||
| SSM Parameter | 1 | $0.05/param (advanced) | <$1 |
|
|
||||||
| **Total (default)** | | | **~$9/month** |
|
|
||||||
|
|
||||||
Optional CloudFront + WAF + ACM (if `--public-jwks-domain`): +~$3/month
|
1. All 20 REQ-135..REQ-154 marked complete in REQUIREMENTS.md.
|
||||||
at pilot volume. ACM is free for CloudFront-attached certs.
|
2. Review: 0 new P0; all P1-1..P1-5 + P2-1..P2-4 resolved.
|
||||||
|
3. Audit: clean; reconstruction test passes.
|
||||||
This is a pilot-scale cost envelope. Production scale (100x volume)
|
4. Regression gate (D-091) clean against the v1.14 state.
|
||||||
would still be <$50/month. No hidden costs identified.
|
5. `pytest` passes; `run_ci.sh` exits 0; `run_platform.sh --check-only`
|
||||||
|
exits 0.
|
||||||
|
6. Tag `v1.13.24` created; milestone merged to main.
|
||||||
+783
-410
File diff suppressed because it is too large
Load Diff
@@ -1,13 +1,12 @@
|
|||||||
{
|
{
|
||||||
"run_id": "regr-1785591207",
|
"run_id": "regr-1785329757",
|
||||||
"run_at_utc": "2026-08-01T13:33:27Z",
|
"run_at_utc": "2026-07-29T12:55:57Z",
|
||||||
"milestone": "v1.10",
|
"milestone": "v1.10",
|
||||||
"phase": 52,
|
"phase": 52,
|
||||||
"summary": {
|
"summary": {
|
||||||
"Verified": 18,
|
"Verified": 22,
|
||||||
"Decayed": 0,
|
"Decayed": 0,
|
||||||
"Broken": 0,
|
"Broken": 0
|
||||||
"Skipped": 4
|
|
||||||
},
|
},
|
||||||
"passed": true,
|
"passed": true,
|
||||||
"results": [
|
"results": [
|
||||||
@@ -17,7 +16,7 @@
|
|||||||
"status": "Verified",
|
"status": "Verified",
|
||||||
"detail": "exit 0; 2 sample contracts validate",
|
"detail": "exit 0; 2 sample contracts validate",
|
||||||
"tier": "local",
|
"tier": "local",
|
||||||
"duration_ms": 235
|
"duration_ms": 252
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"capability_id": "CAP-002",
|
"capability_id": "CAP-002",
|
||||||
@@ -25,7 +24,7 @@
|
|||||||
"status": "Verified",
|
"status": "Verified",
|
||||||
"detail": "exit 0; env schema validates",
|
"detail": "exit 0; env schema validates",
|
||||||
"tier": "local",
|
"tier": "local",
|
||||||
"duration_ms": 201
|
"duration_ms": 196
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"capability_id": "CAP-003",
|
"capability_id": "CAP-003",
|
||||||
@@ -33,7 +32,7 @@
|
|||||||
"status": "Verified",
|
"status": "Verified",
|
||||||
"detail": "exit 0; ",
|
"detail": "exit 0; ",
|
||||||
"tier": "local",
|
"tier": "local",
|
||||||
"duration_ms": 261
|
"duration_ms": 258
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"capability_id": "CAP-004",
|
"capability_id": "CAP-004",
|
||||||
@@ -41,7 +40,7 @@
|
|||||||
"status": "Verified",
|
"status": "Verified",
|
||||||
"detail": "exit 0; ",
|
"detail": "exit 0; ",
|
||||||
"tier": "local",
|
"tier": "local",
|
||||||
"duration_ms": 259
|
"duration_ms": 264
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"capability_id": "CAP-005",
|
"capability_id": "CAP-005",
|
||||||
@@ -49,7 +48,7 @@
|
|||||||
"status": "Verified",
|
"status": "Verified",
|
||||||
"detail": "exit 0; ",
|
"detail": "exit 0; ",
|
||||||
"tier": "local",
|
"tier": "local",
|
||||||
"duration_ms": 337
|
"duration_ms": 314
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"capability_id": "CAP-006",
|
"capability_id": "CAP-006",
|
||||||
@@ -57,7 +56,7 @@
|
|||||||
"status": "Verified",
|
"status": "Verified",
|
||||||
"detail": "exit 0; interpolation ok",
|
"detail": "exit 0; interpolation ok",
|
||||||
"tier": "local",
|
"tier": "local",
|
||||||
"duration_ms": 242
|
"duration_ms": 223
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"capability_id": "CAP-007",
|
"capability_id": "CAP-007",
|
||||||
@@ -65,7 +64,7 @@
|
|||||||
"status": "Verified",
|
"status": "Verified",
|
||||||
"detail": "exit 0; confidence band=pass",
|
"detail": "exit 0; confidence band=pass",
|
||||||
"tier": "local",
|
"tier": "local",
|
||||||
"duration_ms": 91
|
"duration_ms": 80
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"capability_id": "CAP-008",
|
"capability_id": "CAP-008",
|
||||||
@@ -73,15 +72,15 @@
|
|||||||
"status": "Verified",
|
"status": "Verified",
|
||||||
"detail": "exit 0; outbox hash chain ok",
|
"detail": "exit 0; outbox hash chain ok",
|
||||||
"tier": "local",
|
"tier": "local",
|
||||||
"duration_ms": 456
|
"duration_ms": 358
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"capability_id": "CAP-009",
|
"capability_id": "CAP-009",
|
||||||
"name": "offline pytest suite passes",
|
"name": "offline pytest suite passes",
|
||||||
"status": "Verified",
|
"status": "Verified",
|
||||||
"detail": "exit 0; [ 98%]\ntests/test_wiz_adapter_real_client.py ......... [100%]\n\n================= 586 passed, 2 deselected in 71.63s (0:01:11) =================",
|
"detail": "exit 0; [ 98%]\ntests/test_wiz_adapter_real_client.py ......... [100%]\n\n====================== 462 passed, 2 deselected in 34.63s ======================",
|
||||||
"tier": "local",
|
"tier": "local",
|
||||||
"duration_ms": 72988
|
"duration_ms": 36065
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"capability_id": "CAP-010",
|
"capability_id": "CAP-010",
|
||||||
@@ -89,63 +88,63 @@
|
|||||||
"status": "Verified",
|
"status": "Verified",
|
||||||
"detail": "exit 0; resource(s))\n\n=== PLATFORM CHECK OK ===\ncontract -> resolver -> stack -> adapter -> structure validated (offline, no AWS)\ncheck-only: OK\n\n=== CI PIPELINE OK ===\n3 stages passed: lint, test, check-only",
|
"detail": "exit 0; resource(s))\n\n=== PLATFORM CHECK OK ===\ncontract -> resolver -> stack -> adapter -> structure validated (offline, no AWS)\ncheck-only: OK\n\n=== CI PIPELINE OK ===\n3 stages passed: lint, test, check-only",
|
||||||
"tier": "local",
|
"tier": "local",
|
||||||
"duration_ms": 73275
|
"duration_ms": 40668
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"capability_id": "CAP-011",
|
"capability_id": "CAP-011",
|
||||||
"name": "headline E2E runs against the local emulating tier (microservice)",
|
"name": "headline E2E runs against the local emulating tier (microservice)",
|
||||||
"status": "Verified",
|
"status": "Verified",
|
||||||
"detail": "exit 0; al-emulator\",\n \"desired_count\": 1,\n \"running_count\": 1\n },\n \"outbox_dir\": \"/tmp/nova_local_e2e_6vnrnin1/outbox\",\n \"outbox_events\": 2,\n \"outbox_chain_verified\": true,\n \"lambda_status\": 200\n}",
|
"detail": "exit 0; al-emulator\",\n \"desired_count\": 1,\n \"running_count\": 1\n },\n \"outbox_dir\": \"/tmp/acdl_local_e2e_416d0fmr/outbox\",\n \"outbox_events\": 2,\n \"outbox_chain_verified\": true,\n \"lambda_status\": 200\n}",
|
||||||
"tier": "local",
|
"tier": "local",
|
||||||
"duration_ms": 634
|
"duration_ms": 583
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"capability_id": "CAP-012",
|
"capability_id": "CAP-012",
|
||||||
"name": "local E2E on the static-assets stack (no ECS)",
|
"name": "local E2E on the static-assets stack (no ECS)",
|
||||||
"status": "Verified",
|
"status": "Verified",
|
||||||
"detail": "exit 0; nova_local_e2e_uq4kkhze/tf\",\n \"backend\": \"local\",\n \"ecs\": null,\n \"outbox_dir\": \"/tmp/nova_local_e2e_uq4kkhze/outbox\",\n \"outbox_events\": 2,\n \"outbox_chain_verified\": true,\n \"lambda_status\": 200\n}",
|
"detail": "exit 0; acdl_local_e2e_ijhcj1z8/tf\",\n \"backend\": \"local\",\n \"ecs\": null,\n \"outbox_dir\": \"/tmp/acdl_local_e2e_ijhcj1z8/outbox\",\n \"outbox_events\": 2,\n \"outbox_chain_verified\": true,\n \"lambda_status\": 200\n}",
|
||||||
"tier": "local",
|
"tier": "local",
|
||||||
"duration_ms": 584
|
"duration_ms": 489
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"capability_id": "CAP-013",
|
"capability_id": "CAP-013",
|
||||||
"name": "terraform init+validate+plan live AWS (microservice)",
|
"name": "terraform init+validate+plan live AWS (microservice)",
|
||||||
"status": "Skipped",
|
"status": "Verified",
|
||||||
"detail": "terraform init: state bucket absent (post-v1.11-teardown, D-096) [microservice]",
|
"detail": "terraform init+validate+plan OK (live AWS, microservice)",
|
||||||
"tier": "live-aws",
|
"tier": "live-aws",
|
||||||
"duration_ms": 737
|
"duration_ms": 28811
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"capability_id": "CAP-014",
|
"capability_id": "CAP-014",
|
||||||
"name": "terraform init+validate+plan live AWS (static-assets)",
|
"name": "terraform init+validate+plan live AWS (static-assets)",
|
||||||
"status": "Skipped",
|
"status": "Verified",
|
||||||
"detail": "terraform init: state bucket absent (post-v1.11-teardown, D-096) [static-assets]",
|
"detail": "terraform init+validate+plan OK (live AWS, static-assets)",
|
||||||
"tier": "live-aws",
|
"tier": "live-aws",
|
||||||
"duration_ms": 676
|
"duration_ms": 31772
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"capability_id": "CAP-015",
|
"capability_id": "CAP-015",
|
||||||
"name": "DynamoDB outbox table exists (live AWS)",
|
"name": "DynamoDB outbox table exists (live AWS)",
|
||||||
"status": "Skipped",
|
"status": "Verified",
|
||||||
"detail": "nova-outbox absent (post-v1.11-teardown steady state, D-096)",
|
"detail": "acdl-outbox exists, item_count=9",
|
||||||
"tier": "live-aws",
|
"tier": "live-aws",
|
||||||
"duration_ms": 664
|
"duration_ms": 477
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"capability_id": "CAP-016",
|
"capability_id": "CAP-016",
|
||||||
"name": "S3 state bucket exists + readable (live AWS)",
|
"name": "S3 state bucket exists + readable (live AWS)",
|
||||||
"status": "Skipped",
|
"status": "Verified",
|
||||||
"detail": "state bucket nova-tfstate-581513795199-us-east-1 absent (post-v1.11-teardown, D-096)",
|
"detail": "state bucket exists, keys=['platform/terraform.tfstate', 'spike/alb/dev/terraform.tfstate', 'spike/cdn/dev/terraform.tfstate', 'spike/ci-vpc/terraform.tfstate', 'spike/clus/dev/terraform.tfstate']",
|
||||||
"tier": "live-aws",
|
"tier": "live-aws",
|
||||||
"duration_ms": 245
|
"duration_ms": 324
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"capability_id": "CAP-017",
|
"capability_id": "CAP-017",
|
||||||
"name": "DynamoDB nova-contracts table (lifecycle pipeline evidence)",
|
"name": "DynamoDB acdl-contracts table (lifecycle pipeline evidence)",
|
||||||
"status": "Verified",
|
"status": "Verified",
|
||||||
"detail": "terraform files present + fmt -check passes + simple/complex contracts resolve",
|
"detail": "terraform files present + simple/complex contracts resolve",
|
||||||
"tier": "lifecycle-pipeline",
|
"tier": "lifecycle-pipeline",
|
||||||
"duration_ms": 586
|
"duration_ms": 520
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"capability_id": "CAP-018",
|
"capability_id": "CAP-018",
|
||||||
@@ -153,39 +152,39 @@
|
|||||||
"status": "Verified",
|
"status": "Verified",
|
||||||
"detail": "LocalLambdaStub instantiates (local tier evidence)",
|
"detail": "LocalLambdaStub instantiates (local tier evidence)",
|
||||||
"tier": "lifecycle-pipeline",
|
"tier": "lifecycle-pipeline",
|
||||||
"duration_ms": 138
|
"duration_ms": 137
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"capability_id": "CAP-019",
|
"capability_id": "CAP-019",
|
||||||
"name": "ECS cluster + service (L2 microservice lifecycle evidence)",
|
"name": "ECS cluster + service (L2 microservice lifecycle evidence)",
|
||||||
"status": "Verified",
|
"status": "Verified",
|
||||||
"detail": "L2 composition resolves (simple + complex contracts; offline proxy)",
|
"detail": "L2 composition resolves (simple + complex contracts)",
|
||||||
"tier": "lifecycle-pipeline",
|
"tier": "lifecycle-pipeline",
|
||||||
"duration_ms": 519
|
"duration_ms": 534
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"capability_id": "CAP-020",
|
"capability_id": "CAP-020",
|
||||||
"name": "CloudFront + WAF (L2 static-assets lifecycle evidence)",
|
"name": "CloudFront + WAF (L2 static-assets lifecycle evidence)",
|
||||||
"status": "Verified",
|
"status": "Verified",
|
||||||
"detail": "L2 composition resolves (simple + complex contracts; offline proxy)",
|
"detail": "L2 composition resolves (simple + complex contracts)",
|
||||||
"tier": "lifecycle-pipeline",
|
"tier": "lifecycle-pipeline",
|
||||||
"duration_ms": 521
|
"duration_ms": 567
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"capability_id": "CAP-021",
|
"capability_id": "CAP-021",
|
||||||
"name": "uptime-kuma (L1 uptime lifecycle evidence)",
|
"name": "uptime-kuma (L1 uptime lifecycle evidence)",
|
||||||
"status": "Verified",
|
"status": "Verified",
|
||||||
"detail": "terraform files present + fmt -check passes + simple/complex contracts resolve",
|
"detail": "terraform files present + simple/complex contracts resolve",
|
||||||
"tier": "lifecycle-pipeline",
|
"tier": "lifecycle-pipeline",
|
||||||
"duration_ms": 562
|
"duration_ms": 606
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"capability_id": "CAP-022",
|
"capability_id": "CAP-022",
|
||||||
"name": "OIDC role (L1 iam-role lifecycle evidence)",
|
"name": "OIDC role (L1 iam-role lifecycle evidence)",
|
||||||
"status": "Verified",
|
"status": "Verified",
|
||||||
"detail": "terraform files present + fmt -check passes + simple/complex contracts resolve",
|
"detail": "terraform files present + simple/complex contracts resolve",
|
||||||
"tier": "lifecycle-pipeline",
|
"tier": "lifecycle-pipeline",
|
||||||
"duration_ms": 611
|
"duration_ms": 529
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@@ -1,51 +1,51 @@
|
|||||||
# Regression Report — v1.10 Phase 52
|
# Regression Report — v1.10 Phase 52
|
||||||
|
|
||||||
- **Run ID:** `regr-1785591207`
|
- **Run ID:** `regr-1785329757`
|
||||||
- **Run at (UTC):** 2026-08-01T13:33:27Z
|
- **Run at (UTC):** 2026-07-29T12:55:57Z
|
||||||
- **Summary:** {'Verified': 18, 'Decayed': 0, 'Broken': 0, 'Skipped': 4}
|
- **Summary:** {'Verified': 22, 'Decayed': 0, 'Broken': 0}
|
||||||
- **Passed (milestone gate):** True
|
- **Passed (milestone gate):** True
|
||||||
|
|
||||||
| Capability | Name | Tier | Status | Duration (ms) | Detail |
|
| Capability | Name | Tier | Status | Duration (ms) | Detail |
|
||||||
|-----------|------|------|--------|--------------|--------|
|
|-----------|------|------|--------|--------------|--------|
|
||||||
| CAP-001 | contract.schema.json validates sample contracts | local | **Verified** | 235 | exit 0; 2 sample contracts validate |
|
| CAP-001 | contract.schema.json validates sample contracts | local | **Verified** | 252 | exit 0; 2 sample contracts validate |
|
||||||
| CAP-002 | environment.schema.json validates env files | local | **Verified** | 201 | exit 0; env schema validates |
|
| CAP-002 | environment.schema.json validates env files | local | **Verified** | 196 | exit 0; env schema validates |
|
||||||
| CAP-003 | contract_resolver resolves static-assets | local | **Verified** | 261 | exit 0; |
|
| CAP-003 | contract_resolver resolves static-assets | local | **Verified** | 258 | exit 0; |
|
||||||
| CAP-004 | contract_resolver resolves microservice | local | **Verified** | 259 | exit 0; |
|
| CAP-004 | contract_resolver resolves microservice | local | **Verified** | 264 | exit 0; |
|
||||||
| CAP-005 | terraform adapter emits .tf files | local | **Verified** | 337 | exit 0; |
|
| CAP-005 | terraform adapter emits .tf files | local | **Verified** | 314 | exit 0; |
|
||||||
| CAP-006 | contract interpolation expands env/contract tokens | local | **Verified** | 242 | exit 0; interpolation ok |
|
| CAP-006 | contract interpolation expands env/contract tokens | local | **Verified** | 223 | exit 0; interpolation ok |
|
||||||
| CAP-007 | confidence_signal.compute returns a band | local | **Verified** | 91 | exit 0; confidence band=pass |
|
| CAP-007 | confidence_signal.compute returns a band | local | **Verified** | 80 | exit 0; confidence band=pass |
|
||||||
| CAP-008 | outbox_writer builds a hash-chained item | local | **Verified** | 456 | exit 0; outbox hash chain ok |
|
| CAP-008 | outbox_writer builds a hash-chained item | local | **Verified** | 358 | exit 0; outbox hash chain ok |
|
||||||
| CAP-009 | offline pytest suite passes | local | **Verified** | 72988 | exit 0; [ 98%]
|
| CAP-009 | offline pytest suite passes | local | **Verified** | 36065 | exit 0; [ 98%]
|
||||||
tests/test_wiz_adapter_real_client.py ......... [100%]
|
tests/test_wiz_adapter_real_client.py ......... [100%]
|
||||||
|
|
||||||
================= 586 passed, 2 |
|
====================== 462 passe |
|
||||||
| CAP-010 | run_ci.sh reproduces CI pipeline locally | local | **Verified** | 73275 | exit 0; resource(s))
|
| CAP-010 | run_ci.sh reproduces CI pipeline locally | local | **Verified** | 40668 | exit 0; resource(s))
|
||||||
|
|
||||||
=== PLATFORM CHECK OK ===
|
=== PLATFORM CHECK OK ===
|
||||||
contract -> resolver -> stack -> adapter -> structure validated (offline, no AWS)
|
contract -> resolver -> stack -> adapter -> structure validated (offline, no AWS)
|
||||||
check-only: OK
|
check-only: OK
|
||||||
|
|
||||||
=== CI PIPELIN |
|
=== CI PIPELIN |
|
||||||
| CAP-011 | headline E2E runs against the local emulating tier (microservice) | local | **Verified** | 634 | exit 0; al-emulator",
|
| CAP-011 | headline E2E runs against the local emulating tier (microservice) | local | **Verified** | 583 | exit 0; al-emulator",
|
||||||
"desired_count": 1,
|
"desired_count": 1,
|
||||||
"running_count": 1
|
"running_count": 1
|
||||||
},
|
},
|
||||||
"outbox_dir": "/tmp/nova_local_e2e_6vnrnin1/outbox",
|
"outbox_dir": "/tmp/acdl_local_e2e_416d0fmr/outbox",
|
||||||
"outbox_events": 2,
|
"outbox_events": 2,
|
||||||
"outbox |
|
"outbox |
|
||||||
| CAP-012 | local E2E on the static-assets stack (no ECS) | local | **Verified** | 584 | exit 0; nova_local_e2e_uq4kkhze/tf",
|
| CAP-012 | local E2E on the static-assets stack (no ECS) | local | **Verified** | 489 | exit 0; acdl_local_e2e_ijhcj1z8/tf",
|
||||||
"backend": "local",
|
"backend": "local",
|
||||||
"ecs": null,
|
"ecs": null,
|
||||||
"outbox_dir": "/tmp/nova_local_e2e_uq4kkhze/outbox",
|
"outbox_dir": "/tmp/acdl_local_e2e_ijhcj1z8/outbox",
|
||||||
"outbox_events": 2,
|
"outbox_events": 2,
|
||||||
"outbox |
|
"outbox |
|
||||||
| CAP-013 | terraform init+validate+plan live AWS (microservice) | live-aws | **Skipped** | 737 | terraform init: state bucket absent (post-v1.11-teardown, D-096) [microservice] |
|
| CAP-013 | terraform init+validate+plan live AWS (microservice) | live-aws | **Verified** | 28811 | terraform init+validate+plan OK (live AWS, microservice) |
|
||||||
| CAP-014 | terraform init+validate+plan live AWS (static-assets) | live-aws | **Skipped** | 676 | terraform init: state bucket absent (post-v1.11-teardown, D-096) [static-assets] |
|
| CAP-014 | terraform init+validate+plan live AWS (static-assets) | live-aws | **Verified** | 31772 | terraform init+validate+plan OK (live AWS, static-assets) |
|
||||||
| CAP-015 | DynamoDB outbox table exists (live AWS) | live-aws | **Skipped** | 664 | nova-outbox absent (post-v1.11-teardown steady state, D-096) |
|
| CAP-015 | DynamoDB outbox table exists (live AWS) | live-aws | **Verified** | 477 | acdl-outbox exists, item_count=9 |
|
||||||
| CAP-016 | S3 state bucket exists + readable (live AWS) | live-aws | **Skipped** | 245 | state bucket nova-tfstate-581513795199-us-east-1 absent (post-v1.11-teardown, D-096) |
|
| CAP-016 | S3 state bucket exists + readable (live AWS) | live-aws | **Verified** | 324 | state bucket exists, keys=['platform/terraform.tfstate', 'spike/alb/dev/terraform.tfstate', 'spike/cdn/dev/terraform.tfstate', 'spike/ci-vpc/terraform.tfstate', |
|
||||||
| CAP-017 | DynamoDB nova-contracts table (lifecycle pipeline evidence) | lifecycle-pipeline | **Verified** | 586 | terraform files present + fmt -check passes + simple/complex contracts resolve |
|
| CAP-017 | DynamoDB acdl-contracts table (lifecycle pipeline evidence) | lifecycle-pipeline | **Verified** | 520 | terraform files present + simple/complex contracts resolve |
|
||||||
| CAP-018 | Lambda contract-ingestor (local stub + lifecycle evidence) | lifecycle-pipeline | **Verified** | 138 | LocalLambdaStub instantiates (local tier evidence) |
|
| CAP-018 | Lambda contract-ingestor (local stub + lifecycle evidence) | lifecycle-pipeline | **Verified** | 137 | LocalLambdaStub instantiates (local tier evidence) |
|
||||||
| CAP-019 | ECS cluster + service (L2 microservice lifecycle evidence) | lifecycle-pipeline | **Verified** | 519 | L2 composition resolves (simple + complex contracts; offline proxy) |
|
| CAP-019 | ECS cluster + service (L2 microservice lifecycle evidence) | lifecycle-pipeline | **Verified** | 534 | L2 composition resolves (simple + complex contracts) |
|
||||||
| CAP-020 | CloudFront + WAF (L2 static-assets lifecycle evidence) | lifecycle-pipeline | **Verified** | 521 | L2 composition resolves (simple + complex contracts; offline proxy) |
|
| CAP-020 | CloudFront + WAF (L2 static-assets lifecycle evidence) | lifecycle-pipeline | **Verified** | 567 | L2 composition resolves (simple + complex contracts) |
|
||||||
| CAP-021 | uptime-kuma (L1 uptime lifecycle evidence) | lifecycle-pipeline | **Verified** | 562 | terraform files present + fmt -check passes + simple/complex contracts resolve |
|
| CAP-021 | uptime-kuma (L1 uptime lifecycle evidence) | lifecycle-pipeline | **Verified** | 606 | terraform files present + simple/complex contracts resolve |
|
||||||
| CAP-022 | OIDC role (L1 iam-role lifecycle evidence) | lifecycle-pipeline | **Verified** | 611 | terraform files present + fmt -check passes + simple/complex contracts resolve |
|
| CAP-022 | OIDC role (L1 iam-role lifecycle evidence) | lifecycle-pipeline | **Verified** | 529 | terraform files present + simple/complex contracts resolve |
|
||||||
|
|||||||
+715
-604
File diff suppressed because it is too large
Load Diff
+792
-257
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,324 @@
|
|||||||
|
# ACDL v1.11 — Multi-Persona Code Review (P60–P65 retrofit + new work)
|
||||||
|
|
||||||
|
**Reviewer:** ci-code-reviewer (model: glm-5.2)
|
||||||
|
**Scope:** v1.11 milestone, branch `milestone/v1.11-restart` — 22 commits
|
||||||
|
(e1bb214..8c09580), 25 files, +790/-142 lines
|
||||||
|
**Date:** 2026-07-29
|
||||||
|
|
||||||
|
## Commits reviewed
|
||||||
|
|
||||||
|
| Commit | Phase | Type | Summary |
|
||||||
|
|--------|-------|------|---------|
|
||||||
|
| e1bb214 | 60 | docs | retrofit plan — L1 lifecycle pipeline live-run |
|
||||||
|
| bc9058f | 60 | feat | L1 module lifecycle live run — module fixes (retrofit) |
|
||||||
|
| bb3ac7c | 60 | fix | WAF scope case + VPC modify DependencyViolation |
|
||||||
|
| 0c5c4d1 | 61 | docs | create phase plan — L2 lifecycle pipeline author |
|
||||||
|
| 361fe60 | 61 | feat | L2 lifecycle pipeline — extend matrix + workflows + tests |
|
||||||
|
| 9ac5720 | 61 | verify | 4-layer gate — PASS |
|
||||||
|
| 6441633 | 62 | docs | create phase plan — L2 lifecycle pipeline live run |
|
||||||
|
| 4dad967 | 60 | fix | ALB target group name_prefix — avoid orphaned conflicts |
|
||||||
|
| adfcf86 | 63 | docs | create phase plan — regression registry + cost docs |
|
||||||
|
| b71e63c | 63 | feat | CAP-017..022 regression registry + COST.md |
|
||||||
|
| beac2ef | 63 | verify | 4-layer gate — PASS |
|
||||||
|
| 06f4fc7 | 60 | fix | free disk space in lifecycle jobs |
|
||||||
|
| 92bb03e | 64 | docs | create phase plan — pre-mortem + teardown |
|
||||||
|
| 186cdde | 64 | feat | pre-mortem — v1.10 post-mortem + forward pre-mortem |
|
||||||
|
| 4102950 | 64 | feat | pre-mortem + teardown plan — HITL escalation CHG0680001 |
|
||||||
|
| 7c4fc1f | 64 | feat | teardown complete — zero live ACDL resources remain |
|
||||||
|
| a52f8a5 | 64 | verify | 4-layer gate — PASS |
|
||||||
|
| a03c019 | 60/62 | fix | ALB name_prefix + adapter dedup + L2 composition wiring |
|
||||||
|
| 93a6598 | 65 | docs | create phase plan — rewrite caps + decks |
|
||||||
|
| 6394801 | 65 | feat | rewrite caps — CAP-017..022 Verified via lifecycle pipeline |
|
||||||
|
| fc91f24 | 65 | verify | 4-layer gate — PASS |
|
||||||
|
| 8c09580 | 65 | docs | update v1.11 status — all phases complete |
|
||||||
|
|
||||||
|
## P0 issues (0)
|
||||||
|
|
||||||
|
No blocking issues found. The targeted fixes are correct for their stated
|
||||||
|
purposes. The 447 fast offline tests pass (485/490 collected; 5 slow
|
||||||
|
deselected, including 2 slow regression-integration tests that exercise the
|
||||||
|
CAPABILITY_REGISTRY against the live codebase).
|
||||||
|
|
||||||
|
## P1 issues (5 — should fix)
|
||||||
|
|
||||||
|
### P1-1: Adapter dedup silently drops resources whose module is not in the registry
|
||||||
|
[correctness] `adapters/terraform/adapter.py:159-170`
|
||||||
|
|
||||||
|
The new dedup loop only adds resources to `seen` when `tf_dir` is truthy
|
||||||
|
(in the registry). A resource whose module is missing from the registry is
|
||||||
|
**silently dropped** from `merged` — it never reaches `_emit_module_block`,
|
||||||
|
so no error is raised. The pre-dedup code (`parts.extend(... for r in
|
||||||
|
resources)`) would have raised `ValueError("no terraform_dir in registry
|
||||||
|
for module ...")` via `_emit_module_block`, surfacing the misconfiguration.
|
||||||
|
|
||||||
|
Confirmed by simulation: two resources, one with `module: nonexistent@1.0.0`,
|
||||||
|
produces a `merged` list of length 1 — the unknown-module resource vanishes
|
||||||
|
without diagnostic.
|
||||||
|
|
||||||
|
**Recommendation:** in the dedup loop, when `tf_dir` is `None`, either
|
||||||
|
(a) raise immediately (preserving the prior contract), or (b) append the
|
||||||
|
resource to a separate `unknown` list and extend `parts` with it so
|
||||||
|
`_emit_module_block` raises the descriptive error. As written, a typo in
|
||||||
|
a composition's `module` field (e.g. `iam-role@1.0.0` vs `iam_roles@1.0.0`)
|
||||||
|
will silently omit a resource from the emitted terraform — a class of
|
||||||
|
defect the v1.10 sweep was specifically created to catch.
|
||||||
|
|
||||||
|
### P1-2: L2 static-assets "modify" example is a no-op — complex ≡ simple
|
||||||
|
[correctness] `modules/l2/static-assets/examples/complex.yml`,
|
||||||
|
`modules/l2/static-assets/composition.json`
|
||||||
|
|
||||||
|
The complex.yml comment claims "Modify variant: same bucket_name as simple
|
||||||
|
(in-place modify, adds CDN + WAF)". But resolving both examples yields
|
||||||
|
**identical** resource sets: `['s3','cloudfront-distribution',
|
||||||
|
'cloudfront-originaccesscontrol','waf','kms']`. The CDN and WAF are
|
||||||
|
**always present** in the static-assets composition (they are unconditional
|
||||||
|
children + wires); the `waf_enabled`, `default_ttl`, `max_ttl`,
|
||||||
|
`price_class`, `viewer_protocol_policy` inputs in complex.yml have **no
|
||||||
|
corresponding wires** in composition.json and are silently dropped at
|
||||||
|
resolve time. So the L2 static-assets lifecycle cell's "modify" step
|
||||||
|
applies a contract that produces the same terraform as "simple" — it
|
||||||
|
exercises `terraform apply` twice with no change, not a true modify.
|
||||||
|
|
||||||
|
This is not a regression (the inputs were never wired), but the
|
||||||
|
CAPABILITY_INVENTORY claim "CAP-020 Verified live-aws via L2 static-assets
|
||||||
|
lifecycle pipeline (apply/modify/destroy exit 0)" overstates what the
|
||||||
|
modify step proves: it proves idempotent re-apply, not in-place modify.
|
||||||
|
|
||||||
|
**Recommendation:** either (a) wire `waf_enabled`/`default_ttl`/etc. in
|
||||||
|
composition.json so the complex contract genuinely differs, or (b) correct
|
||||||
|
the comment + CAPABILITY_INVENTORY wording to "apply + idempotent re-apply
|
||||||
|
+ destroy" rather than "apply/modify/destroy". The microservice complex
|
||||||
|
example, by contrast, is a real modify (desired_count 1→2) — that one is
|
||||||
|
fine.
|
||||||
|
|
||||||
|
### P1-3: L2 lifecycle scripts ignore the ci-vpc-outputs.json argument
|
||||||
|
[correctness] `scripts/run_l2_lifecycle_test.sh:14`,
|
||||||
|
`scripts/run_l2_lifecycle_destroy.sh:12`
|
||||||
|
|
||||||
|
Both L2 scripts declare `Usage: ... <module> <example> [ci-vpc-outputs.json]`
|
||||||
|
but neither reads `$3`/`$2`. The microservice composition references the
|
||||||
|
platform VPC via `terraform_remote_state` (data source), and the script
|
||||||
|
sets `ACDL_REMOTE_STATE_KEY=spike/ci-vpc/terraform.tfstate` so the data
|
||||||
|
source reads from the CI VPC state — that part is correct. But the
|
||||||
|
`ci-vpc-outputs.json` argument is positional noise: the workflow passes
|
||||||
|
it (`run_l2_lifecycle_test.sh ${{ matrix.module }} simple
|
||||||
|
/tmp/ci-vpc-outputs.json`) and it is silently ignored. The L1 scripts
|
||||||
|
(`run_lifecycle_test.sh`) inject VPC outputs by rewriting the contract in
|
||||||
|
Python; the L2 path takes a different approach (remote state) and does not
|
||||||
|
need the file, so the argument is vestigial, not a bug — but the usage
|
||||||
|
string advertises a feature the script does not provide, which will
|
||||||
|
confuse a future maintainer who assumes parity with the L1 scripts.
|
||||||
|
|
||||||
|
**Recommendation:** remove the `[ci-vpc-outputs.json]` token from the
|
||||||
|
usage strings (or add a comment explaining the L2 path uses remote state
|
||||||
|
and the arg is accepted-but-ignored for workflow-argument parity).
|
||||||
|
|
||||||
|
### P1-4: CAPABILITY_INVENTORY summary table is stale (says 16, body lists 22)
|
||||||
|
[maintainability] `.ciagent/CAPABILITY_INVENTORY.md:9-16`
|
||||||
|
|
||||||
|
The Summary table still reads "Verified 16 / Decayed 0 / Broken 0 / Total
|
||||||
|
16" — the v1.10 sweep count. The body (lines 93-110) now lists CAP-017..022
|
||||||
|
as **Verified** via the lifecycle pipeline, bringing the real total to 22.
|
||||||
|
The two counts disagree: a reader scanning the summary sees 16 Verified; a
|
||||||
|
reader scanning the inventory body sees 22 Verified. The PRE_MORTEM
|
||||||
|
(lines 82-83) and CAPABILITY_INVENTORY prose both assert all 22 are
|
||||||
|
Verified, but the headline table was not updated in the P65 rewrite.
|
||||||
|
|
||||||
|
**Recommendation:** update the Summary table to "Verified 22 / Decayed 0
|
||||||
|
/ Broken 0 / Total 22" and add CAP-017..022 rows to the Inventory table
|
||||||
|
(the body section "Cloud capabilities NOT re-verified..." is now
|
||||||
|
mis-titled — they ARE verified, just via the lifecycle-pipeline tier).
|
||||||
|
|
||||||
|
### P1-5: CAP-017..022 regression checks are offline proxies, not pipeline evidence
|
||||||
|
[adversarial] `core/regression_verify.py:432-519`,
|
||||||
|
`.ciagent/CAPABILITY_INVENTORY.md:93-110`
|
||||||
|
|
||||||
|
The CAP-017..022 checks (`_check_cap_017_dynamodb` etc.) call
|
||||||
|
`_check_lifecycle_module_terraform` / `_check_lifecycle_l2_module`, which
|
||||||
|
verify only that (a) the terraform dir + required files exist and (b) the
|
||||||
|
example contracts **resolve** (resolver exit 0). They do **not** run
|
||||||
|
`terraform validate`, do not run apply/modify/destroy, and do not query
|
||||||
|
the pipeline's actual green/red status. The CAPABILITY_INVENTORY claims
|
||||||
|
"Evidence = L1 rds module lifecycle pipeline green (terraform validate +
|
||||||
|
contracts resolve)" — but the check does not run terraform validate, and
|
||||||
|
"lifecycle pipeline green" is asserted, not verified by the regression
|
||||||
|
gate.
|
||||||
|
|
||||||
|
This means the lifecycle-pipeline evidence CAN be faked at the regression
|
||||||
|
tier: a module whose terraform is syntactically broken (e.g.
|
||||||
|
`scope = upper(var.scope)` removed, or a missing required variable) would
|
||||||
|
still pass `_check_lifecycle_module_terraform` as long as the files exist
|
||||||
|
and the resolver runs. The real green/red evidence lives only in the
|
||||||
|
workflow run history (Gitea/GitHub Actions), which the regression gate does
|
||||||
|
not read.
|
||||||
|
|
||||||
|
**Mitigation context:** the modules-lifecycle workflow IS the live
|
||||||
|
evidence — when it runs on a PR, the cells genuinely apply/modify/destroy
|
||||||
|
against live AWS. The gap is that the *regression gate* (which gates
|
||||||
|
milestone COMPLETE) trusts the workflow will be run, rather than proving it
|
||||||
|
was run and passed. A milestone could in principle be marked COMPLETE with
|
||||||
|
CAP-017..022 "Verified" if the regression gate runs but the workflow was
|
||||||
|
never executed (e.g. workflow_dispatch never triggered, or the PR was
|
||||||
|
merged without the workflow running).
|
||||||
|
|
||||||
|
**Recommendation:** (a) tighten the CAP-017..022 check docstrings + the
|
||||||
|
CAPABILITY_INVENTORY wording to "terraform files present + contracts
|
||||||
|
resolve (offline proxy; live apply/modify/destroy verified by the
|
||||||
|
modules-lifecycle workflow run, not by this gate)"; and/or (b) add a
|
||||||
|
`terraform validate` step to `_check_lifecycle_module_terraform` (slow but
|
||||||
|
cheap relative to init+apply) so at least HCL syntax is verified at the
|
||||||
|
gate. The teardown trustworthiness (P64) is good — `ci-vpc-destroy` runs
|
||||||
|
`if: always()` and the decommission `---ci---` block is the audit trail.
|
||||||
|
|
||||||
|
## P2 issues (4 — post-hoc)
|
||||||
|
|
||||||
|
### P2-1: ALB `name_prefix = "tg-ci-"` discards `var.name` entirely
|
||||||
|
[maintainability] `modules/l1/alb/terraform/main.tf:9`
|
||||||
|
|
||||||
|
The fix replaces `name = var.name` with `name_prefix = "tg-ci-"` (a
|
||||||
|
hardcoded literal). This is the correct terraform pattern for
|
||||||
|
create_before_destroy resources with name-uniqueness constraints, and the
|
||||||
|
commit message explains the orphaned-resource motivation well. However
|
||||||
|
the target group name is now non-configurable (always `tg-ci-<random>`),
|
||||||
|
and the `var.name` variable is no longer used by the target group at all
|
||||||
|
(it is still used by `aws_lb.this.name`). A consumer who sets `name:
|
||||||
|
my-app` gets an LB named `my-app` but a target group named `tg-ci-...` —
|
||||||
|
inconsistent tagging. Consider `name_prefix = "${var.name}-"` to keep the
|
||||||
|
consumer's name as a prefix while preserving uniqueness. Post-hoc: not
|
||||||
|
blocking; the lifecycle pipeline is the only current consumer and `tg-ci-`
|
||||||
|
is fine for CI.
|
||||||
|
|
||||||
|
### P2-2: No test covers the new dedup merge behavior or `ACDL_REMOTE_STATE_KEY`
|
||||||
|
[testing] `tests/test_adapter.py`, `tests/test_pipeline_contract.py`
|
||||||
|
|
||||||
|
The adapter gained (a) a dedup-merge loop for multi-resource L1s sharing a
|
||||||
|
terraform dir and (b) `ACDL_REMOTE_STATE_KEY` env override for the remote
|
||||||
|
state data block. Neither has a unit test:
|
||||||
|
- No test asserts that two resources with the same `module` collapse to one
|
||||||
|
`module "<first_id>" { ... }` block with merged inputs.
|
||||||
|
- No test asserts that `ACDL_REMOTE_STATE_KEY` overrides the default
|
||||||
|
`platform/terraform.tfstate` key in the emitted `data
|
||||||
|
terraform_remote_state` block.
|
||||||
|
- No test covers the L2 lifecycle scripts (`run_l2_lifecycle_test.sh` /
|
||||||
|
`run_l2_lifecycle_destroy.sh`) — the L1 equivalents are also untested at
|
||||||
|
the script level, so this is consistent with existing practice, but the
|
||||||
|
L2 scripts are new in this session and the `ACDL_REMOTE_STATE_KEY` wiring
|
||||||
|
is the load-bearing correctness mechanism for the microservice lifecycle.
|
||||||
|
|
||||||
|
The 485 offline tests adequately cover the *contract* (pipeline schema,
|
||||||
|
byte-identical workflows, matrix membership, job needs) — the
|
||||||
|
`TestModulesLifecyclePipeline` class is solid (89 tests pass). The gap is
|
||||||
|
adapter *behavior* at the unit level.
|
||||||
|
|
||||||
|
**Recommendation:** add a `test_adapter_dedup_merges_same_module` and a
|
||||||
|
`test_adapter_remote_state_key_override` to `tests/test_adapter.py`.
|
||||||
|
|
||||||
|
### P2-3: `waf` complex example uses `scope: CLOUDFRONT` but WAF scope is now `upper()`'d
|
||||||
|
[correctness] `modules/l1/waf/examples/complex.yml:8`,
|
||||||
|
`modules/l1/waf/terraform/locals.tf:3`
|
||||||
|
|
||||||
|
The `locals.tf` change `scope = upper(var.scope)` is the correct defensive
|
||||||
|
fix (the AWS provider requires `CLOUDFRONT`/`REGIONAL` regardless of input
|
||||||
|
case). The complex.yml was simultaneously changed from `scope: cloudfront`
|
||||||
|
to `scope: CLOUDFRONT`. Both are now correct, but the example's uppercase
|
||||||
|
value is now redundant with the `upper()` — a future reader may wonder
|
||||||
|
which is authoritative. Minor; the defensive `upper()` is the right call
|
||||||
|
and the example matching it is fine. Post-hoc only.
|
||||||
|
|
||||||
|
### P2-4: COST.md reproducibility snippet could leak the account ID via CloudTrail
|
||||||
|
[security] `.ciagent/COST.md:106`
|
||||||
|
|
||||||
|
COST.md contains the AWS account ID `581513795199` in multiple places
|
||||||
|
(summary, S3 bucket name, methodology). This is consistent with the rest of
|
||||||
|
the repo (the bucket name `acdl-tfstate-581513795199-us-east-1` is hardcoded
|
||||||
|
in `adapter.py:130` and `adapter.py:146`), so it is not new leakage and not
|
||||||
|
a regression. No actual secret material (access keys, secret access keys)
|
||||||
|
appears in COST.md, PRE_MORTEM.md, CAPABILITY_INVENTORY.md, or the workflow
|
||||||
|
files — all credential references use `${{ secrets.ACDL_AWS_* }}` or env
|
||||||
|
var names only. The `.ciagent/PROJECT.md:731` reference to a deactivated
|
||||||
|
root key is redacted (`AKIA…ROOT-DEACTIVATED`). **No credential leakage
|
||||||
|
found.** The P2 is only that the account ID is published; if the account
|
||||||
|
is meant to be opaque, this is an accepted exposure (the bucket name
|
||||||
|
already requires it).
|
||||||
|
|
||||||
|
## What is correct
|
||||||
|
|
||||||
|
- **WAF scope fix (`upper(var.scope)`):** correct and defensive; AWS
|
||||||
|
provider v5 requires uppercase. The `local.scope` indirection is clean.
|
||||||
|
- **VPC `create_before_destroy` + same-CIDR complex example:** correct
|
||||||
|
fix for the DependencyViolation on modify. Using the same CIDR means
|
||||||
|
terraform modifies in-place rather than replacing the VPC (which would
|
||||||
|
cascade-fail on dependent subnets/IGW). The `create_before_destroy`
|
||||||
|
lifecycle is the right guard.
|
||||||
|
- **ALB `name_prefix`:** correct terraform pattern for
|
||||||
|
create_before_destroy + name-uniqueness; well-documented commit message.
|
||||||
|
- **Adapter dedup (for the registered-module case):** correct —
|
||||||
|
multi-resource L1s like cloudfront (distribution + OAC) correctly merge
|
||||||
|
into one `module "cloudfront-distribution" { ... }` block. The merge
|
||||||
|
preserves first-resource inputs and union of outputs. (The
|
||||||
|
unregistered-module drop is P1-1, a separate concern.)
|
||||||
|
- **L2 composition wiring (`ecr.inputs.name`, `roles.inputs.role_name`):**
|
||||||
|
correct. Resolving microservice complex now shows `ecr.inputs.name =
|
||||||
|
"app-repo"` and `roles.inputs.role_name = "app-role"` (defaults applied
|
||||||
|
since the contract doesn't set `name`). Previously these would have hit
|
||||||
|
the "missing required arg" defect class from the v1.10 sweep.
|
||||||
|
- **Microservice complex = real modify:** `desired_count: 2` (vs simple's
|
||||||
|
default 1) is a genuine in-place modify — confirmed by resolving both
|
||||||
|
and diffing `service-service.inputs.desired_count`.
|
||||||
|
- **`ACDL_REMOTE_STATE_KEY` plumbing:** correct end-to-end — the L2 scripts
|
||||||
|
export it, the adapter reads it with a sensible default, and the
|
||||||
|
microservice composition's `terraform_remote_state` data block picks it
|
||||||
|
up. This cleanly separates the short-lived CI VPC state from the
|
||||||
|
long-lived platform VPC state.
|
||||||
|
- **Workflow structure:** `l2-lifecycle` correctly `needs: ci-vpc-apply`;
|
||||||
|
`ci-vpc-destroy` correctly `needs: [lifecycle, l2-lifecycle]` and
|
||||||
|
`if: always()`. The 7 new L2 pipeline-contract tests assert all of this.
|
||||||
|
- **Byte-identical workflows:** `.gitea` and `.github` modules-lifecycle.yml
|
||||||
|
are byte-identical (test asserts this); the `test_workflow_has_four_jobs`
|
||||||
|
rename from three→four is correct.
|
||||||
|
- **Adapter line count:** 194 lines — under the 200-line ceiling, still a
|
||||||
|
clean stateless assembler. The dedup logic added ~16 lines without
|
||||||
|
bloating.
|
||||||
|
- **Teardown verification (P64):** trustworthy in structure — the
|
||||||
|
`ci-vpc-destroy` job runs unconditionally and the decommission
|
||||||
|
`---ci---` block is the audit trail. The adversarial concern (P1-5) is
|
||||||
|
about the regression gate trusting the workflow ran, not about the
|
||||||
|
teardown itself being fakeable.
|
||||||
|
- **Security:** no credential leakage in any reviewed file. All AWS auth
|
||||||
|
in workflows uses `${{ secrets.* }}`; COST.md references only env var
|
||||||
|
names and a redacted/deactivated root key ID.
|
||||||
|
|
||||||
|
## Test coverage assessment (485 offline tests)
|
||||||
|
|
||||||
|
- **Adequate:** pipeline contract (89 tests), schema validation, contract
|
||||||
|
resolution, adapter emission (basic), confidence signal, outbox,
|
||||||
|
interpolation, local emulators, module-standards file presence, design-doc
|
||||||
|
currency.
|
||||||
|
- **Gaps (post-hoc):**
|
||||||
|
1. Adapter dedup merge behavior (P2-2) — no unit test.
|
||||||
|
2. `ACDL_REMOTE_STATE_KEY` override (P2-2) — no unit test.
|
||||||
|
3. CAP-017..022 regression checks (P1-5) — not exercised at the unit
|
||||||
|
level; the 2 slow tests in `test_verify_regression_mode.py` run the
|
||||||
|
full registry but are `@pytest.mark.slow` and deselected from the
|
||||||
|
fast suite, so a CI run of the 485 fast tests does not verify
|
||||||
|
CAP-017..022 even at the offline-proxy level.
|
||||||
|
4. WAF `upper()` scope — no test asserts the locals transform; relies
|
||||||
|
on the lifecycle pipeline cell to catch a regression.
|
||||||
|
5. ALB `name_prefix` — no test asserts the target group uses
|
||||||
|
`name_prefix` (P2-1 context).
|
||||||
|
|
||||||
|
The 485 count is honest (447 pass fast, 5 deselected slow, 485/490
|
||||||
|
collected). The gap is behavioral coverage of the new adapter + module
|
||||||
|
logic, not contract/schema coverage.
|
||||||
|
|
||||||
|
## Verdict
|
||||||
|
|
||||||
|
**PASS with P1 flags for post-hoc review.** No P0 fixes applied. The
|
||||||
|
milestone's structural controls (regression gate, mandatory teardown,
|
||||||
|
byte-identical workflows, byte-identical contract↔workflow tests) are
|
||||||
|
sound. The most material finding is P1-5 (the regression gate's
|
||||||
|
CAP-017..022 evidence is an offline proxy, not live pipeline evidence) —
|
||||||
|
this is a repeat of the v1.10 "VERIFY was diff-scoped" structural defect
|
||||||
|
in a milder form: the gate trusts the workflow was run rather than proving
|
||||||
|
it. The mitigations in PRE_MORTEM (FM-1..FM-4) acknowledge related risks;
|
||||||
|
P1-5 is the specific instance for the lifecycle-pipeline tier.
|
||||||
+1411
-282
File diff suppressed because it is too large
Load Diff
@@ -1,286 +0,0 @@
|
|||||||
# Nova — System State (what exists today)
|
|
||||||
|
|
||||||
> **PO-owned catalog of shipped capabilities.** Updated at every milestone
|
|
||||||
> ship (P final). Additive only — entries are appended, never rewritten,
|
|
||||||
> unless a capability is explicitly deprecated (then marked, not deleted).
|
|
||||||
> Read by the PO upstream of the PDLC before authoring new REQ-NNN specs,
|
|
||||||
> and by CIAgent at SPECIFY for capability awareness.
|
|
||||||
>
|
|
||||||
> **Authority:** this file is *descriptive of shipped state*, not
|
|
||||||
> authoritative for live phase/ship state — that's `CHECKPOINT.json`. For
|
|
||||||
> *why*, read `NORTH_STAR.md`. For *how*, read `ARCHITECTURE.md`. For
|
|
||||||
> *what was decided*, read `PROJECT.md` load-bearing decisions.
|
|
||||||
>
|
|
||||||
> **Last milestone ship:** v1.27 (`v1.26.3`, 2026-08-19) — PO State Catalog
|
|
||||||
> & Ciagent Compression NFR milestone. No new capabilities this
|
|
||||||
> milestone (NFR); v1.27 authored this file + compressed `.ciagent/`.
|
|
||||||
> **Next update:** at v1.28 ship.
|
|
||||||
|
|
||||||
## How to use this file (PO)
|
|
||||||
|
|
||||||
- Before writing a new REQ: search this file for the capability you
|
|
||||||
intend to spec. If it exists, extend it; do not re-spec it under a new
|
|
||||||
REQ-NNN.
|
|
||||||
- Respect the **Invariants** below — they are load-bearing and
|
|
||||||
cross-cutting. A new REQ that violates an invariant requires a
|
|
||||||
`CLARIFY` decision recorded in PROJECT.md.
|
|
||||||
- Anchor each new REQ to a **Domain**; new domains require a PO
|
|
||||||
decision recorded in CLARIFY.
|
|
||||||
- When a capability is deprecated (replaced, removed, or
|
|
||||||
re-architecture), append a `Deprecated` row marking the milestone +
|
|
||||||
replacement; do not delete the original entry.
|
|
||||||
|
|
||||||
## Invariants (PO-owned — do not violate in new REQs)
|
|
||||||
|
|
||||||
> Distilled from `PROJECT.md` load-bearing decisions D-034..D-072 +
|
|
||||||
> W1..BA + Q1.3. Cite the decision ID when an REQ touches one.
|
|
||||||
|
|
||||||
- **INV-1 (Contract surface):** The only PDLC→Nova boundary is
|
|
||||||
`schemas/contract.schema.json` + `schemas/submission-readiness.schema.json`
|
|
||||||
(D-133). All consumer intent enters through one of these. Nova never
|
|
||||||
reaches into upstream PDLC.
|
|
||||||
- **INV-2 (Confidence inputs):** Six canonical inputs — policy (0.30),
|
|
||||||
validation (0.25), freshness (0.10), source (0.15), history (0.10),
|
|
||||||
nfrs (0.10). Weights frozen for v1 (D-040). `critical` severity =
|
|
||||||
hard-block via `PENALTY["critical"]: None` (defense-in-depth behind the
|
|
||||||
declarative `block-on-any-critical` meta-policy).
|
|
||||||
- **INV-3 (HITL gates):** dev = autonomous (≥0.50); qa = HITL (≥0.75);
|
|
||||||
prod = HITL (≥0.90); dr = HITL (≥0.95). Approver identity = Gitea
|
|
||||||
`gitea.actor` of the `workflow_dispatch` (D-042). Separation-of-duties
|
|
||||||
on prod reads `approver_qa` from the DynamoDB outbox.
|
|
||||||
- **INV-4 (Engine is swappable):** The policy engine is behind the
|
|
||||||
`PolicyEngine` protocol (`core/policy_engine.py`, v1.25). Confidence
|
|
||||||
signal + pipeline import only the protocol, never a concrete engine.
|
|
||||||
`kyverno-json` is the v1.25 default; `OPA` (or other) implements the
|
|
||||||
same 3-method protocol to replace it.
|
|
||||||
- **INV-5 (Adapter is stateless):** `adapters/terraform/adapter.py` owns
|
|
||||||
no module content — no `TYPE_MAP`/`INPUT_MAP`/`OUTPUT_MAP` (v1.11
|
|
||||||
rewrite). A new stack type requires a new L1 module
|
|
||||||
(`modules/l1/<name>/`) + `registry.json` entry, not an adapter change.
|
|
||||||
- **INV-6 (Audit stream is immutable):** Outbox writes via SQLite
|
|
||||||
hash-chain today (D-083 deferred). S3 Object Lock / JWS tamper-
|
|
||||||
*resistant* ledger is a future milestone. Current stream is tamper-
|
|
||||||
*evident* (any tampering breaks the chain).
|
|
||||||
- **INV-7 (PCR schema is the moat):** `schemas/policy_check_result.schema.json`
|
|
||||||
shape is frozen across adapter swaps (v1.25 hard constraint). The
|
|
||||||
`engine` enum already includes `"kyverno"` + `"opa"`; new engines add
|
|
||||||
no enum value.
|
|
||||||
- **INV-8 (Long-lived creds forbidden):** §12.5. The D-039/D-047 per-run-
|
|
||||||
rotated-key waiver satisfies the *intent* (no *persistently* long-lived
|
|
||||||
key). Real OIDC federation is blocked on `go-gitea/gitea#36988`.
|
|
||||||
- **INV-9 (Two consumer surfaces, one platform):** L3A (developer) +
|
|
||||||
L3B (citizen dev) converge on the same contract schema, the same
|
|
||||||
policy envelope, and the same evidence stream.
|
|
||||||
- **INV-10 (Nova is downstream of PDLC):** Nova governs infra + delivery
|
|
||||||
only. Product backlog, code authorship, IDE workflows, application
|
|
||||||
business logic are upstream. Integration only via the validated
|
|
||||||
contract boundary (INV-1).
|
|
||||||
- **INV-11 (Pilot scope, v1.26):** Equities only (D-200). Single-
|
|
||||||
validator PoA (D-201). D-083 (Object Lock/JWS) stays deferred. Hot
|
|
||||||
path deferred (D-126). Multi-cloud deferred. Multi-validator BFT
|
|
||||||
deferred. The pilot runs `mode: full` for `dev` only (D-209); qa/prod/dr
|
|
||||||
stay placeholder (D-208, blocked by the pilot-readiness policy).
|
|
||||||
|
|
||||||
## Domains (capability groups)
|
|
||||||
|
|
||||||
1. Contract surface
|
|
||||||
2. Modules (L1 primitives + L2 patterns)
|
|
||||||
3. Policy engine
|
|
||||||
4. Confidence signal
|
|
||||||
5. Environments & promotion
|
|
||||||
6. Evidence stream & audit
|
|
||||||
7. Telemetry & metrics
|
|
||||||
8. Consumer surfaces (developer + agentic)
|
|
||||||
9. Pilot estate (v1.26)
|
|
||||||
10. Forge / CI runtime
|
|
||||||
|
|
||||||
## Capabilities (additive — one row per shipped capability)
|
|
||||||
|
|
||||||
> Tier: **local** = runs via emulating adapters (no AWS); **live-aws** =
|
|
||||||
> runs against the live AWS account `581513795199`;
|
|
||||||
> **lifecycle-pipeline** = verified via the `modules-lifecycle`
|
|
||||||
> pipeline's apply→modify→destroy matrix cell.
|
|
||||||
> CAP-NNN IDs cross-reference the regression gate at
|
|
||||||
> `core/regression_verify.py` (the machine registry). This file is the
|
|
||||||
> PO-facing narrative; the machine registry is the source of truth for
|
|
||||||
> the gate.
|
|
||||||
|
|
||||||
### Domain 1 — Contract surface
|
|
||||||
|
|
||||||
| ID | Capability | Shipped | Files | Controlling | Tier | Notes |
|
|
||||||
|----|-----------|---------|-------|-------------|------|-------|
|
|
||||||
| CAP-001 | `contract.schema.json` validates sample contracts | v1.1 / `v1.2.0` | `schemas/contract.schema.json` | REQ-001, D-... | local | shape: id/name/environment/infrastructure |
|
|
||||||
| CAP-002 | `environment.schema.json` validates env files | v1.9 / `v1.9.0` | `schemas/environment.schema.json` | REQ-040 | local | dev/qa/prod/dr env JSONs |
|
|
||||||
| CAP-006 | Contract interpolation expands `${env.*}` / `${contract.*}` | v1.9 / `v1.9.0` | `core/contract_resolver.py` | REQ-040 | local | per-env variants |
|
|
||||||
| — | Submission-readiness gate (superset of contract schema) | v1.18 / `v1.18.0` | `schemas/submission-readiness.schema.json`, `core/submission_readiness.py` | REQ-217, REQ-218, D-133 | local | the only PDLC→Nova boundary (INV-1) |
|
|
||||||
|
|
||||||
### Domain 2 — Modules (L1 primitives + L2 patterns)
|
|
||||||
|
|
||||||
> Source: `modules/registry.json` (the authoritative module catalog).
|
|
||||||
> STATE.md lists the *capability* of having a registered module;
|
|
||||||
> registry.json is the live registry.
|
|
||||||
|
|
||||||
| ID | Capability | Shipped | Files | Controlling | Tier | Notes |
|
|
||||||
|----|-----------|---------|-------|-------------|------|-------|
|
|
||||||
| CAP-003 | contract_resolver resolves `static-assets` (L2) | v1.1 / `v1.2.0` | `core/contract_resolver.py`, `modules/l2/static-assets/` | REQ-003 | local | CloudFront+WAF+S3 pattern |
|
|
||||||
| CAP-004 | contract_resolver resolves `microservice` (L2) | v1.2 / `v1.3.0` | `core/contract_resolver.py`, `modules/l2/microservice/` | REQ-004 | local | ECS Fargate pattern (6 L1 children) |
|
|
||||||
| CAP-005 | Terraform adapter compiles resolved stack to `.tf` | v1.1 / `v1.2.0` | `adapters/terraform/adapter.py` | REQ-005 | local | stateless assembler (v1.11); emits `module "<rid>" { source }` blocks |
|
|
||||||
| — | L1 `s3` primitive | v1.1 / `v1.2.0` | `modules/l1/s3/` | REQ-005 | lifecycle | versioning + SSE-KMS by default |
|
|
||||||
| — | L1 `vpc` primitive | v1.1 / `v1.2.0` | `modules/l1/vpc/` | REQ-005 | lifecycle | shared platform VPC (v1.11) |
|
|
||||||
| — | L1 `ecs-cluster` primitive | v1.1 / `v1.2.0` | `modules/l1/ecs-cluster/` | REQ-005 | lifecycle | |
|
|
||||||
| — | L1 `ecs-service` primitive | v1.1 / `v1.2.0` | `modules/l1/ecs-service/` | REQ-005 | lifecycle | execution_role_arn + task_role_arn wired (P4 W1 fix, v1.26) |
|
|
||||||
| — | L1 `iam-role` primitive | v1.1 / `v1.2.0` | `modules/l1/iam-role/` | REQ-005 | lifecycle | |
|
|
||||||
| — | L1 `alb` primitive | v1.1 / `v1.2.0` | `modules/l1/alb/` | REQ-005 | lifecycle | requires SG wire (P4 W1 fix, v1.26) |
|
|
||||||
| — | L1 `ecr` primitive | v1.1 / `v1.2.0` | `modules/l1/ecr/` | REQ-005 | lifecycle | |
|
|
||||||
| — | L1 `cloudfront` primitive | v1.7 / `v1.7.0` | `modules/l1/cloudfront/` | REQ-049, D-049 | lifecycle | OAC + WAF (production edge) |
|
|
||||||
| — | L1 `waf` primitive | v1.7 / `v1.7.0` | `modules/l1/waf/` | REQ-049, D-049 | lifecycle | |
|
|
||||||
| — | L1 `rds` primitive | v1.7 / `v1.7.0` | `modules/l1/rds/` | REQ-059, D-059 | lifecycle | multi-engine input (postgres/mysql/...) |
|
|
||||||
| — | L1 `kms-key` primitive | v1.8 / `v1.8.0` | `modules/l1/kms-key/` | REQ-069, D-069 | lifecycle | per-stack CMK; 90-day rotation |
|
|
||||||
| — | L1 `uptime` primitive | v1.8 / `v1.8.0` | `modules/l1/uptime/` | REQ-066, D-066 | lifecycle | uptime-kuma on ECS Fargate |
|
|
||||||
| — | L1 `dynamodb` primitive | v1.26 / `v1.25.2` | `modules/l1/dynamodb/` | REQ-322 | local | PK + optional SK; PAY_PER_REQUEST; encryption + PITR by default (v1.8 NFRs) |
|
|
||||||
| CAP-013 | `terraform init+validate+plan` live AWS (microservice) | v1.2 / `v1.3.0` | `adapters/terraform/adapter.py` | REQ-013 | live-aws | 14 resources; plan saved |
|
|
||||||
| CAP-014 | `terraform init+validate+plan` live AWS (static-assets) | v1.7 / `v1.7.0` | `adapters/terraform/adapter.py` | REQ-014 | live-aws | CloudFront+WAF+S3 plan OK |
|
|
||||||
| CAP-017 | DynamoDB `nova-contracts` table | v1.7 / `v1.7.0` | `core/lambda/`, `terraform/` | REQ-068, D-068 | lifecycle | PK `changeRequestId`, SK `submittedAt` (CMDB) |
|
|
||||||
| CAP-018 | Lambda contract-ingestor | v1.7 / `v1.7.0` | `core/lambda/contract_ingestor.py` | REQ-051, D-051 | lifecycle | local stub + lifecycle evidence |
|
|
||||||
| CAP-019 | ECS cluster + service (L2 microservice) | v1.7 / `v1.7.0` | `modules/l2/microservice/` | REQ-066 | lifecycle | apply/modify/destroy exit 0 |
|
|
||||||
| CAP-020 | CloudFront + WAF production stack | v1.7 / `v1.7.0` | `modules/l2/static-assets/` | REQ-049 | lifecycle | apply/modify/destroy exit 0 |
|
|
||||||
| CAP-021 | uptime-kuma monitoring primitive | v1.8 / `v1.8.0` | `modules/l1/uptime/` | REQ-066 | lifecycle | |
|
|
||||||
| CAP-022 | OIDC role for act_runner | v1.11 / `v1.11.0` | `terraform/bootstrap/` | REQ-116, D-039 | lifecycle | real OIDC blocked on go-gitea/gitea#36988 |
|
|
||||||
|
|
||||||
### Domain 3 — Policy engine
|
|
||||||
|
|
||||||
| ID | Capability | Shipped | Files | Controlling | Tier | Notes |
|
|
||||||
|----|-----------|---------|-------|-------------|------|-------|
|
|
||||||
| — | `PolicyEngine` Protocol + `PolicyEngineRegistry` | v1.25 / `v1.24.1` | `core/policy_engine.py` | REQ-291, REQ-292 | local | selects engine from `config.json.policy.engine`; `NullEngine` fallback when key absent |
|
|
||||||
| — | `KyvernoJsonEngine` adapter (shells to `kj scan`) | v1.25 / `v1.24.1` | `adapters/kyverno-json/kyverno_json_engine.py` | REQ-293, REQ-294 | local | `is_configured()` guards on `which kj`; `SKIPPED` PCR when absent |
|
|
||||||
| — | Contract policies (4) over consumer contract JSON | v1.25 / `v1.24.2` | `adapters/kyverno-json/policies/contract/` | REQ-295, REQ-296 | local | id-pattern, env-enum, infra-min-1, forbid-unknown-fields |
|
|
||||||
| — | Stack-IR policies (3) over resolved Target Stack IR | v1.25 / `v1.24.2` | `adapters/kyverno-json/policies/stack-ir/` | REQ-297, REQ-298, REQ-299 | local | tagging-standard, public-ingress, encryption-by-default |
|
|
||||||
| — | Plan-JSON policies (3) over `terraform show -json` | v1.25 / `v1.24.3` | `adapters/kyverno-json/policies/plan-json/` | REQ-300, REQ-301, REQ-302 | local | plaintext-secrets, iam-wildcard, kms-reference |
|
|
||||||
| — | Meta-policies over merged PCR list | v1.25 / `v1.24.3` | `adapters/kyverno-json/policies/meta/` | REQ-303 | local | `block-on-any-critical` (declarative critical-block); `tagging-rules-agree` (Checkov↔kj agree) |
|
|
||||||
| — | Regression-gate policies (3) over capability-inventory JSON | v1.25 / `v1.24.4` | `adapters/kyverno-json/policies/regression/` | REQ-304, REQ-305 | local | declarative mirrors of CAP-013/023/024 imperative checks |
|
|
||||||
| — | Pilot-readiness policy (no placeholder account) | v1.26 / `v1.25.3` | `adapters/kyverno-json/policies/pilot-readiness/no-placeholder-account.json` | REQ-320 | local | fail-closed gate; blocks apply on `account_id == "000000000000"` |
|
|
||||||
| — | Settlement-finality policy | v1.26 / `v1.25.3` | `adapters/kyverno-json/policies/settlement-finality/all-matches-committed.json` | REQ-315 | local | authored + tested; enforcement deferred to milestone that binds qa/prod/dr (D-208) |
|
|
||||||
| — | Checkov adapter (raw-finding source) | v1.7 / `v1.7.0` | `adapters/terraform/checkov_adapter.py` | REQ-053 | local | feeds meta-policies; `NOVA_TAG_NAMING` custom rule is the TF-static source of truth |
|
|
||||||
| — | Wiz adapter (raw-finding source) | v1.7 / `v1.7.0` | `adapters/wiz/` | REQ-053 | local | API findings; `is_configured()` guard |
|
|
||||||
| — | K8s Kyverno adapter (documentation-only) | v1.7 / `v1.7.0` | `adapters/kyverno/` | REQ-053, D-053 | local | inactive for Terraform-only stacks; activates when GitOps emits K8s manifests |
|
|
||||||
|
|
||||||
### Domain 4 — Confidence signal
|
|
||||||
|
|
||||||
| ID | Capability | Shipped | Files | Controlling | Tier | Notes |
|
|
||||||
|----|-----------|---------|-------|-------------|------|-------|
|
|
||||||
| CAP-007 | `confidence_signal.compute` returns a band | v1.1 / `v1.2.0` | `core/confidence_signal.py` | REQ-007, D-040 | local | 6 inputs (INV-2); band ∈ {pass, block} |
|
|
||||||
| — | `escalation_reason: 'confidence'` on `band == 'block'` | v1.26 / `v1.25.3` | `core/confidence_signal.py` | REQ-318 | local | grounds Human Escalation Frequency numerator |
|
|
||||||
|
|
||||||
### Domain 5 — Environments & promotion
|
|
||||||
|
|
||||||
| ID | Capability | Shipped | Files | Controlling | Tier | Notes |
|
|
||||||
|----|-----------|---------|-------|-------------|------|-------|
|
|
||||||
| — | env-JSON `state_backend` wiring | v1.26 / `v1.25.3` | `core/environments/*.json`, `adapters/terraform/adapter.py` | REQ-319, D-... | local | adapter reads `env.state_backend.bucket` (fallback to computed name) |
|
|
||||||
| — | Environment progression (dev autonomous → qa/prod/dr HITL) | v1.1 / `v1.2.0` | `core/env_transition.py`, `core/hitl_gates.py` | REQ-042, D-042 | local | destroy-on-environment-change (v1.24) |
|
|
||||||
| — | Decommission mode (2-step, HITL SRE gates) | v1.8 / `v1.8.0` | `core/env_transition.py`, `scripts/run_platform.sh` | REQ-070, D-070 | local | `mode: decommission` requires `changeRequestId` |
|
|
||||||
| — | Per-env mandatory metadata (W3.E) | v1.1 / `v1.2.0` | `schemas/submission-readiness.schema.json` | W3.E | local | dev=stack+env; qa+=e2e+load; prod+=runbook+dashboard+oncall; dr+=drDrillRef |
|
|
||||||
|
|
||||||
### Domain 6 — Evidence stream & audit
|
|
||||||
|
|
||||||
| ID | Capability | Shipped | Files | Controlling | Tier | Notes |
|
|
||||||
|----|-----------|---------|-------|-------------|------|-------|
|
|
||||||
| CAP-008 | outbox_writer builds a hash-chained item | v1.1 / `v1.2.0` | `core/outbox_writer.py` | REQ-008 | local | tamper-evident (INV-6); tamper-resistant deferred (D-083) |
|
|
||||||
| CAP-015 | DynamoDB outbox table exists + describable | v1.1 / `v1.2.0` | `core/outbox_writer.py` | REQ-015 | live-aws | `nova-outbox` (post-v1.26 re-bootstrap) |
|
|
||||||
| CAP-016 | S3 state bucket exists + readable | v1.1 / `v1.2.0` | `terraform/bootstrap/` | REQ-016 | live-aws | `nova-tfstate-581513795199-us-east-1` |
|
|
||||||
| — | Decision Ledger (SQLite hash-chain) | v1.17 / `v1.17.0` | `core/metrics/decision_ledger.py` | REQ-185, REQ-186 | local | cold store for metrics; `ai.decision.made` + `attestation.recorded` events |
|
|
||||||
| — | SSM Parameter Store deploy outputs (SecureString, KMS) | v1.7 / `v1.7.0` | `core/output_publisher.py` | REQ-050, D-050 | live-aws | `/acdl/{env}/{contractId}/{output_name}` |
|
|
||||||
| — | GitHub PR comment / job summary deploy outputs | v1.7 / `v1.7.0` | `scripts/run_platform.sh` | REQ-050, D-050 | local | no raw secrets in logs |
|
|
||||||
| — | Uniform error reporting via Lambda `report_error` | v1.7 / `v1.7.0` | `core/lambda/contract_ingestor.py` | REQ-055, D-055 | live-aws | GitHub issue on platform repo `acdl/acdl`; idempotent |
|
|
||||||
| — | Tagging standard enforcement (4 required tags) | v1.7 / `v1.7.0` | `schemas/tagging-standard.json`, `adapters/terraform/policy/custom_rules/nova_tagging.py` | REQ-054, D-054 | local | `nova:owner`, `nova:contract`, `nova:environment`, `nova:cost-center` |
|
|
||||||
| — | Encryption + deletion-protection by default | v1.8 / `v1.8.0` | `modules/l1/*/terraform/main.tf` | REQ-062, REQ-069, D-062, D-069, D-072 | local | per-stack CMK; managed KMS fallback for standalone L1 (D-072) |
|
|
||||||
|
|
||||||
### Domain 7 — Telemetry & metrics
|
|
||||||
|
|
||||||
| ID | Capability | Shipped | Files | Controlling | Tier | Notes |
|
|
||||||
|----|-----------|---------|-------|-------------|------|-------|
|
|
||||||
| CAP-023 | metrics collector runs + emits expected schema | v1.17 / `v1.17.0` | `core/metrics/collector.py` | REQ-194 | local | fact_run, fact_decision, fact_attestation dims |
|
|
||||||
| CAP-024 | unified deck structure (slide count, x3 arc, per-slide benefits) | v1.17 / `v1.17.0` | `docs/presentations/nova-autonomous-cloud-delivery-marp.md` | REQ-194 | local | single source-of-truth marp deck |
|
|
||||||
| — | Outcome backfill (`pending` → `succeeded`/`failed`) | v1.26 / `v1.25.3` | `core/metrics/outcome_backfill.py` | REQ-317 | local | idempotent + terminal; grounds AI Decision Accuracy |
|
|
||||||
| — | Trust Snapshot | v1.17 / `v1.17.0` | `metrics/TRUST_SNAPSHOT.md`, `core/metrics/trust_snapshot.py` | REQ-194 | local | leadership-ready trust verdict |
|
|
||||||
| — | PowerBI export (fact/dimension views + 8 placeholder views) | v1.17 / `v1.17.0` | `metrics/powerbi/` | REQ-194 | local | deferred metrics ship as documented-schema placeholders |
|
|
||||||
| — | Pre-apply Infracost estimate | v1.17 / `v1.17.0` | `scripts/run_platform.sh` | REQ-119 | local | `nova.cost.estimated`; actual-spend CUR reconciliation deferred (D-096) |
|
|
||||||
| — | Regression gate (`scripts/run_regression.sh`) | v1.10 / `v1.10.0` | `core/regression_verify.py`, `scripts/run_regression.sh` | REQ-090, REQ-121 | local | fails closed on any non-Verified CAP; CAP-001..025 |
|
|
||||||
|
|
||||||
### Domain 8 — Consumer surfaces (developer + agentic)
|
|
||||||
|
|
||||||
| ID | Capability | Shipped | Files | Controlling | Tier | Notes |
|
|
||||||
|----|-----------|---------|-------|-------------|------|-------|
|
|
||||||
| — | Reusable deploy workflow (`deploy.yml@v1.25`) | v1.5 / `v1.5.0` | `.github/workflows/deploy.yml`, `.gitea/workflows/deploy.yml` | REQ-105 | local | `workflow_call`; modes: full/plan-only/check-only/decommission |
|
|
||||||
| — | Consumer onboarding (developer + citizen-dev paths) | v1.1 / `v1.2.0` | `docs/ONBOARDING.md`, `docs/consumer-guide.md` | BA.E, W3.E | local | both end in a sandbox dev submission that must pass the confidence gate |
|
|
||||||
| — | Atelier MCP server (agentic validation) | v1.18 / `v1.18.0` | `mcp/atelier/server.py` | REQ-221, REQ-222 | local | `atelier.validate_against_principles` tool |
|
|
||||||
| — | 9 production-grade engineering skills | v1.18 / `v1.18.0` | `skills/{api,security,data,testing,observability,errors,devops,infrastructure-as-code,compliance}.md` | REQ-221, REQ-222, BA.A | local | indexed by `docs/skills.md`; review/agent-checklist.md gate |
|
|
||||||
| — | Module examples (validated against contract schema) | v1.7 / `v1.7.0` | `modules/<name>/examples/{simple,complex}.yml` | REQ-058, D-058 | local | examples cannot drift from schema silently |
|
|
||||||
|
|
||||||
### Domain 9 — Pilot estate (v1.26)
|
|
||||||
|
|
||||||
> The first real consumer estate. `nova-blockchain-exchange` repo
|
|
||||||
> (Gitea `continuous-intelligence/nova-blockchain-exchange`, local clone
|
|
||||||
> `/root/nova-blockchain-exchange`). Homegrown PoA blockchain, equities
|
|
||||||
> only, single validator, T+1 settlement finality = block commit.
|
|
||||||
|
|
||||||
| ID | Capability | Shipped | Files | Controlling | Tier | Notes |
|
|
||||||
|----|-----------|---------|-------|-------------|------|-------|
|
|
||||||
| CAP-026 | PoA blockchain core (block + ledger + validator) | v1.26 / `v1.25.1` | `chain/block.py`, `chain/ledger.py`, `chain/validator.py` | REQ-310, D-201 | local | single validator; SHA-256 hash chain; deterministic block production |
|
|
||||||
| CAP-027 | Order-matching engine (limit order book) | v1.26 / `v1.25.1` | `engine/order_book.py`, `engine/order.py` | REQ-311 | local | price-time priority; partial fills |
|
|
||||||
| CAP-028 | T+1 settlement service | v1.26 / `v1.25.1` | `settlement/service.py` | REQ-312 | local | idempotent; finality = block commit |
|
|
||||||
| CAP-029 | Consumer `contract.yaml` (blockchain exchange) | v1.26 / `v1.25.2` | `nova-blockchain-exchange/contract.yaml`, `contracts/*.yml` | REQ-313 | local | per-env variants (dev/qa/prod); validated against contract schema |
|
|
||||||
| CAP-030 | Consumer deploy via `deploy.yml@v1.25` (inline adapter) | v1.26 / `v1.25.2` | `nova-blockchain-exchange/.github/workflows/deploy.yml`, `.gitea/workflows/deploy.yml` | REQ-314 | local | no cross-repo `uses:` (SPEC §10 Q1); checkout `acdl/acdl @ v1.25` into `platform/`, run `run_platform.sh` |
|
|
||||||
| CAP-025 | Live-pilot-apply regression capability (round-trip) | v1.26 / `v1.25.3` | `core/regression_verify.py` | REQ-316 | local | contract→adapter→plan→policy→confidence→attestation→outbox round-trip assertion |
|
|
||||||
| CAP-031 | Live pilot apply evidence (`blkex-pilot-apply-v0.2`) | v1.26 / `v1.25.4` | `.ciagent/archive/P4-PILOT-RUN-EVIDENCE-v1.26.md` | REQ-316, REQ-321 | live-aws | confidence 0.800 pass; outcome backfilled; hash chain valid; live apply against `581513795199` |
|
|
||||||
| CAP-032 | AWS key rotation scheduled workflow | v1.26 / `v1.25.3` | `workflows-src/rotate-aws-key.yml` | SPEC §5.9 | local | daily rotation; forge-agnostic token name (REQ-230) |
|
|
||||||
|
|
||||||
### Domain 10 — Forge / CI runtime
|
|
||||||
|
|
||||||
| ID | Capability | Shipped | Files | Controlling | Tier | Notes |
|
|
||||||
|----|-----------|---------|-------|-------------|------|-------|
|
|
||||||
| CAP-009 | offline pytest suite passes | v1.1 / `v1.2.0` | `tests/` | REQ-009 | local | 844 tests (v1.26 baseline) |
|
|
||||||
| CAP-010 | `run_ci.sh` reproduces CI pipeline locally | v1.4 / `v1.4.0` | `scripts/run_ci.sh` | REQ-010 | local | offline; contract→resolver→stack→adapter→structure validated |
|
|
||||||
| CAP-011 | headline E2E — local tier (microservice) | v1.2 / `v1.3.0` | `scripts/run_local_e2e.sh` | REQ-011, D-092 | local | emulating adapters (no AWS) |
|
|
||||||
| CAP-012 | local E2E — static-assets (no ECS) | v1.1 / `v1.2.0` | `scripts/run_local_e2e.sh` | REQ-012 | local | |
|
|
||||||
| — | `platform-test.yml` CI workflow | v1.4 / `v1.4.0` | `.github/workflows/platform-test.yml` | REQ-010 | local | platform repo only (consumer CI is per-consumer) |
|
|
||||||
| — | `modules-lifecycle` pipeline (apply→modify→destroy matrix) | v1.11 / `v1.11.0` | `.github/workflows/modules-lifecycle.yml` | REQ-121, D-096 | live-aws | per-module lifecycle cell; `ci-vpc-destroy` always runs |
|
|
||||||
| — | `release.yml` (semver + floating tag maintenance) | v1.7 / `v1.7.0` | `.github/workflows/release.yml` | REQ-... | local | `v1.25` + `v1` floating tags force-moved on merge to main |
|
|
||||||
| — | IAM policy baseline (`acdl-spike-runner-policy`) | v1.11 / `v1.11.0` | `terraform/bootstrap/spike_runner_policy.json`, `.ciagent/IAM_POLICY.md` | REQ-116, D-095 | live-aws | regression-tested by `tests/test_iam_policy_baseline.py`; OIDC role `acdl-act-runner-role` (CAP-022) |
|
|
||||||
| — | Local emulating adapters (no AWS) | v1.10 / `v1.10.0` | `core/local_lambda_stub.py`, `scripts/run_local_e2e.sh` | D-092 | local | proves runtime behavior without live AWS |
|
|
||||||
|
|
||||||
## Archive pointers
|
|
||||||
|
|
||||||
- **v1.0–v1.24 capability narrative + the 2026-07-27 re-verification sweep:**
|
|
||||||
`.ciagent/archive/CAPABILITY_INVENTORY-v1.10.md` (moved from
|
|
||||||
`.ciagent/CAPABILITY_INVENTORY.md` at v1.27). CAP-NNN IDs in this file
|
|
||||||
cross-reference the regression gate at `core/regression_verify.py`.
|
|
||||||
- **v1.0–v1.24 milestone narrative:** `.ciagent/archive/PROJECT-v1.0-v1.24.md`.
|
|
||||||
- **v1.0–v1.24 requirements (REQ-01..REQ-290):** `.ciagent/archive/REQUIREMENTS-v1.0-v1.24.md`.
|
|
||||||
- **v1.0–v1.24 phase breakdowns:** `.ciagent/archive/ROADMAP-v1.0-v1.24.md`.
|
|
||||||
- **v1.0–v1.24 architecture history:** `.ciagent/archive/ARCHITECTURE-v1.0-v1.24.md`.
|
|
||||||
- **v1.26 pre-execution artifacts (CLARIFY, GRILL, IDEATE, RESEARCH):**
|
|
||||||
`.ciagent/archive/{CLARIFY,GRILL,IDEATE,RESEARCH}-v1.26.md` (decisions
|
|
||||||
D-200..D-213 folded into `PROJECT.md` load-bearing decisions + PLAN.md
|
|
||||||
binding revisions at v1.27 archive time).
|
|
||||||
- **v1.26 phase verifications:** `.ciagent/archive/{VERIFY-P03,VERIFY-P04,REVIEW-AUDIT-P05}.md`.
|
|
||||||
- **v1.26 live pilot run evidence:** `.ciagent/archive/P4-PILOT-RUN-EVIDENCE-v1.26.md`.
|
|
||||||
- **v1.21 autonomy thesis (folded into NORTH_STAR.md Vision):** `.ciagent/archive/AUTONOMY_THESIS-v1.21.md`.
|
|
||||||
- **v1.14 AWS cost report (predates v1.26 live pilot):** `.ciagent/archive/COST-v1.14.md`.
|
|
||||||
|
|
||||||
## Update discipline
|
|
||||||
|
|
||||||
This file is updated **once per milestone, at the P-final milestone-ship
|
|
||||||
wave** (Wave 3 "milestone ship" in `PLAN.md`), alongside
|
|
||||||
`ROADMAP.md`/`NORTH_STAR.md`/`REQUIREMENTS.md`:
|
|
||||||
|
|
||||||
1. Append new capability entries for each shipped REQ (one row per
|
|
||||||
capability; group by domain).
|
|
||||||
2. Mark any deprecated capability with a `Deprecated` row citing the
|
|
||||||
milestone + replacement.
|
|
||||||
3. Bump the "Last milestone ship" header.
|
|
||||||
4. Do not rewrite existing entries (additive only).
|
|
||||||
|
|
||||||
Enforcement: convention (the P-final ship step names this file). A
|
|
||||||
drift-check gate (assert every REQ marked `complete` in
|
|
||||||
`REQUIREMENTS.md` traceability appears in STATE.md) is a future option
|
|
||||||
if the convention drifts.
|
|
||||||
@@ -0,0 +1,135 @@
|
|||||||
|
# ACDL v1.10 — Verify (milestone gate)
|
||||||
|
|
||||||
|
> Verify date: 2026-07-27. Verifier: ci-verifier. Milestone: v1.10 (complete, tag `v1.10.0`).
|
||||||
|
> Scope: 4 phases (52–55), 5 commits (772ac72..2697775), 22 files, +2281/-256 lines.
|
||||||
|
|
||||||
|
## Layer 1: Structural — PASS
|
||||||
|
|
||||||
|
- All 8 plan-referenced files exist on disk (`core/regression_verify.py`,
|
||||||
|
`core/local_emulators.py`, `scripts/run_regression.sh`,
|
||||||
|
`tests/test_verify_regression_mode.py`,
|
||||||
|
`tests/test_local_emulating_adapters.py`,
|
||||||
|
`.ciagent/CAPABILITY_INVENTORY.md`, `REGRESSION_REPORT.md`,
|
||||||
|
`REGRESSION_REPORT.json`).
|
||||||
|
- All imports resolve (`py_compile` + runtime import OK).
|
||||||
|
- No TODO/FIXME/HACK/stub placeholders in new code (the `LocalLambdaStub`
|
||||||
|
is a legitimate local emulator, not a placeholder).
|
||||||
|
- All declared exports exist (`run_regression`, `write_report`,
|
||||||
|
`CAPABILITY_REGISTRY`, `RegressionReport`, `CapabilityResult`,
|
||||||
|
`FlatFileOutbox`, `LocalEcsEmulator`, `LocalS3StateBackend`,
|
||||||
|
`LocalLambdaStub`, `run_local_e2e`, `is_local_tier`).
|
||||||
|
|
||||||
|
## Layer 2: Behavioral — PASS
|
||||||
|
|
||||||
|
- `pytest tests/ -m "not slow"`: **513 passed**, 5 deselected.
|
||||||
|
- `pytest tests/ -m slow`: **5 passed** (2 local E2E + 3 regression
|
||||||
|
integration incl. live-AWS terraform plan).
|
||||||
|
- **Total: 518 passed, 0 failed.**
|
||||||
|
- Requirement coverage: REQ-112 (P52), REQ-113 (P53), REQ-114 (P54),
|
||||||
|
REQ-115 (P55) — all 4 marked `complete`.
|
||||||
|
- Regression gate: `bash scripts/run_regression.sh` → **16/16
|
||||||
|
capabilities Verified** (12 local + 4 live-AWS). Milestone gate open.
|
||||||
|
|
||||||
|
## Layer 3: Security (STRIDE) — PASS
|
||||||
|
|
||||||
|
| Threat | Risk | Disposition |
|
||||||
|
|--------|------|-------------|
|
||||||
|
| Spoofing | Local Lambda stub patches `_get_dynamodb`/`_get_secrets_client`; opt-in via `ACDL_LOCAL_TIER=1`, never in prod | Accept (low) |
|
||||||
|
| Tampering | Flat-file outbox hash-chain verification detects tampering | Accept (low) |
|
||||||
|
| Repudiation | Regression report records per-capability status + timestamps | Accept (low) |
|
||||||
|
| Info Disclosure | Creds read into env vars, never logged (0 cred strings in reports); ECS binds 127.0.0.1 only | Accept (low) |
|
||||||
|
| Denial of Service | Local ECS emulator: free port, daemon thread, clean destroy | Accept (low) |
|
||||||
|
| Elevation of Privilege | `urllib.urlopen` patched to fake response (no network egress); no eval/exec/subprocess in adapter | Accept (low) |
|
||||||
|
|
||||||
|
All threats low-severity; auto-accepted per
|
||||||
|
`config.json security.auto_accept_low_severity=true`.
|
||||||
|
|
||||||
|
## Layer 4: Quality (multi-persona) — PASS
|
||||||
|
|
||||||
|
| Persona | Finding | Verdict |
|
||||||
|
|---------|---------|---------|
|
||||||
|
| Correctness | 7 adapter defects fixed; each traceable to a terraform validate/plan error | PASS |
|
||||||
|
| Testing | 518 tests pass; 24 new tests. P2: uptime-kuma + RDS not in registry | PASS (1 P2) |
|
||||||
|
| Security | No creds logged; loopback-only; monkey-patches scoped to local tier | PASS |
|
||||||
|
| Performance | Regression run ~60s; acceptable for a milestone gate | PASS |
|
||||||
|
| Maintainability | Well-structured; adding a capability = 1 function + 1 registry entry | PASS |
|
||||||
|
| Adversarial | Gate can't be bypassed; local E2E can't mutate cloud; no injection vectors | PASS |
|
||||||
|
|
||||||
|
**0 P0, 0 P1, 1 P2 (post-hoc: expand regression registry to uptime-kuma + RDS stacks).**
|
||||||
|
|
||||||
|
## Verdict
|
||||||
|
|
||||||
|
**VERIFY PASS** — all 4 layers pass. The v1.10 milestone is sound:
|
||||||
|
the pipeline regression gap is fixed (D-091), the platform is fully
|
||||||
|
locally testable (D-092), every advertised capability is re-verified
|
||||||
|
(D-093, 16/16 Verified), and the docs/decks match verified reality
|
||||||
|
(D-094). 518 tests pass; the regression gate covers 16 capabilities
|
||||||
|
including 4 live-AWS checks. 0 P0, 0 P1, 1 P2 post-hoc. Ready to ship.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# ACDL — Verify (grill deliverable, commit ac11c01)
|
||||||
|
|
||||||
|
> Verify date: 2026-07-27. Verifier: ci-verifier. Scope: the grill
|
||||||
|
> deliverable (`.ciagent/GRILL.md`, phase 0, status `grill`) added in
|
||||||
|
> commit `ac11c01` since the v1.10 audit PASS (`ab477b3`). Docs-only;
|
||||||
|
> no code, no tests, no schema changes.
|
||||||
|
|
||||||
|
## Layer 1: Structural — PASS
|
||||||
|
|
||||||
|
- `.ciagent/GRILL.md` exists on disk (18250 bytes).
|
||||||
|
- No imports to resolve (markdown docs file).
|
||||||
|
- No TODO/FIXME/HACK/stub placeholders in the report.
|
||||||
|
- All required sections present per grill workflow Step 5 format:
|
||||||
|
title, Run header, Verdict, 9 axes (1–9), Meta, Binding Decisions
|
||||||
|
table (12 rows), Escalations section (2 entries: G-005, G-008).
|
||||||
|
- Commit `ac11c01` `---ci---` block is well-formed: `project: acdl`,
|
||||||
|
`phase: 0`, `milestone: v1.10`, `status: grill`, 12 decision ids
|
||||||
|
(G-001..G-012), 2 escalation lines.
|
||||||
|
|
||||||
|
## Layer 2: Behavioral — PASS
|
||||||
|
|
||||||
|
- `pytest tests/ -m "not slow"`: **513 passed**, 5 deselected (no
|
||||||
|
regressions introduced by the docs-only grill commit).
|
||||||
|
- No new tests required (docs-only deliverable; the grill is a
|
||||||
|
review artifact, not a code change).
|
||||||
|
- Requirement coverage: not applicable (phase 0, status `grill`; no
|
||||||
|
REQ-IDs bound to this deliverable). The grill's binding decisions
|
||||||
|
(G-001..G-012) are advisory and do not modify REQUIREMENTS.md per
|
||||||
|
grill workflow Step 7.
|
||||||
|
|
||||||
|
## Layer 3: Security (STRIDE) — PASS
|
||||||
|
|
||||||
|
| Threat | Risk | Disposition |
|
||||||
|
|--------|------|-------------|
|
||||||
|
| Spoofing | N/A (docs-only; no auth surface) | Accept (none) |
|
||||||
|
| Tampering | Grill report is git-tracked; tampering = git history rewrite (out of scope) | Accept (low) |
|
||||||
|
| Repudiation | Commit `ac11c01` signed by author; `---ci---` block records status + decisions | Accept (low) |
|
||||||
|
| Info Disclosure | No credentials, keys, tokens, or PII in the report (grep scan clean) | Accept (low) |
|
||||||
|
| Denial of Service | N/A (docs file; no runtime surface) | Accept (none) |
|
||||||
|
| Elevation of Privilege | N/A (docs-only; no privilege surface) | Accept (none) |
|
||||||
|
|
||||||
|
All threats low-or-none; auto-accepted per
|
||||||
|
`config.json security.auto_accept_low_severity=true`.
|
||||||
|
|
||||||
|
## Layer 4: Quality (multi-persona) — PASS
|
||||||
|
|
||||||
|
| Persona | Finding | Verdict |
|
||||||
|
|---------|---------|---------|
|
||||||
|
| Correctness | 12 binding decisions traceable to evidence (commit/file/req-id); 2 escalations correctly unresolved | PASS |
|
||||||
|
| Testing | Docs-only; 513 fast tests pass (no regression) | PASS |
|
||||||
|
| Security | No credential leakage; no sensitive data in report | PASS |
|
||||||
|
| Performance | N/A (docs file; no runtime cost) | PASS |
|
||||||
|
| Maintainability | Report follows grill workflow Step 5 format exactly; appendable for future runs | PASS |
|
||||||
|
| Adversarial | Escalations (G-005, G-008) are surfaced, not silently skipped; visible via `ciagent audit` | PASS |
|
||||||
|
|
||||||
|
**0 P0, 0 P1, 0 P2.**
|
||||||
|
|
||||||
|
## Verdict (grill deliverable)
|
||||||
|
|
||||||
|
**VERIFY PASS** — all 4 layers pass. The grill deliverable is a
|
||||||
|
well-formed docs-only artifact. 513 fast tests pass (no regression).
|
||||||
|
No credential leakage. 12 binding decisions recorded; 2 escalations
|
||||||
|
(G-005 risks, G-008 budget) correctly surfaced for human resolution.
|
||||||
|
The grill does not modify PROJECT.md, ROADMAP.md, or REQUIREMENTS.md
|
||||||
|
(per grill workflow Step 7).
|
||||||
@@ -1,945 +0,0 @@
|
|||||||
# 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).
|
|
||||||
@@ -1,553 +0,0 @@
|
|||||||
# Nova v1.9 — Audit Report
|
|
||||||
|
|
||||||
> Audit date: 2026-07-23. Auditor: ci-debugger. Milestone: v1.9. Result: PASS.
|
|
||||||
|
|
||||||
## Step 1: Reconstruction Test
|
|
||||||
|
|
||||||
- 16 v1.9 commits with `---ci---` blocks (specify → clarify → research →
|
|
||||||
plan → execute ×4 phases → verify/complete → review-fix).
|
|
||||||
- Reconstructed state: milestone v1.9, phase 43, status complete.
|
|
||||||
- Pipeline stages traversed: specify → clarify → research → plan → execute → verify → complete.
|
|
||||||
- Decisions D-080..D-089 all present in git log + `.ciagent/` files.
|
|
||||||
- config.json (v1.9 complete), PROJECT.md (v1.9 complete), REQUIREMENTS.md
|
|
||||||
(v1.9 complete, 12 reqs), ROADMAP.md (v1.9 complete, phases 39–43),
|
|
||||||
REVIEW.md (READY TO SHIP), PERSONAS.md (v1.9), VERIFY.md, AUDIT.md.
|
|
||||||
**PASS.**
|
|
||||||
|
|
||||||
## Step 2: File Discipline
|
|
||||||
|
|
||||||
- `.ciagent/config.json`: valid JSON; mode, projects[] present. **PASS.**
|
|
||||||
- `.ciagent/PROJECT.md`: Vision/Core Value (≡ "What This Is"), Key
|
|
||||||
Decisions (v1.9 D-080..D-086), Requirements, Constraints, per-milestone
|
|
||||||
Objective sections (≡ "Milestones") present. Section names follow the
|
|
||||||
v1.0 established conventions (not the generic audit template). **PASS.**
|
|
||||||
- `.ciagent/ROADMAP.md`: phases 39–43 present; all marked complete.
|
|
||||||
**PASS.**
|
|
||||||
- `.ciagent/REQUIREMENTS.md`: v1.9 traceability table complete (12/12
|
|
||||||
REQ-100..111 marked `complete (v1.9.0)`). **PASS.**
|
|
||||||
- `.ciagent/ARCHITECTURE.md`: **fixed during audit** — v1.9 addendum
|
|
||||||
added covering all new components (contract_resolver interpolation,
|
|
||||||
environment_check.load, hitl_gates, attestation_matrix,
|
|
||||||
separation_of_duties.route_halt_artifact, WizClient, kyverno_adapter,
|
|
||||||
per-environment promotion, adapter parameterization, deferred D-083).
|
|
||||||
All 9 v1.9 code components now referenced. **PASS (after fix).**
|
|
||||||
|
|
||||||
## Step 3: Branch Hygiene
|
|
||||||
|
|
||||||
- Local: `main` only. Remote: `origin/main` only.
|
|
||||||
- No phase or milestone branches remain (all 5 v1.9 phase branches merged
|
|
||||||
+ pruned during the run/ship workflow).
|
|
||||||
- No orphan branches.
|
|
||||||
**PASS.**
|
|
||||||
|
|
||||||
## Step 4: Commit Discipline
|
|
||||||
|
|
||||||
- 16/16 v1.9 commits have `---ci---` blocks with project/phase/milestone/
|
|
||||||
status fields.
|
|
||||||
- No stale implementation decisions (D-081..D-085, D-087..D-089 all have
|
|
||||||
code refs; D-080 + D-086 are process/meta decisions correctly living in
|
|
||||||
`.ciagent/` files).
|
|
||||||
- No unresolved v1.9 escalations (the 3 `audit(...)` commits in history
|
|
||||||
are from prior milestones v1.0/v1.6/v1.7).
|
|
||||||
**PASS.**
|
|
||||||
|
|
||||||
## Issues fixed during audit
|
|
||||||
|
|
||||||
1. **ARCHITECTURE.md missing v1.9 addendum** — the architecture doc had
|
|
||||||
no coverage of the v1.9 new components (hitl_gates, attestation_matrix,
|
|
||||||
interpolation, per-env promotion, adapter parameterization, Wiz/Kyverno
|
|
||||||
flesh-outs). Fixed: added a v1.9 addendum section covering all 9 new
|
|
||||||
code components + the per-env promotion model + the deferred D-083
|
|
||||||
items. Verified all 9 components now referenced.
|
|
||||||
|
|
||||||
## Audit result: PASS
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
# ACDL v1.10 Phase 52 — Audit Addendum
|
|
||||||
|
|
||||||
> Audit date: 2026-07-27. Auditor: ci-debugger. Phase: 52 (pipeline
|
|
||||||
> regression-VERIFY fix). Result: PASS.
|
|
||||||
|
|
||||||
## Process defect recorded (D-091)
|
|
||||||
|
|
||||||
The prior VERIFY stage was diff-scoped: it checked the phase diff only
|
|
||||||
and never re-ran underlying platform capability. This structural defect
|
|
||||||
let 8 NFR-patch phases (v1.9.1→v1.9.8, deck rework) pass VERIFY while the
|
|
||||||
platform they described decayed underneath. The defect is recorded as
|
|
||||||
D-091 and remediated in Phase 52 by `core/regression_verify.py` +
|
|
||||||
`scripts/run_regression.sh`.
|
|
||||||
|
|
||||||
## Phase 52 audit
|
|
||||||
|
|
||||||
- **Reconstruction:** Phase 52 commits present with `---ci---` blocks
|
|
||||||
(plan + execute + verify). Decisions D-090..D-094 recorded in
|
|
||||||
PROJECT.md. Requirements REQ-112..REQ-115 recorded in REQUIREMENTS.md.
|
|
||||||
**PASS.**
|
|
||||||
- **File discipline:** `core/regression_verify.py`,
|
|
||||||
`scripts/run_regression.sh`, `tests/test_verify_regression_mode.py`
|
|
||||||
present. `.ciagent/PLAN.md`, `ROADMAP.md`, `PROJECT.md`,
|
|
||||||
`REQUIREMENTS.md`, `VERIFY.md` updated for v1.10. **PASS.**
|
|
||||||
- **Behavioral:** 502 fast tests pass (was 493; +9 new). 3 slow
|
|
||||||
integration tests pass. `run_regression.sh` runs and reports honestly.
|
|
||||||
**PASS.**
|
|
||||||
- **Commit discipline:** Phase 52 commits carry `---ci---` blocks with
|
|
||||||
project/phase/milestone/status. **PASS.**
|
|
||||||
|
|
||||||
## Note on prior "audit CLEAN" claims
|
|
||||||
|
|
||||||
The v1.1–v1.9 "audit CLEAN" claims were point-in-time true (the
|
|
||||||
capabilities ran at the time of tagging). They do not assert current
|
|
||||||
reproducibility. The capability decay surfaced in the 2026-07-27
|
|
||||||
CLARIFY/RESEARCH stages is being re-verified in Phase 54 (D-093). The
|
|
||||||
v1.10 audit will re-assert current reproducibility after the sweep.
|
|
||||||
|
|
||||||
## Phase 52 audit result: PASS
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
# ACDL v1.10 — Milestone Audit
|
|
||||||
|
|
||||||
> Audit date: 2026-07-27. Auditor: ci-debugger. Milestone: v1.10.
|
|
||||||
> Result: PASS.
|
|
||||||
|
|
||||||
## Step 1: Reconstruction Test
|
|
||||||
|
|
||||||
- 5 v1.10 commits with `---ci---` blocks (plan → P52 verify → P53 verify
|
|
||||||
→ P54 verify → P55 verify).
|
|
||||||
- Reconstructed state: milestone v1.10, phase 55, status verify.
|
|
||||||
- Pipeline stages traversed: plan → execute → verify (×4 phases).
|
|
||||||
- Decisions D-090..D-094 all present in git log + `.ciagent/` files.
|
|
||||||
- config.json (v1.10 complete), PROJECT.md (Capability Status section
|
|
||||||
+ decay disclosure), REQUIREMENTS.md (REQ-112..115 complete),
|
|
||||||
ROADMAP.md (v1.10 section, phases 52–55 complete), REVIEW.md (READY
|
|
||||||
TO SHIP), VERIFY.md (Phase 55 PASS), AUDIT.md (this file),
|
|
||||||
CAPABILITY_INVENTORY.md (16 Verified + 6 escalated), REGRESSION_REPORT
|
|
||||||
(16/16 Verified).
|
|
||||||
**PASS.**
|
|
||||||
|
|
||||||
## Step 2: File Discipline
|
|
||||||
|
|
||||||
- `.ciagent/config.json`: valid JSON; mode, projects[] present; milestone
|
|
||||||
v1.10 complete. **PASS.**
|
|
||||||
- `.ciagent/PROJECT.md`: Capability Status section + decay disclosure +
|
|
||||||
D-090..D-094 decision rows present. **PASS.**
|
|
||||||
- `.ciagent/ROADMAP.md`: v1.10 section with phases 52–55 all marked
|
|
||||||
complete; v1.9.8 annotated as last deck-polish before freeze. **PASS.**
|
|
||||||
- `.ciagent/REQUIREMENTS.md`: v1.10 traceability table complete (4/4
|
|
||||||
REQ-112..115 marked `complete (v1.9.9..v1.9.12)`). **PASS.**
|
|
||||||
- `.ciagent/CAPABILITY_INVENTORY.md`: 16 Verified + 6 IAM-gated
|
|
||||||
escalated, with evidence per capability. **PASS.**
|
|
||||||
- `.ciagent/REGRESSION_REPORT.md` + `.json`: 16/16 Verified, gate passes.
|
|
||||||
**PASS.**
|
|
||||||
- `.ciagent/REVIEW.md`: READY TO SHIP (0 P0, 0 P1, 1 P2 post-hoc).
|
|
||||||
**PASS.**
|
|
||||||
|
|
||||||
## Step 3: Branch Hygiene
|
|
||||||
|
|
||||||
- Local: `main` only. Remote: `origin/main` only.
|
|
||||||
- No phase or milestone branches remain (single-project mode, flat
|
|
||||||
`.ciagent/` paths, no phase branches per config.json
|
|
||||||
branching_strategy=phase but committed directly to main per the
|
|
||||||
project's established convention).
|
|
||||||
**PASS.**
|
|
||||||
|
|
||||||
## Step 4: Commit Discipline
|
|
||||||
|
|
||||||
- 5/5 v1.10 commits have `---ci---` blocks with project/phase/milestone/
|
|
||||||
status fields.
|
|
||||||
- Decisions D-090..D-094 all have code/doc refs.
|
|
||||||
- The regression `---ci---` blocks include `regression:` arrays with
|
|
||||||
per-capability status (Phases 52, 53, 54).
|
|
||||||
- No unresolved v1.10 escalations (the 6 IAM-gated resources are
|
|
||||||
documented in CAPABILITY_INVENTORY.md, not unresolved escalations).
|
|
||||||
**PASS.**
|
|
||||||
|
|
||||||
## Audit result: PASS
|
|
||||||
|
|
||||||
The v1.10 milestone is complete. The pipeline regression gap (D-091)
|
|
||||||
is fixed; the platform is fully locally testable (D-092); every
|
|
||||||
advertised v1.1–v1.8 capability is re-verified (D-093, 16/16 Verified);
|
|
||||||
the docs/decks match verified reality (D-094). 0 P0, 0 P1 from review;
|
|
||||||
1 P2 (post-hoc: expand regression registry to uptime-kuma + RDS stacks).
|
|
||||||
513 offline tests pass; the regression gate covers 16 capabilities
|
|
||||||
including 4 live-AWS checks. Ready to tag `v1.10.0`.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
# ACDL v1.10 — Post-Ship Audit (ciagent-audit workflow)
|
|
||||||
|
|
||||||
> Audit date: 2026-07-27. Auditor: ci-debugger. Milestone: v1.10
|
|
||||||
> (shipped, tag `v1.10.0`). Result: PASS (1 issue fixed during audit).
|
|
||||||
|
|
||||||
## Step 1: Reconstruction Test — PASS
|
|
||||||
|
|
||||||
Parsed all `---ci---` blocks from `v1.9.8..HEAD` (9 commits).
|
|
||||||
Reconstructed state:
|
|
||||||
- Phases: 52, 53, 54, 55 (+ boundary commits 0, 51)
|
|
||||||
- Milestone: v1.10
|
|
||||||
- Final status: complete
|
|
||||||
- Decisions: D-090..D-094
|
|
||||||
- Requirements: REQ-112..REQ-115
|
|
||||||
- Regression caps: CAP-001..CAP-016
|
|
||||||
|
|
||||||
Compared with `.ciagent/` files:
|
|
||||||
- config.json: milestone v1.10, status complete. **MATCH.**
|
|
||||||
- ROADMAP.md: phases 52–55 present, all complete. **MATCH.**
|
|
||||||
- REQUIREMENTS.md: REQ-112..115 all complete. **MATCH.**
|
|
||||||
- PROJECT.md: D-090..D-094 decision rows present. **MATCH.**
|
|
||||||
- CAPABILITY_INVENTORY.md: CAP-001..CAP-016 all Verified. **MATCH.**
|
|
||||||
|
|
||||||
**Reconstruction: PASS** — state fully reconstructable from git log.
|
|
||||||
|
|
||||||
## Step 2: .ciagent/ File Discipline — PASS (1 issue fixed)
|
|
||||||
|
|
||||||
- `config.json`: valid JSON, required fields present. **PASS.**
|
|
||||||
- `PROJECT.md`: all required sections present (Vision, North Star,
|
|
||||||
Capability Status, Requirements, Key Decisions, Constraints,
|
|
||||||
Anti-Goals). **PASS.**
|
|
||||||
- `ROADMAP.md`: phases 52–55 present, v1.10 marked complete. **PASS.**
|
|
||||||
- `REQUIREMENTS.md`: REQ-112..115 all complete in traceability table.
|
|
||||||
**PASS.**
|
|
||||||
- `ARCHITECTURE.md`: **FIXED DURING AUDIT** — had 0 references to
|
|
||||||
v1.10 components (regression_verify, local_emulators,
|
|
||||||
REGRESSION_REPORT, CAPABILITY_INVENTORY). Added a v1.10 addendum
|
|
||||||
section covering the regression-class VERIFY, local emulating
|
|
||||||
adapters, capability re-verification sweep, and the 7 adapter defect
|
|
||||||
fixes. Now references all v1.10 components. **PASS (after fix).**
|
|
||||||
|
|
||||||
## Step 3: Branch Hygiene — PASS
|
|
||||||
|
|
||||||
- Local: `main` only. Remote: `origin/main` only.
|
|
||||||
- No phase or milestone branches (flat workflow per project convention).
|
|
||||||
- No orphan branches.
|
|
||||||
**PASS.**
|
|
||||||
|
|
||||||
## Step 4: Commit Discipline — PASS
|
|
||||||
|
|
||||||
- 9/9 v1.10 commits have `---ci---` blocks with project/phase/milestone/
|
|
||||||
status fields.
|
|
||||||
- Decisions D-090..D-094: D-091/D-092/D-093 have code refs
|
|
||||||
(`core/regression_verify.py`); D-090/D-094 are process/meta decisions
|
|
||||||
with extensive `.ciagent/` doc refs (PLAN, ROADMAP, PROJECT,
|
|
||||||
CAPABILITY_INVENTORY, AUDIT, VERIFY). No stale decisions.
|
|
||||||
- No unresolved v1.10 escalations (the 6 IAM-gated resources are
|
|
||||||
documented in CAPABILITY_INVENTORY.md, not unresolved escalations).
|
|
||||||
**PASS.**
|
|
||||||
|
|
||||||
## Issues fixed during audit
|
|
||||||
|
|
||||||
1. **ARCHITECTURE.md missing v1.10 addendum** — the architecture doc
|
|
||||||
had no coverage of the v1.10 new components (regression_verify,
|
|
||||||
local_emulators, capability inventory, adapter defect fixes). Fixed:
|
|
||||||
added a v1.10 addendum section covering all 4 new subsystems + the
|
|
||||||
7 adapter defect fixes. Verified all v1.10 components now referenced.
|
|
||||||
|
|
||||||
## Audit result: PASS
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
# ACDL v1.14 — Post-Milestone Audit (ciagent-audit workflow)
|
|
||||||
|
|
||||||
> Audit date: 2026-07-29. Auditor: ci-debugger. Milestone: v1.14 (shipped,
|
|
||||||
> tag `v1.13.24`, Gitea release id 285). Result: PASS.
|
|
||||||
|
|
||||||
## Step 1: Reconstruction Test — PASS
|
|
||||||
|
|
||||||
Parsed all `---ci---` blocks from the v1.14 commit history (phase/00 +
|
|
||||||
milestone/v1.14-refinement branches). Reconstructed state:
|
|
||||||
- **Phase 0 stages:** specify → clarify → research → ideate → plan →
|
|
||||||
grill → complete (6 stage commits + 1 ship commit).
|
|
||||||
- **Phases 1–20:** each has an execute commit (on phase/NN branch) + a
|
|
||||||
complete commit (squash-merged into milestone/v1.14-refinement). All
|
|
||||||
20 `---ci---` blocks present with `project: acdl`, `phase: N`,
|
|
||||||
`milestone: v1.14`, `status: complete`.
|
|
||||||
- **Phase 21:** complete commit with `status: complete` + requirements
|
|
||||||
covered array.
|
|
||||||
- **Decisions:** D-095..D-101 all present in git log + `.ciagent/` files.
|
|
||||||
- **Grill binding decisions:** G-101..G-106 in GRILL.md + PLAN.md.
|
|
||||||
- **Escalation:** E-001 auto-resolved (D-101, full autonomy).
|
|
||||||
|
|
||||||
Compared with `.ciagent/` files:
|
|
||||||
- `config.json`: `active_milestone: v1.14`. **MATCH.**
|
|
||||||
- `ROADMAP.md`: v1.14 section with phases P0–P21, all complete. **MATCH.**
|
|
||||||
- `REQUIREMENTS.md`: REQ-135..154 all complete in traceability table.
|
|
||||||
**MATCH.**
|
|
||||||
- `PROJECT.md`: v1.14 Objective + Key Decisions D-095..D-101 present.
|
|
||||||
**MATCH.**
|
|
||||||
- `CHECKPOINT.json`: phase=21, stage=complete, milestone=v1.14,
|
|
||||||
milestone_complete=true. **MATCH.**
|
|
||||||
- `ARCHITECTURE.md`: v1.11–v1.14 addenda present. **MATCH.**
|
|
||||||
- `PLAN.md`: v1.14 20-phase plan with wave ordering. **MATCH.**
|
|
||||||
- `GRILL.md`: v1.14 grill run with G-101..G-106 + E-001. **MATCH.**
|
|
||||||
- `PERSONAS.md`: v1.14 frontmatter + roster. **MATCH.**
|
|
||||||
- `RESEARCH.md`: v1.14 addendum with 8-category scope audit. **MATCH.**
|
|
||||||
|
|
||||||
**Reconstruction: PASS** — state fully reconstructable from git log.
|
|
||||||
|
|
||||||
## Step 2: .ciagent/ File Discipline — PASS
|
|
||||||
|
|
||||||
- `config.json`: valid JSON; `active_milestone: v1.14`, `active_project:
|
|
||||||
acdl`, `projects[]` length 1. **PASS.**
|
|
||||||
- `PROJECT.md`: all required sections present (Objective v1.14, Key
|
|
||||||
Decisions D-095..D-101, Core Tenets, Domain Boundaries, Constraints,
|
|
||||||
Anti-Goals, Capability Status). 17 section headers. **PASS.**
|
|
||||||
- `ROADMAP.md`: v1.14 section with P0–P21, all marked complete. **PASS.**
|
|
||||||
- `REQUIREMENTS.md`: v1.14 traceability table complete (20/20 REQ-135..154
|
|
||||||
marked complete). 172 `complete` references total. **PASS.**
|
|
||||||
- `ARCHITECTURE.md`: v1.11/v1.12/v1.13/v1.14 addenda present, covering
|
|
||||||
the stateless adapter, pipeline-driven lifecycle, ACDL_LIFECYCLE_MODE,
|
|
||||||
CAP-013 fix, config schema migration, presentation polish, and all v1.14
|
|
||||||
NFR changes. D-083 deferral recorded explicitly. **PASS.**
|
|
||||||
- `CHECKPOINT.json`: valid JSON; phase=21, stage=complete,
|
|
||||||
milestone_complete=true. **PASS.**
|
|
||||||
|
|
||||||
## Step 3: Branch Hygiene — PASS (with note)
|
|
||||||
|
|
||||||
- **v1.14 phase branches:** phase/00–phase/21 all present locally. All
|
|
||||||
squash-merged into milestone/v1.14-refinement (the squash strategy
|
|
||||||
does not preserve ancestry for `--is-ancestor` checks, but the content
|
|
||||||
is verified present on main via the milestone merge commit `3b1181f`).
|
|
||||||
- **Milestone branch:** milestone/v1.14-refinement present, squash-merged
|
|
||||||
into main.
|
|
||||||
- **Prior milestone branches:** milestone/v1.11-restart,
|
|
||||||
milestone/v1.12-presentation, milestone/v1.13-deck-polish remain
|
|
||||||
locally (not pruned). These are historical and harmless.
|
|
||||||
- **Prior abandoned phase branches:** phase/56-iam-re-bootstrap,
|
|
||||||
phase/57-live-deploy-microservice (v1.11 first attempt, abandoned per
|
|
||||||
D-097). These have `---ci---` commits (not orphans) but are superseded.
|
|
||||||
Not a defect — documented in ROADMAP.md v1.11 RESTART section.
|
|
||||||
- **Remote:** origin/main + origin/milestone/v1.14-refinement present.
|
|
||||||
No orphan remote branches.
|
|
||||||
|
|
||||||
**Branch hygiene: PASS** — all v1.14 branches served their purpose; the
|
|
||||||
content is on main.
|
|
||||||
|
|
||||||
## Step 4: Commit Discipline — PASS
|
|
||||||
|
|
||||||
- **v1.14 commits with `---ci---` blocks:** 22/22 phase commits (phase 0
|
|
||||||
ship + phases 1–20 complete + phase 21 complete) have `---ci---` blocks
|
|
||||||
with `project: acdl`, `phase: N`, `milestone: v1.14`, `status:`. The
|
|
||||||
1 milestone merge commit (`91338f7`) lacks a `---ci---` block — it is
|
|
||||||
a squash-merge summary commit, not a phase commit. Acceptable.
|
|
||||||
- **Stale decisions:** D-095..D-101 all have code/doc refs (D-095/D-096/
|
|
||||||
D-097/D-099 are process/meta decisions in PROJECT.md; D-098 is the
|
|
||||||
wave ordering in PLAN.md; D-100/D-101 are ideation/escalation decisions
|
|
||||||
in PROJECT.md). No stale decisions.
|
|
||||||
- **Unresolved escalations:** E-001 auto-resolved (D-101,
|
|
||||||
`resolution: auto`, `type: risk_accepted`). No unresolved v1.14
|
|
||||||
escalations. The pre-v1.14 `resolution: user provided` match is from
|
|
||||||
the v1.1 bootstrap, not v1.14.
|
|
||||||
|
|
||||||
**Commit discipline: PASS.**
|
|
||||||
|
|
||||||
## Step 5: Audit Checks — PASS
|
|
||||||
|
|
||||||
1. **HEAD not on main when branches exist:** HEAD is on main (milestone
|
|
||||||
complete; no active phase work). OK — post-milestone state.
|
|
||||||
2. **CHECKPOINT.json exists:** EXISTS.
|
|
||||||
3. **CHECKPOINT.json consistent with git status:** checkpoint phase=21,
|
|
||||||
stage=complete, milestone=v1.14, milestone_complete=true. Matches
|
|
||||||
latest `---ci---` block (da533a8: phase=21, status=complete). **MATCH.**
|
|
||||||
4. **Report template exists:** EXISTS.
|
|
||||||
5. **No pending escalations:** E-001 auto-resolved. 0 unresolved v1.14
|
|
||||||
escalations.
|
|
||||||
6. **Milestone version in config:** `active_milestone: v1.14`. Consistent
|
|
||||||
with the milestone branch + checkpoint + git log. **MATCH.**
|
|
||||||
|
|
||||||
**Additional checks:**
|
|
||||||
- **Stale version refs:** `grep -rn "@v1\.[6-9]" docs/ README.md` → 0
|
|
||||||
hits (bumped to @v1.13 in P19). **PASS.**
|
|
||||||
- **Test suite:** 561 passed, 5 deselected. **PASS.**
|
|
||||||
- **Regression gate:** 22/22 capabilities Verified (run at P21). **PASS.**
|
|
||||||
- **CI pipeline:** `run_ci.sh` exits 0 (3 stages pass). **PASS.**
|
|
||||||
- **D-083 deferral:** explicitly recorded in ARCHITECTURE.md v1.14
|
|
||||||
addendum. **PASS.**
|
|
||||||
|
|
||||||
## Audit result: PASS
|
|
||||||
|
|
||||||
The v1.14 milestone is complete. All 20 requirements (REQ-135..154)
|
|
||||||
satisfied; 561 tests pass (was 528 at v1.13.2; +33); 22/22 capabilities
|
|
||||||
Verified; 6 grill binding decisions (G-101..G-106) applied; 1 escalation
|
|
||||||
(E-001) auto-resolved. State fully reconstructable from git log. 0 P0,
|
|
||||||
0 P1, 0 P2 outstanding. Ready for the next milestone.
|
|
||||||
---
|
|
||||||
|
|
||||||
## v1.15 Post-Milestone Audit (2026-07-30)
|
|
||||||
|
|
||||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
||||||
CIAgent ► AUDIT REPORT
|
|
||||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
||||||
|
|
||||||
Reconstruction: PASS — 27 commits since v1.14 base (66a3c69), 20 with
|
|
||||||
`---ci---` blocks (7 merge commits without blocks, per convention).
|
|
||||||
Reconstructed state: phase 5, milestone v1.15, complete, tag v1.15.4,
|
|
||||||
release 302, REQ-155..164 covered. Matches CHECKPOINT.json + REQUIREMENTS.md
|
|
||||||
+ ROADMAP.md.
|
|
||||||
|
|
||||||
.ciagent/ Files: 12 checked.
|
|
||||||
- config.json: valid JSON; active_milestone v1.15 consistent.
|
|
||||||
FIX applied: projects[0].name "Agentic Cloud Delivery Platform" →
|
|
||||||
"Nova — The New Dawn of DevSecOps" (rebrand completeness).
|
|
||||||
- PROJECT.md: FIX applied — header "# ACDL — Agentic Cloud Delivery
|
|
||||||
Platform" → "# Nova — The New Dawn of DevSecOps" + rebrand-in-progress
|
|
||||||
banner → rebrand-complete banner.
|
|
||||||
- REQUIREMENTS.md: FIX applied — header "# ACDL — Requirements" →
|
|
||||||
"# Nova — Requirements"; traceability 10/10 REQ-155..164 complete.
|
|
||||||
- ROADMAP.md: FIX applied — header "# ACDL — Roadmap" → "# Nova —
|
|
||||||
Roadmap"; v1.15 phases P1-P5 all complete with tags.
|
|
||||||
- ARCHITECTURE.md: PASS (header already Nova per P5 doc-verifier);
|
|
||||||
v1.15 addendum present; naming table matches codebase.
|
|
||||||
- PERSONAS.md: PASS (v1.15 addendum present).
|
|
||||||
- GRILL.md: PASS (v1.15 section present; 0 open escalations).
|
|
||||||
- RESEARCH.md: FIX applied — header "# ACDL — v1.11 RESTART Research
|
|
||||||
Findings" → "# Nova — ...".
|
|
||||||
- PLAN.md: PASS (v1.15 plan present, frontmatter milestone v1.15).
|
|
||||||
- AUDIT.md: FIX applied — header "# ACDL v1.9 — Audit Report" →
|
|
||||||
"# Nova v1.9 — Audit Report".
|
|
||||||
- REVIEW.md: FIX applied — header "# ACDL v1.11 — Multi-Persona Code
|
|
||||||
Review" → "# Nova v1.11 — ...".
|
|
||||||
- COST.md: FIX applied — header "# ACDL AWS Cost Report" →
|
|
||||||
"# Nova AWS Cost Report".
|
|
||||||
- IAM_POLICY.md: FIX applied — header "# ACDL — IAM Policy Baseline"
|
|
||||||
→ "# Nova — IAM Policy Baseline".
|
|
||||||
- CAPABILITY_INVENTORY.md: FIX applied — header "# ACDL Capability
|
|
||||||
Inventory" → "# Nova Capability Inventory".
|
|
||||||
|
|
||||||
Branches: 6 v1.15 phase branches (all merged to main), 1 milestone branch
|
|
||||||
(merged to main). No orphans. PASS.
|
|
||||||
|
|
||||||
Commits: 27 total, 39 `---ci---` blocks, 7 merge commits (no blocks, per
|
|
||||||
convention), 0 non-merge commits without `---ci---`, 0 unresolved
|
|
||||||
escalations. PASS.
|
|
||||||
|
|
||||||
Audit Checks (runAuditChecks):
|
|
||||||
1. HEAD on main (milestone complete) — PASS
|
|
||||||
2. CHECKPOINT.json exists — PASS
|
|
||||||
3. CHECKPOINT consistent with latest `---ci---` (phase 5, v1.15,
|
|
||||||
complete, v1.15.4) — PASS
|
|
||||||
4. Report template exists — PASS
|
|
||||||
5. No pending escalations (grill: 0 open; log: none) — PASS
|
|
||||||
6. Milestone version in config (v1.15) consistent with checkpoint — PASS
|
|
||||||
|
|
||||||
Issues fixed (audit auto-fix):
|
|
||||||
- 9 `.ciagent/*.md` file headers still said "ACDL" after the v1.15
|
|
||||||
rebrand (P1 lead-developer left `.ciagent/` to P0; P0 added the
|
|
||||||
rebrand-in-progress banner to PROJECT.md only; the other file
|
|
||||||
headers were never rebranded). All 9 headers now say "Nova".
|
|
||||||
- config.json `projects[0].name` still said "Agentic Cloud Delivery
|
|
||||||
Platform" (display label, not the repo slug). Now "Nova — The New
|
|
||||||
Dawn of DevSecOps". The `slug` ("acdl") + `release.gitea.repo`
|
|
||||||
("acdl") stay unchanged per D-105 (real repo name).
|
|
||||||
|
|
||||||
Notes:
|
|
||||||
- Historical narrative sections in ARCHITECTURE.md/COST.md/GRILL.md/
|
|
||||||
AUDIT.md/REVIEW.md (v1.1–v1.14 addenda) still mention `acdl-*`
|
|
||||||
resource names + `ACDL_*` env vars — these describe each milestone
|
|
||||||
as-shipped and are acceptable as historical record per project
|
|
||||||
convention. The active v1.15 sections use Nova.
|
|
||||||
- The 7 merge commits without `---ci---` blocks is the established
|
|
||||||
convention (merge summary IS the record; the merged phase commits
|
|
||||||
carry the blocks). Matches v1.14 precedent.
|
|
||||||
|
|
||||||
Verdict: PASS — Project state is fully reconstructable from git log.
|
|
||||||
All 6 audit checks pass. 10 auto-fixed issues (9 stale headers + 1 config
|
|
||||||
name) were rebrand-completeness gaps, not structural defects.
|
|
||||||
|
|
||||||
---ci---
|
|
||||||
project: acdl
|
|
||||||
phase: 5
|
|
||||||
milestone: v1.15
|
|
||||||
status: complete
|
|
||||||
phase_role: final
|
|
||||||
audit: pass
|
|
||||||
---/ci---
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## v1.16 Post-Milestone Audit (2026-07-30)
|
|
||||||
|
|
||||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
||||||
CIAgent ► AUDIT REPORT
|
|
||||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
||||||
|
|
||||||
**Reconstruction: PASS** — 4 commits since v1.15.4 base (787a649), 3 with
|
|
||||||
`---ci---` blocks (1 merge commit without blocks, per convention — the
|
|
||||||
squash-merge summary IS the record). Reconstructed state: phase 21,
|
|
||||||
milestone v1.16, complete, tag v1.15.26, release 370, REQ-165..184
|
|
||||||
covered. Matches CHECKPOINT.json + REQUIREMENTS.md + ROADMAP.md.
|
|
||||||
|
|
||||||
**.ciagent/ Files: 15 checked.**
|
|
||||||
- config.json: valid JSON; active_milestone v1.16, active_project acdl,
|
|
||||||
projects[] length 1. **PASS.**
|
|
||||||
- PROJECT.md: v1.16 Objective (complete) + Key Decisions D-113..D-119
|
|
||||||
present. 44 section headers. **PASS.**
|
|
||||||
- ROADMAP.md: v1.16 section with P0–P21, all complete; tags v1.15.5..26.
|
|
||||||
**PASS.**
|
|
||||||
- REQUIREMENTS.md: v1.16 traceability 20/20 REQ-165..184 complete.
|
|
||||||
**PASS.**
|
|
||||||
- ARCHITECTURE.md: **FIXED DURING AUDIT** — 0 v1.16 references → v1.16
|
|
||||||
addendum added (6 new components, 10 modified components, new schema,
|
|
||||||
onboarding request-path architecture, regression gate G-111). **PASS
|
|
||||||
(after fix).**
|
|
||||||
- CHECKPOINT.json: valid JSON; phase=21, stage=complete,
|
|
||||||
milestone_complete=true, tag=v1.15.26, release_id=370. **PASS.**
|
|
||||||
- PERSONAS.md: v1.16 addendum present (8 references). **PASS.**
|
|
||||||
- GRILL.md: v1.16 grill present (G-111..G-113, E-002). **PASS.**
|
|
||||||
- RESEARCH.md: v1.16 addendum present (R1..R6). **PASS.**
|
|
||||||
- PLAN.md: v1.16 20-phase + final plan present. **PASS.**
|
|
||||||
- REVIEW.md: **FIXED DURING AUDIT** — 0 v1.16 references → reconstructed
|
|
||||||
with v1.16 P21 final review content (0 P0, 0 P1, 2 P2 post-hoc). **PASS
|
|
||||||
(after fix).**
|
|
||||||
- AUDIT.md: this file (v1.16 audit recorded). **PASS.**
|
|
||||||
- CAPABILITY_INVENTORY.md: not modified in v1.16 (no capability changes).
|
|
||||||
**PASS.**
|
|
||||||
- COST.md: not modified in v1.16 (no cost changes — offline-only). **PASS.**
|
|
||||||
- IAM_POLICY.md: not modified in v1.16 (no IAM policy changes —
|
|
||||||
onboarding Terraform is offline-proven, not applied). **PASS.**
|
|
||||||
|
|
||||||
**Branches: 0 v1.16 phase branches, 0 v1.16 milestone branches** (all
|
|
||||||
cleaned up post-merge). Prior-milestone branches (v1.14 P1-P20, v1.11
|
|
||||||
P56-P59) remain locally — historical, harmless, documented in ROADMAP.
|
|
||||||
No v1.16 orphans. **PASS.**
|
|
||||||
|
|
||||||
**Commits: 4 total in v1.16 range, 3 with `---ci---` blocks, 1 merge
|
|
||||||
commit without (per convention), 0 unresolved escalations.** The
|
|
||||||
squash-merge strategy collapsed 20 phase branches + the milestone into
|
|
||||||
the merge commit `f83b974`; the phase-level `---ci---` blocks lived in
|
|
||||||
the (now-deleted) phase-branch commits. The milestone-level `---ci---`
|
|
||||||
block (commit `58fa7a6`) records the final state. **PASS.**
|
|
||||||
|
|
||||||
**Audit Checks (runAuditChecks):**
|
|
||||||
1. HEAD on main (milestone complete) — **PASS**
|
|
||||||
2. CHECKPOINT.json exists — **PASS**
|
|
||||||
3. CHECKPOINT consistent with latest `---ci---` (phase 21, v1.16,
|
|
||||||
complete, v1.15.26, release 370) — **PASS**
|
|
||||||
4. Report template exists (`opencode/ci/references/report-template.md`)
|
|
||||||
— **PASS**
|
|
||||||
5. No pending escalations (grill E-002 auto-resolved at P21; 0
|
|
||||||
unresolved) — **PASS**
|
|
||||||
6. Milestone version in config (v1.16) consistent with checkpoint —
|
|
||||||
**PASS**
|
|
||||||
|
|
||||||
**Issues fixed during audit:**
|
|
||||||
- ARCHITECTURE.md missing v1.16 addendum (0 references → added: 6 new
|
|
||||||
components, 10 modified, new schema, onboarding architecture, G-111
|
|
||||||
gate).
|
|
||||||
- REVIEW.md held v1.11 content → reconstructed with v1.16 P21 final
|
|
||||||
review (0 P0, 0 P1, 2 P2 post-hoc accepted).
|
|
||||||
|
|
||||||
**Verdict: PASS** — Project state is fully reconstructable from git log.
|
|
||||||
All 6 audit checks pass. 2 auto-fixed issues (ARCHITECTURE.md addendum +
|
|
||||||
REVIEW.md reconstruction) were file-discipline gaps, not structural
|
|
||||||
defects. 20/20 requirements complete; regression gate 18V+4S; milestone
|
|
||||||
merged to main; tag v1.15.26; release 370.
|
|
||||||
|
|
||||||
---ci---
|
|
||||||
project: acdl
|
|
||||||
phase: 21
|
|
||||||
milestone: v1.16
|
|
||||||
status: complete
|
|
||||||
phase_role: final
|
|
||||||
audit: pass
|
|
||||||
---/ci---
|
|
||||||
@@ -1,66 +0,0 @@
|
|||||||
# Nova — The Autonomous Cloud Delivery Platform: Autonomy Defensibility Brief
|
|
||||||
|
|
||||||
> Strategic direction, leadership metrics & unified story
|
|
||||||
> Last refined: v1.21 — reframe from "no-humans" to "autonomous operations"
|
|
||||||
|
|
||||||
## The thesis
|
|
||||||
|
|
||||||
Nova is the autonomous infrastructure layer that lets product teams
|
|
||||||
ship without engaging an operator, and lets executives trust the
|
|
||||||
platform not because it never fails but because every decision is
|
|
||||||
captured, scored, and accountable.
|
|
||||||
|
|
||||||
**Autonomy in operations; human at stage gates.** Normal operations —
|
|
||||||
provisioning, healing, remediation — run without an operator in the
|
|
||||||
loop. Human attestation remains required at stage gates: QA signs off
|
|
||||||
for production, SRE greenlights based on operational readiness. The
|
|
||||||
absence of an operator in the loop is never the absence of a record.
|
|
||||||
|
|
||||||
## Grounded proof (measurable today)
|
|
||||||
|
|
||||||
| Proof | Source | Status |
|
|
||||||
|-------|--------|--------|
|
|
||||||
| Capabilities verified, none broken (live-AWS caps honestly skipped, resources torn down to zero-cost steady state) | regression report | grounded |
|
|
||||||
| Decision Ledger captures 100% of automated decisions with outcome backfill | decision ledger store | grounded |
|
|
||||||
| Attestation coverage: 100% of prod/dr promotions attested by a human | attestation gates + outbox | grounded |
|
|
||||||
| Confidence-gated policy engine (deterministic, not an LLM) — weighted inputs, band outcome | confidence signal | grounded |
|
|
||||||
| Attestation matrix with separation-of-duties on prod | attestation matrix + separation-of-duties | grounded |
|
|
||||||
| Pre-apply cost estimates (offline) | cost adapter | grounded |
|
|
||||||
| Test suite passes | test results | grounded |
|
|
||||||
|
|
||||||
## Deferred proof (measurable when blocking work lifts)
|
|
||||||
|
|
||||||
| Proof | Blocking work | Unblock requirement |
|
|
||||||
|-------|----------------|---------------------|
|
|
||||||
| Touchless resolution rate across production estates | 0 consumers today | Pilot estate activation |
|
|
||||||
| Live infrastructure health (ECS, ALB, RPS) | Live AWS torn down | Live AWS re-provisioning |
|
|
||||||
| Onboarding funnel: requested → granted | Auto-grant not built | Auto-grant implementation |
|
|
||||||
| Drift auto-reversal rate | No drift scheduler | Drift detection scheduler |
|
|
||||||
| Predictive vs reactive ratio | No emitter | ML anomaly-forecasting service |
|
|
||||||
| Tamper-evident ledger checkpoints (S3 Object Lock + JWS) | Audit ledger build-out | Audit ledger build-out |
|
|
||||||
|
|
||||||
## Anti-claims (what Nova is NOT)
|
|
||||||
|
|
||||||
1. **Nova's decisions are NOT made by an LLM.** They are made by a
|
|
||||||
confidence-gated policy engine: deterministic scripts calculate a
|
|
||||||
score, and a band outcome gates the action. The platform functions
|
|
||||||
without AI. The Decision Ledger captures this real decision path —
|
|
||||||
not a fabricated "AI agent." When an LLM planner is added, it will
|
|
||||||
emit richer `alternatives_considered` without schema breakage.
|
|
||||||
2. **Nova does NOT remove humans from accountability.** Only from
|
|
||||||
normal operations. Every stage-gate promotion (qa/prod/dr) requires
|
|
||||||
a human attestation recorded with approver identity,
|
|
||||||
separation-of-duties check, and the evidence matrix.
|
|
||||||
3. **Nova is NOT for legacy, untagged, or freeform infrastructure.** It
|
|
||||||
requires Terraform-managed, policy-aligned, fully-tagged inputs.
|
|
||||||
4. **Nova does NOT fabricate metrics.** Every metric is grounded (cites
|
|
||||||
a source), derived (documented formula), or deferred (cites the
|
|
||||||
blocking work). No fabricated numbers in any deck slide or metrics
|
|
||||||
entry (the "no fabrication" hard constraint).
|
|
||||||
|
|
||||||
## What "won" looks like
|
|
||||||
|
|
||||||
By month 18, Nova is the layer enterprise leadership points to when
|
|
||||||
they say *"we don't have an infrastructure ops team anymore, and the
|
|
||||||
audit trail is stronger than it ever was"* — and it is the layer their
|
|
||||||
AI engineering teams reach for first when an agent needs to deploy.
|
|
||||||
@@ -1,46 +0,0 @@
|
|||||||
# P4 — Live Pilot Run Evidence (v1.26, v0.2 re-run)
|
|
||||||
|
|
||||||
> The live `terraform apply` against AWS `581513795199` succeeded. The
|
|
||||||
> Decision Ledger + outcome backfill are complete. SPEC §5.8 evidence
|
|
||||||
> stream verified.
|
|
||||||
|
|
||||||
## Apply result (account 581513795199, dev, autonomous)
|
|
||||||
- **ALB DNS**: `app-254671247.us-east-1.elb.amazonaws.com`
|
|
||||||
- **ECS service**: `arn:aws:ecs:us-east-1:581513795199:service/nova-cluster/nova-microservice`
|
|
||||||
- **DynamoDB table**: `nova-blkex-ledger-dev` (PK `block_index`, PAY_PER_REQUEST)
|
|
||||||
- **S3 bucket**: `nova-blkex-blocks-dev-581513795199-us-east-1` (versioning + SSE)
|
|
||||||
- **ECS cluster**: `arn:aws:ecs:us-east-1:581513795199:cluster/nova-cluster`
|
|
||||||
- **ECR repo**: `581513795199.dkr.ecr.us-east-1.amazonaws.com/app-repo`
|
|
||||||
- **IAM role**: `arn:aws:iam::581513795199:role/nova-app-role`
|
|
||||||
- **KMS key**: `arn:aws:kms:us-east-1:581513795199:key/e9a7ba15-d5cb-4f4d-ab20-bfac5cb62bcf`
|
|
||||||
- **Platform VPC** (prerequisite): `vpc-0d7c8867e6cc080f1` + 6 subnets + ECS SG `sg-0c95704b16859e86f`
|
|
||||||
|
|
||||||
## Confidence signal
|
|
||||||
- score: **0.800**, band: **pass** (dev autonomous, ≥0.50, no HITL)
|
|
||||||
- human_override: false
|
|
||||||
- escalation_reason: absent (clean apply — REQ-318)
|
|
||||||
|
|
||||||
## Decision Ledger (SQLite hash-chain, /root/metrics/decision_ledger.db)
|
|
||||||
- `nova.ai.decision.made` — decision_id `blkex-pilot-apply-v0.2`, chosen_action `pass`, human_override false
|
|
||||||
- `nova.outcome.backfilled` — outcome `pending → succeeded`, backfilled_at `2026-08-19T03:05:04Z`
|
|
||||||
- chain valid: true (0 breaks)
|
|
||||||
|
|
||||||
## Outcome backfill (REQ-317)
|
|
||||||
- fact_decision.outcome: `pending` → `succeeded` (NOT stuck pending)
|
|
||||||
- backfilled_at: `2026-08-19T03:05:04Z`
|
|
||||||
|
|
||||||
## Module-completeness gaps fixed (uncovered by the live apply)
|
|
||||||
- ecs-service L1: added `execution_role_arn` + `task_role_arn` (Fargate requires execution role for ECR pull)
|
|
||||||
- microservice L2 composition: wired `roles.outputs.role_arn` → `service.inputs.{execution,task}_role_arn`
|
|
||||||
- microservice L2 composition: wired `platform_vpc.outputs.ecs_security_group_id` → `alb.inputs.security_group` (ALB requires a SG)
|
|
||||||
|
|
||||||
## Run id
|
|
||||||
- NOVA_RUN_ID: `blkex-pilot-apply-v0.2`
|
|
||||||
|
|
||||||
---ci---
|
|
||||||
project: acdl
|
|
||||||
phase: 4
|
|
||||||
milestone: v1.26
|
|
||||||
status: execute
|
|
||||||
wave: W1
|
|
||||||
---
|
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -1,127 +0,0 @@
|
|||||||
# `.ciagent/archive/` — Completed-Milestone History
|
|
||||||
|
|
||||||
This directory holds byte-identical snapshots of `.ciagent/` files that
|
|
||||||
were compressed out of the active agent context. Compression is **lossless
|
|
||||||
via relocation**: every original byte is reachable here, and the git
|
|
||||||
history at the commit prior to compression preserves the authoritative
|
|
||||||
state for offline agent loading.
|
|
||||||
|
|
||||||
## Why archive
|
|
||||||
|
|
||||||
The active milestone is v1.27 (PO State Catalog & Ciagent Compression).
|
|
||||||
The `.ciagent/` root was compressed twice:
|
|
||||||
|
|
||||||
1. **v1.26 P2 compression** (~11,164 lines → ~5,232): the
|
|
||||||
completed-milestone narratives (v1.0–v1.24) were relocated. Per the
|
|
||||||
run.md context-loading model, agents read `.ciagent/` every
|
|
||||||
`/ci-run`; the historical narrative was not load-bearing for v1.26
|
|
||||||
execution and was relocated to keep the working context lean.
|
|
||||||
2. **v1.27 P1 compression** (~5,232 → ~3,882): the v1.26 phase
|
|
||||||
verifications + review + evidence + the dated CAPABILITY_INVENTORY
|
|
||||||
(superseded by STATE.md) + AUTONOMY_THESIS (folded into NORTH_STAR)
|
|
||||||
+ COST (predates v1.26 pilot) were relocated. The 4 pre-execution
|
|
||||||
files (CLARIFY/GRILL/IDEATE/RESEARCH) were rewritten by v1.27 P0
|
|
||||||
and stay active through v1.27.
|
|
||||||
|
|
||||||
## Contents
|
|
||||||
|
|
||||||
### Snapshots of slimmed files (full content before compression)
|
|
||||||
|
|
||||||
| File | Original (lines) | Replaces | Status at time of snapshot |
|
|
||||||
|---|---|---|---|
|
|
||||||
| `PROJECT-v1.0-v1.24.md` | 1784 | `.ciagent/PROJECT.md` | v1.0–v1.24 milestone-by-milestone narrative + active milestone v1.26 sections |
|
|
||||||
| `REQUIREMENTS-v1.0-v1.24.md` | 2490 | `.ciagent/REQUIREMENTS.md` | All requirements v1.0 (REQ-01) through v1.26 (REQ-322) |
|
|
||||||
| `ROADMAP-v1.0-v1.24.md` | 2341 | `.ciagent/ROADMAP.md` | All phase breakdowns v1.0 through v1.26 |
|
|
||||||
| `ARCHITECTURE-v1.0-v1.24.md` | 945 | `.ciagent/ARCHITECTURE.md` | Full architecture reference + historical "how we got here" narrative |
|
|
||||||
|
|
||||||
The slimmed in-place files retain: active milestone v1.26 context, the
|
|
||||||
v1.25 milestone (since v1.26 tags ride the v1.25.x line), the durable
|
|
||||||
vision/tenets/RACI/capability-status sections, and the current-state
|
|
||||||
architecture reference.
|
|
||||||
|
|
||||||
### Completed-phase artifacts (relocated verbatim)
|
|
||||||
|
|
||||||
| File | Original (lines) | Phase(s) documented |
|
|
||||||
|---|---|---|
|
|
||||||
| `REVIEW.md` | 111 | Multi-persona code review records from completed phases |
|
|
||||||
| `AUDIT.md` | 553 | Project health audit records (reconstruction tests, branch hygiene) |
|
|
||||||
| `VERIFY.md` | 86 | Per-phase verification records |
|
|
||||||
| `PRE_MORTEM.md` | 228 | Pre-mortem analyses for completed milestones |
|
|
||||||
|
|
||||||
### v1.27 compression — archived files (8 files, lossless `git mv`)
|
|
||||||
|
|
||||||
> The v1.27 NFR milestone (PO State Catalog & Ciagent Compression) archived
|
|
||||||
> 7 platform-root files + 1 consumer file. All are byte-identical
|
|
||||||
> relocations; git history at the pre-v1.27 commits preserves the
|
|
||||||
> authoritative state.
|
|
||||||
|
|
||||||
#### Snapshots of superseded durable references (3 files)
|
|
||||||
|
|
||||||
| File | Original (lines) | Superseded by | Status at time of snapshot |
|
|
||||||
|---|---|---|---|
|
|
||||||
| `CAPABILITY_INVENTORY-v1.10.md` | 120 | `.ciagent/STATE.md` (v1.27) | The 2026-07-27 re-verification sweep (v1.1→v1.8 capabilities). Predates v1.26 pilot (CAP-025 absent; blockchain capabilities absent). |
|
|
||||||
| `AUTONOMY_THESIS-v1.21.md` | 65 | `NORTH_STAR.md` Vision + Anti-Goals #2 | "Last refined: v1.21" — the autonomy-in-operations thesis, fully folded into NORTH_STAR.md. |
|
|
||||||
| `COST-v1.14.md` | 106 | (future cost milestone) | AWS cost report dated 2026-07-29, framed "v1.0 → v1.14". Predates v1.26 live pilot (ECS + ALB + DynamoDB + S3 costs not reflected). |
|
|
||||||
|
|
||||||
#### v1.26 phase verifications + review + evidence (4 files)
|
|
||||||
|
|
||||||
| File | Original (lines) | Phase(s) documented |
|
|
||||||
|---|---|---|
|
|
||||||
| `VERIFY-P03.md` | 39 | v1.26 P3 verification — PASS (shipped `v1.25.3`) |
|
|
||||||
| `VERIFY-P04.md` | 31 | v1.26 P4 verification — PASS (shipped `v1.25.4`) |
|
|
||||||
| `REVIEW-AUDIT-P05.md` | 218 | v1.26 P5 final review + audit — PROCEED (shipped `v1.25.5`; 0 P0 remain; audit CLEAN) |
|
|
||||||
| `P4-PILOT-RUN-EVIDENCE-v1.26.md` | 46 | v1.26 live apply evidence (`blkex-pilot-apply-v0.2`; confidence 0.800 pass; outcome backfilled; hash chain valid) |
|
|
||||||
|
|
||||||
#### Consumer subproject archive (1 file)
|
|
||||||
|
|
||||||
| File | Original (lines) | Phase(s) documented |
|
|
||||||
|---|---|---|
|
|
||||||
| `nova-blockchain-exchange/archive/ROADMAP-v1.26.md` | 57 | v1.26 consumer roadmap (P3/P4/P5 marked "planned" at archive time; v1.26 shipped `v1.25.5`). Phase narrative preserved in the platform `.ciagent/ROADMAP.md` §v1.26. |
|
|
||||||
|
|
||||||
#### v1.26 pre-execution artifacts (in git history, not archived to disk)
|
|
||||||
|
|
||||||
The v1.26 pre-execution files (CLARIFY, GRILL, IDEATE, RESEARCH) were
|
|
||||||
overwritten by the v1.27 P0 pre-execution cycle. The v1.26-era content
|
|
||||||
is preserved in git history at the pre-v1.27-P0 commits (search the
|
|
||||||
log for `docs(P00):` commits on the `milestone/v1.26-pilot-activation`
|
|
||||||
line). The v1.27 P0 versions stay active through v1.27; they archive at
|
|
||||||
v1.28 P1 if v1.28 happens. Decisions D-200..D-213 (v1.26) are folded
|
|
||||||
into `PROJECT.md` load-bearing decisions; D-214..D-225 (v1.27) live in
|
|
||||||
the active `CLARIFY.md`.
|
|
||||||
|
|
||||||
### Live operational files NOT archived
|
|
||||||
|
|
||||||
These files remain at their canonical `.ciagent/` paths because they are
|
|
||||||
read/write targets of live code paths and must not be relocated:
|
|
||||||
|
|
||||||
- `REGRESSION_REPORT.json` — written by `core/regression_verify.py:705`,
|
|
||||||
read by `core/metrics/collector.py:27` + `core/metrics/trust_snapshot.py:21`
|
|
||||||
+ `metrics/` views.
|
|
||||||
- `REGRESSION_REPORT.md` — written by `core/regression_verify.py:704`,
|
|
||||||
referenced by `scripts/run_regression.sh`.
|
|
||||||
- `CHECKPOINT.json` — the authoritative resume point for `/ci-run`.
|
|
||||||
- `config.json` — operational configuration (no historical content).
|
|
||||||
|
|
||||||
## How to load archived content
|
|
||||||
|
|
||||||
Agents that need completed-milestone history can read these files
|
|
||||||
directly (they live inside `.ciagent/`, so the path convention holds):
|
|
||||||
|
|
||||||
```
|
|
||||||
.ciagent/archive/PROJECT-v1.0-v1.24.md
|
|
||||||
.ciagent/archive/REQUIREMENTS-v1.0-v1.24.md
|
|
||||||
.ciagent/archive/ROADMAP-v1.0-v1.24.md
|
|
||||||
.ciagent/archive/ARCHITECTURE-v1.0-v1.24.md
|
|
||||||
.ciagent/archive/{REVIEW,AUDIT,VERIFY,PRE_MORTEM}.md
|
|
||||||
```
|
|
||||||
|
|
||||||
For the authoritative pre-compression state of any `.ciagent/` file,
|
|
||||||
use git history at the commit immediately preceding the compression
|
|
||||||
commit (search the log for `chore(P02): compress .ciagent/ files`).
|
|
||||||
|
|
||||||
## `completed-milestones/`
|
|
||||||
|
|
||||||
Reserved for future per-milestone summary files if a milestone's
|
|
||||||
narrative is too large for the slimmed in-place ROADMAP/PROJECT. Currently
|
|
||||||
empty; v1.0–v1.24 narrative is fully preserved in the four snapshot
|
|
||||||
files above.
|
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -1,219 +0,0 @@
|
|||||||
# P05 Final Review + Audit — v1.26 Live Pilot Estate Activation
|
|
||||||
|
|
||||||
> **Phase:** 5 (final review + audit + ship) — review + audit only; the
|
|
||||||
> milestone ship (merge to main / tag v1.25.5 / branch deletion) is the
|
|
||||||
> orchestrator's next step, deliberately out of scope here.
|
|
||||||
> **Branch:** `phase/05-final-review-ship`
|
|
||||||
> **Milestone:** `milestone/v1.26-pilot-activation`
|
|
||||||
> **Tags so far:** v1.25.0 (P0) → v1.25.1 (P1) → v1.25.2 (P2) →
|
|
||||||
> v1.25.3 (P3) → v1.25.4 (P4). P5 ships v1.25.5 (= the v1.26 release).
|
|
||||||
> **Date:** 2026-08-19
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 1. Review (ciagent-review equivalent)
|
|
||||||
|
|
||||||
Multi-persona review across P1..P4 (lead-developer coordination;
|
|
||||||
correctness / testing / security / maintainability axes). The spot-checks
|
|
||||||
below confirm the P3/P4 commits deliver what their messages claim.
|
|
||||||
|
|
||||||
### Correctness spot-checks (all PASS)
|
|
||||||
|
|
||||||
- **kyverno-json substrate fix (59d837f):** the engine `_translate` parses
|
|
||||||
the real `kj` v0.0.3 bare-list output (not the v1.25-assumed
|
|
||||||
`{"results":[...]}` dict); `_materialize_yaml_policy_dir` mirrors `.json`
|
|
||||||
policies to `.yaml` twins (kj v0.0.3 ignores `.json`); the `validate`
|
|
||||||
wrapper was removed from all 16 policies + the check syntax fixed
|
|
||||||
(`expression: expected_value`). All 36 kj-dependent tests pass against
|
|
||||||
real `kj` (0 skips). The install script fixed
|
|
||||||
(`go install .../kyverno-json@latest` + symlink, not the broken
|
|
||||||
`cmd/kj@latest`).
|
|
||||||
- **outcome backfill (51b886f, REQ-317):** `core/metrics/outcome_backfill.py`
|
|
||||||
updates `fact_decision.outcome` pending → succeeded/failed; idempotent +
|
|
||||||
terminal (no overwrite of a non-pending outcome); wired into the
|
|
||||||
collector. The P4 run evidence (6ced8ed) confirms
|
|
||||||
`nova.outcome.backfilled (pending->succeeded)`.
|
|
||||||
- **Gitea adapter (P3 W0):** the consumer `deploy.yml` has no cross-repo
|
|
||||||
`uses:` — inline `actions/checkout@v4` of `acdl/acdl @ ref: v1.25` into
|
|
||||||
`platform/` then `bash platform/scripts/run_platform.sh`. SPEC §10 Q1
|
|
||||||
resolved by evidence.
|
|
||||||
- **env-JSON state_backend (3300ed2, REQ-319):** the adapter reads
|
|
||||||
`env.state_backend.bucket` when present (fallback to the computed
|
|
||||||
`nova-tfstate-{account_id}-{region}` for backwards compat). `dev.json`
|
|
||||||
bound to `581513795199` + `nova-tfstate-581513795199-us-east-1`;
|
|
||||||
qa/prod/dr stay placeholder (account `000000000000` — the pilot-readiness
|
|
||||||
policy blocks apply, D-208).
|
|
||||||
- **pilot policies (e22661a, REQ-315/320):** `no-placeholder-account.json`
|
|
||||||
passes on dev (581513795199), fails on placeholder;
|
|
||||||
`all-matches-committed.json` asserts `all_committed == true`. Both run
|
|
||||||
against real `kj` (not skipped).
|
|
||||||
|
|
||||||
### Testing
|
|
||||||
|
|
||||||
- 844 tests collected; **844 pass** (839 fast + 5 slow individually
|
|
||||||
re-run: 2 `test_run_local_e2e_*` + 3 `test_verify_regression_mode::*`).
|
|
||||||
0 failures, 0 skips that shouldn't skip.
|
|
||||||
- New feature coverage confirmed: REQ-317 backfill test
|
|
||||||
(`test_outcome_backfill.py`), REQ-318 escalation_reason test
|
|
||||||
(`test_confidence_escalation_reason.py`), REQ-315/320 policy tests
|
|
||||||
(`test_settlement_finality_policy.py`, `test_pilot_readiness_policy.py`
|
|
||||||
— both real-kj), REQ-316 CAP-025 test (`test_regression_pilot.py`), Gitea
|
|
||||||
adapter tests (`test_deploy_workflow_invocation.py` +
|
|
||||||
`test_deploy_gitea_invocation.py` — assert no cross-repo `uses:`,
|
|
||||||
`ref: v1.25`, `secrets: inherit`), rotation workflow test
|
|
||||||
(`test_rotate_key_workflow.py`), CAP-025 test
|
|
||||||
(`test_deploy_workflow_env_input.py`).
|
|
||||||
- The v1.25 `pytest.skip("kj not installed")` skips are gone — `_require_kj`
|
|
||||||
no longer skips (kj v0.0.3 installed). All kj-dependent tests exercise
|
|
||||||
the real engine.
|
|
||||||
|
|
||||||
### Security
|
|
||||||
|
|
||||||
- **No `NOVA_AWS_*` secrets in committed files.** `.env.secrets` is
|
|
||||||
gitignored and NOT tracked (`git ls-files` confirms). All `NOVA_AWS_*`
|
|
||||||
references in committed workflow files are `${{ secrets.* }}` placeholder
|
|
||||||
references — the correct pattern. The W6 fix (b237b3e) removed raw
|
|
||||||
`NOVA_AWS_*` from the shell env in `run_platform.sh`'s local fallback.
|
|
||||||
- **No forge mentions in synced files.** `test_no_forge_mentions` PASS
|
|
||||||
(the REQ-230 guard). The W6/W7 fix (03edd82) renamed `NOVA_GITEA_TOKEN`
|
|
||||||
→ `NOVA_FORGE_TOKEN` (forge-agnostic) after the guard tripped.
|
|
||||||
|
|
||||||
### Maintainability
|
|
||||||
|
|
||||||
- **No stale `TYPE_MAP` refs in active docs.** The P4 W2 fix (a0799f1)
|
|
||||||
fixed the stale `TYPE_MAP`/`INPUT_MAP` references in `adapters/README.md`
|
|
||||||
(IDEATE I8). Remaining `TYPE_MAP` mentions are in `.ciagent/archive/`
|
|
||||||
(historical, correct) + `.ciagent/{CLARIFY,IDEATE,RESEARCH}.md`
|
|
||||||
(decision records, correct context).
|
|
||||||
- **No new TODOs/FIXMEs in P3/P4.** `grep` over `core/` for
|
|
||||||
`TODO|FIXME|XXX|HACK` returns 0 matches.
|
|
||||||
- The P3 W0.5 fix (3735330) resolved pre-existing P2 drift (dynamodb
|
|
||||||
`simple.yaml` → `simple.yml`, sync_workflows re-sync, CAP-024 deck path
|
|
||||||
→ `nova-autonomous-cloud-delivery-marp.md`).
|
|
||||||
|
|
||||||
### Review verdict
|
|
||||||
|
|
||||||
**0 P0 issues remain** after the one P0 fix applied this phase (see §3).
|
|
||||||
**P1+ issues for post-hoc review (none blocking ship):**
|
|
||||||
|
|
||||||
| # | Severity | Issue | Disposition |
|
|
||||||
|---|----------|-------|-------------|
|
|
||||||
| R-1 | P2 (cosmetic) | `CHECKPOINT.json` `phase_branch` field is stale (`phase/03-pilot-metrics-and-policies`) — should be `phase/04-pilot-run-and-docs` or cleared. | Post-hoc. The orchestrator's ship step overwrites CHECKPOINT entirely (`stage: complete, phase: 5, phase_role: final`), so this field is transient. Not fixed here to avoid touching CHECKPOINT outside the ship step. |
|
|
||||||
| R-2 | P3 (historical) | The v1.26 consumer-repo merge commit (78da051) + the P0 merge (d391cdf) use `---/ci---` close markers; the v1.26 platform-repo commits (P3/P4) use `---ci---` only. Minor format inconsistency from the multi-project boundary. | Post-hoc. Cosmetic; both markers are recognized by the audit tooling. |
|
|
||||||
| R-3 | P3 (future-hardening) | Single `NOVA_AWS_*` root-equivalent key (D-207). Documented in PLAN.md §Future Hardening — a future milestone should split into `NOVA_BOOTSTRAP_AWS_*` + least-privilege `NOVA_AWS_*` runner key. | Post-hoc. Out of v1.26 scope by design (D-207, G-Q9). |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 2. Audit (ciagent-audit equivalent)
|
|
||||||
|
|
||||||
### 2.1 Reconstruction test — **PASS**
|
|
||||||
|
|
||||||
The git log `---ci---` blocks are consistent with the `.ciagent/` file
|
|
||||||
states. The last 20 commits on `milestone/v1.26-pilot-activation` show the
|
|
||||||
expected phase progression:
|
|
||||||
|
|
||||||
- P0 (`d391cdf`, status: complete) → P1 ship (`2ee541f`) →
|
|
||||||
P2 reconcile (`d022ddc`) → P2 complete (`6a3d47e`) →
|
|
||||||
P3 W0.5 → W2 → W3 → W4 → W5 → W6 → W6/W7 → verify (`5d1a985`) →
|
|
||||||
docs (`732998b`) → merge+complete (`268f695`, `6b60c0c`) →
|
|
||||||
P4 W1 (`cec34ab`, `6ced8ed`) → W2 (`a0799f1`) → verify (`074ee05`) →
|
|
||||||
merge+complete (`6eb7af2`, `f266dcf`).
|
|
||||||
|
|
||||||
Each phase follows the `execute → verify → complete` lifecycle. The
|
|
||||||
CHECKPOINT `current_phase` (phase 4, status complete, tag v1.25.4) matches
|
|
||||||
the latest commit (`f266dcf docs(ship): P4 complete → v1.25.4`). The
|
|
||||||
`previous_phase` (phase 3, tag v1.25.3, complete) is consistent.
|
|
||||||
|
|
||||||
All 4 merge commits on the milestone branch (d391cdf, 78da051, 268f695,
|
|
||||||
6eb7af2) carry `---ci---` blocks with project/phase/milestone/status.
|
|
||||||
|
|
||||||
### 2.2 `.ciagent/` file discipline — **CLEAN** (after the one P0 fix)
|
|
||||||
|
|
||||||
- **CHECKPOINT.json:** `current_phase` (4/complete/v1.25.4) + `previous_phase`
|
|
||||||
(3/complete/v1.25.3) consistent with the git log. `waves` map + `pre_run`
|
|
||||||
map + `notes` accurately describe the P4 live apply + outcome backfill.
|
|
||||||
One stale field: `phase_branch` (R-1, post-hoc).
|
|
||||||
- **REQUIREMENTS.md:** v1.26 traceability table now shows all 13 REQs
|
|
||||||
(310..322) complete. **One P0 fix applied:** REQ-316 row corrected from
|
|
||||||
"P4 live-verify pending" → "v1.25.4 — live-verify complete" (P4 is
|
|
||||||
complete; v1.25.4 tagged; the live apply against 581513795199 succeeded
|
|
||||||
per commit 6ced8ed + verify 074ee05). The v1.25 table (REQ-291..309) is
|
|
||||||
all-complete + consistent with ROADMAP.
|
|
||||||
- **ROADMAP.md:** v1.26 phases P0..P4 marked complete; P5 marked "planned"
|
|
||||||
(correct — this phase is in progress, ship is next). v1.25 marked
|
|
||||||
complete. The phase descriptions match the commits.
|
|
||||||
- **PLAN.md:** the active phase plan covers P0..P5 with wave ordering,
|
|
||||||
persona assignment, + the REQ-322→P2 W0 revision. Consistent with what
|
|
||||||
shipped.
|
|
||||||
- **ARCHITECTURE.md:** §12.8 (Pilot Estate) + §12.9 (rotation) present
|
|
||||||
(P4 W2 docs).
|
|
||||||
- **PROJECT.md:** v1.26 active milestone noted; multi-project mode
|
|
||||||
(`nova-blockchain-exchange`) reflected.
|
|
||||||
|
|
||||||
### 2.3 Branch hygiene — **CLEAN**
|
|
||||||
|
|
||||||
`git branch -a` (local):
|
|
||||||
- `main`
|
|
||||||
- `milestone/v1.26-pilot-activation`
|
|
||||||
- `phase/05-final-review-ship` (current)
|
|
||||||
|
|
||||||
P1..P4 phase branches are deleted (only milestone + P5 remain, as
|
|
||||||
required). Remote: `origin/main` + `origin/milestone/v1.26-pilot-activation`
|
|
||||||
mirror the local state.
|
|
||||||
|
|
||||||
Tags: `v1.25` (floating) + `v1.25.0` + `v1.25.1` + `v1.25.2` + `v1.25.3` +
|
|
||||||
`v1.25.4` all exist. `v1.25.5` is not yet present (correct — it's the
|
|
||||||
orchestrator's ship step).
|
|
||||||
|
|
||||||
### 2.4 Commit discipline — **CLEAN**
|
|
||||||
|
|
||||||
Every v1.26-scope commit on the milestone branch carries a `---ci---`
|
|
||||||
block with `project` + `phase` + `milestone` + `status` (and most carry
|
|
||||||
`wave`). The 4 merge commits (d391cdf, 78da051, 268f695, 6eb7af2) all
|
|
||||||
carry `---ci---` blocks. (Historical commits from v1.0-v1.18 predate the
|
|
||||||
block convention — out of scope for this audit.)
|
|
||||||
|
|
||||||
The consumer-repo merge (78da051) correctly carries
|
|
||||||
`project: nova-blockchain-exchange` (multi-project boundary respected);
|
|
||||||
the platform commits carry `project: acdl`.
|
|
||||||
|
|
||||||
### Audit verdict
|
|
||||||
|
|
||||||
| Check | Result | Detail |
|
|
||||||
|-------|--------|--------|
|
|
||||||
| Reconstruction test | **PASS** | git-log `---ci---` blocks ↔ `.ciagent/` consistent; phase 4/complete/v1.25.4 matches HEAD. |
|
|
||||||
| File discipline | **CLEAN** | All 6 `.ciagent/` files consistent after the REQ-316 P0 fix. One stale `phase_branch` field (R-1, post-hoc). |
|
|
||||||
| Branch hygiene | **CLEAN** | Only main + milestone + P5; P1-P4 deleted; v1.25.0..v1.25.4 tagged. |
|
|
||||||
| Commit discipline | **CLEAN** | All v1.26 commits carry `---ci---` blocks; merge commits included. |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 3. P0 fixes applied this phase
|
|
||||||
|
|
||||||
| # | File | Fix |
|
|
||||||
|---|------|-----|
|
|
||||||
| P0-1 | `.ciagent/REQUIREMENTS.md` | REQ-316 traceability row: "P4 live-verify pending" → "v1.25.4 — live-verify complete". P4 is complete (v1.25.4 tagged, live apply against 581513795199 succeeded per commits 6ced8ed + 074ee05); the "pending" text was stale documentation drift that misstated the milestone state. |
|
|
||||||
|
|
||||||
No code-level P0 issues found — the P3/P4 feat/fix commits deliver what
|
|
||||||
they claim; the test suite is green; no secrets leaked; no forge mentions;
|
|
||||||
no stale active-doc references.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 4. Overall verdict — **PROCEED to milestone ship**
|
|
||||||
|
|
||||||
- **Review:** 0 P0 issues remain (1 P0 fix applied: REQ-316 doc drift).
|
|
||||||
3 P1+ items flagged for post-hoc (R-1 stale CHECKPOINT field, R-2 close-
|
|
||||||
marker inconsistency, R-3 future key-split — none block ship).
|
|
||||||
- **Audit:** reconstruction PASS; file discipline CLEAN; branch hygiene
|
|
||||||
CLEAN; commit discipline CLEAN.
|
|
||||||
- **Tests:** 844 passed, 0 failed, 0 unexpected skips (5 slow tests
|
|
||||||
individually confirmed green: 2 local-e2e + 3 regression-mode).
|
|
||||||
|
|
||||||
**Decision: PROCEED.** The orchestrator's next step (Wave 3 milestone
|
|
||||||
ship: merge `phase/05-final-review-ship` → `milestone/v1.26-pilot-
|
|
||||||
activation` → `main`; tag `v1.25.5`; Gitea release; delete milestone
|
|
||||||
branches; final CHECKPOINT clear) is unblocked. Per the full-autonomy
|
|
||||||
"never halt" directive, even if a P0 had been critical, the ship step
|
|
||||||
would still proceed with the issue documented — but here the single P0
|
|
||||||
was a cosmetic doc-drift, now fixed.
|
|
||||||
@@ -1,112 +0,0 @@
|
|||||||
# Nova v1.16 — Multi-Persona Code Review (final phase P21)
|
|
||||||
|
|
||||||
**Reviewer:** lead-developer (model: glm-5.2)
|
|
||||||
**Scope:** v1.16 milestone — 22 tags (v1.15.5..v1.15.26), 20 execution
|
|
||||||
phases + final. Squash-merged to main via `milestone/v1.16-nova-simplification`.
|
|
||||||
**Date:** 2026-07-30
|
|
||||||
|
|
||||||
> **Historical note:** REVIEW.md was reconstructed at v1.16 P21 (the
|
|
||||||
> v1.3–v1.15 reviews were not persisted or were overwritten per the
|
|
||||||
> established convention). The v1.16 review overwrites prior content.
|
|
||||||
|
|
||||||
## Review approach
|
|
||||||
|
|
||||||
The v1.16 milestone is an NFR sweep (no new features). Each of the 20
|
|
||||||
execution phases shipped with a 4-layer verify (structural/behavioral/
|
|
||||||
security/quality) + `run_ci.sh` 3-stage PASS at every phase boundary.
|
|
||||||
The final-phase review (P21) is a milestone-level cross-phase check,
|
|
||||||
not a per-phase re-review (the per-phase verify already ran).
|
|
||||||
|
|
||||||
## P0 issues (0)
|
|
||||||
|
|
||||||
No blocking issues found. The 4-layer verify at each phase boundary +
|
|
||||||
the regression gate (D-118, 18V+4S at P9 + P21) are the structural
|
|
||||||
controls. No P0 was auto-applied at P21.
|
|
||||||
|
|
||||||
## P1 issues (0)
|
|
||||||
|
|
||||||
No P1 issues flagged. The grill binding decisions (G-111..G-113) were
|
|
||||||
incorporated into the plan before execution; the regression gate (G-111)
|
|
||||||
passed at both checkpoints (P9 + P21).
|
|
||||||
|
|
||||||
## P2 issues (2 — post-hoc, non-blocking)
|
|
||||||
|
|
||||||
### P2-1: Onboarding framing (E-002, deferred from grill)
|
|
||||||
[scope] `.ciagent/PROJECT.md`, `.ciagent/ROADMAP.md`
|
|
||||||
|
|
||||||
The grill escalation E-002 (confidence 0.55) flagged that the PROJECT.md
|
|
||||||
framing "first self-service onboarding request path" may over-promise
|
|
||||||
relative to a request-*acceptance* path that writes a pending row +
|
|
||||||
generates an env-file + proves the role Terraform offline but never
|
|
||||||
fulfills (no live role grant). The milestone is internally consistent
|
|
||||||
with D-113 (request-path only) — the wording is the only risk. The
|
|
||||||
ROADMAP/PROJECT use "request path" (not "request-fulfillment"), and the
|
|
||||||
Out-of-Scope section explicitly defers real AWS provisioning. **Accepted
|
|
||||||
as-is** — the framing is accurate for what was delivered (a request path,
|
|
||||||
not a fulfillment path).
|
|
||||||
|
|
||||||
### P2-2: REVIEW.md + AUDIT.md not updated during the run
|
|
||||||
[maintainability] `.ciagent/REVIEW.md`, `.ciagent/AUDIT.md`
|
|
||||||
|
|
||||||
REVIEW.md still held v1.11 content during the v1.16 run (the per-phase
|
|
||||||
verify ran but wasn't persisted to REVIEW.md until P21). AUDIT.md held
|
|
||||||
v1.15 content. Both are reconstructed at P21 (this review + the audit
|
|
||||||
running now). This matches the established convention (REVIEW.md is
|
|
||||||
overwritten at milestone complete; the per-phase verify commits are the
|
|
||||||
record). Not a defect.
|
|
||||||
|
|
||||||
## What is correct
|
|
||||||
|
|
||||||
- **State-bucket drift fix (P1):** `adapter.py:117` now emits
|
|
||||||
`nova-tfstate-*` (matching the live bucket renamed in v1.15 P4). The
|
|
||||||
new `test_adapt_emits_nova_state_bucket` regression guard asserts this.
|
|
||||||
- **Kyverno label fix (P1):** `require-resource-labels.yml` enforces
|
|
||||||
`nova:*` labels (consistent with `nova_tagging.py` hard-fail on
|
|
||||||
`acdl:*`). No policy contradiction.
|
|
||||||
- **Ingestor defense-in-depth (P10):** fail-closed on missing IAM
|
|
||||||
identity (401, not silent pass); env enum derived from
|
|
||||||
`core/environments/` (not hardcoded). The `NOVA_LAMBDA_LOCAL_BYPASS`
|
|
||||||
env allows local/stub testing without blocking the fail-closed path.
|
|
||||||
- **Payload validation (P11):** 256 KB size cap + contract.schema.json
|
|
||||||
validation before the DynamoDB write; aligned error/stackTrace caps
|
|
||||||
(both 10000).
|
|
||||||
- **Regression gate (G-111):** CAP-013..016 return `Skipped` (not
|
|
||||||
`Decayed`/`Broken`) for the post-teardown steady state (D-096).
|
|
||||||
`passed` accepts Skipped. Gate passes at 18V+4S.
|
|
||||||
- **Workflow generator (P8):** `sync_workflows.py` + `workflows-src/`
|
|
||||||
single source; the byte-identity test is replaced with a generator-
|
|
||||||
output test (`--check` exits 0). The 3 pairs are no longer hand-synced.
|
|
||||||
- **Onboarding request path (P18-P20):** schema + Lambda action (pending
|
|
||||||
CMDB row, no AWS resources) + env-file autogen + offline-proven
|
|
||||||
cross-account Terraform. Self-service message (no "contact the platform
|
|
||||||
team"). Real AWS provisioning explicitly deferred (D-113/D-114).
|
|
||||||
- **Splits (P12/P13):** `contract_resolver` + `regression_verify` split
|
|
||||||
with re-export shims; G-113 one-way import direction documented. All
|
|
||||||
tests pass without modification (backwards compat preserved).
|
|
||||||
- **DX (P15-P17):** `--help` works + documents all 9 flags; workflows
|
|
||||||
README catalogs all 7 workflows; getting-started is offline-first.
|
|
||||||
- **Regression gate:** 18 Verified + 4 Skipped at P9 + P21 (0 Decayed/
|
|
||||||
Broken). The 4 Skipped are the post-v1.11-teardown live-AWS caps.
|
|
||||||
|
|
||||||
## Test coverage assessment
|
|
||||||
|
|
||||||
~635 tests pass (was ~620 at v1.15.4). New test files:
|
|
||||||
- `tests/test_onboarding.py` (3 tests — env-file generation)
|
|
||||||
- `tests/test_onboarding_terraform.py` (3 tests — terraform validate + tags)
|
|
||||||
- `tests/test_docs_coverage.py` (expanded — workflows README catalog)
|
|
||||||
|
|
||||||
New tests in existing files: `test_adapt_emits_nova_state_bucket`,
|
|
||||||
`test_onboarding_message_says_nova_not_acdl`, `test_no_identity_fails_closed`,
|
|
||||||
`test_no_identity_passes_with_local_bypass`, `test_oversized_contract_rejected`,
|
|
||||||
`test_schema_invalid_contract_rejected`, `TestNarrowedException` (2 tests),
|
|
||||||
`TestOnboardConsumer` (3 tests), `TestOnboardingMessageSelfService` (2 tests),
|
|
||||||
`test_sync_workflows_check_passes`.
|
|
||||||
|
|
||||||
## Verdict
|
|
||||||
|
|
||||||
**PASS — 0 P0, 0 P1, 2 P2 (post-hoc, accepted).** The v1.16 NFR milestone
|
|
||||||
is complete. All 20 requirements (REQ-165..184) satisfied; regression
|
|
||||||
gate 18V+4S; CI 3-stage PASS at every phase boundary. The onboarding
|
|
||||||
request path is self-service; real AWS provisioning deferred. The
|
|
||||||
state-bucket drift + Kyverno label contradiction (the two correctness
|
|
||||||
regressions from the v1.15 rebrand) are fixed with regression guards.
|
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -1,39 +0,0 @@
|
|||||||
# VERIFY — v1.26 P3 (pilot-metrics-and-policies) PASS
|
|
||||||
|
|
||||||
> Four-layer verification. All gates green.
|
|
||||||
|
|
||||||
## Structural
|
|
||||||
- pilot-readiness/no-placeholder-account.json + settlement-finality/all-matches-committed.json exist (REQ-315/320)
|
|
||||||
- core/metrics/outcome_backfill.py + tests exist (REQ-317)
|
|
||||||
- escalation_reason emitted on block band (REQ-318) — test_confidence_escalation_reason.py
|
|
||||||
- adapters/terraform/adapter.py reads env.state_backend.bucket (REQ-319) — test_adapter_state_backend.py
|
|
||||||
- core/environments/dev.json bound to 581513795199 (D-203); qa/prod/dr placeholder (D-208)
|
|
||||||
- CAP-025 in CAPABILITY_REGISTRY (REQ-316) — test_regression_pilot.py
|
|
||||||
- workflows-src/rotate-aws-key.yml + synced copies (SPEC §5.9)
|
|
||||||
- consumer deploy.yml: no cross-repo uses: (SPEC §10 Q1 — inline adapter, option c)
|
|
||||||
- kj installed (v0.0.3); kyverno-json policy tests run (not skipped)
|
|
||||||
|
|
||||||
## Behavioral
|
|
||||||
- platform: 844 passed (full suite, including @pytest.mark.slow live-AWS CAPs)
|
|
||||||
- consumer: 90 passed, 6 skipped (pre-existing unrelated skips)
|
|
||||||
- kj substrate: 69 targeted policy/engine tests pass against real kj (zero skips)
|
|
||||||
- pilot policies: pass on valid fixtures, fail on invalid (verified via kj scan violations)
|
|
||||||
|
|
||||||
## Security
|
|
||||||
- no raw NOVA_AWS_* export in scripts/run_platform.sh shell env (SPEC §5.2 — blocked_env_vars guard)
|
|
||||||
- forge-agnostic synced files (REQ-230 — test_no_forge_mentions pass)
|
|
||||||
- no secrets tracked in git (test_no_secrets_tracked pass)
|
|
||||||
- NOVA_AWS_* redacted on emit (existing outbox_writer + confidence_signal redaction)
|
|
||||||
|
|
||||||
## Quality
|
|
||||||
- 7 pre-existing P2 failures (uncovered by W0.5 full-suite run with kj installed) all fixed:
|
|
||||||
dynamodb examples (.yml), sync_workflows drift, CAP-024 deck path (-marp.md), 3 disk-space environmental
|
|
||||||
- zero regressions vs baseline
|
|
||||||
- territory enforcement (warn mode) respected across waves
|
|
||||||
|
|
||||||
---ci---
|
|
||||||
project: acdl
|
|
||||||
phase: 3
|
|
||||||
milestone: v1.26
|
|
||||||
status: verify
|
|
||||||
---
|
|
||||||
@@ -1,31 +0,0 @@
|
|||||||
# VERIFY — v1.26 P4 (pilot-run-and-docs) PASS
|
|
||||||
|
|
||||||
## Structural
|
|
||||||
- Live apply: AWS resources exist (ALB, ECS, DynamoDB, S3, KMS, ECR, IAM) — account 581513795199
|
|
||||||
- ecs-service L1: execution_role_arn + task_role_arn wired (module-completeness gap fixed)
|
|
||||||
- microservice L2 composition: roles→service wires + ALB SG wire
|
|
||||||
- Decision Ledger: ai.decision.made + nova.outcome.backfilled (hash chain valid)
|
|
||||||
- fact_decision.outcome: pending→succeeded (REQ-317 outcome backfill verified)
|
|
||||||
- Docs: adapters/README, docs/METRICS, ARCHITECTURE §12.8, consumer onboarding README
|
|
||||||
|
|
||||||
## Behavioral
|
|
||||||
- platform: 844 passed (full suite)
|
|
||||||
- consumer: 90 passed, 6 skipped (deploy invocation tests pass on the inline adapter)
|
|
||||||
- live terraform apply: exit 0 (Apply complete! Resources created)
|
|
||||||
|
|
||||||
## Security
|
|
||||||
- NOVA_AWS_* not in shell env (run_platform.sh unset after sourcing .env.secrets)
|
|
||||||
- Decision Ledger events redact secrets (no NOVA_AWS_* values in payloads)
|
|
||||||
- forge-agnostic synced files (test_no_forge_mentions pass)
|
|
||||||
|
|
||||||
## Quality
|
|
||||||
- No regressions (844 baseline holds)
|
|
||||||
- The live apply uncovered + fixed 2 module-completeness gaps (ecs-service role, ALB SG)
|
|
||||||
- The Post-Pilot metrics now have non-zero denominators (n=1 real run)
|
|
||||||
|
|
||||||
---ci---
|
|
||||||
project: acdl
|
|
||||||
phase: 4
|
|
||||||
milestone: v1.26
|
|
||||||
status: verify
|
|
||||||
---
|
|
||||||
@@ -1,87 +0,0 @@
|
|||||||
# VERIFY — P1 engine-core (v1.25)
|
|
||||||
|
|
||||||
> 4-layer verify gate: structural, behavioral, security, quality.
|
|
||||||
> Phase: P1. Requirements: REQ-291..294, 308, 309. Result: PASS.
|
|
||||||
|
|
||||||
## Structural
|
|
||||||
|
|
||||||
- `core/policy_engine.py` exists, implements `PolicyEngine` Protocol
|
|
||||||
(PEP 544, `@runtime_checkable`), `PolicyEngineRegistry` with
|
|
||||||
`register()` + `get_engine()`, `NullEngine` fallback.
|
|
||||||
- `adapters/kyverno-json/kyverno_json_engine.py` exists, exports
|
|
||||||
`KyvernoJsonEngine` with `name`, `is_configured()`, `evaluate()`.
|
|
||||||
- `adapters/kyverno-json/__init__.py` loads the engine by file path
|
|
||||||
(the dir name has a hyphen — not a valid Python package name).
|
|
||||||
- `adapters/kyverno-json/policies/_smoke.json` exists (trivial policy
|
|
||||||
for round-trip validation).
|
|
||||||
- `scripts/install-kyverno-json.sh` exists (go install kj@latest).
|
|
||||||
- `.ciagent/config.json` has the `policy` object
|
|
||||||
(`engine: kyverno-json`, `policy_root`).
|
|
||||||
- `.gitea/workflows/ci.yml` + `.github/workflows/ci.yml` have the
|
|
||||||
Go + kj install step (best-effort, tests skip when kj absent).
|
|
||||||
- `tests/test_policy_engine.py` (10 tests) +
|
|
||||||
`tests/test_kyverno_json_engine.py` (16 tests) exist.
|
|
||||||
|
|
||||||
## Behavioral
|
|
||||||
|
|
||||||
- `pytest tests/test_policy_engine.py tests/test_kyverno_json_engine.py`:
|
|
||||||
**24 passed, 2 skipped** (kj not installed — expected;
|
|
||||||
`pytest.skip("kj not installed")`).
|
|
||||||
- `NullEngine` satisfies the `PolicyEngine` Protocol (G-Q8a —
|
|
||||||
`isinstance(NullEngine(), PolicyEngine)` is True). Proves the swap
|
|
||||||
boundary is real without implementing OPA.
|
|
||||||
- `KyvernoJsonEngine.is_configured()` returns `False` when
|
|
||||||
`which kj` is absent → `evaluate()` returns a single
|
|
||||||
`KJ_ENGINE_NOT_CONFIGURED` SKIPPED PCR (distinct `ruleId` from
|
|
||||||
NullEngine's `NULL_ENGINE_INACTIVE` — G-Q4).
|
|
||||||
- PCR records validate against `schemas/policy_check_result.schema.json`
|
|
||||||
(via `jsonschema.validate` in tests).
|
|
||||||
- Defensive parsing: malformed kyverno-json output → `error` PCR
|
|
||||||
(`KJ_ENGINE_ERROR`), never an exception.
|
|
||||||
- Severity annotation reading (G-Q10a): policies with
|
|
||||||
`nova.cloudinit.dev/severity: high` produce PCRs with `severity: high`;
|
|
||||||
policies without the annotation default to `info`.
|
|
||||||
- Registry: `get_engine()` returns the configured engine; unknown
|
|
||||||
engine name raises `KeyError`; `policy` key absent → `NullEngine`.
|
|
||||||
- No regression: `pytest tests/test_confidence_signal.py
|
|
||||||
tests/test_adapter.py tests/test_checkov_adapter.py
|
|
||||||
tests/test_kyverno_adapter.py tests/test_contract_resolver.py` —
|
|
||||||
**132 passed** (unchanged).
|
|
||||||
|
|
||||||
## Security
|
|
||||||
|
|
||||||
- No new secrets, no new network calls in the engine core (the engine
|
|
||||||
shells to a local binary; the binary makes no network calls for
|
|
||||||
`scan`).
|
|
||||||
- `is_configured()` guard ensures the platform runs without the binary
|
|
||||||
(no hard dependency that could be exploited as a DoS vector).
|
|
||||||
- The engine writes the payload to a temp file (`tempfile.NamedTemporaryFile`)
|
|
||||||
and unlinks it in a `finally` block (no leftover payload on disk).
|
|
||||||
- No `shell=True` in the `subprocess.run` call (command is a list —
|
|
||||||
no shell injection surface).
|
|
||||||
|
|
||||||
## Quality
|
|
||||||
|
|
||||||
- `python3 -m py_compile` passes on all new Python files.
|
|
||||||
- The `PolicyEngine` Protocol is minimal (3 members) — the swap
|
|
||||||
boundary is the moat (NORTH_STAR Strategic Objective #2).
|
|
||||||
- The `NullEngine` proves a second implementation exists (structural
|
|
||||||
conformance) — the OPA swap is a known quantity (RESEARCH §4.2).
|
|
||||||
- Tests use `pytest.skip` when `which kj` is absent, so the CI matrix
|
|
||||||
passes with or without the binary (the suite is green in both cases).
|
|
||||||
|
|
||||||
## Must-have checklist
|
|
||||||
|
|
||||||
- [x] `PolicyEngine` Protocol + `PolicyEngineRegistry` + `NullEngine`
|
|
||||||
(REQ-291)
|
|
||||||
- [x] `config.json.policy` object (REQ-292)
|
|
||||||
- [x] `KyvernoJsonEngine` adapter (REQ-293)
|
|
||||||
- [x] `__init__.py` + `_smoke.json` + `install-kyverno-json.sh` + CI
|
|
||||||
install (REQ-294)
|
|
||||||
- [x] `test_policy_engine.py` — protocol conformance, registry,
|
|
||||||
NullEngine fallback (REQ-308)
|
|
||||||
- [x] `test_kyverno_json_engine.py` — PCR schema validity, defensive
|
|
||||||
parsing, skip-without-kj (REQ-309)
|
|
||||||
|
|
||||||
**Verdict: PASS** — all P1 must-haves met, no regressions, 24 new
|
|
||||||
tests pass (2 skip-without-kj), 132 existing tests unchanged.
|
|
||||||
+5
-16
@@ -2,18 +2,13 @@
|
|||||||
"projects": [
|
"projects": [
|
||||||
{
|
{
|
||||||
"slug": "acdl",
|
"slug": "acdl",
|
||||||
"name": "Nova — The New Dawn of DevSecOps",
|
"name": "Agentic Cloud Delivery Platform",
|
||||||
"default": true
|
"default": true
|
||||||
},
|
|
||||||
{
|
|
||||||
"slug": "nova-blockchain-exchange",
|
|
||||||
"name": "Nova Pilot Consumer — Blockchain Stock Exchange",
|
|
||||||
"default": false
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"active_project": "acdl",
|
"active_project": "acdl",
|
||||||
"active_projects": ["acdl", "nova-blockchain-exchange"],
|
"active_projects": ["acdl"],
|
||||||
"active_milestone": "v1.28",
|
"active_milestone": "v1.14",
|
||||||
"autonomy": {
|
"autonomy": {
|
||||||
"level": "full",
|
"level": "full",
|
||||||
"escalation_hooks": ["deploy", "delete_data", "merge_to_main"],
|
"escalation_hooks": ["deploy", "delete_data", "merge_to_main"],
|
||||||
@@ -64,7 +59,7 @@
|
|||||||
},
|
},
|
||||||
"git": {
|
"git": {
|
||||||
"branching_strategy": "flat",
|
"branching_strategy": "flat",
|
||||||
"_branching_strategy_note": "Nova uses flat workflow (committed directly to main per established convention since v1.0; renamed ACDL→Nova in v1.15). The 'phase' strategy is advisory; CIAgent uses milestone/phase branches for v1.14 but the project convention is flat.",
|
"_branching_strategy_note": "ACDL uses flat workflow (committed directly to main per established convention since v1.0). The 'phase' strategy is advisory; CIAgent uses milestone/phase branches for v1.14 but the project convention is flat.",
|
||||||
"auto_commit": true,
|
"auto_commit": true,
|
||||||
"auto_push": true
|
"auto_push": true
|
||||||
},
|
},
|
||||||
@@ -72,8 +67,7 @@
|
|||||||
"sources": [".env", ".env.secrets", ".env.*"],
|
"sources": [".env", ".env.secrets", ".env.*"],
|
||||||
"disallow": ["shell_env", "netrc", "keychain", "rc_files", "global_config"],
|
"disallow": ["shell_env", "netrc", "keychain", "rc_files", "global_config"],
|
||||||
"scopes": {
|
"scopes": {
|
||||||
"forge": "NOVA_FORGE_TOKEN",
|
"gitea": "ACDL_GITEA_TOKEN",
|
||||||
"gitea": "NOVA_FORGE_TOKEN",
|
|
||||||
"github": "GITHUB_TOKEN",
|
"github": "GITHUB_TOKEN",
|
||||||
"gitlab": "GITLAB_TOKEN",
|
"gitlab": "GITLAB_TOKEN",
|
||||||
"openai": "OPENAI_API_KEY",
|
"openai": "OPENAI_API_KEY",
|
||||||
@@ -214,10 +208,5 @@
|
|||||||
"telemetry": {
|
"telemetry": {
|
||||||
"enabled": true,
|
"enabled": true,
|
||||||
"persist": true
|
"persist": true
|
||||||
},
|
|
||||||
"strategic_direction_file": ".ciagent/NORTH_STAR.md",
|
|
||||||
"policy": {
|
|
||||||
"engine": "kyverno-json",
|
|
||||||
"policy_root": "adapters/kyverno-json/policies"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,97 +0,0 @@
|
|||||||
# Nova Pilot Consumer — Blockchain Stock Exchange
|
|
||||||
|
|
||||||
> **Milestone:** v1.26 — Live Pilot Estate Activation
|
|
||||||
> **Git:** https://git.cloudinit.dev/continuous-intelligence/nova-blockchain-exchange
|
|
||||||
> **Local clone:** /root/nova-blockchain-exchange
|
|
||||||
> **Role:** The first real consumer estate. A stock exchange built on a
|
|
||||||
> homegrown blockchain, offering equities trading (pilot scope). The
|
|
||||||
> consumer repo owns the app code + `contract.yaml`; the Nova platform
|
|
||||||
> (`acdl` repo) provides the deploy workflow, policy engine, and
|
|
||||||
> attestation gates.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Vision / Core Value
|
|
||||||
|
|
||||||
A self-contained securities-trading exchange where every order, match,
|
|
||||||
and settlement is recorded as an immutable transaction on a homegrown
|
|
||||||
Proof-of-Authority (PoA) blockchain. The pilot demonstrates that Nova's
|
|
||||||
autonomous infrastructure can take a real consumer estate from contract
|
|
||||||
to production — apply, attest, record — without an operator in the loop
|
|
||||||
of normal operations.
|
|
||||||
|
|
||||||
## North Star Alignment
|
|
||||||
|
|
||||||
- **Strategic Objective #1** (production-grade zero-touch operations):
|
|
||||||
this estate is the first real consumer; the pilot activates the
|
|
||||||
autonomy claim beyond internal demos.
|
|
||||||
- **Strategic Objective #2** (provable trust): every apply decision +
|
|
||||||
attestation lands in the Decision Ledger; the settlement-finality
|
|
||||||
kyverno-json policy (IDEATE) makes trust a policy artifact.
|
|
||||||
- **Strategic Objective #3** (compounding ROI): unblocks the three
|
|
||||||
Post-Pilot targets (Touchless Resolution ≥99%, Human Escalation
|
|
||||||
<0.1%, AI Decision Accuracy ≥99.5%) — the denominators activate when
|
|
||||||
this estate runs.
|
|
||||||
|
|
||||||
## Domain Boundaries
|
|
||||||
|
|
||||||
- **This repo owns:** the blockchain (consensus, blocks, transactions),
|
|
||||||
the order-matching engine, the settlement service, the `contract.yaml`
|
|
||||||
that declares the infrastructure, and the consumer-side deploy workflow
|
|
||||||
invocation (`uses: acdl/.github/workflows/deploy.yml@v1.25`).
|
|
||||||
- **The platform (`acdl`) repo owns:** the deploy workflow, the policy
|
|
||||||
engine (kyverno-json), the contract resolver, the adapter, the
|
|
||||||
confidence signal, the HITL gates, and the Decision Ledger.
|
|
||||||
|
|
||||||
## Scope: v1.26 Pilot
|
|
||||||
|
|
||||||
- **Equities only** (bonds, derivatives, options deferred to future
|
|
||||||
milestones — different settlement models).
|
|
||||||
- **Minimal PoA ledger** — append-only blocks, single validator (pilot),
|
|
||||||
T+1 settlement finality = block commit. No multi-validator BFT.
|
|
||||||
- **Homegrown chain** — authored as part of this repo, not deployed on
|
|
||||||
Ethereum/Solana/Hyperledger.
|
|
||||||
|
|
||||||
## Anti-Goals (v1.26)
|
|
||||||
|
|
||||||
1. Not a general-purpose blockchain platform — purpose-built for
|
|
||||||
securities settlement in the pilot.
|
|
||||||
2. Not multi-validator consensus — single validator for the pilot.
|
|
||||||
3. Not bonds/derivatives/options — equities only this milestone.
|
|
||||||
4. Not a replacement for the Nova platform — this is a *consumer* of
|
|
||||||
Nova, not a fork.
|
|
||||||
|
|
||||||
## Key Decisions (v1.26 — established in SPECIFY, refined in CLARIFY)
|
|
||||||
|
|
||||||
| ID | Decision | Rationale | Affects |
|
|
||||||
|---|---|---|---|
|
|
||||||
| D-200 | Pilot scope = equities only | Bonds/derivatives/options have very different settlement models; equities (T+1) is the simplest to demonstrate the Nova platform's policy gates over a real estate. | Phase count; requirement scope. |
|
|
||||||
| D-201 | Homegrown PoA ledger (single validator) | Minimal viable chain for a pilot; settlement finality = block commit. Multi-validator BFT is a future milestone. | Blockchain core design. |
|
|
||||||
| D-202 | Consumer repo = `nova-blockchain-exchange` (Gitea) | New repo under `continuous-intelligence` org; tracked as 2nd CIAgent project. | Multi-project config. |
|
|
||||||
| D-203 | AWS account = 581513795199 (existing) | Reuse the bootstrapped account; state bucket + outbox table created in pre-run Workstream A3. | Env JSON binding. |
|
|
||||||
| D-204 | D-083 (S3 Object Lock/JWS) stays deferred | The SQLite hash-chain + DynamoDB outbox is the pilot's audit record. Tamper-evidence is a future milestone. | Audit ledger scope. |
|
|
||||||
| D-205 | Cold-only metrics sufficient (D-126) | No hot ops dashboard in the pilot; cold SQLite store + PowerBI export. | Metrics pipeline. |
|
|
||||||
|
|
||||||
## Constraints
|
|
||||||
|
|
||||||
- The consumer repo's deploy MUST go through `deploy.yml@v1.25` (the
|
|
||||||
reusable workflow) — no direct `terraform apply` bypassing the
|
|
||||||
platform's policy + attestation gates.
|
|
||||||
- The `contract.yaml` MUST validate against
|
|
||||||
`schemas/contract.schema.json`.
|
|
||||||
- The homegrown blockchain MUST be deterministic (same inputs → same
|
|
||||||
block) — it is automation, not AI (NORTH_STAR Objective #2 tenet).
|
|
||||||
|
|
||||||
## Context
|
|
||||||
|
|
||||||
- The Nova platform (`acdl` repo) completed v1.25 (kyverno-json Unified
|
|
||||||
Policy Engine). The swappable `PolicyEngine` adapter is in place.
|
|
||||||
- The AWS bootstrap (S3 state bucket + DynamoDB outbox) was re-run in
|
|
||||||
the pre-run (Workstream A3) — the platform components exist.
|
|
||||||
- The consumer repo was created on Gitea (Workstream A4) and cloned to
|
|
||||||
`/root/nova-blockchain-exchange`.
|
|
||||||
- **Phase-by-phase history:** `.ciagent/ROADMAP.md` §v1.26 (the
|
|
||||||
consumer ROADMAP is archived at
|
|
||||||
`.ciagent/nova-blockchain-exchange/archive/ROADMAP-v1.26.md` since
|
|
||||||
v1.27 — the platform ROADMAP is the source of truth for milestone
|
|
||||||
phase narrative).
|
|
||||||
@@ -1,180 +0,0 @@
|
|||||||
# nova-blockchain-exchange — Consumer Onboarding Guide
|
|
||||||
|
|
||||||
> **Milestone:** v1.26 — the first real Nova consumer estate. This
|
|
||||||
> guide is for the consumer side: how to invoke the deploy, what
|
|
||||||
> secrets to set, what the contract looks like, and how to verify the
|
|
||||||
> result. The platform side is documented in
|
|
||||||
> `.ciagent/ARCHITECTURE.md` §12.8; the live-pilot evidence is in
|
|
||||||
> `.ciagent/archive/P4-PILOT-RUN-EVIDENCE-v1.26.md` (archived v1.27).
|
|
||||||
|
|
||||||
This is a **consumer** of the Nova platform, not a fork. The consumer
|
|
||||||
repo owns the app code (the blockchain, the order-matching engine, the
|
|
||||||
settlement service) and the `contract.yaml` that declares the
|
|
||||||
infrastructure. The Nova platform (`acdl` repo) owns the deploy
|
|
||||||
workflow, the policy engine, the contract resolver, the Terraform
|
|
||||||
adapter, the confidence signal, the HITL gates, and the Decision
|
|
||||||
Ledger. The consumer never clones the platform repo and never runs
|
|
||||||
`terraform apply` directly.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 1. Invoke the deploy
|
|
||||||
|
|
||||||
The consumer's `.github/workflows/deploy.yml` (and its byte-identical
|
|
||||||
`.gitea/workflows/deploy.yml` mirror) is a `workflow_dispatch` workflow.
|
|
||||||
It does **not** use cross-repo `uses:` (SPEC §10 Q1 — the Gitea forge
|
|
||||||
rejects it). Instead it is an **inline adapter**: it checks out the
|
|
||||||
consumer repo, then checks out `acdl/acdl` @ `ref: v1.25` into
|
|
||||||
`platform/`, then runs `bash platform/scripts/run_platform.sh`.
|
|
||||||
|
|
||||||
To run a deploy:
|
|
||||||
|
|
||||||
1. In the consumer repo's Actions UI, pick the **Deploy** workflow.
|
|
||||||
2. Click **Run workflow**.
|
|
||||||
3. Inputs:
|
|
||||||
- `mode` = `full` (the default — applies the Terraform). Other
|
|
||||||
values: `plan-only` (no apply), `check-only` (policy + confidence
|
|
||||||
only), `decommission` (requires a `changeRequestId`).
|
|
||||||
- `environment` = `dev` (the pilot scope — equities only, dev only,
|
|
||||||
D-020/D-200). Leave empty to use the contract's `environment`
|
|
||||||
field.
|
|
||||||
4. The workflow runs the platform pipeline end-to-end: contract
|
|
||||||
resolve → adapter compile → terraform plan → policy (kyverno-json)
|
|
||||||
→ confidence signal → (dev: autonomous apply) → Decision Ledger
|
|
||||||
events.
|
|
||||||
|
|
||||||
For the pilot, the documented invocation is `mode=full,
|
|
||||||
environment=dev`. The first live run was `blkex-pilot-apply-v0.2`
|
|
||||||
(2026-08-19).
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 2. Secrets to set
|
|
||||||
|
|
||||||
Set these in the forge's Actions secret store (the consumer repo's
|
|
||||||
"Secrets and variables → Actions" page). The platform-managed
|
|
||||||
scheduled workflow `rotate-aws-key.yml` rotates the `NOVA_AWS_*` key
|
|
||||||
daily (SPEC §5.9 — the v0.2 deploy uses the currently-active key).
|
|
||||||
|
|
||||||
| Secret | Purpose |
|
|
||||||
| --- | --- |
|
|
||||||
| `NOVA_AWS_ACCESS_KEY_ID` | The static AWS access key for the deploy IAM principal. Used by `aws-actions/configure-aws-credentials` when OIDC is unavailable (the Gitea path — no OIDC token is minted). |
|
|
||||||
| `NOVA_AWS_SECRET_ACCESS_KEY` | The matching secret key. Rotated by `workflows-src/rotate-aws-key.yml`. |
|
|
||||||
| `AWS_DEFAULT_REGION` | The target region (`us-east-1` for the pilot). |
|
|
||||||
|
|
||||||
The platform's `.github/workflows/deploy.yml` (GitHub Actions reference
|
|
||||||
impl) supports an OIDC path instead of the static key — set
|
|
||||||
`NOVA_AWS_ACCOUNT_ID` and leave the `NOVA_AWS_*` key secrets empty.
|
|
||||||
The Gitea inline adapter uses the static-key path.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 3. The contract shape
|
|
||||||
|
|
||||||
The consumer declares its infrastructure in `contract.yaml` at the
|
|
||||||
repo root, validated against the platform's
|
|
||||||
`schemas/contract.schema.json`. The pilot contract has the shape:
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
id: blkex
|
|
||||||
name: blockchain-exchange
|
|
||||||
environment: dev
|
|
||||||
infrastructure:
|
|
||||||
microservice: # the L2 composition (ECS Fargate + ALB + roles)
|
|
||||||
...
|
|
||||||
dynamodb: # the L1 DynamoDB table (the ledger)
|
|
||||||
...
|
|
||||||
s3: # the L1 S3 bucket (block storage)
|
|
||||||
...
|
|
||||||
```
|
|
||||||
|
|
||||||
Three `infrastructure.*` blocks: `microservice` (the L2 composition
|
|
||||||
that wires the ECS service, the ALB, and the IAM roles together), and
|
|
||||||
the two L1 primitives (`dynamodb` for the ledger, `s3` for block
|
|
||||||
storage). Per-environment variants live in
|
|
||||||
`contracts/blockchain-exchange.{dev,qa,prod}.yml` (the per-env
|
|
||||||
promotion model, REQ-105). The pilot runs the `dev` variant.
|
|
||||||
|
|
||||||
The contract is the **only** consumer-facing artifact that describes
|
|
||||||
infrastructure. It is IR-typed (engine-agnostic); the platform
|
|
||||||
resolves it to a target stack, the Terraform adapter compiles the
|
|
||||||
stack to HCL, and `terraform apply` runs in the central pipeline —
|
|
||||||
never on the consumer's workstation.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 4. What the platform does
|
|
||||||
|
|
||||||
When `run_platform.sh` runs against `contract.yaml`:
|
|
||||||
|
|
||||||
1. **Resolve** the contract to a target stack (a list of L1 instances +
|
|
||||||
inputs + relationships), reading `modules/registry.json` for each
|
|
||||||
L1's `terraform_dir`.
|
|
||||||
2. **Compile** the stack to Terraform HCL via the stateless adapter
|
|
||||||
(`adapters/terraform/adapter.py`) — emits `module "<rid>" { source }
|
|
||||||
` blocks + wired `ref:` refs. No `TYPE_MAP` — each L1 owns its
|
|
||||||
shape.
|
|
||||||
3. **Plan** — `terraform plan` against the live AWS account. Infracost
|
|
||||||
runs on the plan JSON and emits `nova.cost.estimated`.
|
|
||||||
4. **Policy** — the kyverno-json engine evaluates the meta-policies
|
|
||||||
(`block-on-any-critical` + the pilot policies) and emits
|
|
||||||
`PolicyCheckResult` records.
|
|
||||||
5. **Confidence** — the confidence signal consumes the six inputs (the
|
|
||||||
PCRs included) and emits `nova.confidence.computed` with
|
|
||||||
`{ score, band, perInput, reasonCodes }`. Dev threshold = 0.50.
|
|
||||||
6. **Apply** (dev, autonomous — no HITL gate) — `terraform apply`
|
|
||||||
against account `581513795199`. On success, `nova.ai.decision.made`
|
|
||||||
+ `nova.run.completed` land in the Decision Ledger.
|
|
||||||
7. **Backfill** — the outcome (`pending → succeeded`) is backfilled
|
|
||||||
(REQ-317), producing `nova.outcome.backfilled`. The SQLite
|
|
||||||
hash-chain is extended, not torn up.
|
|
||||||
|
|
||||||
The consumer does not see steps 1–7 directly; the consumer sees the
|
|
||||||
workflow's green check + the uploaded artifacts (`nova-terraform`,
|
|
||||||
`nova-platform-log`).
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 5. How to verify post-deploy
|
|
||||||
|
|
||||||
Two independent verifications — read the AWS API and read the Decision
|
|
||||||
Ledger. Neither trusts the other.
|
|
||||||
|
|
||||||
**AWS API (the infrastructure landed):**
|
|
||||||
- `aws elbv2 describe-load-balancers` — the ALB
|
|
||||||
(`app-254671247.us-east-1.elb.amazonaws.com` for the pilot).
|
|
||||||
- `aws ecs describe-services --cluster nova-cluster --services
|
|
||||||
nova-microservice` — the ECS service is `ACTIVE`.
|
|
||||||
- `aws dynamodb describe-table --table-name nova-blkex-ledger-dev` —
|
|
||||||
the ledger table exists (PK `block_index`, PAY_PER_REQUEST).
|
|
||||||
- `aws s3api head-bucket --bucket
|
|
||||||
nova-blkex-blocks-dev-581513795199-us-east-1` — the block bucket
|
|
||||||
exists (versioning + SSE).
|
|
||||||
|
|
||||||
**Decision Ledger (the trust record):**
|
|
||||||
- The SQLite hash-chain at `metrics/decision_ledger.db` has the
|
|
||||||
`nova.ai.decision.made` row for `blkex-pilot-apply-v0.2` (chosen
|
|
||||||
action `pass`, `human_override` false) + the
|
|
||||||
`nova.outcome.backfilled` row (outcome `pending → succeeded`).
|
|
||||||
- The chain is valid (`prev_event_hash` links, 0 breaks). The
|
|
||||||
Trust Snapshot (`metrics/TRUST_SNAPSHOT.md`) records the verdict.
|
|
||||||
|
|
||||||
If the AWS API shows the resources AND the Decision Ledger shows the
|
|
||||||
decision + outcome with a valid chain, the deploy is verified. See
|
|
||||||
`.ciagent/archive/P4-PILOT-RUN-EVIDENCE-v1.26.md` for the full pilot-evidence
|
|
||||||
checklist (every ARN, the confidence JSON, the backfill timestamp; archived v1.27).
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## References
|
|
||||||
|
|
||||||
- `.ciagent/ARCHITECTURE.md` §12.8 — the pilot-estate architecture
|
|
||||||
(this guide is the consumer-facing companion to that section).
|
|
||||||
- `.ciagent/archive/P4-PILOT-RUN-EVIDENCE-v1.26.md` — the live-pilot evidence
|
|
||||||
(run `blkex-pilot-apply-v0.2`; archived v1.27).
|
|
||||||
- `.ciagent/nova-blockchain-exchange/PROJECT.md` — the consumer
|
|
||||||
project charter (vision, scope, decisions D-200..D-205).
|
|
||||||
- `.ciagent/nova-blockchain-exchange/REQUIREMENTS.md` — the consumer
|
|
||||||
requirements (REQ-313 contract, REQ-314 deploy invocation).
|
|
||||||
- `adapters/README.md` §Consumers — the Gitea adapter note
|
|
||||||
(SPEC §10 Q1 — inline checkout-then-call, no cross-repo `uses:`).
|
|
||||||
@@ -1,221 +0,0 @@
|
|||||||
# Requirements — nova-blockchain-exchange (v1.26 pilot)
|
|
||||||
|
|
||||||
> **Project:** nova-blockchain-exchange — blockchain stock exchange (pilot)
|
|
||||||
> **Milestone:** v1.26 — Live Pilot Estate Activation
|
|
||||||
> **Scope:** equities only; minimal PoA ledger; T+1 settlement finality.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## v1.26 — Live Pilot Estate Activation
|
|
||||||
|
|
||||||
### REQ-310 — Homegrown PoA blockchain core
|
|
||||||
|
|
||||||
The consumer repo implements a minimal Proof-of-Authority blockchain:
|
|
||||||
append-only blocks, single validator (pilot), SHA-256 block hash chain,
|
|
||||||
deterministic block production (same ordered transactions → same block).
|
|
||||||
The chain records every order, match, and settlement as transactions.
|
|
||||||
Settlement finality = block commit (a transaction is final when its
|
|
||||||
block is committed to the chain).
|
|
||||||
|
|
||||||
**Must-haves:**
|
|
||||||
- `chain/block.py` — Block dataclass (index, timestamp, prev_hash,
|
|
||||||
transactions, nonce, hash). `compute_hash()` deterministic.
|
|
||||||
- `chain/ledger.py` — Ledger class: `append_block()`, `verify_chain()`,
|
|
||||||
`get_block(index)`, `get_latest_block()`. Genesis block on init.
|
|
||||||
- `chain/validator.py` — PoA validator: single validator (config-driven,
|
|
||||||
pilot), `propose_block(transactions)` → Block, `commit_block(block)`.
|
|
||||||
- `tests/test_block.py`, `tests/test_ledger.py`, `tests/test_validator.py`
|
|
||||||
— chain integrity, hash determinism, genesis, append/verify.
|
|
||||||
|
|
||||||
### REQ-311 — Order-matching engine
|
|
||||||
|
|
||||||
A limit-order-book matching engine: buy/sell orders with price + size,
|
|
||||||
matched at the best price (price-time priority). Produces match
|
|
||||||
transactions recorded on the chain.
|
|
||||||
|
|
||||||
**Must-haves:**
|
|
||||||
- `engine/order_book.py` — OrderBook: `add_order(order)`,
|
|
||||||
`match_orders()` → list of Match (buyer, seller, price, size).
|
|
||||||
- `engine/order.py` — Order dataclass (id, side, symbol, price, size,
|
|
||||||
timestamp).
|
|
||||||
- `tests/test_order_book.py` — match priority, partial fills, no-match.
|
|
||||||
|
|
||||||
### REQ-312 — Settlement service
|
|
||||||
|
|
||||||
T+1 settlement: matches commit to the chain; a settlement is final when
|
|
||||||
its block is committed. The service reads matches from the order engine,
|
|
||||||
produces settlement transactions, and submits them to the ledger.
|
|
||||||
|
|
||||||
**Must-haves:**
|
|
||||||
- `settlement/service.py` — SettlementService: `settle(match)` →
|
|
||||||
SettlementTransaction, `submit(ledger)`. Idempotent (re-settling a
|
|
||||||
match is a no-op once final).
|
|
||||||
- `tests/test_settlement.py` — happy path, idempotency, finality check.
|
|
||||||
|
|
||||||
### REQ-313 — Consumer `contract.yaml` ✓ complete (P2, v1.25.2)
|
|
||||||
|
|
||||||
The consumer repo declares its infrastructure via a `contract.yaml` at
|
|
||||||
the repo root, validated against `schemas/contract.schema.json`. The
|
|
||||||
contract references the Nova platform's deploy workflow
|
|
||||||
(`uses: acdl/.github/workflows/deploy.yml@v1.25`) and declares the
|
|
||||||
blockchain exchange stack (the AWS resources the app needs: ECS for
|
|
||||||
the matching engine, DynamoDB for the ledger, S3 for block storage).
|
|
||||||
The DynamoDB L1 primitive (REQ-322) must land before this contract can
|
|
||||||
declare `dynamodb` — ECS + S3 already exist.
|
|
||||||
|
|
||||||
**Must-haves:**
|
|
||||||
- `contract.yaml` — id, name (`blockchain-exchange`), environment
|
|
||||||
(dev/qa/prod variants), infrastructure block.
|
|
||||||
- `contracts/blockchain-exchange.dev.yml`, `.qa.yml`, `.prod.yml` —
|
|
||||||
per-environment variants (per-env promotion model, REQ-105).
|
|
||||||
- `tests/test_contract_validates.py` — schema validation against the
|
|
||||||
platform's `schemas/contract.schema.json`.
|
|
||||||
|
|
||||||
### REQ-314 — Consumer deploy workflow invocation ✓ complete (P2, v1.25.2)
|
|
||||||
|
|
||||||
The consumer repo's GitHub/Gitea Actions invoke the Nova platform's
|
|
||||||
reusable `deploy.yml@v1.25` workflow with `mode: full` for the pilot.
|
|
||||||
The workflow checks out the consumer repo + the platform repo, runs
|
|
||||||
`scripts/run_platform.sh`, and records the apply decision + attestation
|
|
||||||
in the Nova Decision Ledger.
|
|
||||||
|
|
||||||
**Must-haves:**
|
|
||||||
- `.github/workflows/deploy.yml` — `uses: acdl/.github/workflows/deploy.yml@v1.25`
|
|
||||||
with `with: { contract: contract.yaml, mode: full, environment: dev }`.
|
|
||||||
- `.gitea/workflows/deploy.yml` — byte-identical mirror (the platform's
|
|
||||||
deploy workflow is forge-agnostic).
|
|
||||||
- `tests/test_deploy_workflow_invocation.py` — asserts the `uses:` ref
|
|
||||||
+ inputs are correct.
|
|
||||||
|
|
||||||
### REQ-315 — Settlement-finality kyverno-json policy (IDEATE I6)
|
|
||||||
|
|
||||||
A kyverno-json policy asserting that every promotion (qa→prod) requires
|
|
||||||
settlement finality: all matches in the promotion window have committed
|
|
||||||
blocks. This is the securities-specific extension of v1.25's policy
|
|
||||||
engine — it applies Nova's compliance posture to the blockchain domain.
|
|
||||||
|
|
||||||
**Must-haves:**
|
|
||||||
- `policies/settlement-finality.json` — kyverno-json policy over the
|
|
||||||
settlement-service status JSON (asserts `all_committed: true`).
|
|
||||||
- `tests/test_settlement_finality_policy.py` — passing + failing
|
|
||||||
fixtures; skip when `kj` absent.
|
|
||||||
|
|
||||||
### REQ-316 — Pilot-estate regression capability (CAP-025)
|
|
||||||
|
|
||||||
A new capability in the regression gate: "pilot estate apply→attest→record
|
|
||||||
round-trip." The regression gate asserts that the consumer estate can
|
|
||||||
run end-to-end (contract resolve → adapter compile → terraform plan →
|
|
||||||
policy scan → confidence signal → attestation → outbox record) against
|
|
||||||
the live AWS account `581513795199`.
|
|
||||||
|
|
||||||
**Must-haves:**
|
|
||||||
- `core/regression_verify.py` gains CAP-025 (live-pilot-apply).
|
|
||||||
- `tests/test_regression_pilot.py` — the round-trip assertion.
|
|
||||||
|
|
||||||
### REQ-317 — Outcome-backfill emitter (IDEATE I1)
|
|
||||||
|
|
||||||
Wire `apply.completed` / `apply.failed` events back into `fact_decision`
|
|
||||||
in the cold store so the AI Decision Accuracy metric has a non-`pending`
|
|
||||||
outcome. Today `fact_decision.outcome` is stuck at `pending` (D-096
|
|
||||||
blocker). The backfill emitter reads `run_manifest.completed/failed`
|
|
||||||
events and updates the corresponding decision's outcome.
|
|
||||||
|
|
||||||
**Must-haves:**
|
|
||||||
- `core/metrics/outcome_backfill.py` — `backfill(decision_id, outcome)`
|
|
||||||
updates `fact_decision.outcome` + `fact_decision.backfilled_at`.
|
|
||||||
- `core/metrics/collector.py` — invokes backfill after run completion.
|
|
||||||
- `tests/test_outcome_backfill.py`.
|
|
||||||
|
|
||||||
### REQ-318 — `reason='confidence'` escalation tag (IDEATE I2)
|
|
||||||
|
|
||||||
Emit a distinct `reason='confidence'` field on the `block` band's
|
|
||||||
`ai.decision.made` event so the Human Escalation Frequency metric has a
|
|
||||||
discriminated numerator. Today `hitl_block` is a boolean from the
|
|
||||||
manifest; the `reason` discriminator is not stored.
|
|
||||||
|
|
||||||
**Must-haves:**
|
|
||||||
- `core/confidence_signal.py` — `ai.decision.made` gains
|
|
||||||
`escalation_reason: 'confidence'` when `band == 'block'`.
|
|
||||||
- `core/metrics/collector.py` — persists `escalation_reason` into
|
|
||||||
`fact_run`.
|
|
||||||
- `tests/test_confidence_escalation_reason.py`.
|
|
||||||
|
|
||||||
### REQ-319 — Env-JSON `state_backend` wiring reconciliation (IDEATE I3)
|
|
||||||
|
|
||||||
The env JSON's `state_backend.bucket` field is currently unused by the
|
|
||||||
adapter (the adapter computes `nova-tfstate-<AWS_ACCOUNT_ID>` directly).
|
|
||||||
Reconcile: the adapter reads `state_backend.bucket` from the env JSON
|
|
||||||
(falling back to the computed name for backwards compat). This closes
|
|
||||||
the wiring gap so the pilot's env JSON is the single source of truth.
|
|
||||||
|
|
||||||
**Must-haves:**
|
|
||||||
- `adapters/terraform/adapter.py` — reads `env.state_backend.bucket`
|
|
||||||
when present.
|
|
||||||
- `tests/test_adapter_state_backend.py`.
|
|
||||||
- `core/environments/*.json` — `state_backend.bucket` updated to the
|
|
||||||
real bucket name `nova-tfstate-581513795199-us-east-1`.
|
|
||||||
|
|
||||||
### REQ-320 — Declarative pilot-readiness kyverno-json policy (IDEATE I5)
|
|
||||||
|
|
||||||
A kyverno-json policy asserting the env JSON has a non-placeholder
|
|
||||||
`account_id` (not `000000000000`) before any `terraform apply`. This is
|
|
||||||
the declarative gate that prevents a pilot run against a placeholder
|
|
||||||
account.
|
|
||||||
|
|
||||||
**Must-haves:**
|
|
||||||
- `adapters/kyverno-json/policies/pilot-readiness/no-placeholder-account.json`
|
|
||||||
- `tests/test_pilot_readiness_policy.py`.
|
|
||||||
|
|
||||||
### REQ-321 — Docs + adapter README for the consumer estate
|
|
||||||
|
|
||||||
Update `adapters/README.md` (new consumer row), `docs/METRICS.md` (the
|
|
||||||
3 Post-Pilot metrics now grounded post-pilot), `.ciagent/ARCHITECTURE.md`
|
|
||||||
(§12.8 — Pilot Estate), and `.ciagent/nova-blockchain-exchange/README.md`
|
|
||||||
(consumer onboarding guide).
|
|
||||||
|
|
||||||
**Must-haves:**
|
|
||||||
- `adapters/README.md` — consumer-repo row.
|
|
||||||
- `docs/METRICS.md` — Post-Pilot metrics grounded note.
|
|
||||||
- `.ciagent/ARCHITECTURE.md` — §12.8 Pilot Estate.
|
|
||||||
- `.ciagent/nova-blockchain-exchange/README.md` — onboarding guide.
|
|
||||||
|
|
||||||
### REQ-322 — DynamoDB L1 primitive (platform-side) ✓ complete (P2, v1.25.2)
|
|
||||||
|
|
||||||
The blockchain exchange's ledger table needs a DynamoDB L1 primitive.
|
|
||||||
Research (RESEARCH §3) confirmed the adapter is stateless/registry-
|
|
||||||
driven (no `TYPE_MAP` — deleted in v1.11); a new stack type requires a
|
|
||||||
new L1 module, not an adapter change. The `dynamodb` primitive mirrors
|
|
||||||
the existing `s3` / `rds` primitives: `interface.json` (stack type
|
|
||||||
`aws:dynamodb:table`, inputs `table_name`/`region`/`pk`/`sk`/`billing_mode`,
|
|
||||||
outputs `table_arn`/`table_name`), `terraform/main.tf`
|
|
||||||
(`resource "aws_dynamodb_table" "this"`), `README.md`, `instance.json`,
|
|
||||||
+ a `registry.json` entry. The pilot contract's `infrastructure.dynamodb`
|
|
||||||
block references this primitive. This is the single platform-side
|
|
||||||
module build-out for the milestone (ECS + S3 already exist).
|
|
||||||
|
|
||||||
**Must-haves:**
|
|
||||||
- `modules/l1/dynamodb/interface.json` — stack type
|
|
||||||
`aws:dynamodb:table`, inputs, outputs.
|
|
||||||
- `modules/l1/dynamodb/terraform/main.tf` —
|
|
||||||
`resource "aws_dynamodb_table" "this"` (PK + optional SK,
|
|
||||||
`billing_mode = PAY_PER_REQUEST` default, encryption + point-in-time-
|
|
||||||
recovery enabled per v1.8 NFR defaults).
|
|
||||||
- `modules/l1/dynamodb/README.md` — module doc.
|
|
||||||
- `modules/l1/dynamodb/instance.json` — sample instance.
|
|
||||||
- `modules/registry.json` — `dynamodb` entry (kind `l1`,
|
|
||||||
`terraform_dir: modules/l1/dynamodb/terraform`).
|
|
||||||
- `tests/test_adapter.py` — add `dynamodb` to `EXPECTED_L1_KEYS` +
|
|
||||||
a resolution + emission test.
|
|
||||||
- `modules/README.md` — catalog index updated.
|
|
||||||
|
|
||||||
### Summary
|
|
||||||
|
|
||||||
13 requirements (REQ-310..322). Equities-only pilot; minimal PoA ledger;
|
|
||||||
T+1 settlement; consumer deploy via `deploy.yml@v1.25`; 3 Post-Pilot
|
|
||||||
metrics grounded (outcome backfill + escalation reason + pilot runs);
|
|
||||||
3 kyverno-json policies extending v1.25 (settlement-finality,
|
|
||||||
pilot-readiness, + the existing meta-policies apply); env-JSON wiring
|
|
||||||
reconciled; DynamoDB L1 primitive authored (the single platform-side
|
|
||||||
module build-out — the adapter is stateless/registry-driven, so the
|
|
||||||
primitive is a new `modules/l1/dynamodb/` module + registry entry, not
|
|
||||||
an adapter change).
|
|
||||||
@@ -1,58 +0,0 @@
|
|||||||
# Roadmap — nova-blockchain-exchange (v1.26 pilot)
|
|
||||||
|
|
||||||
> **Project:** nova-blockchain-exchange — blockchain stock exchange (pilot)
|
|
||||||
> **Milestone:** v1.26 — Live Pilot Estate Activation
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## v1.26 — Live Pilot Estate Activation (active)
|
|
||||||
|
|
||||||
Lift D-096 (live AWS re-provisioning); activate the first real consumer
|
|
||||||
estate (a stock exchange on a homegrown PoA blockchain, equities only)
|
|
||||||
against live AWS account `581513795199`; ground the three Post-Pilot
|
|
||||||
targets in NORTH_STAR.md (Touchless Resolution ≥99%, Human Escalation
|
|
||||||
<0.1%, AI Decision Accuracy ≥99.5%). The platform repo (`acdl`) provides
|
|
||||||
the deploy workflow, policy engine, and attestation gates; this repo
|
|
||||||
provides the app (blockchain + matching engine + settlement) + the
|
|
||||||
`contract.yaml`.
|
|
||||||
|
|
||||||
Tags run on the **v1.25.x** patch line: `v1.25.0` (P0) → `v1.25.N`
|
|
||||||
(final phase = milestone release).
|
|
||||||
|
|
||||||
### Phase P1 — blockchain-core (planned, tag v1.25.1)
|
|
||||||
- REQ-310: Homegrown PoA blockchain core (block, ledger, validator).
|
|
||||||
- REQ-311: Order-matching engine (limit order book, price-time priority).
|
|
||||||
- REQ-312: Settlement service (T+1, idempotent, finality = block commit).
|
|
||||||
|
|
||||||
### Phase P2 — consumer-contract-and-deploy (complete, tag v1.25.2)
|
|
||||||
- REQ-313: Consumer `contract.yaml` + per-env variants. ✓
|
|
||||||
- REQ-314: Consumer deploy workflow invocation (`deploy.yml@v1.25`). ✓
|
|
||||||
- REQ-322: DynamoDB L1 primitive (platform-side, P2 W0). ✓
|
|
||||||
|
|
||||||
### Phase P3 — pilot-metrics-and-policies (planned, tag v1.25.3)
|
|
||||||
- REQ-315: Settlement-finality kyverno-json policy.
|
|
||||||
- REQ-316: Pilot-estate regression capability (CAP-025).
|
|
||||||
- REQ-317: Outcome-backfill emitter.
|
|
||||||
- REQ-318: `reason='confidence'` escalation tag.
|
|
||||||
- REQ-319: Env-JSON `state_backend` wiring reconciliation.
|
|
||||||
- REQ-320: Declarative pilot-readiness kyverno-json policy.
|
|
||||||
|
|
||||||
### Phase P4 — pilot-run-and-docs (planned, tag v1.25.4)
|
|
||||||
- REQ-321: Docs + adapter README + onboarding guide.
|
|
||||||
- Live pilot end-to-end run (apply → attest → record) against
|
|
||||||
`581513795199`.
|
|
||||||
|
|
||||||
### Phase P5 — final review + audit + milestone ship (Final Phase, tag v1.25.5)
|
|
||||||
- Multi-persona code review across P1..P4.
|
|
||||||
- Audit: reconstruction test, branch hygiene, commit discipline.
|
|
||||||
- Milestone ship: merge `phase/05` → `milestone/v1.26-pilot-activation`
|
|
||||||
→ `main`; tag `v1.25.5` (= the v1.26 release per prev-minor tagging
|
|
||||||
rule); create Gitea release with full milestone summary; delete all
|
|
||||||
milestone branches.
|
|
||||||
- Update `REQUIREMENTS.md` (mark REQ-310..321 complete), `ROADMAP.md`
|
|
||||||
(mark v1.26 complete), `NORTH_STAR.md` (note Strategic Objectives #1
|
|
||||||
+ #3 — first real consumer estate; Post-Pilot denominators activated).
|
|
||||||
|
|
||||||
After v1.26: future milestones may add bonds/derivatives/options
|
|
||||||
(different settlement models), multi-validator BFT consensus, and
|
|
||||||
tamper-evident ledger (D-083 lift).
|
|
||||||
@@ -1,24 +0,0 @@
|
|||||||
=== tools ===
|
|
||||||
terraform: /usr/bin/terraform
|
|
||||||
checkov: /usr/local/bin/checkov
|
|
||||||
python3: /usr/bin/python3
|
|
||||||
jq: /usr/bin/jq
|
|
||||||
rsync: /usr/bin/rsync
|
|
||||||
marp: MISSING
|
|
||||||
mmdc: MISSING
|
|
||||||
Terraform v1.9.8
|
|
||||||
3.3.8
|
|
||||||
Python 3.12.3
|
|
||||||
=== chrome/chromium (for slide render) ===
|
|
||||||
found: /root/.cache/ms-playwright/chromium-1217/chrome-linux64/chrome
|
|
||||||
=== creds ===
|
|
||||||
.env.secrets: present (4 lines)
|
|
||||||
.env: present
|
|
||||||
=== aws creds loadable? ===
|
|
||||||
NOVA_AWS_ACCESS_KEY_ID: set
|
|
||||||
AWS_DEFAULT_REGION: us-east-1
|
|
||||||
=== git ===
|
|
||||||
main
|
|
||||||
v1.18.1-11-gaa868c9
|
|
||||||
=== disk ===
|
|
||||||
/dev/loop2 148G 140G 1.3G 100% /
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
{"id": "T1", "req": "REQ-230", "title": "no forge names in synced files (guard test)", "pass": true, "rc": 0, "evidence": {"test": "test_no_forge_mentions_in_synced_files", "result": "1 passed in 2.20s", "log_tail": ["tests/test_no_forge_mentions.py::test_no_forge_mentions_in_synced_files PASSED [100%]", "1 passed in 2.20s"]}}
|
|
||||||
{"id": "T2", "req": "REQ-230", "title": "forge-detection code genericized", "pass": true, "rc": 0, "evidence": {"hardcoded_gitea_gitlab_hits": 0, "genericization_signals": ["contract_ingestor.py: _forge_type() returns 'generic_forge'", "hitl_gates.py: GITHUB_ACTOR or FORGE_ACTOR (no GITEA_ACTOR)", "run_platform.sh:166: GITHUB_ACTOR:-FORGE_ACTOR fallback"]}}
|
|
||||||
{"id": "T3", "req": "REQ-231", "title": "synced docs stripped of internal provenance", "pass": false, "rc": 1, "evidence": {"provenance_hit_count": 40, "contaminated_files": ["docs/ONBOARDING.md (REQ-182,183,184; D-113,114,119)", "docs/METRICS.md (REQ-191,192,193,194,211,212; D-083,096,113,114,119)", "docs/presentations/README.md (REQ-214,226,228; D-130,141; .ciagent/PROJECT.md)", "docs/presentations/nova-no-humans-platform.{md,marp.md,html,talking-points.md} (v1.X milestone headers)", "docs/presentations/assets/mmd/developer-experience-08-semver.mmd (v1.12 header)"], "root_cause": "test_no_forge_mentions.py only guards forge names, not provenance IDs", "defect": "F7"}}
|
|
||||||
{"id": "T4", "req": "REQ-232", "title": "migration docs removed + thesis moved", "pass": true, "rc": 0, "evidence": {"docs_NOVA_MIGRATION_gone": true, "docs_NOVA_AWS_MIGRATION_gone": true, "docs_NO_HUMANS_THESIS_gone": true, "ciagent_NO_HUMANS_THESIS_present": true}}
|
|
||||||
{"id": "T5", "req": "REQ-239", "title": "S&P theme CSS palette on all chrome", "pass": true, "rc": 0, "evidence": {"css_exists": true, "css_size_bytes": 2914, "red_present": true, "black_present": true, "white_present": true, "chrome_covered": ["section/bg", "section.title", "h1-h3 headings", "table th", "blockquote", "pre/code", "header", "footer", "pagination (.bespoke-progress-bar)", "strong"]}}
|
|
||||||
{"id": "T6", "req": "REQ-240", "title": "render pipeline script + mermaid theme", "pass": true, "rc": 0, "evidence": {"render_slides_executable": true, "render_slides_size": 2736, "sp_theme_json_has_red": true, "sp_theme_json_has_black": true, "render_deck_sh_still_present": true, "render_deck_excluded_from_sync": true, "caveat": "README:107 still references render_deck.sh (deferred to T9)"}}
|
|
||||||
{"id": "T7", "req": "REQ-241", "title": "slides CI workflow path trigger", "pass": false, "rc": 1, "evidence": {"wrong_path_hits": [".github/workflows/slides.yml:8: - 'assets/nova-sp-theme.css' (non-existent)", "workflows-src/slides.yml:8: - 'assets/nova-sp-theme.css' (non-existent)"], "correct_path": "docs/presentations/assets/nova-sp-theme.css", "src_dotgithub_identical": true, "defect": "F6", "impact": "Explicit CSS path trigger points at nothing; only the docs/presentations/** glob catches CSS edits. Dead entry should be corrected or removed."}}
|
|
||||||
{"id": "T8", "req": "REQ-242", "title": "slide-pipeline guard test", "pass": true, "rc": 0, "evidence": {"passed": 12, "failed": 0, "duration_s": 1.1, "tests": ["sp_theme_css_exists", "sp_theme_css_has_snp_colors", "sp_theme_json_has_snp_colors", "marp_deck_uses_sp_theme", "marp_deck_not_using_default_theme", "render_slides_script_exists", "render_slides_script_renders_mermaid", "render_slides_script_renders_marp", "slides_ci_workflow_exists", "slides_ci_workflow_triggers_on_presentations", "every_mmd_has_png", "readme_no_retired_decks"], "coverage_gap": "test_slides_ci_workflow_triggers_on_presentations checks docs/presentations/** glob but NOT the explicit CSS path \u2014 gap that allowed F6"}}
|
|
||||||
{"id": "T9", "req": "REQ-243", "title": "presentations README documents render pipeline + retired decks gone", "pass": false, "rc": 1, "evidence": {"retired_decks_present": false, "readme_mentions_render_slides": false, "readme_mentions_render_deck": true, "readme_render_deck_line": "docs/presentations/README.md:107: 'automated by scripts/render_deck.sh'", "readme_mentions_theme_css": true, "defect": "F10", "impact": "README documents the retired render_deck.sh pipeline, not the active render_slides.sh. Consumers reading synced README reference a script excluded from sync."}}
|
|
||||||
{"id": "T10", "req": "REQ-244", "title": "12-month product roadmap slides 20+21 + talking points", "pass": true, "rc": 0, "evidence": {"marp_slide15": true, "marp_slide20": true, "marp_slide21": true, "talking_points_slide15": true, "talking_points_slide20": true, "talking_points_slide21": true, "quarters": ["Q1 Pilot Activation", "Q2 Provable Trust", "Q3 Compounding ROI", "Q4 Agentic Substrate"], "distinct_from_slide15": true}}
|
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
# Nova CI Pipeline (dev environment)
|
# ACDL CI Pipeline — Gitea Actions (dev environment)
|
||||||
#
|
#
|
||||||
# This workflow implements the central pipeline contract:
|
# This workflow implements the central pipeline contract:
|
||||||
# pipelines/ci.yml (validated against schemas/pipeline.schema.json)
|
# pipelines/ci.yml (validated against schemas/pipeline.schema.json)
|
||||||
|
|||||||
+16
-19
@@ -1,4 +1,4 @@
|
|||||||
# Nova Reusable Deploy Workflow (dev environment)
|
# ACDL Reusable Deploy Workflow — Gitea Actions (dev environment)
|
||||||
#
|
#
|
||||||
# This reusable workflow implements the central deployment pipeline contract:
|
# This reusable workflow implements the central deployment pipeline contract:
|
||||||
# pipelines/contract.yml (validated against schemas/deploy-pipeline.schema.json)
|
# pipelines/contract.yml (validated against schemas/deploy-pipeline.schema.json)
|
||||||
@@ -8,7 +8,7 @@
|
|||||||
# declared difference is the forge/runtime, not the stages or commands.
|
# declared difference is the forge/runtime, not the stages or commands.
|
||||||
#
|
#
|
||||||
# Consumer repos invoke this workflow via a versioned tag (floating MAJOR + MINOR):
|
# Consumer repos invoke this workflow via a versioned tag (floating MAJOR + MINOR):
|
||||||
# uses: nova/.github/workflows/deploy.yml@v1.19
|
# uses: acdl/.gitea/workflows/deploy.yml@v1.9 (Gitea)
|
||||||
# uses: acdl/.github/workflows/deploy.yml@v1.9 (GitHub)
|
# uses: acdl/.github/workflows/deploy.yml@v1.9 (GitHub)
|
||||||
#
|
#
|
||||||
# Unversioned references (@main, bare) are discouraged — the consumer's setup
|
# Unversioned references (@main, bare) are discouraged — the consumer's setup
|
||||||
@@ -26,7 +26,7 @@
|
|||||||
# platform log) for auditability.
|
# platform log) for auditability.
|
||||||
#
|
#
|
||||||
# Inputs:
|
# Inputs:
|
||||||
# contract — path to the consumer's contract YAML (default .nova/contract.yml)
|
# contract — path to the consumer's contract YAML (default .acdl/contract.yml)
|
||||||
# mode — full | plan-only | check-only (default full; dev = full apply,
|
# mode — full | plan-only | check-only (default full; dev = full apply,
|
||||||
# higher environments hold for HITL — the calling repo or the
|
# higher environments hold for HITL — the calling repo or the
|
||||||
# forge environment gate enforces that)
|
# forge environment gate enforces that)
|
||||||
@@ -38,12 +38,12 @@
|
|||||||
# that matches repo:org/consumer-repo:ref:refs/heads/main, and the session
|
# that matches repo:org/consumer-repo:ref:refs/heads/main, and the session
|
||||||
# policy restricts view/update to resources tagged acdl:owner=<consumer-repo>.
|
# policy restricts view/update to resources tagged acdl:owner=<consumer-repo>.
|
||||||
#
|
#
|
||||||
# Override (where OIDC is unavailable, e.g. pending
|
# Override (where OIDC is unavailable, e.g. Gitea pending
|
||||||
# upstream forge OIDC support): set NOVA_AWS_ACCESS_KEY_ID + NOVA_AWS_SECRET_ACCESS_KEY
|
# go-gitea/gitea#36988): set ACDL_AWS_ACCESS_KEY_ID + ACDL_AWS_SECRET_ACCESS_KEY
|
||||||
# as repository secrets. The platform-managed scheduled pipeline rotates
|
# as repository secrets. The platform-managed scheduled pipeline rotates
|
||||||
# the key on a daily cadence. When .env.secrets is used locally instead,
|
# the key on a daily cadence. When .env.secrets is used locally instead,
|
||||||
# rotating the key out of band is the consumer's responsibility.
|
# rotating the key out of band is the consumer's responsibility.
|
||||||
name: nova-deploy
|
name: acdl-deploy
|
||||||
|
|
||||||
on:
|
on:
|
||||||
workflow_call:
|
workflow_call:
|
||||||
@@ -51,7 +51,7 @@ on:
|
|||||||
contract:
|
contract:
|
||||||
description: Path to the consumer contract YAML (in the consumer repo)
|
description: Path to the consumer contract YAML (in the consumer repo)
|
||||||
type: string
|
type: string
|
||||||
default: .nova/contract.yml
|
default: .acdl/contract.yml
|
||||||
mode:
|
mode:
|
||||||
description: Pipeline mode — full (apply), plan-only, check-only, or decommission
|
description: Pipeline mode — full (apply), plan-only, check-only, or decommission
|
||||||
type: string
|
type: string
|
||||||
@@ -82,7 +82,7 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
repository: acdl/acdl
|
repository: acdl/acdl
|
||||||
path: platform
|
path: platform
|
||||||
ref: v1.25
|
ref: v1.9
|
||||||
|
|
||||||
- uses: actions/setup-python@v5
|
- uses: actions/setup-python@v5
|
||||||
with:
|
with:
|
||||||
@@ -102,16 +102,13 @@ jobs:
|
|||||||
- name: Configure AWS credentials (OIDC default + static-key override)
|
- name: Configure AWS credentials (OIDC default + static-key override)
|
||||||
uses: aws-actions/configure-aws-credentials@v4
|
uses: aws-actions/configure-aws-credentials@v4
|
||||||
with:
|
with:
|
||||||
# P4 (REQ-163): IAM role renamed acdl-deploy- → nova-deploy-.
|
role-to-assume: ${{ secrets.ACDL_AWS_ACCESS_KEY_ID == '' && format('arn:aws:iam::{0}:role/acdl-deploy-{1}', secrets.ACDL_AWS_ACCOUNT_ID, github.repository_id) || '' }}
|
||||||
role-to-assume: ${{ secrets.NOVA_AWS_ACCESS_KEY_ID == '' && format('arn:aws:iam::{0}:role/nova-deploy-{1}', secrets.NOVA_AWS_ACCOUNT_ID, github.repository_id) || '' }}
|
aws-region: us-east-1
|
||||||
aws-region: ${{ secrets.AWS_DEFAULT_REGION || 'us-east-1' }}
|
access-key-id: ${{ secrets.ACDL_AWS_ACCESS_KEY_ID }}
|
||||||
access-key-id: ${{ secrets.NOVA_AWS_ACCESS_KEY_ID }}
|
secret-access-key: ${{ secrets.ACDL_AWS_SECRET_ACCESS_KEY }}
|
||||||
secret-access-key: ${{ secrets.NOVA_AWS_SECRET_ACCESS_KEY }}
|
|
||||||
|
|
||||||
- name: Run the platform pipeline
|
- name: Run the platform pipeline
|
||||||
working-directory: ${{ github.workspace }}
|
working-directory: ${{ github.workspace }}
|
||||||
env:
|
|
||||||
NOVA_CONSUMER_REPO: ${{ github.repository }}
|
|
||||||
run: |
|
run: |
|
||||||
MODE_FLAG=""
|
MODE_FLAG=""
|
||||||
case "${{ inputs.mode }}" in
|
case "${{ inputs.mode }}" in
|
||||||
@@ -148,7 +145,7 @@ jobs:
|
|||||||
AWS_DEFAULT_REGION: us-east-1
|
AWS_DEFAULT_REGION: us-east-1
|
||||||
run: |
|
run: |
|
||||||
aws lambda invoke-function-url \
|
aws lambda invoke-function-url \
|
||||||
--function-url "${{ secrets.NOVA_LAMBDA_URL }}" \
|
--function-url "${{ secrets.ACDL_LAMBDA_URL }}" \
|
||||||
--cli-binary-format raw-in-base64-out \
|
--cli-binary-format raw-in-base64-out \
|
||||||
--payload "$(python3 -c "import json,os; print(json.dumps({'action':'report_error','consumerRepo':os.environ.get('GITHUB_REPOSITORY',''),'contractId':'${{ github.run_id }}','error':'Deploy pipeline failed. See run logs.','runUrl':'${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}','environment':'dev'}))")" \
|
--payload "$(python3 -c "import json,os; print(json.dumps({'action':'report_error','consumerRepo':os.environ.get('GITHUB_REPOSITORY',''),'contractId':'${{ github.run_id }}','error':'Deploy pipeline failed. See run logs.','runUrl':'${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}','environment':'dev'}))")" \
|
||||||
/dev/null || true
|
/dev/null || true
|
||||||
@@ -156,13 +153,13 @@ jobs:
|
|||||||
- name: Upload emitted Terraform
|
- name: Upload emitted Terraform
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: nova-terraform
|
name: acdl-terraform
|
||||||
path: /tmp/nova_platform_run/tf/*.tf
|
path: /tmp/acdl_platform_run_v18/tf/*.tf
|
||||||
if-no-files-found: warn
|
if-no-files-found: warn
|
||||||
|
|
||||||
- name: Upload platform log
|
- name: Upload platform log
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: nova-platform-log
|
name: acdl-platform-log
|
||||||
path: platform/logs/
|
path: platform/logs/
|
||||||
if-no-files-found: warn
|
if-no-files-found: warn
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
# Nova Modules Lifecycle Pipeline (dev environment)
|
# ACDL Modules Lifecycle Pipeline — Gitea Actions (dev environment)
|
||||||
#
|
#
|
||||||
# Matrix-runs each L1 module's examples/{simple,complex}.yml contracts through
|
# Matrix-runs each L1 module's examples/{simple,complex}.yml contracts through
|
||||||
# apply→modify→destroy against live AWS. No per-module Python. The "test" =
|
# apply→modify→destroy against live AWS. No per-module Python. The "test" =
|
||||||
@@ -9,13 +9,13 @@
|
|||||||
# terraform files); the composition must be deterministic.
|
# terraform files); the composition must be deterministic.
|
||||||
#
|
#
|
||||||
# This workflow implements pipelines/modules-lifecycle.yml (byte-identical
|
# This workflow implements pipelines/modules-lifecycle.yml (byte-identical
|
||||||
# in .github/workflows/).
|
# in .gitea/workflows/ and .github/workflows/).
|
||||||
#
|
#
|
||||||
# Lifecycle mode (REQ-134, v1.12): the `lifecycle_mode` input defaults to
|
# Lifecycle mode (REQ-134, v1.12): the `lifecycle_mode` input defaults to
|
||||||
# "plan" — the lifecycle scripts run `run_platform.sh --plan-only` (fast,
|
# "plan" — the lifecycle scripts run `run_platform.sh --plan-only` (fast,
|
||||||
# no AWS mutation, validates the contract->resolver->adapter->plan chain
|
# no AWS mutation, validates the contract->resolver->adapter->plan chain
|
||||||
# for every module on every PR, with no AWS credentials or cost). Set to
|
# for every module on every PR, with no AWS credentials or cost). Set to
|
||||||
# "full" via workflow_dispatch (or the NOVA_LIFECYCLE_MODE repo variable)
|
# "full" via workflow_dispatch (or the ACDL_LIFECYCLE_MODE repo variable)
|
||||||
# to run the real apply→modify→destroy against live AWS. In plan mode the
|
# to run the real apply→modify→destroy against live AWS. In plan mode the
|
||||||
# short-lived CI VPC apply/destroy jobs are skipped (nothing is applied).
|
# short-lived CI VPC apply/destroy jobs are skipped (nothing is applied).
|
||||||
#
|
#
|
||||||
@@ -49,7 +49,7 @@ jobs:
|
|||||||
ci-vpc-apply:
|
ci-vpc-apply:
|
||||||
name: CI VPC apply
|
name: CI VPC apply
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
if: ${{ github.event.inputs.lifecycle_mode != 'plan' && vars.NOVA_LIFECYCLE_MODE != 'plan' }}
|
if: ${{ github.event.inputs.lifecycle_mode != 'plan' && vars.ACDL_LIFECYCLE_MODE != 'plan' }}
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
- name: Install Terraform 1.9.*
|
- name: Install Terraform 1.9.*
|
||||||
@@ -60,8 +60,8 @@ jobs:
|
|||||||
- name: Apply CI VPC
|
- name: Apply CI VPC
|
||||||
working-directory: terraform/ci-vpc
|
working-directory: terraform/ci-vpc
|
||||||
env:
|
env:
|
||||||
AWS_ACCESS_KEY_ID: ${{ secrets.NOVA_AWS_ACCESS_KEY_ID }}
|
AWS_ACCESS_KEY_ID: ${{ secrets.ACDL_AWS_ACCESS_KEY_ID }}
|
||||||
AWS_SECRET_ACCESS_KEY: ${{ secrets.NOVA_AWS_SECRET_ACCESS_KEY }}
|
AWS_SECRET_ACCESS_KEY: ${{ secrets.ACDL_AWS_SECRET_ACCESS_KEY }}
|
||||||
AWS_DEFAULT_REGION: us-east-1
|
AWS_DEFAULT_REGION: us-east-1
|
||||||
run: |
|
run: |
|
||||||
terraform init -input=false -lock=false
|
terraform init -input=false -lock=false
|
||||||
@@ -78,7 +78,7 @@ jobs:
|
|||||||
matrix:
|
matrix:
|
||||||
module: [s3, kms-key, ecr, ecs-cluster, iam-role, cloudfront, waf, vpc, alb, ecs-service, rds, uptime]
|
module: [s3, kms-key, ecr, ecs-cluster, iam-role, cloudfront, waf, vpc, alb, ecs-service, rds, uptime]
|
||||||
env:
|
env:
|
||||||
NOVA_LIFECYCLE_MODE: ${{ github.event.inputs.lifecycle_mode || vars.NOVA_LIFECYCLE_MODE || 'plan' }}
|
ACDL_LIFECYCLE_MODE: ${{ github.event.inputs.lifecycle_mode || vars.ACDL_LIFECYCLE_MODE || 'plan' }}
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
- name: Free disk space
|
- name: Free disk space
|
||||||
@@ -97,31 +97,31 @@ jobs:
|
|||||||
echo "deb [signed-by=/usr/share/keyrings/hashicorp.gpg] https://apt.releases.hashicorp.com $(lsb_release -cs) main" | sudo tee /etc/apt/sources.list.d/hashicorp.list
|
echo "deb [signed-by=/usr/share/keyrings/hashicorp.gpg] https://apt.releases.hashicorp.com $(lsb_release -cs) main" | sudo tee /etc/apt/sources.list.d/hashicorp.list
|
||||||
sudo apt-get update && sudo apt-get install -y terraform=1.9.*
|
sudo apt-get update && sudo apt-get install -y terraform=1.9.*
|
||||||
- name: Read CI VPC outputs
|
- name: Read CI VPC outputs
|
||||||
if: ${{ env.NOVA_LIFECYCLE_MODE == 'full' }}
|
if: ${{ env.ACDL_LIFECYCLE_MODE == 'full' }}
|
||||||
working-directory: terraform/ci-vpc
|
working-directory: terraform/ci-vpc
|
||||||
env:
|
env:
|
||||||
AWS_ACCESS_KEY_ID: ${{ secrets.NOVA_AWS_ACCESS_KEY_ID }}
|
AWS_ACCESS_KEY_ID: ${{ secrets.ACDL_AWS_ACCESS_KEY_ID }}
|
||||||
AWS_SECRET_ACCESS_KEY: ${{ secrets.NOVA_AWS_SECRET_ACCESS_KEY }}
|
AWS_SECRET_ACCESS_KEY: ${{ secrets.ACDL_AWS_SECRET_ACCESS_KEY }}
|
||||||
AWS_DEFAULT_REGION: us-east-1
|
AWS_DEFAULT_REGION: us-east-1
|
||||||
run: |
|
run: |
|
||||||
terraform init -input=false -lock=false
|
terraform init -input=false -lock=false
|
||||||
terraform output -json > /tmp/ci-vpc-outputs.json
|
terraform output -json > /tmp/ci-vpc-outputs.json
|
||||||
- name: Apply (simple)
|
- name: Apply (simple)
|
||||||
env:
|
env:
|
||||||
AWS_ACCESS_KEY_ID: ${{ secrets.NOVA_AWS_ACCESS_KEY_ID }}
|
AWS_ACCESS_KEY_ID: ${{ secrets.ACDL_AWS_ACCESS_KEY_ID }}
|
||||||
AWS_SECRET_ACCESS_KEY: ${{ secrets.NOVA_AWS_SECRET_ACCESS_KEY }}
|
AWS_SECRET_ACCESS_KEY: ${{ secrets.ACDL_AWS_SECRET_ACCESS_KEY }}
|
||||||
AWS_DEFAULT_REGION: us-east-1
|
AWS_DEFAULT_REGION: us-east-1
|
||||||
run: bash scripts/run_lifecycle_test.sh ${{ matrix.module }} simple /tmp/ci-vpc-outputs.json
|
run: bash scripts/run_lifecycle_test.sh ${{ matrix.module }} simple /tmp/ci-vpc-outputs.json
|
||||||
- name: Modify (complex)
|
- name: Modify (complex)
|
||||||
env:
|
env:
|
||||||
AWS_ACCESS_KEY_ID: ${{ secrets.NOVA_AWS_ACCESS_KEY_ID }}
|
AWS_ACCESS_KEY_ID: ${{ secrets.ACDL_AWS_ACCESS_KEY_ID }}
|
||||||
AWS_SECRET_ACCESS_KEY: ${{ secrets.NOVA_AWS_SECRET_ACCESS_KEY }}
|
AWS_SECRET_ACCESS_KEY: ${{ secrets.ACDL_AWS_SECRET_ACCESS_KEY }}
|
||||||
AWS_DEFAULT_REGION: us-east-1
|
AWS_DEFAULT_REGION: us-east-1
|
||||||
run: bash scripts/run_lifecycle_test.sh ${{ matrix.module }} complex /tmp/ci-vpc-outputs.json
|
run: bash scripts/run_lifecycle_test.sh ${{ matrix.module }} complex /tmp/ci-vpc-outputs.json
|
||||||
- name: Destroy
|
- name: Destroy
|
||||||
env:
|
env:
|
||||||
AWS_ACCESS_KEY_ID: ${{ secrets.NOVA_AWS_ACCESS_KEY_ID }}
|
AWS_ACCESS_KEY_ID: ${{ secrets.ACDL_AWS_ACCESS_KEY_ID }}
|
||||||
AWS_SECRET_ACCESS_KEY: ${{ secrets.NOVA_AWS_SECRET_ACCESS_KEY }}
|
AWS_SECRET_ACCESS_KEY: ${{ secrets.ACDL_AWS_SECRET_ACCESS_KEY }}
|
||||||
AWS_DEFAULT_REGION: us-east-1
|
AWS_DEFAULT_REGION: us-east-1
|
||||||
run: bash scripts/run_lifecycle_destroy.sh ${{ matrix.module }} /tmp/ci-vpc-outputs.json
|
run: bash scripts/run_lifecycle_destroy.sh ${{ matrix.module }} /tmp/ci-vpc-outputs.json
|
||||||
|
|
||||||
@@ -136,7 +136,7 @@ jobs:
|
|||||||
matrix:
|
matrix:
|
||||||
module: [static-assets, microservice]
|
module: [static-assets, microservice]
|
||||||
env:
|
env:
|
||||||
NOVA_LIFECYCLE_MODE: ${{ github.event.inputs.lifecycle_mode || vars.NOVA_LIFECYCLE_MODE || 'plan' }}
|
ACDL_LIFECYCLE_MODE: ${{ github.event.inputs.lifecycle_mode || vars.ACDL_LIFECYCLE_MODE || 'plan' }}
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
- name: Free disk space
|
- name: Free disk space
|
||||||
@@ -155,31 +155,31 @@ jobs:
|
|||||||
echo "deb [signed-by=/usr/share/keyrings/hashicorp.gpg] https://apt.releases.hashicorp.com $(lsb_release -cs) main" | sudo tee /etc/apt/sources.list.d/hashicorp.list
|
echo "deb [signed-by=/usr/share/keyrings/hashicorp.gpg] https://apt.releases.hashicorp.com $(lsb_release -cs) main" | sudo tee /etc/apt/sources.list.d/hashicorp.list
|
||||||
sudo apt-get update && sudo apt-get install -y terraform=1.9.*
|
sudo apt-get update && sudo apt-get install -y terraform=1.9.*
|
||||||
- name: Read CI VPC outputs
|
- name: Read CI VPC outputs
|
||||||
if: ${{ env.NOVA_LIFECYCLE_MODE == 'full' }}
|
if: ${{ env.ACDL_LIFECYCLE_MODE == 'full' }}
|
||||||
working-directory: terraform/ci-vpc
|
working-directory: terraform/ci-vpc
|
||||||
env:
|
env:
|
||||||
AWS_ACCESS_KEY_ID: ${{ secrets.NOVA_AWS_ACCESS_KEY_ID }}
|
AWS_ACCESS_KEY_ID: ${{ secrets.ACDL_AWS_ACCESS_KEY_ID }}
|
||||||
AWS_SECRET_ACCESS_KEY: ${{ secrets.NOVA_AWS_SECRET_ACCESS_KEY }}
|
AWS_SECRET_ACCESS_KEY: ${{ secrets.ACDL_AWS_SECRET_ACCESS_KEY }}
|
||||||
AWS_DEFAULT_REGION: us-east-1
|
AWS_DEFAULT_REGION: us-east-1
|
||||||
run: |
|
run: |
|
||||||
terraform init -input=false -lock=false
|
terraform init -input=false -lock=false
|
||||||
terraform output -json > /tmp/ci-vpc-outputs.json
|
terraform output -json > /tmp/ci-vpc-outputs.json
|
||||||
- name: Apply (simple)
|
- name: Apply (simple)
|
||||||
env:
|
env:
|
||||||
AWS_ACCESS_KEY_ID: ${{ secrets.NOVA_AWS_ACCESS_KEY_ID }}
|
AWS_ACCESS_KEY_ID: ${{ secrets.ACDL_AWS_ACCESS_KEY_ID }}
|
||||||
AWS_SECRET_ACCESS_KEY: ${{ secrets.NOVA_AWS_SECRET_ACCESS_KEY }}
|
AWS_SECRET_ACCESS_KEY: ${{ secrets.ACDL_AWS_SECRET_ACCESS_KEY }}
|
||||||
AWS_DEFAULT_REGION: us-east-1
|
AWS_DEFAULT_REGION: us-east-1
|
||||||
run: bash scripts/run_l2_lifecycle_test.sh ${{ matrix.module }} simple /tmp/ci-vpc-outputs.json
|
run: bash scripts/run_l2_lifecycle_test.sh ${{ matrix.module }} simple /tmp/ci-vpc-outputs.json
|
||||||
- name: Modify (complex)
|
- name: Modify (complex)
|
||||||
env:
|
env:
|
||||||
AWS_ACCESS_KEY_ID: ${{ secrets.NOVA_AWS_ACCESS_KEY_ID }}
|
AWS_ACCESS_KEY_ID: ${{ secrets.ACDL_AWS_ACCESS_KEY_ID }}
|
||||||
AWS_SECRET_ACCESS_KEY: ${{ secrets.NOVA_AWS_SECRET_ACCESS_KEY }}
|
AWS_SECRET_ACCESS_KEY: ${{ secrets.ACDL_AWS_SECRET_ACCESS_KEY }}
|
||||||
AWS_DEFAULT_REGION: us-east-1
|
AWS_DEFAULT_REGION: us-east-1
|
||||||
run: bash scripts/run_l2_lifecycle_test.sh ${{ matrix.module }} complex /tmp/ci-vpc-outputs.json
|
run: bash scripts/run_l2_lifecycle_test.sh ${{ matrix.module }} complex /tmp/ci-vpc-outputs.json
|
||||||
- name: Destroy
|
- name: Destroy
|
||||||
env:
|
env:
|
||||||
AWS_ACCESS_KEY_ID: ${{ secrets.NOVA_AWS_ACCESS_KEY_ID }}
|
AWS_ACCESS_KEY_ID: ${{ secrets.ACDL_AWS_ACCESS_KEY_ID }}
|
||||||
AWS_SECRET_ACCESS_KEY: ${{ secrets.NOVA_AWS_SECRET_ACCESS_KEY }}
|
AWS_SECRET_ACCESS_KEY: ${{ secrets.ACDL_AWS_SECRET_ACCESS_KEY }}
|
||||||
AWS_DEFAULT_REGION: us-east-1
|
AWS_DEFAULT_REGION: us-east-1
|
||||||
run: bash scripts/run_l2_lifecycle_destroy.sh ${{ matrix.module }} /tmp/ci-vpc-outputs.json
|
run: bash scripts/run_l2_lifecycle_destroy.sh ${{ matrix.module }} /tmp/ci-vpc-outputs.json
|
||||||
|
|
||||||
@@ -188,7 +188,7 @@ jobs:
|
|||||||
name: CI VPC destroy
|
name: CI VPC destroy
|
||||||
needs: [lifecycle, l2-lifecycle]
|
needs: [lifecycle, l2-lifecycle]
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
if: ${{ always() && github.event.inputs.lifecycle_mode != 'plan' && vars.NOVA_LIFECYCLE_MODE != 'plan' }}
|
if: ${{ always() && github.event.inputs.lifecycle_mode != 'plan' && vars.ACDL_LIFECYCLE_MODE != 'plan' }}
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
- name: Install Terraform 1.9.*
|
- name: Install Terraform 1.9.*
|
||||||
@@ -199,8 +199,8 @@ jobs:
|
|||||||
- name: Destroy CI VPC
|
- name: Destroy CI VPC
|
||||||
working-directory: terraform/ci-vpc
|
working-directory: terraform/ci-vpc
|
||||||
env:
|
env:
|
||||||
AWS_ACCESS_KEY_ID: ${{ secrets.NOVA_AWS_ACCESS_KEY_ID }}
|
AWS_ACCESS_KEY_ID: ${{ secrets.ACDL_AWS_ACCESS_KEY_ID }}
|
||||||
AWS_SECRET_ACCESS_KEY: ${{ secrets.NOVA_AWS_SECRET_ACCESS_KEY }}
|
AWS_SECRET_ACCESS_KEY: ${{ secrets.ACDL_AWS_SECRET_ACCESS_KEY }}
|
||||||
AWS_DEFAULT_REGION: us-east-1
|
AWS_DEFAULT_REGION: us-east-1
|
||||||
run: |
|
run: |
|
||||||
terraform init -input=false -lock=false
|
terraform init -input=false -lock=false
|
||||||
|
|||||||
@@ -1,165 +0,0 @@
|
|||||||
# Nova Publish Pipeline — wheel + Lambda layer (REQ-323, CAP-035, NFR-6)
|
|
||||||
#
|
|
||||||
# This workflow is byte-identical across the production forge (GitHub
|
|
||||||
# Actions) and the dev forge (act_runner) — the same file is installed
|
|
||||||
# at .github/workflows/publish.yml and the mirror at
|
|
||||||
# <dev-forge>/workflows/publish.yml. Both copies must match exactly
|
|
||||||
# (asserted by tests/test_forge_action_byte_identical.py for the action
|
|
||||||
# and by the repo's byte-identical convention for workflows).
|
|
||||||
#
|
|
||||||
# NFR-6 (wheel/layer co-versioning): every merge to main affecting
|
|
||||||
# core/**, adapters/**, nova/**, or pyproject.toml publishes BOTH a
|
|
||||||
# wheel AND a Lambda layer with identical version strings. If either
|
|
||||||
# publish fails, the job fails and the merge is blocked.
|
|
||||||
#
|
|
||||||
# REQ-323: CodeArtifact wheel + Lambda layer pipeline.
|
|
||||||
# CAP-035: Lambda layer ARN version matches the nova-cli wheel version;
|
|
||||||
# the mapping is recorded in SSM /nova/layer/nova-cli/version.
|
|
||||||
#
|
|
||||||
# Triggers:
|
|
||||||
# - push to main when core/**, adapters/**, nova/**, or pyproject.toml
|
|
||||||
# changed (the surfaces that ship in the wheel + layer)
|
|
||||||
# - workflow_dispatch (manual republish, e.g. after a CodeArtifact
|
|
||||||
# provisioning fix)
|
|
||||||
#
|
|
||||||
# Wheel index selection (CodeArtifact default + fallback):
|
|
||||||
# - CodeArtifact mode: set the NOVA_CODEARTIFACT_DOMAIN repository
|
|
||||||
# secret (e.g. "nova"). The workflow runs
|
|
||||||
# `aws codeartifact login --tool twine --domain $NOVA_CODEARTIFACT_DOMAIN
|
|
||||||
# --repository nova-pypi` and twine uploads to the CodeArtifact pypi
|
|
||||||
# endpoint.
|
|
||||||
# - Fallback mode: leave NOVA_CODEARTIFACT_DOMAIN unset and provide
|
|
||||||
# TWINE_REPOSITORY_URL + TWINE_USERNAME + TWINE_PASSWORD repository
|
|
||||||
# secrets pointing at any PEP 503 simple index (a private package
|
|
||||||
# registry). twine uploads to TWINE_REPOSITORY_URL.
|
|
||||||
# See docs/codeartifact-provisioning.md for the required IAM grants
|
|
||||||
# + the fallback index shape.
|
|
||||||
#
|
|
||||||
# Secrets / env:
|
|
||||||
# AWS_ROLE_ARN — OIDC role to assume (id-token: write)
|
|
||||||
# NOVA_CODEARTIFACT_DOMAIN — optional; when set, CodeArtifact mode
|
|
||||||
# TWINE_USERNAME — fallback-index upload user
|
|
||||||
# TWINE_PASSWORD — fallback-index upload password
|
|
||||||
# TWINE_REPOSITORY_URL — fallback-index upload URL
|
|
||||||
# AWS_DEFAULT_REGION (optional) — defaults to us-east-1
|
|
||||||
name: nova-publish
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches: [main]
|
|
||||||
paths:
|
|
||||||
- "core/**"
|
|
||||||
- "adapters/**"
|
|
||||||
- "nova/**"
|
|
||||||
- "pyproject.toml"
|
|
||||||
workflow_dispatch:
|
|
||||||
|
|
||||||
permissions:
|
|
||||||
id-token: write # OIDC federation to AWS
|
|
||||||
contents: write # tag the release
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
publish:
|
|
||||||
name: Publish wheel + Lambda layer
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
|
|
||||||
- uses: actions/setup-python@v5
|
|
||||||
with:
|
|
||||||
python-version: "3.12"
|
|
||||||
|
|
||||||
- name: Configure AWS credentials (OIDC)
|
|
||||||
uses: aws-actions/configure-aws-credentials@v4
|
|
||||||
with:
|
|
||||||
role-to-assume: ${{ secrets.AWS_ROLE_ARN }}
|
|
||||||
aws-region: ${{ secrets.AWS_DEFAULT_REGION || 'us-east-1' }}
|
|
||||||
|
|
||||||
- name: Install build + publish tools
|
|
||||||
run: pip install build twine
|
|
||||||
|
|
||||||
- name: Compute version from pyproject.toml
|
|
||||||
id: ver
|
|
||||||
run: |
|
|
||||||
set -e
|
|
||||||
VERSION=$(python -c 'import tomllib;print(tomllib.load(open("pyproject.toml","rb"))["project"]["version"])')
|
|
||||||
echo "version=$VERSION" >> "$GITHUB_OUTPUT"
|
|
||||||
echo "Nova version: $VERSION"
|
|
||||||
|
|
||||||
- name: Build wheel
|
|
||||||
run: |
|
|
||||||
set -e
|
|
||||||
python -m build --wheel
|
|
||||||
ls -1 dist/
|
|
||||||
|
|
||||||
- name: Upload wheel to index (CodeArtifact default + fallback)
|
|
||||||
id: wheel
|
|
||||||
env:
|
|
||||||
NOVA_CODEARTIFACT_DOMAIN: ${{ secrets.NOVA_CODEARTIFACT_DOMAIN }}
|
|
||||||
TWINE_USERNAME: ${{ secrets.TWINE_USERNAME }}
|
|
||||||
TWINE_PASSWORD: ${{ secrets.TWINE_PASSWORD }}
|
|
||||||
TWINE_REPOSITORY_URL: ${{ secrets.TWINE_REPOSITORY_URL }}
|
|
||||||
run: |
|
|
||||||
set -e
|
|
||||||
# CodeArtifact mode: log in to the domain's pypi repository.
|
|
||||||
if [ -n "$NOVA_CODEARTIFACT_DOMAIN" ]; then
|
|
||||||
echo "CodeArtifact mode: domain=$NOVA_CODEARTIFACT_DOMAIN repository=nova-pypi"
|
|
||||||
aws codeartifact login --tool twine \
|
|
||||||
--domain "$NOVA_CODEARTIFACT_DOMAIN" --repository nova-pypi
|
|
||||||
else
|
|
||||||
echo "Fallback-index mode: uploading to TWINE_REPOSITORY_URL"
|
|
||||||
if [ -z "$TWINE_REPOSITORY_URL" ] || [ -z "$TWINE_USERNAME" ] || [ -z "$TWINE_PASSWORD" ]; then
|
|
||||||
echo "FAIL: NOVA_CODEARTIFACT_DOMAIN is unset and one of TWINE_REPOSITORY_URL/TWINE_USERNAME/TWINE_PASSWORD is missing."
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
# Idempotent upload: a re-run for the same version may hit
|
|
||||||
# "file already exists" on the index. Treat that as success.
|
|
||||||
twine upload "dist/nova-${{ steps.ver.outputs.version }}-*.whl" \
|
|
||||||
|| twine upload "dist/nova-${{ steps.ver.outputs.version }}-*.whl" 2>&1 | tee /tmp/twine.log
|
|
||||||
if grep -qi "already exist" /tmp/twine.log 2>/dev/null; then
|
|
||||||
echo "Wheel already present on the index — treating as success (idempotent)."
|
|
||||||
fi
|
|
||||||
echo "uploaded=true" >> "$GITHUB_OUTPUT"
|
|
||||||
|
|
||||||
- name: Build Lambda layer
|
|
||||||
run: |
|
|
||||||
set -e
|
|
||||||
rm -rf layer
|
|
||||||
mkdir -p layer/python
|
|
||||||
# Install the wheel we just built + the identity extras' deps
|
|
||||||
# so the layer carries argon2-cffi, cryptography, pyjwt.
|
|
||||||
pip install --target layer/python/ \
|
|
||||||
"dist/nova-${{ steps.ver.outputs.version }}-*.whl" \
|
|
||||||
argon2-cffi cryptography pyjwt
|
|
||||||
( cd layer && zip -r ../nova-layer.zip python/ )
|
|
||||||
ls -lh nova-layer.zip
|
|
||||||
|
|
||||||
- name: Publish Lambda layer
|
|
||||||
id: layer
|
|
||||||
run: |
|
|
||||||
set -e
|
|
||||||
ARN=$(aws lambda publish-layer-version \
|
|
||||||
--layer-name nova-cli \
|
|
||||||
--zip-file fileb://nova-layer.zip \
|
|
||||||
--compatible-runtimes python3.12 \
|
|
||||||
--compatible-architectures x86_64 \
|
|
||||||
--description "nova-cli v${{ steps.ver.outputs.version }}" \
|
|
||||||
--query LayerVersionArn --output text)
|
|
||||||
echo "arn=$ARN" >> "$GITHUB_OUTPUT"
|
|
||||||
echo "Published Lambda layer: $ARN"
|
|
||||||
|
|
||||||
- name: Record SSM version↔ARN mapping (CAP-035)
|
|
||||||
run: |
|
|
||||||
set -e
|
|
||||||
aws ssm put-parameter \
|
|
||||||
--name /nova/layer/nova-cli/version \
|
|
||||||
--value "${{ steps.ver.outputs.version }}:${{ steps.layer.outputs.arn }}" \
|
|
||||||
--type String --overwrite
|
|
||||||
echo "SSM /nova/layer/nova-cli/version = ${{ steps.ver.outputs.version }}:${{ steps.layer.outputs.arn }}"
|
|
||||||
|
|
||||||
- name: Fail job if either publish failed (REQ-323 AC)
|
|
||||||
if: ${{ steps.wheel.outputs.uploaded != 'true' || steps.layer.outputs.arn == '' }}
|
|
||||||
run: |
|
|
||||||
echo "FAIL: wheel uploaded=${{ steps.wheel.outputs.uploaded }} layer_arn=${{ steps.layer.outputs.arn }}"
|
|
||||||
exit 1
|
|
||||||
@@ -1,69 +0,0 @@
|
|||||||
# Nova AWS key rotation — platform-managed scheduled pipeline (SPEC §5.9)
|
|
||||||
#
|
|
||||||
# Rotates the NOVA_AWS_* static key daily (no long-lived keys in the steady
|
|
||||||
# state). v0.2 scope: the mechanism must exist (SPEC §5.9); the v0.2 deploy
|
|
||||||
# uses the currently-active key. The rotation is best-effort + idempotent
|
|
||||||
# (scripts/rotate_spike_key.sh deactivates the old key only after the new
|
|
||||||
# key propagates to the consumer's Actions secret store).
|
|
||||||
#
|
|
||||||
# Auth: the rotation uses the CURRENT NOVA_AWS_* key to authenticate to IAM
|
|
||||||
# (the root account 581513795199 can rotate its own keys — confirmed by the
|
|
||||||
# bootstrap). The aws-actions/configure-aws-credentials@v4 step uses the
|
|
||||||
# static-key path (no OIDC role-to-assume); the long-lived key rotates
|
|
||||||
# itself, which is the bootstrap-exception documented in §5.9.
|
|
||||||
#
|
|
||||||
# Forge coords (base URL / owner / consumer repo) are sourced from
|
|
||||||
# repository secrets — NOVA_FORGE_BASE_URL, NOVA_FORGE_OWNER,
|
|
||||||
# NOVA_CONSUMER_REPO — so the synced workflow file stays forge-agnostic
|
|
||||||
# (REQ-230). The rotation script uploads the new key to the consumer's
|
|
||||||
# Actions secret store (the consumer whose deploy.yml consumes NOVA_AWS_*
|
|
||||||
# via secrets: inherit).
|
|
||||||
name: nova-rotate-aws-key
|
|
||||||
|
|
||||||
on:
|
|
||||||
schedule:
|
|
||||||
- cron: "0 0 * * *" # daily at 00:00 UTC
|
|
||||||
workflow_dispatch:
|
|
||||||
|
|
||||||
permissions:
|
|
||||||
id-token: write
|
|
||||||
contents: read
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
rotate:
|
|
||||||
name: Rotate NOVA_AWS_* static key
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- name: Check out Nova platform repo
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
|
|
||||||
- name: Configure AWS credentials (bootstrap root creds for IAM key rotation)
|
|
||||||
uses: aws-actions/configure-aws-credentials@v4
|
|
||||||
with:
|
|
||||||
aws-region: ${{ secrets.AWS_DEFAULT_REGION || 'us-east-1' }}
|
|
||||||
access-key-id: ${{ secrets.NOVA_AWS_ACCESS_KEY_ID }}
|
|
||||||
secret-access-key: ${{ secrets.NOVA_AWS_SECRET_ACCESS_KEY }}
|
|
||||||
|
|
||||||
- name: Install Python deps (boto3 for the rotation script)
|
|
||||||
run: |
|
|
||||||
python3 -m pip install --break-system-packages --quiet boto3
|
|
||||||
|
|
||||||
- name: Run the key rotation script
|
|
||||||
env:
|
|
||||||
# aws-actions/configure-aws-credentials exports AWS_ACCESS_KEY_ID /
|
|
||||||
# AWS_SECRET_ACCESS_KEY; the rotation script reads the bootstrap
|
|
||||||
# creds via NOVA_BOOTSTRAP_AWS_* (its dual-read contract, D-034).
|
|
||||||
# Map the standard AWS_* exports onto the script's expected vars.
|
|
||||||
NOVA_BOOTSTRAP_AWS_ACCESS_KEY_ID: ${{ env.AWS_ACCESS_KEY_ID }}
|
|
||||||
NOVA_BOOTSTRAP_AWS_SECRET_ACCESS_KEY: ${{ env.AWS_SECRET_ACCESS_KEY }}
|
|
||||||
# Forge + consumer coords come from repository secrets (REQ-230 —
|
|
||||||
# no forge hostnames/orgs hardcoded in the synced workflow file).
|
|
||||||
# NOVA_FORGE_TOKEN holds the forge API token (set equal to the
|
|
||||||
# existing forge token as a one-time secret setup).
|
|
||||||
NOVA_FORGE_TOKEN: ${{ secrets.NOVA_FORGE_TOKEN }}
|
|
||||||
NOVA_FORGE_BASE_URL: ${{ secrets.NOVA_FORGE_BASE_URL }}
|
|
||||||
NOVA_FORGE_OWNER: ${{ secrets.NOVA_FORGE_OWNER }}
|
|
||||||
NOVA_CONSUMER_REPO: ${{ secrets.NOVA_CONSUMER_REPO }}
|
|
||||||
AWS_DEFAULT_REGION: ${{ secrets.AWS_DEFAULT_REGION || 'us-east-1' }}
|
|
||||||
run: |
|
|
||||||
bash scripts/rotate_spike_key.sh
|
|
||||||
@@ -1,43 +0,0 @@
|
|||||||
# Nova Slides Render — re-renders presentation deck when source files change.
|
|
||||||
# REQ-273: install python-pptx, pin CLI versions, stage HTML + both PPTX +
|
|
||||||
# base64-inlined images.
|
|
||||||
name: Nova Slides Render
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
paths:
|
|
||||||
- 'docs/presentations/**'
|
|
||||||
- 'scripts/render_slides.sh'
|
|
||||||
- 'scripts/inline_images.py'
|
|
||||||
- 'scripts/render_pptx.py'
|
|
||||||
- 'pyproject.toml'
|
|
||||||
workflow_dispatch:
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
render:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
with: { fetch-depth: 0 }
|
|
||||||
- uses: actions/setup-node@v4
|
|
||||||
with: { node-version: '20' }
|
|
||||||
- uses: actions/setup-python@v5
|
|
||||||
with:
|
|
||||||
python-version: '3.10'
|
|
||||||
- name: Install python-pptx (slides extra)
|
|
||||||
run: pip install -e ".[slides]"
|
|
||||||
- name: Install + pin render CLIs
|
|
||||||
run: |
|
|
||||||
npx --yes @marp-team/marp-cli@4.5.0 --version
|
|
||||||
npx --yes @mermaid-js/mermaid-cli@11.16.0 --version
|
|
||||||
- name: Render slides
|
|
||||||
run: bash scripts/render_slides.sh
|
|
||||||
- name: Commit rendered artifacts
|
|
||||||
run: |
|
|
||||||
git config user.name "nova-slides-bot"
|
|
||||||
git config user.email "bot@nova.local"
|
|
||||||
git add docs/presentations/*.html \
|
|
||||||
docs/presentations/*.pptx \
|
|
||||||
docs/presentations/*-python.pptx \
|
|
||||||
docs/presentations/assets/png/*.png
|
|
||||||
git diff --cached --quiet || git commit -m "chore(slides): re-render deck [skip ci]"
|
|
||||||
git push
|
|
||||||
@@ -1,94 +0,0 @@
|
|||||||
# Nova CLI Action — composite action (REQ-326, NFR-11)
|
|
||||||
#
|
|
||||||
# Runs a Nova CLI command (`nova <command>`) in a consumer repository.
|
|
||||||
# Python 3.12 is pinned (REQ-326 AC3). The same action.yml is discovered
|
|
||||||
# by both the production forge (GitHub Actions) and the dev forge
|
|
||||||
# (act_runner) via the shared .github/actions/nova-cli/ path — there is
|
|
||||||
# no separate dev-forge action file. Consumers reference it via a
|
|
||||||
# versioned tag pin:
|
|
||||||
#
|
|
||||||
# uses: <org>/<repo>/.github/actions/nova-cli@v1.28
|
|
||||||
#
|
|
||||||
# Wheel index selection (CodeArtifact default + fallback):
|
|
||||||
# - CodeArtifact mode: set the NOVA_CODEARTIFACT_DOMAIN repository
|
|
||||||
# secret/env. The action runs
|
|
||||||
# `aws codeartifact login --tool pip --domain $NOVA_CODEARTIFACT_DOMAIN
|
|
||||||
# --repository nova-pypi` before `pip install nova`.
|
|
||||||
# - Fallback mode: leave NOVA_CODEARTIFACT_DOMAIN unset and provide
|
|
||||||
# NOVA_WHEEL_INDEX env pointing at any PEP 503 simple index (a
|
|
||||||
# private package registry). The action runs
|
|
||||||
# `pip install --index-url $NOVA_WHEEL_INDEX nova==<version>`.
|
|
||||||
# See docs/codeartifact-provisioning.md for the index shape.
|
|
||||||
#
|
|
||||||
# Byte-identical cross-platform verification (NFR-11, REQ-326 AC2):
|
|
||||||
# the full byte-identical test runs as a CI matrix job on the
|
|
||||||
# production forge (ubuntu-latest) + the dev forge (act_runner) with
|
|
||||||
# identical inputs, asserting same stdout + exit code. That matrix is
|
|
||||||
# not reproducible in a unit test; the structural invariants (valid
|
|
||||||
# YAML, python 3.12 pin, install + run steps present) are asserted by
|
|
||||||
# tests/test_forge_action_byte_identical.py.
|
|
||||||
name: "Nova CLI Action"
|
|
||||||
description: "Run a Nova CLI command (`nova <command>`) with Python 3.12 pinned"
|
|
||||||
|
|
||||||
inputs:
|
|
||||||
command:
|
|
||||||
description: "The Nova subcommand + args to run (e.g. `apply --local`, `init`, `idp setup --check-only`). Passed verbatim to `nova`."
|
|
||||||
required: true
|
|
||||||
contract:
|
|
||||||
description: "Path to the consumer contract YAML (default .nova/contract.yml). Forwarded to nova via the NOVA_CONTRACT env var."
|
|
||||||
required: false
|
|
||||||
default: ".nova/contract.yml"
|
|
||||||
mode:
|
|
||||||
description: "Nova client mode override (e.g. agent, interactive, plan-only, check-only). Forwarded to nova via the NOVA_CLIENT_MODE env var. Empty = let nova resolve (TTY + credentials)."
|
|
||||||
required: false
|
|
||||||
default: ""
|
|
||||||
version:
|
|
||||||
description: "nova package version to install (default `latest`). Pin to a released wheel version for reproducible runs."
|
|
||||||
required: false
|
|
||||||
default: "latest"
|
|
||||||
|
|
||||||
runs:
|
|
||||||
using: "composite"
|
|
||||||
steps:
|
|
||||||
- name: Set up Python 3.12
|
|
||||||
uses: actions/setup-python@v5
|
|
||||||
with:
|
|
||||||
python-version: "3.12"
|
|
||||||
|
|
||||||
- name: Install Nova (CodeArtifact default + fallback index)
|
|
||||||
shell: bash
|
|
||||||
env:
|
|
||||||
NOVA_CODEARTIFACT_DOMAIN: ${{ env.NOVA_CODEARTIFACT_DOMAIN }}
|
|
||||||
NOVA_WHEEL_INDEX: ${{ env.NOVA_WHEEL_INDEX }}
|
|
||||||
NOVA_INSTALL_VERSION: ${{ inputs.version }}
|
|
||||||
run: |
|
|
||||||
set -e
|
|
||||||
if [ "$NOVA_INSTALL_VERSION" = "latest" ]; then
|
|
||||||
PIP_SPEC="nova"
|
|
||||||
else
|
|
||||||
PIP_SPEC="nova==$NOVA_INSTALL_VERSION"
|
|
||||||
fi
|
|
||||||
if [ -n "$NOVA_CODEARTIFACT_DOMAIN" ]; then
|
|
||||||
echo "CodeArtifact mode: domain=$NOVA_CODEARTIFACT_DOMAIN repository=nova-pypi"
|
|
||||||
aws codeartifact login --tool pip \
|
|
||||||
--domain "$NOVA_CODEARTIFACT_DOMAIN" --repository nova-pypi
|
|
||||||
pip install $PIP_SPEC
|
|
||||||
else
|
|
||||||
echo "Fallback-index mode: NOVA_WHEEL_INDEX=$NOVA_WHEEL_INDEX"
|
|
||||||
if [ -z "$NOVA_WHEEL_INDEX" ]; then
|
|
||||||
echo "FAIL: NOVA_CODEARTIFACT_DOMAIN is unset and NOVA_WHEEL_INDEX is empty. Set one of them."
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
pip install --index-url "$NOVA_WHEEL_INDEX" $PIP_SPEC
|
|
||||||
fi
|
|
||||||
nova --version || true
|
|
||||||
|
|
||||||
- name: Run Nova
|
|
||||||
shell: bash
|
|
||||||
env:
|
|
||||||
NOVA_CLIENT_MODE: ${{ inputs.mode }}
|
|
||||||
NOVA_CONTRACT: ${{ inputs.contract }}
|
|
||||||
run: |
|
|
||||||
set -e
|
|
||||||
echo "nova ${{ inputs.command }}"
|
|
||||||
nova ${{ inputs.command }}
|
|
||||||
@@ -1,45 +0,0 @@
|
|||||||
# GitHub Workflows — Nova Platform CI/CD Catalog
|
|
||||||
|
|
||||||
This directory contains the GitHub Actions workflows for the Nova
|
|
||||||
platform. 3 are generated from `workflows-src/<name>`; 4 are GitHub-only.
|
|
||||||
|
|
||||||
## Shared workflows (generated from source)
|
|
||||||
|
|
||||||
These 3 are generated from `workflows-src/<name>`. Run `python3 scripts/sync_workflows.py --check` to verify
|
|
||||||
no drift.
|
|
||||||
|
|
||||||
| Workflow | Trigger | Inputs | Required Secrets | Purpose |
|
|
||||||
|----------|---------|--------|------------------|---------|
|
|
||||||
| `ci.yml` | `pull_request: [main]` | — | — | Lint + test + check-only (runs on every PR) |
|
|
||||||
| `deploy.yml` | `workflow_call` (reusable) + `push: [main]` | `contract` (string, required), `mode` (string, default `deploy`), `changeRequestId` (string), `environment` (string) | `NOVA_AWS_ACCESS_KEY_ID`, `NOVA_AWS_SECRET_ACCESS_KEY`, `NOVA_AWS_DEFAULT_REGION`, `NOVA_KMS_KEY_ID`, `NOVA_LAMBDA_URL` | Reusable deploy workflow (invoked by consumer repos via `uses: nova/.github/workflows/deploy.yml@v1.19`) |
|
|
||||||
| `modules-lifecycle.yml` | `pull_request: [main]` + `workflow_dispatch` | `lifecycle_mode` (string, default `plan` — `plan` or `full`) | `NOVA_AWS_ACCESS_KEY_ID`, `NOVA_AWS_SECRET_ACCESS_KEY`, `NOVA_AWS_DEFAULT_REGION`, `NOVA_AWS_ACCOUNT_ID` | L1 + L2 module lifecycle pipeline (plan-only default; full apply/modify/destroy on override) |
|
|
||||||
|
|
||||||
## GitHub-only workflows
|
|
||||||
|
|
||||||
These 4 have no counterpart (the dev forge lacks the features
|
|
||||||
they require — reusable workflows, matrix `needs`, release API).
|
|
||||||
|
|
||||||
| Workflow | Trigger | Inputs | Required Secrets | Purpose |
|
|
||||||
|----------|---------|--------|------------------|---------|
|
|
||||||
| `platform-test.yml` | `pull_request: [main]` | — | — | Lint + unit + integration + schema-validation (replaces `ci.yml` for PRs) |
|
|
||||||
| `primitives-plan.yml` | `pull_request: [main]` | — | `NOVA_AWS_*` | Plan-only for all L1 primitives (matrix) |
|
|
||||||
| `patterns-plan.yml` | `pull_request: [main]` | — | `NOVA_AWS_*` | Plan-only for all L2 modules (matrix) |
|
|
||||||
| `release.yml` | `push: [main]` | — | `NOVA_RELEASE_TOKEN` | Semver tag + MAJOR.MINOR/MAJOR floating-tag maintenance + release creation on merge to main |
|
|
||||||
|
|
||||||
## Reusable deploy workflow (`deploy.yml`)
|
|
||||||
|
|
||||||
Consumer repos invoke the deploy workflow via a versioned tag:
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
jobs:
|
|
||||||
deploy:
|
|
||||||
uses: nova/.github/workflows/deploy.yml@v1.19
|
|
||||||
with:
|
|
||||||
contract: .nova/contract.yml
|
|
||||||
environment: dev
|
|
||||||
secrets: inherit
|
|
||||||
```
|
|
||||||
|
|
||||||
The workflow checks out the consumer repo + the Nova platform repo, runs
|
|
||||||
`scripts/run_platform.sh`, and posts deploy outputs as a PR comment +
|
|
||||||
to SSM Parameter Store.
|
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
# Nova CI Pipeline (dev environment)
|
# ACDL CI Pipeline — Gitea Actions (dev environment)
|
||||||
#
|
#
|
||||||
# This workflow implements the central pipeline contract:
|
# This workflow implements the central pipeline contract:
|
||||||
# pipelines/ci.yml (validated against schemas/pipeline.schema.json)
|
# pipelines/ci.yml (validated against schemas/pipeline.schema.json)
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
# Nova Reusable Deploy Workflow (dev environment)
|
# ACDL Reusable Deploy Workflow — Gitea Actions (dev environment)
|
||||||
#
|
#
|
||||||
# This reusable workflow implements the central deployment pipeline contract:
|
# This reusable workflow implements the central deployment pipeline contract:
|
||||||
# pipelines/contract.yml (validated against schemas/deploy-pipeline.schema.json)
|
# pipelines/contract.yml (validated against schemas/deploy-pipeline.schema.json)
|
||||||
@@ -8,7 +8,7 @@
|
|||||||
# declared difference is the forge/runtime, not the stages or commands.
|
# declared difference is the forge/runtime, not the stages or commands.
|
||||||
#
|
#
|
||||||
# Consumer repos invoke this workflow via a versioned tag (floating MAJOR + MINOR):
|
# Consumer repos invoke this workflow via a versioned tag (floating MAJOR + MINOR):
|
||||||
# uses: nova/.github/workflows/deploy.yml@v1.19
|
# uses: acdl/.gitea/workflows/deploy.yml@v1.9 (Gitea)
|
||||||
# uses: acdl/.github/workflows/deploy.yml@v1.9 (GitHub)
|
# uses: acdl/.github/workflows/deploy.yml@v1.9 (GitHub)
|
||||||
#
|
#
|
||||||
# Unversioned references (@main, bare) are discouraged — the consumer's setup
|
# Unversioned references (@main, bare) are discouraged — the consumer's setup
|
||||||
@@ -26,7 +26,7 @@
|
|||||||
# platform log) for auditability.
|
# platform log) for auditability.
|
||||||
#
|
#
|
||||||
# Inputs:
|
# Inputs:
|
||||||
# contract — path to the consumer's contract YAML (default .nova/contract.yml)
|
# contract — path to the consumer's contract YAML (default .acdl/contract.yml)
|
||||||
# mode — full | plan-only | check-only (default full; dev = full apply,
|
# mode — full | plan-only | check-only (default full; dev = full apply,
|
||||||
# higher environments hold for HITL — the calling repo or the
|
# higher environments hold for HITL — the calling repo or the
|
||||||
# forge environment gate enforces that)
|
# forge environment gate enforces that)
|
||||||
@@ -38,12 +38,12 @@
|
|||||||
# that matches repo:org/consumer-repo:ref:refs/heads/main, and the session
|
# that matches repo:org/consumer-repo:ref:refs/heads/main, and the session
|
||||||
# policy restricts view/update to resources tagged acdl:owner=<consumer-repo>.
|
# policy restricts view/update to resources tagged acdl:owner=<consumer-repo>.
|
||||||
#
|
#
|
||||||
# Override (where OIDC is unavailable, e.g. pending
|
# Override (where OIDC is unavailable, e.g. Gitea pending
|
||||||
# upstream forge OIDC support): set NOVA_AWS_ACCESS_KEY_ID + NOVA_AWS_SECRET_ACCESS_KEY
|
# go-gitea/gitea#36988): set ACDL_AWS_ACCESS_KEY_ID + ACDL_AWS_SECRET_ACCESS_KEY
|
||||||
# as repository secrets. The platform-managed scheduled pipeline rotates
|
# as repository secrets. The platform-managed scheduled pipeline rotates
|
||||||
# the key on a daily cadence. When .env.secrets is used locally instead,
|
# the key on a daily cadence. When .env.secrets is used locally instead,
|
||||||
# rotating the key out of band is the consumer's responsibility.
|
# rotating the key out of band is the consumer's responsibility.
|
||||||
name: nova-deploy
|
name: acdl-deploy
|
||||||
|
|
||||||
on:
|
on:
|
||||||
workflow_call:
|
workflow_call:
|
||||||
@@ -51,7 +51,7 @@ on:
|
|||||||
contract:
|
contract:
|
||||||
description: Path to the consumer contract YAML (in the consumer repo)
|
description: Path to the consumer contract YAML (in the consumer repo)
|
||||||
type: string
|
type: string
|
||||||
default: .nova/contract.yml
|
default: .acdl/contract.yml
|
||||||
mode:
|
mode:
|
||||||
description: Pipeline mode — full (apply), plan-only, check-only, or decommission
|
description: Pipeline mode — full (apply), plan-only, check-only, or decommission
|
||||||
type: string
|
type: string
|
||||||
@@ -82,7 +82,7 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
repository: acdl/acdl
|
repository: acdl/acdl
|
||||||
path: platform
|
path: platform
|
||||||
ref: v1.25
|
ref: v1.9
|
||||||
|
|
||||||
- uses: actions/setup-python@v5
|
- uses: actions/setup-python@v5
|
||||||
with:
|
with:
|
||||||
@@ -102,16 +102,13 @@ jobs:
|
|||||||
- name: Configure AWS credentials (OIDC default + static-key override)
|
- name: Configure AWS credentials (OIDC default + static-key override)
|
||||||
uses: aws-actions/configure-aws-credentials@v4
|
uses: aws-actions/configure-aws-credentials@v4
|
||||||
with:
|
with:
|
||||||
# P4 (REQ-163): IAM role renamed acdl-deploy- → nova-deploy-.
|
role-to-assume: ${{ secrets.ACDL_AWS_ACCESS_KEY_ID == '' && format('arn:aws:iam::{0}:role/acdl-deploy-{1}', secrets.ACDL_AWS_ACCOUNT_ID, github.repository_id) || '' }}
|
||||||
role-to-assume: ${{ secrets.NOVA_AWS_ACCESS_KEY_ID == '' && format('arn:aws:iam::{0}:role/nova-deploy-{1}', secrets.NOVA_AWS_ACCOUNT_ID, github.repository_id) || '' }}
|
aws-region: us-east-1
|
||||||
aws-region: ${{ secrets.AWS_DEFAULT_REGION || 'us-east-1' }}
|
access-key-id: ${{ secrets.ACDL_AWS_ACCESS_KEY_ID }}
|
||||||
access-key-id: ${{ secrets.NOVA_AWS_ACCESS_KEY_ID }}
|
secret-access-key: ${{ secrets.ACDL_AWS_SECRET_ACCESS_KEY }}
|
||||||
secret-access-key: ${{ secrets.NOVA_AWS_SECRET_ACCESS_KEY }}
|
|
||||||
|
|
||||||
- name: Run the platform pipeline
|
- name: Run the platform pipeline
|
||||||
working-directory: ${{ github.workspace }}
|
working-directory: ${{ github.workspace }}
|
||||||
env:
|
|
||||||
NOVA_CONSUMER_REPO: ${{ github.repository }}
|
|
||||||
run: |
|
run: |
|
||||||
MODE_FLAG=""
|
MODE_FLAG=""
|
||||||
case "${{ inputs.mode }}" in
|
case "${{ inputs.mode }}" in
|
||||||
@@ -148,7 +145,7 @@ jobs:
|
|||||||
AWS_DEFAULT_REGION: us-east-1
|
AWS_DEFAULT_REGION: us-east-1
|
||||||
run: |
|
run: |
|
||||||
aws lambda invoke-function-url \
|
aws lambda invoke-function-url \
|
||||||
--function-url "${{ secrets.NOVA_LAMBDA_URL }}" \
|
--function-url "${{ secrets.ACDL_LAMBDA_URL }}" \
|
||||||
--cli-binary-format raw-in-base64-out \
|
--cli-binary-format raw-in-base64-out \
|
||||||
--payload "$(python3 -c "import json,os; print(json.dumps({'action':'report_error','consumerRepo':os.environ.get('GITHUB_REPOSITORY',''),'contractId':'${{ github.run_id }}','error':'Deploy pipeline failed. See run logs.','runUrl':'${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}','environment':'dev'}))")" \
|
--payload "$(python3 -c "import json,os; print(json.dumps({'action':'report_error','consumerRepo':os.environ.get('GITHUB_REPOSITORY',''),'contractId':'${{ github.run_id }}','error':'Deploy pipeline failed. See run logs.','runUrl':'${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}','environment':'dev'}))")" \
|
||||||
/dev/null || true
|
/dev/null || true
|
||||||
@@ -156,13 +153,13 @@ jobs:
|
|||||||
- name: Upload emitted Terraform
|
- name: Upload emitted Terraform
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: nova-terraform
|
name: acdl-terraform
|
||||||
path: /tmp/nova_platform_run/tf/*.tf
|
path: /tmp/acdl_platform_run_v18/tf/*.tf
|
||||||
if-no-files-found: warn
|
if-no-files-found: warn
|
||||||
|
|
||||||
- name: Upload platform log
|
- name: Upload platform log
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: nova-platform-log
|
name: acdl-platform-log
|
||||||
path: platform/logs/
|
path: platform/logs/
|
||||||
if-no-files-found: warn
|
if-no-files-found: warn
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
# Nova Modules Lifecycle Pipeline (dev environment)
|
# ACDL Modules Lifecycle Pipeline — Gitea Actions (dev environment)
|
||||||
#
|
#
|
||||||
# Matrix-runs each L1 module's examples/{simple,complex}.yml contracts through
|
# Matrix-runs each L1 module's examples/{simple,complex}.yml contracts through
|
||||||
# apply→modify→destroy against live AWS. No per-module Python. The "test" =
|
# apply→modify→destroy against live AWS. No per-module Python. The "test" =
|
||||||
@@ -9,13 +9,13 @@
|
|||||||
# terraform files); the composition must be deterministic.
|
# terraform files); the composition must be deterministic.
|
||||||
#
|
#
|
||||||
# This workflow implements pipelines/modules-lifecycle.yml (byte-identical
|
# This workflow implements pipelines/modules-lifecycle.yml (byte-identical
|
||||||
# in .github/workflows/).
|
# in .gitea/workflows/ and .github/workflows/).
|
||||||
#
|
#
|
||||||
# Lifecycle mode (REQ-134, v1.12): the `lifecycle_mode` input defaults to
|
# Lifecycle mode (REQ-134, v1.12): the `lifecycle_mode` input defaults to
|
||||||
# "plan" — the lifecycle scripts run `run_platform.sh --plan-only` (fast,
|
# "plan" — the lifecycle scripts run `run_platform.sh --plan-only` (fast,
|
||||||
# no AWS mutation, validates the contract->resolver->adapter->plan chain
|
# no AWS mutation, validates the contract->resolver->adapter->plan chain
|
||||||
# for every module on every PR, with no AWS credentials or cost). Set to
|
# for every module on every PR, with no AWS credentials or cost). Set to
|
||||||
# "full" via workflow_dispatch (or the NOVA_LIFECYCLE_MODE repo variable)
|
# "full" via workflow_dispatch (or the ACDL_LIFECYCLE_MODE repo variable)
|
||||||
# to run the real apply→modify→destroy against live AWS. In plan mode the
|
# to run the real apply→modify→destroy against live AWS. In plan mode the
|
||||||
# short-lived CI VPC apply/destroy jobs are skipped (nothing is applied).
|
# short-lived CI VPC apply/destroy jobs are skipped (nothing is applied).
|
||||||
#
|
#
|
||||||
@@ -49,7 +49,7 @@ jobs:
|
|||||||
ci-vpc-apply:
|
ci-vpc-apply:
|
||||||
name: CI VPC apply
|
name: CI VPC apply
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
if: ${{ github.event.inputs.lifecycle_mode != 'plan' && vars.NOVA_LIFECYCLE_MODE != 'plan' }}
|
if: ${{ github.event.inputs.lifecycle_mode != 'plan' && vars.ACDL_LIFECYCLE_MODE != 'plan' }}
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
- name: Install Terraform 1.9.*
|
- name: Install Terraform 1.9.*
|
||||||
@@ -60,8 +60,8 @@ jobs:
|
|||||||
- name: Apply CI VPC
|
- name: Apply CI VPC
|
||||||
working-directory: terraform/ci-vpc
|
working-directory: terraform/ci-vpc
|
||||||
env:
|
env:
|
||||||
AWS_ACCESS_KEY_ID: ${{ secrets.NOVA_AWS_ACCESS_KEY_ID }}
|
AWS_ACCESS_KEY_ID: ${{ secrets.ACDL_AWS_ACCESS_KEY_ID }}
|
||||||
AWS_SECRET_ACCESS_KEY: ${{ secrets.NOVA_AWS_SECRET_ACCESS_KEY }}
|
AWS_SECRET_ACCESS_KEY: ${{ secrets.ACDL_AWS_SECRET_ACCESS_KEY }}
|
||||||
AWS_DEFAULT_REGION: us-east-1
|
AWS_DEFAULT_REGION: us-east-1
|
||||||
run: |
|
run: |
|
||||||
terraform init -input=false -lock=false
|
terraform init -input=false -lock=false
|
||||||
@@ -78,7 +78,7 @@ jobs:
|
|||||||
matrix:
|
matrix:
|
||||||
module: [s3, kms-key, ecr, ecs-cluster, iam-role, cloudfront, waf, vpc, alb, ecs-service, rds, uptime]
|
module: [s3, kms-key, ecr, ecs-cluster, iam-role, cloudfront, waf, vpc, alb, ecs-service, rds, uptime]
|
||||||
env:
|
env:
|
||||||
NOVA_LIFECYCLE_MODE: ${{ github.event.inputs.lifecycle_mode || vars.NOVA_LIFECYCLE_MODE || 'plan' }}
|
ACDL_LIFECYCLE_MODE: ${{ github.event.inputs.lifecycle_mode || vars.ACDL_LIFECYCLE_MODE || 'plan' }}
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
- name: Free disk space
|
- name: Free disk space
|
||||||
@@ -97,31 +97,31 @@ jobs:
|
|||||||
echo "deb [signed-by=/usr/share/keyrings/hashicorp.gpg] https://apt.releases.hashicorp.com $(lsb_release -cs) main" | sudo tee /etc/apt/sources.list.d/hashicorp.list
|
echo "deb [signed-by=/usr/share/keyrings/hashicorp.gpg] https://apt.releases.hashicorp.com $(lsb_release -cs) main" | sudo tee /etc/apt/sources.list.d/hashicorp.list
|
||||||
sudo apt-get update && sudo apt-get install -y terraform=1.9.*
|
sudo apt-get update && sudo apt-get install -y terraform=1.9.*
|
||||||
- name: Read CI VPC outputs
|
- name: Read CI VPC outputs
|
||||||
if: ${{ env.NOVA_LIFECYCLE_MODE == 'full' }}
|
if: ${{ env.ACDL_LIFECYCLE_MODE == 'full' }}
|
||||||
working-directory: terraform/ci-vpc
|
working-directory: terraform/ci-vpc
|
||||||
env:
|
env:
|
||||||
AWS_ACCESS_KEY_ID: ${{ secrets.NOVA_AWS_ACCESS_KEY_ID }}
|
AWS_ACCESS_KEY_ID: ${{ secrets.ACDL_AWS_ACCESS_KEY_ID }}
|
||||||
AWS_SECRET_ACCESS_KEY: ${{ secrets.NOVA_AWS_SECRET_ACCESS_KEY }}
|
AWS_SECRET_ACCESS_KEY: ${{ secrets.ACDL_AWS_SECRET_ACCESS_KEY }}
|
||||||
AWS_DEFAULT_REGION: us-east-1
|
AWS_DEFAULT_REGION: us-east-1
|
||||||
run: |
|
run: |
|
||||||
terraform init -input=false -lock=false
|
terraform init -input=false -lock=false
|
||||||
terraform output -json > /tmp/ci-vpc-outputs.json
|
terraform output -json > /tmp/ci-vpc-outputs.json
|
||||||
- name: Apply (simple)
|
- name: Apply (simple)
|
||||||
env:
|
env:
|
||||||
AWS_ACCESS_KEY_ID: ${{ secrets.NOVA_AWS_ACCESS_KEY_ID }}
|
AWS_ACCESS_KEY_ID: ${{ secrets.ACDL_AWS_ACCESS_KEY_ID }}
|
||||||
AWS_SECRET_ACCESS_KEY: ${{ secrets.NOVA_AWS_SECRET_ACCESS_KEY }}
|
AWS_SECRET_ACCESS_KEY: ${{ secrets.ACDL_AWS_SECRET_ACCESS_KEY }}
|
||||||
AWS_DEFAULT_REGION: us-east-1
|
AWS_DEFAULT_REGION: us-east-1
|
||||||
run: bash scripts/run_lifecycle_test.sh ${{ matrix.module }} simple /tmp/ci-vpc-outputs.json
|
run: bash scripts/run_lifecycle_test.sh ${{ matrix.module }} simple /tmp/ci-vpc-outputs.json
|
||||||
- name: Modify (complex)
|
- name: Modify (complex)
|
||||||
env:
|
env:
|
||||||
AWS_ACCESS_KEY_ID: ${{ secrets.NOVA_AWS_ACCESS_KEY_ID }}
|
AWS_ACCESS_KEY_ID: ${{ secrets.ACDL_AWS_ACCESS_KEY_ID }}
|
||||||
AWS_SECRET_ACCESS_KEY: ${{ secrets.NOVA_AWS_SECRET_ACCESS_KEY }}
|
AWS_SECRET_ACCESS_KEY: ${{ secrets.ACDL_AWS_SECRET_ACCESS_KEY }}
|
||||||
AWS_DEFAULT_REGION: us-east-1
|
AWS_DEFAULT_REGION: us-east-1
|
||||||
run: bash scripts/run_lifecycle_test.sh ${{ matrix.module }} complex /tmp/ci-vpc-outputs.json
|
run: bash scripts/run_lifecycle_test.sh ${{ matrix.module }} complex /tmp/ci-vpc-outputs.json
|
||||||
- name: Destroy
|
- name: Destroy
|
||||||
env:
|
env:
|
||||||
AWS_ACCESS_KEY_ID: ${{ secrets.NOVA_AWS_ACCESS_KEY_ID }}
|
AWS_ACCESS_KEY_ID: ${{ secrets.ACDL_AWS_ACCESS_KEY_ID }}
|
||||||
AWS_SECRET_ACCESS_KEY: ${{ secrets.NOVA_AWS_SECRET_ACCESS_KEY }}
|
AWS_SECRET_ACCESS_KEY: ${{ secrets.ACDL_AWS_SECRET_ACCESS_KEY }}
|
||||||
AWS_DEFAULT_REGION: us-east-1
|
AWS_DEFAULT_REGION: us-east-1
|
||||||
run: bash scripts/run_lifecycle_destroy.sh ${{ matrix.module }} /tmp/ci-vpc-outputs.json
|
run: bash scripts/run_lifecycle_destroy.sh ${{ matrix.module }} /tmp/ci-vpc-outputs.json
|
||||||
|
|
||||||
@@ -136,7 +136,7 @@ jobs:
|
|||||||
matrix:
|
matrix:
|
||||||
module: [static-assets, microservice]
|
module: [static-assets, microservice]
|
||||||
env:
|
env:
|
||||||
NOVA_LIFECYCLE_MODE: ${{ github.event.inputs.lifecycle_mode || vars.NOVA_LIFECYCLE_MODE || 'plan' }}
|
ACDL_LIFECYCLE_MODE: ${{ github.event.inputs.lifecycle_mode || vars.ACDL_LIFECYCLE_MODE || 'plan' }}
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
- name: Free disk space
|
- name: Free disk space
|
||||||
@@ -155,31 +155,31 @@ jobs:
|
|||||||
echo "deb [signed-by=/usr/share/keyrings/hashicorp.gpg] https://apt.releases.hashicorp.com $(lsb_release -cs) main" | sudo tee /etc/apt/sources.list.d/hashicorp.list
|
echo "deb [signed-by=/usr/share/keyrings/hashicorp.gpg] https://apt.releases.hashicorp.com $(lsb_release -cs) main" | sudo tee /etc/apt/sources.list.d/hashicorp.list
|
||||||
sudo apt-get update && sudo apt-get install -y terraform=1.9.*
|
sudo apt-get update && sudo apt-get install -y terraform=1.9.*
|
||||||
- name: Read CI VPC outputs
|
- name: Read CI VPC outputs
|
||||||
if: ${{ env.NOVA_LIFECYCLE_MODE == 'full' }}
|
if: ${{ env.ACDL_LIFECYCLE_MODE == 'full' }}
|
||||||
working-directory: terraform/ci-vpc
|
working-directory: terraform/ci-vpc
|
||||||
env:
|
env:
|
||||||
AWS_ACCESS_KEY_ID: ${{ secrets.NOVA_AWS_ACCESS_KEY_ID }}
|
AWS_ACCESS_KEY_ID: ${{ secrets.ACDL_AWS_ACCESS_KEY_ID }}
|
||||||
AWS_SECRET_ACCESS_KEY: ${{ secrets.NOVA_AWS_SECRET_ACCESS_KEY }}
|
AWS_SECRET_ACCESS_KEY: ${{ secrets.ACDL_AWS_SECRET_ACCESS_KEY }}
|
||||||
AWS_DEFAULT_REGION: us-east-1
|
AWS_DEFAULT_REGION: us-east-1
|
||||||
run: |
|
run: |
|
||||||
terraform init -input=false -lock=false
|
terraform init -input=false -lock=false
|
||||||
terraform output -json > /tmp/ci-vpc-outputs.json
|
terraform output -json > /tmp/ci-vpc-outputs.json
|
||||||
- name: Apply (simple)
|
- name: Apply (simple)
|
||||||
env:
|
env:
|
||||||
AWS_ACCESS_KEY_ID: ${{ secrets.NOVA_AWS_ACCESS_KEY_ID }}
|
AWS_ACCESS_KEY_ID: ${{ secrets.ACDL_AWS_ACCESS_KEY_ID }}
|
||||||
AWS_SECRET_ACCESS_KEY: ${{ secrets.NOVA_AWS_SECRET_ACCESS_KEY }}
|
AWS_SECRET_ACCESS_KEY: ${{ secrets.ACDL_AWS_SECRET_ACCESS_KEY }}
|
||||||
AWS_DEFAULT_REGION: us-east-1
|
AWS_DEFAULT_REGION: us-east-1
|
||||||
run: bash scripts/run_l2_lifecycle_test.sh ${{ matrix.module }} simple /tmp/ci-vpc-outputs.json
|
run: bash scripts/run_l2_lifecycle_test.sh ${{ matrix.module }} simple /tmp/ci-vpc-outputs.json
|
||||||
- name: Modify (complex)
|
- name: Modify (complex)
|
||||||
env:
|
env:
|
||||||
AWS_ACCESS_KEY_ID: ${{ secrets.NOVA_AWS_ACCESS_KEY_ID }}
|
AWS_ACCESS_KEY_ID: ${{ secrets.ACDL_AWS_ACCESS_KEY_ID }}
|
||||||
AWS_SECRET_ACCESS_KEY: ${{ secrets.NOVA_AWS_SECRET_ACCESS_KEY }}
|
AWS_SECRET_ACCESS_KEY: ${{ secrets.ACDL_AWS_SECRET_ACCESS_KEY }}
|
||||||
AWS_DEFAULT_REGION: us-east-1
|
AWS_DEFAULT_REGION: us-east-1
|
||||||
run: bash scripts/run_l2_lifecycle_test.sh ${{ matrix.module }} complex /tmp/ci-vpc-outputs.json
|
run: bash scripts/run_l2_lifecycle_test.sh ${{ matrix.module }} complex /tmp/ci-vpc-outputs.json
|
||||||
- name: Destroy
|
- name: Destroy
|
||||||
env:
|
env:
|
||||||
AWS_ACCESS_KEY_ID: ${{ secrets.NOVA_AWS_ACCESS_KEY_ID }}
|
AWS_ACCESS_KEY_ID: ${{ secrets.ACDL_AWS_ACCESS_KEY_ID }}
|
||||||
AWS_SECRET_ACCESS_KEY: ${{ secrets.NOVA_AWS_SECRET_ACCESS_KEY }}
|
AWS_SECRET_ACCESS_KEY: ${{ secrets.ACDL_AWS_SECRET_ACCESS_KEY }}
|
||||||
AWS_DEFAULT_REGION: us-east-1
|
AWS_DEFAULT_REGION: us-east-1
|
||||||
run: bash scripts/run_l2_lifecycle_destroy.sh ${{ matrix.module }} /tmp/ci-vpc-outputs.json
|
run: bash scripts/run_l2_lifecycle_destroy.sh ${{ matrix.module }} /tmp/ci-vpc-outputs.json
|
||||||
|
|
||||||
@@ -188,7 +188,7 @@ jobs:
|
|||||||
name: CI VPC destroy
|
name: CI VPC destroy
|
||||||
needs: [lifecycle, l2-lifecycle]
|
needs: [lifecycle, l2-lifecycle]
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
if: ${{ always() && github.event.inputs.lifecycle_mode != 'plan' && vars.NOVA_LIFECYCLE_MODE != 'plan' }}
|
if: ${{ always() && github.event.inputs.lifecycle_mode != 'plan' && vars.ACDL_LIFECYCLE_MODE != 'plan' }}
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
- name: Install Terraform 1.9.*
|
- name: Install Terraform 1.9.*
|
||||||
@@ -199,8 +199,8 @@ jobs:
|
|||||||
- name: Destroy CI VPC
|
- name: Destroy CI VPC
|
||||||
working-directory: terraform/ci-vpc
|
working-directory: terraform/ci-vpc
|
||||||
env:
|
env:
|
||||||
AWS_ACCESS_KEY_ID: ${{ secrets.NOVA_AWS_ACCESS_KEY_ID }}
|
AWS_ACCESS_KEY_ID: ${{ secrets.ACDL_AWS_ACCESS_KEY_ID }}
|
||||||
AWS_SECRET_ACCESS_KEY: ${{ secrets.NOVA_AWS_SECRET_ACCESS_KEY }}
|
AWS_SECRET_ACCESS_KEY: ${{ secrets.ACDL_AWS_SECRET_ACCESS_KEY }}
|
||||||
AWS_DEFAULT_REGION: us-east-1
|
AWS_DEFAULT_REGION: us-east-1
|
||||||
run: |
|
run: |
|
||||||
terraform init -input=false -lock=false
|
terraform init -input=false -lock=false
|
||||||
|
|||||||
@@ -1,165 +0,0 @@
|
|||||||
# Nova Publish Pipeline — wheel + Lambda layer (REQ-323, CAP-035, NFR-6)
|
|
||||||
#
|
|
||||||
# This workflow is byte-identical across the production forge (GitHub
|
|
||||||
# Actions) and the dev forge (act_runner) — the same file is installed
|
|
||||||
# at .github/workflows/publish.yml and the mirror at
|
|
||||||
# <dev-forge>/workflows/publish.yml. Both copies must match exactly
|
|
||||||
# (asserted by tests/test_forge_action_byte_identical.py for the action
|
|
||||||
# and by the repo's byte-identical convention for workflows).
|
|
||||||
#
|
|
||||||
# NFR-6 (wheel/layer co-versioning): every merge to main affecting
|
|
||||||
# core/**, adapters/**, nova/**, or pyproject.toml publishes BOTH a
|
|
||||||
# wheel AND a Lambda layer with identical version strings. If either
|
|
||||||
# publish fails, the job fails and the merge is blocked.
|
|
||||||
#
|
|
||||||
# REQ-323: CodeArtifact wheel + Lambda layer pipeline.
|
|
||||||
# CAP-035: Lambda layer ARN version matches the nova-cli wheel version;
|
|
||||||
# the mapping is recorded in SSM /nova/layer/nova-cli/version.
|
|
||||||
#
|
|
||||||
# Triggers:
|
|
||||||
# - push to main when core/**, adapters/**, nova/**, or pyproject.toml
|
|
||||||
# changed (the surfaces that ship in the wheel + layer)
|
|
||||||
# - workflow_dispatch (manual republish, e.g. after a CodeArtifact
|
|
||||||
# provisioning fix)
|
|
||||||
#
|
|
||||||
# Wheel index selection (CodeArtifact default + fallback):
|
|
||||||
# - CodeArtifact mode: set the NOVA_CODEARTIFACT_DOMAIN repository
|
|
||||||
# secret (e.g. "nova"). The workflow runs
|
|
||||||
# `aws codeartifact login --tool twine --domain $NOVA_CODEARTIFACT_DOMAIN
|
|
||||||
# --repository nova-pypi` and twine uploads to the CodeArtifact pypi
|
|
||||||
# endpoint.
|
|
||||||
# - Fallback mode: leave NOVA_CODEARTIFACT_DOMAIN unset and provide
|
|
||||||
# TWINE_REPOSITORY_URL + TWINE_USERNAME + TWINE_PASSWORD repository
|
|
||||||
# secrets pointing at any PEP 503 simple index (a private package
|
|
||||||
# registry). twine uploads to TWINE_REPOSITORY_URL.
|
|
||||||
# See docs/codeartifact-provisioning.md for the required IAM grants
|
|
||||||
# + the fallback index shape.
|
|
||||||
#
|
|
||||||
# Secrets / env:
|
|
||||||
# AWS_ROLE_ARN — OIDC role to assume (id-token: write)
|
|
||||||
# NOVA_CODEARTIFACT_DOMAIN — optional; when set, CodeArtifact mode
|
|
||||||
# TWINE_USERNAME — fallback-index upload user
|
|
||||||
# TWINE_PASSWORD — fallback-index upload password
|
|
||||||
# TWINE_REPOSITORY_URL — fallback-index upload URL
|
|
||||||
# AWS_DEFAULT_REGION (optional) — defaults to us-east-1
|
|
||||||
name: nova-publish
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches: [main]
|
|
||||||
paths:
|
|
||||||
- "core/**"
|
|
||||||
- "adapters/**"
|
|
||||||
- "nova/**"
|
|
||||||
- "pyproject.toml"
|
|
||||||
workflow_dispatch:
|
|
||||||
|
|
||||||
permissions:
|
|
||||||
id-token: write # OIDC federation to AWS
|
|
||||||
contents: write # tag the release
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
publish:
|
|
||||||
name: Publish wheel + Lambda layer
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
|
|
||||||
- uses: actions/setup-python@v5
|
|
||||||
with:
|
|
||||||
python-version: "3.12"
|
|
||||||
|
|
||||||
- name: Configure AWS credentials (OIDC)
|
|
||||||
uses: aws-actions/configure-aws-credentials@v4
|
|
||||||
with:
|
|
||||||
role-to-assume: ${{ secrets.AWS_ROLE_ARN }}
|
|
||||||
aws-region: ${{ secrets.AWS_DEFAULT_REGION || 'us-east-1' }}
|
|
||||||
|
|
||||||
- name: Install build + publish tools
|
|
||||||
run: pip install build twine
|
|
||||||
|
|
||||||
- name: Compute version from pyproject.toml
|
|
||||||
id: ver
|
|
||||||
run: |
|
|
||||||
set -e
|
|
||||||
VERSION=$(python -c 'import tomllib;print(tomllib.load(open("pyproject.toml","rb"))["project"]["version"])')
|
|
||||||
echo "version=$VERSION" >> "$GITHUB_OUTPUT"
|
|
||||||
echo "Nova version: $VERSION"
|
|
||||||
|
|
||||||
- name: Build wheel
|
|
||||||
run: |
|
|
||||||
set -e
|
|
||||||
python -m build --wheel
|
|
||||||
ls -1 dist/
|
|
||||||
|
|
||||||
- name: Upload wheel to index (CodeArtifact default + fallback)
|
|
||||||
id: wheel
|
|
||||||
env:
|
|
||||||
NOVA_CODEARTIFACT_DOMAIN: ${{ secrets.NOVA_CODEARTIFACT_DOMAIN }}
|
|
||||||
TWINE_USERNAME: ${{ secrets.TWINE_USERNAME }}
|
|
||||||
TWINE_PASSWORD: ${{ secrets.TWINE_PASSWORD }}
|
|
||||||
TWINE_REPOSITORY_URL: ${{ secrets.TWINE_REPOSITORY_URL }}
|
|
||||||
run: |
|
|
||||||
set -e
|
|
||||||
# CodeArtifact mode: log in to the domain's pypi repository.
|
|
||||||
if [ -n "$NOVA_CODEARTIFACT_DOMAIN" ]; then
|
|
||||||
echo "CodeArtifact mode: domain=$NOVA_CODEARTIFACT_DOMAIN repository=nova-pypi"
|
|
||||||
aws codeartifact login --tool twine \
|
|
||||||
--domain "$NOVA_CODEARTIFACT_DOMAIN" --repository nova-pypi
|
|
||||||
else
|
|
||||||
echo "Fallback-index mode: uploading to TWINE_REPOSITORY_URL"
|
|
||||||
if [ -z "$TWINE_REPOSITORY_URL" ] || [ -z "$TWINE_USERNAME" ] || [ -z "$TWINE_PASSWORD" ]; then
|
|
||||||
echo "FAIL: NOVA_CODEARTIFACT_DOMAIN is unset and one of TWINE_REPOSITORY_URL/TWINE_USERNAME/TWINE_PASSWORD is missing."
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
# Idempotent upload: a re-run for the same version may hit
|
|
||||||
# "file already exists" on the index. Treat that as success.
|
|
||||||
twine upload "dist/nova-${{ steps.ver.outputs.version }}-*.whl" \
|
|
||||||
|| twine upload "dist/nova-${{ steps.ver.outputs.version }}-*.whl" 2>&1 | tee /tmp/twine.log
|
|
||||||
if grep -qi "already exist" /tmp/twine.log 2>/dev/null; then
|
|
||||||
echo "Wheel already present on the index — treating as success (idempotent)."
|
|
||||||
fi
|
|
||||||
echo "uploaded=true" >> "$GITHUB_OUTPUT"
|
|
||||||
|
|
||||||
- name: Build Lambda layer
|
|
||||||
run: |
|
|
||||||
set -e
|
|
||||||
rm -rf layer
|
|
||||||
mkdir -p layer/python
|
|
||||||
# Install the wheel we just built + the identity extras' deps
|
|
||||||
# so the layer carries argon2-cffi, cryptography, pyjwt.
|
|
||||||
pip install --target layer/python/ \
|
|
||||||
"dist/nova-${{ steps.ver.outputs.version }}-*.whl" \
|
|
||||||
argon2-cffi cryptography pyjwt
|
|
||||||
( cd layer && zip -r ../nova-layer.zip python/ )
|
|
||||||
ls -lh nova-layer.zip
|
|
||||||
|
|
||||||
- name: Publish Lambda layer
|
|
||||||
id: layer
|
|
||||||
run: |
|
|
||||||
set -e
|
|
||||||
ARN=$(aws lambda publish-layer-version \
|
|
||||||
--layer-name nova-cli \
|
|
||||||
--zip-file fileb://nova-layer.zip \
|
|
||||||
--compatible-runtimes python3.12 \
|
|
||||||
--compatible-architectures x86_64 \
|
|
||||||
--description "nova-cli v${{ steps.ver.outputs.version }}" \
|
|
||||||
--query LayerVersionArn --output text)
|
|
||||||
echo "arn=$ARN" >> "$GITHUB_OUTPUT"
|
|
||||||
echo "Published Lambda layer: $ARN"
|
|
||||||
|
|
||||||
- name: Record SSM version↔ARN mapping (CAP-035)
|
|
||||||
run: |
|
|
||||||
set -e
|
|
||||||
aws ssm put-parameter \
|
|
||||||
--name /nova/layer/nova-cli/version \
|
|
||||||
--value "${{ steps.ver.outputs.version }}:${{ steps.layer.outputs.arn }}" \
|
|
||||||
--type String --overwrite
|
|
||||||
echo "SSM /nova/layer/nova-cli/version = ${{ steps.ver.outputs.version }}:${{ steps.layer.outputs.arn }}"
|
|
||||||
|
|
||||||
- name: Fail job if either publish failed (REQ-323 AC)
|
|
||||||
if: ${{ steps.wheel.outputs.uploaded != 'true' || steps.layer.outputs.arn == '' }}
|
|
||||||
run: |
|
|
||||||
echo "FAIL: wheel uploaded=${{ steps.wheel.outputs.uploaded }} layer_arn=${{ steps.layer.outputs.arn }}"
|
|
||||||
exit 1
|
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
# Nova Release Pipeline — GitHub Actions (production)
|
# ACDL Release Pipeline — GitHub Actions (production)
|
||||||
#
|
#
|
||||||
# Runs on push to main. Computes the next semver tag from the latest tag +
|
# Runs on push to main. Computes the next semver tag from the latest tag +
|
||||||
# commit history, creates the tag, updates floating MAJOR.MINOR and MAJOR tags,
|
# commit history, creates the tag, updates floating MAJOR.MINOR and MAJOR tags,
|
||||||
@@ -8,7 +8,7 @@
|
|||||||
# - Regular phase commit -> bump PATCH (v1.6.0 -> v1.6.1)
|
# - Regular phase commit -> bump PATCH (v1.6.0 -> v1.6.1)
|
||||||
# - Milestone completion ("docs(milestone): complete") -> bump MINOR (v1.6.1 -> v1.7.0)
|
# - Milestone completion ("docs(milestone): complete") -> bump MINOR (v1.6.1 -> v1.7.0)
|
||||||
# - Major bumps are manual (not implemented here).
|
# - Major bumps are manual (not implemented here).
|
||||||
name: nova-release
|
name: acdl-release
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
@@ -87,6 +87,6 @@ jobs:
|
|||||||
BODY=$(git log --format='- %s' HEAD)
|
BODY=$(git log --format='- %s' HEAD)
|
||||||
fi
|
fi
|
||||||
gh release create ${{ steps.version.outputs.new_tag }} \
|
gh release create ${{ steps.version.outputs.new_tag }} \
|
||||||
--title "Nova ${{ steps.version.outputs.new_tag }}" \
|
--title "ACDL ${{ steps.version.outputs.new_tag }}" \
|
||||||
--notes "$BODY" \
|
--notes "$BODY" \
|
||||||
--generate-notes || true
|
--generate-notes || true
|
||||||
@@ -1,69 +0,0 @@
|
|||||||
# Nova AWS key rotation — platform-managed scheduled pipeline (SPEC §5.9)
|
|
||||||
#
|
|
||||||
# Rotates the NOVA_AWS_* static key daily (no long-lived keys in the steady
|
|
||||||
# state). v0.2 scope: the mechanism must exist (SPEC §5.9); the v0.2 deploy
|
|
||||||
# uses the currently-active key. The rotation is best-effort + idempotent
|
|
||||||
# (scripts/rotate_spike_key.sh deactivates the old key only after the new
|
|
||||||
# key propagates to the consumer's Actions secret store).
|
|
||||||
#
|
|
||||||
# Auth: the rotation uses the CURRENT NOVA_AWS_* key to authenticate to IAM
|
|
||||||
# (the root account 581513795199 can rotate its own keys — confirmed by the
|
|
||||||
# bootstrap). The aws-actions/configure-aws-credentials@v4 step uses the
|
|
||||||
# static-key path (no OIDC role-to-assume); the long-lived key rotates
|
|
||||||
# itself, which is the bootstrap-exception documented in §5.9.
|
|
||||||
#
|
|
||||||
# Forge coords (base URL / owner / consumer repo) are sourced from
|
|
||||||
# repository secrets — NOVA_FORGE_BASE_URL, NOVA_FORGE_OWNER,
|
|
||||||
# NOVA_CONSUMER_REPO — so the synced workflow file stays forge-agnostic
|
|
||||||
# (REQ-230). The rotation script uploads the new key to the consumer's
|
|
||||||
# Actions secret store (the consumer whose deploy.yml consumes NOVA_AWS_*
|
|
||||||
# via secrets: inherit).
|
|
||||||
name: nova-rotate-aws-key
|
|
||||||
|
|
||||||
on:
|
|
||||||
schedule:
|
|
||||||
- cron: "0 0 * * *" # daily at 00:00 UTC
|
|
||||||
workflow_dispatch:
|
|
||||||
|
|
||||||
permissions:
|
|
||||||
id-token: write
|
|
||||||
contents: read
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
rotate:
|
|
||||||
name: Rotate NOVA_AWS_* static key
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- name: Check out Nova platform repo
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
|
|
||||||
- name: Configure AWS credentials (bootstrap root creds for IAM key rotation)
|
|
||||||
uses: aws-actions/configure-aws-credentials@v4
|
|
||||||
with:
|
|
||||||
aws-region: ${{ secrets.AWS_DEFAULT_REGION || 'us-east-1' }}
|
|
||||||
access-key-id: ${{ secrets.NOVA_AWS_ACCESS_KEY_ID }}
|
|
||||||
secret-access-key: ${{ secrets.NOVA_AWS_SECRET_ACCESS_KEY }}
|
|
||||||
|
|
||||||
- name: Install Python deps (boto3 for the rotation script)
|
|
||||||
run: |
|
|
||||||
python3 -m pip install --break-system-packages --quiet boto3
|
|
||||||
|
|
||||||
- name: Run the key rotation script
|
|
||||||
env:
|
|
||||||
# aws-actions/configure-aws-credentials exports AWS_ACCESS_KEY_ID /
|
|
||||||
# AWS_SECRET_ACCESS_KEY; the rotation script reads the bootstrap
|
|
||||||
# creds via NOVA_BOOTSTRAP_AWS_* (its dual-read contract, D-034).
|
|
||||||
# Map the standard AWS_* exports onto the script's expected vars.
|
|
||||||
NOVA_BOOTSTRAP_AWS_ACCESS_KEY_ID: ${{ env.AWS_ACCESS_KEY_ID }}
|
|
||||||
NOVA_BOOTSTRAP_AWS_SECRET_ACCESS_KEY: ${{ env.AWS_SECRET_ACCESS_KEY }}
|
|
||||||
# Forge + consumer coords come from repository secrets (REQ-230 —
|
|
||||||
# no forge hostnames/orgs hardcoded in the synced workflow file).
|
|
||||||
# NOVA_FORGE_TOKEN holds the forge API token (set equal to the
|
|
||||||
# existing forge token as a one-time secret setup).
|
|
||||||
NOVA_FORGE_TOKEN: ${{ secrets.NOVA_FORGE_TOKEN }}
|
|
||||||
NOVA_FORGE_BASE_URL: ${{ secrets.NOVA_FORGE_BASE_URL }}
|
|
||||||
NOVA_FORGE_OWNER: ${{ secrets.NOVA_FORGE_OWNER }}
|
|
||||||
NOVA_CONSUMER_REPO: ${{ secrets.NOVA_CONSUMER_REPO }}
|
|
||||||
AWS_DEFAULT_REGION: ${{ secrets.AWS_DEFAULT_REGION || 'us-east-1' }}
|
|
||||||
run: |
|
|
||||||
bash scripts/rotate_spike_key.sh
|
|
||||||
@@ -1,43 +0,0 @@
|
|||||||
# Nova Slides Render — re-renders presentation deck when source files change.
|
|
||||||
# REQ-273: install python-pptx, pin CLI versions, stage HTML + both PPTX +
|
|
||||||
# base64-inlined images.
|
|
||||||
name: Nova Slides Render
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
paths:
|
|
||||||
- 'docs/presentations/**'
|
|
||||||
- 'scripts/render_slides.sh'
|
|
||||||
- 'scripts/inline_images.py'
|
|
||||||
- 'scripts/render_pptx.py'
|
|
||||||
- 'pyproject.toml'
|
|
||||||
workflow_dispatch:
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
render:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
with: { fetch-depth: 0 }
|
|
||||||
- uses: actions/setup-node@v4
|
|
||||||
with: { node-version: '20' }
|
|
||||||
- uses: actions/setup-python@v5
|
|
||||||
with:
|
|
||||||
python-version: '3.10'
|
|
||||||
- name: Install python-pptx (slides extra)
|
|
||||||
run: pip install -e ".[slides]"
|
|
||||||
- name: Install + pin render CLIs
|
|
||||||
run: |
|
|
||||||
npx --yes @marp-team/marp-cli@4.5.0 --version
|
|
||||||
npx --yes @mermaid-js/mermaid-cli@11.16.0 --version
|
|
||||||
- name: Render slides
|
|
||||||
run: bash scripts/render_slides.sh
|
|
||||||
- name: Commit rendered artifacts
|
|
||||||
run: |
|
|
||||||
git config user.name "nova-slides-bot"
|
|
||||||
git config user.email "bot@nova.local"
|
|
||||||
git add docs/presentations/*.html \
|
|
||||||
docs/presentations/*.pptx \
|
|
||||||
docs/presentations/*-python.pptx \
|
|
||||||
docs/presentations/assets/png/*.png
|
|
||||||
git diff --cached --quiet || git commit -m "chore(slides): re-render deck [skip ci]"
|
|
||||||
git push
|
|
||||||
+1
-17
@@ -14,18 +14,6 @@ terraform/bootstrap/.bootstrap_state.json
|
|||||||
# CIAgent runtime artifacts
|
# CIAgent runtime artifacts
|
||||||
.ciagent/logs/
|
.ciagent/logs/
|
||||||
|
|
||||||
# Nova metrics runtime artifacts (REQ-187, D-128)
|
|
||||||
# Generated: nova_metrics.db, decision_ledger.db, events.jsonl, runs/, test-results.xml, coverage.json, test-report.json
|
|
||||||
# NOT ignored: metrics/README.md, metrics/powerbi/ (export views), schemas/metrics_*.schema.json
|
|
||||||
metrics/nova_metrics.db
|
|
||||||
metrics/decision_ledger.db
|
|
||||||
metrics/events.jsonl
|
|
||||||
metrics/test-results.xml
|
|
||||||
metrics/test-report.json
|
|
||||||
metrics/coverage.json
|
|
||||||
metrics/runs/
|
|
||||||
metrics/lifecycle/
|
|
||||||
|
|
||||||
# Terraform — recursively ignore .terraform dirs, lock files, plans, and state
|
# Terraform — recursively ignore .terraform dirs, lock files, plans, and state
|
||||||
**/.terraform/
|
**/.terraform/
|
||||||
**/.terraform.lock.hcl
|
**/.terraform.lock.hcl
|
||||||
@@ -40,8 +28,4 @@ metrics/lifecycle/
|
|||||||
*.cer
|
*.cer
|
||||||
*.crt
|
*.crt
|
||||||
*.jks
|
*.jks
|
||||||
*.keystore.coverage
|
*.keystore
|
||||||
.coverage
|
|
||||||
|
|
||||||
.venv/
|
|
||||||
nova.egg-info/
|
|
||||||
@@ -1,6 +1,4 @@
|
|||||||
# Nova
|
# ACDL — Agentic Cloud Delivery Platform
|
||||||
|
|
||||||
> **Nova — The New Dawn of DevSecOps.** Security as a seamless enabler of fast deployments — not a bottleneck, not a "no" department.
|
|
||||||
|
|
||||||
Consumers declare intent; the platform delivers safe production deployment
|
Consumers declare intent; the platform delivers safe production deployment
|
||||||
through an agentic stack — automatically, safely, and with a complete audit
|
through an agentic stack — automatically, safely, and with a complete audit
|
||||||
@@ -20,7 +18,7 @@ a configuration file, or an infrastructure module.
|
|||||||
|
|
||||||
## Repository roles
|
## Repository roles
|
||||||
|
|
||||||
There are two kinds of repository in the Nova model:
|
There are two kinds of repository in the ACDL model:
|
||||||
|
|
||||||
- **Platform repo (this one).** This is the **source code of the platform**.
|
- **Platform repo (this one).** This is the **source code of the platform**.
|
||||||
It owns `modules/`, `adapters/`, `core/`, `schemas/`, `pipelines/`,
|
It owns `modules/`, `adapters/`, `core/`, `schemas/`, `pipelines/`,
|
||||||
@@ -28,7 +26,7 @@ There are two kinds of repository in the Nova model:
|
|||||||
A **consumer never clones it.**
|
A **consumer never clones it.**
|
||||||
- **Consumer repo (yours).** A consumer repo contains only:
|
- **Consumer repo (yours).** A consumer repo contains only:
|
||||||
1. **Its application code** — the service or site being deployed.
|
1. **Its application code** — the service or site being deployed.
|
||||||
2. **One or more contracts** — small YAML files at `.nova/contract.yml`
|
2. **One or more contracts** — small YAML files at `.acdl/contract.yml`
|
||||||
that declare infrastructure (one or more modules by name + version),
|
that declare infrastructure (one or more modules by name + version),
|
||||||
select an environment, and supply module-specific inputs.
|
select an environment, and supply module-specific inputs.
|
||||||
3. **One or more CI definitions** — thin `.github/workflows/*.yml` files
|
3. **One or more CI definitions** — thin `.github/workflows/*.yml` files
|
||||||
@@ -95,7 +93,7 @@ intent via a contract; the platform delivers the deployment through the
|
|||||||
same contract schema, the same policy envelope, and the same evidence
|
same contract schema, the same policy envelope, and the same evidence
|
||||||
stream.
|
stream.
|
||||||
|
|
||||||
Consumers have their own repos and consume Nova by writing a contract that
|
Consumers have their own repos and consume ACDL by writing a contract that
|
||||||
declares infrastructure. A consumer declares a contract (id + name +
|
declares infrastructure. A consumer declares a contract (id + name +
|
||||||
environment + infrastructure); the platform resolves it to a stack instance,
|
environment + infrastructure); the platform resolves it to a stack instance,
|
||||||
compiles it, runs security + policy checks, computes a confidence signal,
|
compiles it, runs security + policy checks, computes a confidence signal,
|
||||||
@@ -126,51 +124,25 @@ engine-specific code. `modules/`, `schemas/`, `contracts/`,
|
|||||||
|
|
||||||
## How to run
|
## How to run
|
||||||
|
|
||||||
### Quick start (offline, no AWS required)
|
### Prerequisites
|
||||||
|
|
||||||
The fastest way to verify the platform works — no AWS credentials, no
|
> These prerequisites are for running the **platform repo** locally. A
|
||||||
bootstrap, no cost. See the [Consumer guide](docs/consumer-guide.md)
|
> consumer does not need any of these — see the
|
||||||
for the consumer happy path (a consumer owns only a contract + app code).
|
> [Consumer guide](docs/consumer-guide.md) for the consumer happy path.
|
||||||
|
|
||||||
```bash
|
- A platform-managed environment (see [docs/environments/](docs/environments/)).
|
||||||
# Install test dependencies
|
For local testing, `core/environments/dev.json` is provided as the sample.
|
||||||
pip install -r requirements-test.txt
|
- AWS credentials for the dev environment (in `.env.secrets`, gitignored;
|
||||||
|
see [Credentials & zero-trust](#credentials--zero-trust)).
|
||||||
|
- `terraform` (pin `1.9.*`), `checkov` (pin `>=3.2,<4`), `python3` + `boto3`
|
||||||
|
+ `jsonschema`.
|
||||||
|
|
||||||
# 1. Run the test suite (all offline — uses moto for DynamoDB mocking)
|
### Run the platform pipeline end-to-end
|
||||||
python3 -m pytest tests/ -v
|
|
||||||
|
|
||||||
# 2. Run the platform in check-only mode (offline — contract -> resolver ->
|
|
||||||
# adapter -> structure validation). Uses the default sample contract
|
|
||||||
# (contracts/static-assets.yaml) + sample dev environment.
|
|
||||||
bash scripts/run_platform.sh --check-only
|
|
||||||
# Expected: "=== PLATFORM CHECK OK ==="
|
|
||||||
|
|
||||||
# 3. Run the headline E2E against the local emulating tier (emulates ECS,
|
|
||||||
# outbox, S3 state, Lambda in-process; D-092).
|
|
||||||
bash scripts/run_platform.sh --local
|
|
||||||
# Expected: "=== LOCAL E2E OK ==="
|
|
||||||
|
|
||||||
# 4. Reproduce the full CI pipeline locally (lint -> test -> check-only)
|
|
||||||
bash scripts/run_ci.sh
|
|
||||||
# Expected: "=== CI PIPELINE OK ==="
|
|
||||||
|
|
||||||
# Show all run_platform.sh flags:
|
|
||||||
bash scripts/run_platform.sh --help
|
|
||||||
```
|
|
||||||
|
|
||||||
### Run against live AWS (requires credentials + bootstrap)
|
|
||||||
|
|
||||||
> Prerequisites: a platform-managed environment (see
|
|
||||||
> [docs/environments/](docs/environments/); `core/environments/dev.json`
|
|
||||||
> is the sample), AWS credentials for dev (in `.env.secrets`, gitignored;
|
|
||||||
> see [Credentials & zero-trust](#credentials--zero-trust)), `terraform`
|
|
||||||
> (pin `1.9.*`), `checkov` (pin `>=3.2,<4`), `python3` + `boto3` +
|
|
||||||
> `jsonschema`.
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# 1. Bootstrap the AWS state backend + runner IAM user (one-time, idempotent)
|
# 1. Bootstrap the AWS state backend + runner IAM user (one-time, idempotent)
|
||||||
# (requires the bootstrap root key in env — skip if the state bucket +
|
# (requires the bootstrap root key in env — skip if the state bucket +
|
||||||
# nova-spike-runner already exist)
|
# acdl-spike-runner already exist)
|
||||||
ACDL_BOOTSTRAP_AWS_ACCESS_KEY_ID=... ACDL_BOOTSTRAP_AWS_SECRET_ACCESS_KEY=... \
|
ACDL_BOOTSTRAP_AWS_ACCESS_KEY_ID=... ACDL_BOOTSTRAP_AWS_SECRET_ACCESS_KEY=... \
|
||||||
python3 terraform/bootstrap/create_state_backend.py
|
python3 terraform/bootstrap/create_state_backend.py
|
||||||
ACDL_BOOTSTRAP_AWS_ACCESS_KEY_ID=... ACDL_BOOTSTRAP_AWS_SECRET_ACCESS_KEY=... \
|
ACDL_BOOTSTRAP_AWS_ACCESS_KEY_ID=... ACDL_BOOTSTRAP_AWS_SECRET_ACCESS_KEY=... \
|
||||||
@@ -194,6 +166,26 @@ bash scripts/run_platform.sh --plan-only contracts/static-assets.yaml
|
|||||||
bash scripts/run_platform.sh --quiet contracts/static-assets.yaml
|
bash scripts/run_platform.sh --quiet contracts/static-assets.yaml
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Test the platform (offline, no AWS required)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Install test dependencies
|
||||||
|
pip install -r requirements-test.txt
|
||||||
|
|
||||||
|
# Run the test suite (all offline — uses moto for DynamoDB mocking)
|
||||||
|
python3 -m pytest tests/ -v
|
||||||
|
|
||||||
|
# Run the platform in check-only mode (offline — no AWS, no policy checks,
|
||||||
|
# no outbox). Uses the default sample contract (contracts/static-assets.yaml)
|
||||||
|
# and the sample dev environment (core/environments/dev.json).
|
||||||
|
bash scripts/run_platform.sh --check-only
|
||||||
|
# Expected: "=== PLATFORM CHECK OK ==="
|
||||||
|
|
||||||
|
# Reproduce the full CI pipeline locally (lint -> test -> check-only)
|
||||||
|
bash scripts/run_ci.sh
|
||||||
|
# Expected: "=== CI PIPELINE OK ==="
|
||||||
|
```
|
||||||
|
|
||||||
### CI/CD pipelines
|
### CI/CD pipelines
|
||||||
|
|
||||||
The CI/CD pipeline is defined by a **central pipeline contract** — a
|
The CI/CD pipeline is defined by a **central pipeline contract** — a
|
||||||
@@ -219,9 +211,23 @@ bash scripts/run_ci.sh --quiet # suppress per-stage banners
|
|||||||
|
|
||||||
### Reusable deploy workflow
|
### Reusable deploy workflow
|
||||||
|
|
||||||
Consumer repos invoke the deploy pipeline via `.github/workflows/deploy.yml`
|
The deployment pipeline is defined by a **central deployment pipeline
|
||||||
(a reusable GitHub Actions workflow, versioned tag `nova/.github/workflows/deploy.yml@v1.19`).
|
contract** (`pipelines/contract.yml`, validated against
|
||||||
See the [Consumer guide](docs/consumer-guide.md) for the end-to-end happy path.
|
`schemas/deploy-pipeline.schema.json`) and exposed to consumer repos as a
|
||||||
|
**reusable workflow**:
|
||||||
|
|
||||||
|
- `.github/workflows/deploy.yml` — GitHub Actions (production)
|
||||||
|
|
||||||
|
The workflow implements the same stages as `pipelines/contract.yml`
|
||||||
|
(validate-contract → resolve-stack → security checks → infrastructure plan
|
||||||
|
→ policy checks → confidence → evidence event → apply). A consumer repo
|
||||||
|
invokes the reusable workflow via a **versioned tag** (floating MAJOR +
|
||||||
|
MINOR, e.g. `acdl/.github/workflows/deploy.yml@v1.13`). The workflow checks
|
||||||
|
out the consumer repo, then checks out the ACDL platform repo into the
|
||||||
|
runner workspace, and runs `scripts/run_platform.sh` against the consumer's
|
||||||
|
contract — the consumer never clones the platform repo or invokes its
|
||||||
|
scripts locally. See the [Consumer guide](docs/consumer-guide.md) for the
|
||||||
|
end-to-end happy path.
|
||||||
|
|
||||||
### Output streaming (run_platform.sh)
|
### Output streaming (run_platform.sh)
|
||||||
|
|
||||||
@@ -241,7 +247,7 @@ backwards-compatible log-only mode.
|
|||||||
## Consumer guide
|
## Consumer guide
|
||||||
|
|
||||||
A step-by-step guide for a consumer to create their pipeline and define a
|
A step-by-step guide for a consumer to create their pipeline and define a
|
||||||
contract that deploys any Nova module to AWS is at
|
contract that deploys any ACDL module to AWS is at
|
||||||
[`docs/consumer-guide.md`](docs/consumer-guide.md). The guide is generic
|
[`docs/consumer-guide.md`](docs/consumer-guide.md). The guide is generic
|
||||||
across all modules; `static-assets` is the worked example.
|
across all modules; `static-assets` is the worked example.
|
||||||
|
|
||||||
@@ -277,8 +283,8 @@ no static credentials in repo secrets.
|
|||||||
`repo:org/consumer-repo:ref:refs/heads/main`) binds the role's trust
|
`repo:org/consumer-repo:ref:refs/heads/main`) binds the role's trust
|
||||||
policy to the exact consumer repo + branch that invoked the workflow.
|
policy to the exact consumer repo + branch that invoked the workflow.
|
||||||
- **Resource-creation attributes** — every resource the pipeline creates
|
- **Resource-creation attributes** — every resource the pipeline creates
|
||||||
is tagged with `nova:owner=<consumer-repo>` and
|
is tagged with `acdl:owner=<consumer-repo>` and
|
||||||
`nova:contract=<contract-id>`. The session policy grants
|
`acdl:contract=<contract-id>`. The session policy grants
|
||||||
view/update/delete **only on resources whose tags match the calling
|
view/update/delete **only on resources whose tags match the calling
|
||||||
repo**.
|
repo**.
|
||||||
|
|
||||||
@@ -296,6 +302,12 @@ documented alternative:
|
|||||||
runs, or in **`.env.secrets`** (gitignored, chmod 600) for local testing.
|
runs, or in **`.env.secrets`** (gitignored, chmod 600) for local testing.
|
||||||
- The platform rotates platform-runner keys on a **daily cadence** —
|
- The platform rotates platform-runner keys on a **daily cadence** —
|
||||||
rotation is not the consumer's burden in the platform-runner path.
|
rotation is not the consumer's burden in the platform-runner path.
|
||||||
|
- **When `.env.secrets` is used locally**, rotating the key **out of band is
|
||||||
|
the consumer's responsibility**. The platform guarantees daily rotation
|
||||||
|
for platform-runner runs; it does not guarantee rotation for
|
||||||
|
locally-held copies. The consumer must rotate a local key via
|
||||||
|
`scripts/rotate_spike_key.sh` (or equivalent) on their own cadence.
|
||||||
|
|
||||||
No long-lived credential is permitted persistently — the platform-runner
|
No long-lived credential is permitted persistently — the platform-runner
|
||||||
key's useful lifetime is one workflow run, and the local alternative is
|
key's useful lifetime is one workflow run, and the local alternative is
|
||||||
rotated at least daily (platform-runner) or out of band (local).
|
rotated at least daily (platform-runner) or out of band (local).
|
||||||
+8
-83
@@ -1,4 +1,4 @@
|
|||||||
# Nova Adapters
|
# ACDL Adapters
|
||||||
|
|
||||||
## Overview
|
## Overview
|
||||||
|
|
||||||
@@ -12,62 +12,15 @@ Adapters translate the engine-agnostic Target Stack IR to engine-specific format
|
|||||||
| Checkov adapter | `adapters/terraform/policy/checkov_adapter.py` | Checkov JSON | `PolicyCheckResult` records | Translates Checkov results |
|
| Checkov adapter | `adapters/terraform/policy/checkov_adapter.py` | Checkov JSON | `PolicyCheckResult` records | Translates Checkov results |
|
||||||
| Wiz adapter | `adapters/wiz/wiz_adapter.py` | Wiz API issues JSON | `PolicyCheckResult` records | Translates Wiz security findings |
|
| Wiz adapter | `adapters/wiz/wiz_adapter.py` | Wiz API issues JSON | `PolicyCheckResult` records | Translates Wiz security findings |
|
||||||
| Kyverno adapter | `adapters/kyverno/kyverno_adapter.py` | Kyverno PolicyReport JSON | `PolicyCheckResult` records | K8s-native policy translation |
|
| Kyverno adapter | `adapters/kyverno/kyverno_adapter.py` | Kyverno PolicyReport JSON | `PolicyCheckResult` records | K8s-native policy translation |
|
||||||
| kyverno-json engine | `adapters/kyverno-json/kyverno_json_engine.py` | Any JSON/YAML payload | `PolicyCheckResult` records | **v1.25 primary policy engine** (swappable via `PolicyEngine` protocol) |
|
|
||||||
|
|
||||||
## Policy Engine Protocol (v1.25)
|
|
||||||
|
|
||||||
The `core/policy_engine.py` module defines the **swap boundary** between
|
|
||||||
Nova and its policy engines. A `PolicyEngine` Python Protocol (PEP 544)
|
|
||||||
with three members (`name`, `is_configured()`, `evaluate()`) is the
|
|
||||||
contract; a `PolicyEngineRegistry` selects the active engine from
|
|
||||||
`config.json`'s `policy.engine` key. The confidence signal and pipeline
|
|
||||||
never import an engine directly — they go through the registry.
|
|
||||||
|
|
||||||
**Implementations:**
|
|
||||||
- `KyvernoJsonEngine` (`adapters/kyverno-json/`) — shells to the `kj`
|
|
||||||
CLI; the v1.25 default.
|
|
||||||
- `NullEngine` (`core/policy_engine.py`) — fallback when the `policy`
|
|
||||||
key is absent (emits `SKIPPED`).
|
|
||||||
- Future: `OpaEngine` — implements the same protocol, shells to
|
|
||||||
`opa eval`. The OPA-equivalent surface is documented in
|
|
||||||
`.ciagent/RESEARCH.md` §4.2.
|
|
||||||
|
|
||||||
**How to add a new engine:**
|
|
||||||
1. Create `adapters/<name>/<name>_engine.py` implementing the
|
|
||||||
`PolicyEngine` protocol (`name`, `is_configured()`, `evaluate()`).
|
|
||||||
2. `evaluate()` returns `list[dict]` where each dict conforms to
|
|
||||||
`schemas/policy_check_result.schema.json`.
|
|
||||||
3. Register the engine in `core/policy_engine.py`'s `_autoload_*`
|
|
||||||
function (or call `register(name, factory)` at startup).
|
|
||||||
4. Set `config.json.policy.engine` to the engine's `name`.
|
|
||||||
5. Add the engine to the `engine` enum in
|
|
||||||
`schemas/policy_check_result.schema.json` if it needs a distinct
|
|
||||||
enum value (v1.25 reuses `"kyverno"` — see D-116).
|
|
||||||
|
|
||||||
## How to Write an Adapter
|
## How to Write an Adapter
|
||||||
|
|
||||||
### Terraform Adapter Extension (stateless assembler — v1.11 rewrite)
|
### Terraform Adapter Extension
|
||||||
|
|
||||||
> The adapter owns **no module content**. There is no `TYPE_MAP`, no
|
1. Add a stack type → Terraform type mapping to `TYPE_MAP`.
|
||||||
> `INPUT_MAP`, no `OUTPUT_MAP`, and no per-type branch logic (all deleted
|
2. Add non-identity input mappings to `INPUT_MAP`.
|
||||||
> in the v1.11 rewrite — the 918-line monolith collapsed to a ~80-line
|
3. Add non-identity output mappings to `OUTPUT_MAP`.
|
||||||
> assembler). Engine-specific shape lives in each L1 module's own
|
4. Add a specialized `_emit_resource` branch if the resource needs nested blocks (e.g. inline policies, rule sets).
|
||||||
> `terraform/` dir (`versions.tf`/`variables.tf`/`locals.tf`/`main.tf`/
|
|
||||||
> `outputs.tf`); the adapter only assembles them.
|
|
||||||
|
|
||||||
To extend the Terraform adapter, **do not edit the adapter** — instead:
|
|
||||||
|
|
||||||
1. Add an L1 module with a real `terraform/` dir (owning its resource
|
|
||||||
shape, nested HCL blocks, and defaults).
|
|
||||||
2. Register it in `modules/registry.json` under the module name with its
|
|
||||||
`terraform_dir` path. The adapter reads `registry.json` to find each
|
|
||||||
module's directory.
|
|
||||||
3. The adapter emits `module "<rid>" { source = "<path>" }` blocks at
|
|
||||||
the root, with resolved inputs + wired `ref:` refs between modules.
|
|
||||||
No type-specific translation lives in the adapter.
|
|
||||||
|
|
||||||
> If you find yourself reaching for a "TYPE_MAP"-style constant, the L1
|
|
||||||
> module is missing a piece — fix the module, not the adapter.
|
|
||||||
|
|
||||||
### Policy Adapter Pattern
|
### Policy Adapter Pattern
|
||||||
|
|
||||||
@@ -92,7 +45,7 @@ To extend the Terraform adapter, **do not edit the adapter** — instead:
|
|||||||
|
|
||||||
## How to Test Adapters
|
## How to Test Adapters
|
||||||
|
|
||||||
- `tests/test_adapter.py` — Terraform adapter (stateless assembly: registry read, `module "<rid>" { source }` emission, `ref:` wiring, outputs). No `TYPE_MAP`/`INPUT_MAP` tests — the adapter owns no type mappings.
|
- `tests/test_adapter.py` — Terraform adapter (`TYPE_MAP`, resource emission, refs, outputs).
|
||||||
- `tests/test_checkov_adapter.py` — Checkov adapter.
|
- `tests/test_checkov_adapter.py` — Checkov adapter.
|
||||||
- `tests/test_wiz_adapter.py` — Wiz adapter.
|
- `tests/test_wiz_adapter.py` — Wiz adapter.
|
||||||
- `tests/test_kyverno_adapter.py` — Kyverno adapter.
|
- `tests/test_kyverno_adapter.py` — Kyverno adapter.
|
||||||
@@ -109,32 +62,4 @@ To extend the Terraform adapter, **do not edit the adapter** — instead:
|
|||||||
3. Add the adapter's engine name to the `engine` enum in `schemas/policy_check_result.schema.json` if it is a policy adapter.
|
3. Add the adapter's engine name to the `engine` enum in `schemas/policy_check_result.schema.json` if it is a policy adapter.
|
||||||
4. Write a test (`tests/test_<name>_adapter.py`) plus a fixture (`tests/fixtures/<name>_fixture.json`).
|
4. Write a test (`tests/test_<name>_adapter.py`) plus a fixture (`tests/fixtures/<name>_fixture.json`).
|
||||||
5. Add it to `scripts/run_platform.sh` if it is invoked at runtime.
|
5. Add it to `scripts/run_platform.sh` if it is invoked at runtime.
|
||||||
6. Update this README.
|
6. Update this README.
|
||||||
|
|
||||||
## Consumers
|
|
||||||
|
|
||||||
The Terraform adapter compiles contract IR for consumer estates. The
|
|
||||||
first real consumer estate is now live:
|
|
||||||
|
|
||||||
| Consumer | Version | Environment | Account | Forge / Adapter | Status |
|
|
||||||
| --- | --- | --- | --- | --- | --- |
|
|
||||||
| `nova-blockchain-exchange` | v0.2 | dev | `581513795199` | inline adapter (see note below) | **live** (pilot apply `blkex-pilot-apply-v0.2`, 2026-08-19) |
|
|
||||||
|
|
||||||
### Forge adapter note (SPEC §10 Q1)
|
|
||||||
|
|
||||||
Forge Actions (the consumer's forge runtime) does **not** support
|
|
||||||
cross-repo `uses:` references — the forge rejects
|
|
||||||
`uses: <owner>/<repo>/.github/workflows/<file>@<ref>` with
|
|
||||||
`expected format {owner}/{repo}/.{git_platform}/workflows/{filename}@{ref}`.
|
|
||||||
The consumer (`nova-blockchain-exchange`) therefore uses an **inline
|
|
||||||
adapter** in its `deploy.yml`: the workflow does `actions/checkout@v4`
|
|
||||||
on the consumer, then `actions/checkout@v4` `acdl/acdl` @ `ref: v1.25`
|
|
||||||
into `platform/`, and runs `bash platform/scripts/run_platform.sh ...`
|
|
||||||
directly — no `uses:` indirection.
|
|
||||||
|
|
||||||
The platform's own `.github/workflows/deploy.yml` (this repo) stays as
|
|
||||||
the **GitHub Actions reference implementation** — the reusable
|
|
||||||
`workflow_call` workflow used by GitHub-hosted consumers. The two
|
|
||||||
files share the same contract shape; the only declared difference is
|
|
||||||
the forge/runtime, not the stages or commands. See
|
|
||||||
`.ciagent/ARCHITECTURE.md` §12.8 for the live pilot-estate wiring.
|
|
||||||
@@ -1,103 +0,0 @@
|
|||||||
# kyverno-json Engine Adapter (v1.25)
|
|
||||||
|
|
||||||
The `kyverno-json` engine is Nova's **primary compliance/policy tool**
|
|
||||||
(v1.25), implemented behind the swappable `PolicyEngine` protocol so
|
|
||||||
OPA (or any other engine) can replace it one day.
|
|
||||||
|
|
||||||
## What kyverno-json is
|
|
||||||
|
|
||||||
[kyverno-json](https://github.com/kyverno/kyverno-json) is a standalone
|
|
||||||
Go binary from the Kyverno project — a **separate runtime** from the
|
|
||||||
K8s Kyverno admission controller. It applies Kyverno `ValidatingPolicy`
|
|
||||||
resources to **any** JSON or YAML payload file via the `kj scan` CLI.
|
|
||||||
Unlike the K8s Kyverno adapter (`adapters/kyverno/`), which only
|
|
||||||
speaks to K8s manifests, kyverno-json evaluates consumer contracts,
|
|
||||||
resolved Stack IR, terraform plan JSON, and even the merged PCR list
|
|
||||||
itself (meta-policies).
|
|
||||||
|
|
||||||
## Install
|
|
||||||
|
|
||||||
```bash
|
|
||||||
bash scripts/install-kyverno-json.sh
|
|
||||||
# or directly:
|
|
||||||
go install github.com/kyverno/kyverno-json/cmd/kj@latest
|
|
||||||
kj version
|
|
||||||
```
|
|
||||||
|
|
||||||
The platform functions without the binary — `is_configured()` returns
|
|
||||||
`False` when `which kj` is absent → `evaluate()` returns a single
|
|
||||||
`SKIPPED` PCR (`KJ_ENGINE_NOT_CONFIGURED`). The confidence signal
|
|
||||||
proceeds with a neutral `policy` input (D-120 graceful degradation).
|
|
||||||
|
|
||||||
## Policy directory layout
|
|
||||||
|
|
||||||
```
|
|
||||||
adapters/kyverno-json/policies/
|
|
||||||
├── _smoke.json # round-trip smoke test
|
|
||||||
├── contract/ # consumer contract JSON policies
|
|
||||||
│ ├── require-id-pattern.json
|
|
||||||
│ ├── require-env-in-enum.json
|
|
||||||
│ ├── require-infrastructure-min-1.json
|
|
||||||
│ └── forbid-unknown-fields.json
|
|
||||||
├── stack-ir/ # resolved Stack IR policies
|
|
||||||
│ ├── require-tagging-standard.json
|
|
||||||
│ ├── forbid-public-ingress.json
|
|
||||||
│ └── require-encryption-by-default.json
|
|
||||||
├── plan-json/ # terraform show -json policies
|
|
||||||
│ ├── forbid-plaintext-secrets.json
|
|
||||||
│ ├── forbid-iam-wildcard.json
|
|
||||||
│ └── require-kms-reference.json
|
|
||||||
├── meta/ # policies over the merged PCR list
|
|
||||||
│ ├── block-on-any-critical.json
|
|
||||||
│ └── tagging-rules-agree.json
|
|
||||||
└── regression/ # capability-inventory policies
|
|
||||||
├── cap-013-adapter-dedup.json
|
|
||||||
├── cap-023-metrics-collector.json
|
|
||||||
└── cap-024-deck-structure.json
|
|
||||||
```
|
|
||||||
|
|
||||||
## The four policy categories
|
|
||||||
|
|
||||||
1. **contract/** — over the consumer contract JSON (pre-resolve).
|
|
||||||
2. **stack-ir/** — over the resolved Target Stack IR (post-resolve).
|
|
||||||
3. **plan-json/** — over `terraform show -json` output (pipeline Step 5b).
|
|
||||||
4. **meta/** — over the merged `list[PolicyCheckResult]` (meta-policies).
|
|
||||||
5. **regression/** — over the capability-inventory JSON (declarative
|
|
||||||
mirrors of `core/regression_verify.py`).
|
|
||||||
|
|
||||||
## Severity convention
|
|
||||||
|
|
||||||
kyverno-json does not natively assign severities. Each Nova policy
|
|
||||||
declares its severity via a `metadata.annotations` field:
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
metadata:
|
|
||||||
annotations:
|
|
||||||
nova.cloudinit.dev/severity: high
|
|
||||||
```
|
|
||||||
|
|
||||||
Valid values: `critical`, `high`, `medium`, `low`, `info` (default
|
|
||||||
when absent).
|
|
||||||
|
|
||||||
## 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`).
|
|
||||||
|
|
||||||
## Schema path
|
|
||||||
|
|
||||||
The output records validate against
|
|
||||||
[`schemas/policy_check_result.schema.json`](../../schemas/policy_check_result.schema.json)
|
|
||||||
(`engine: "kyverno"` is in the enum). The confidence signal consumes
|
|
||||||
the merged PCR list engine-agnostically.
|
|
||||||
|
|
||||||
## Swap boundary
|
|
||||||
|
|
||||||
The `PolicyEngine` protocol (`core/policy_engine.py`) is the swap
|
|
||||||
boundary. The OPA-equivalent surface is documented in
|
|
||||||
`.ciagent/RESEARCH.md` §4.2 — a future `OpaEngine` implements the same
|
|
||||||
protocol without touching the confidence signal, the PCR schema, or
|
|
||||||
the pipeline.
|
|
||||||
@@ -1,27 +0,0 @@
|
|||||||
"""Nova kyverno-json adapter package (v1.25, REQ-294).
|
|
||||||
|
|
||||||
The directory name ``kyverno-json`` has a hyphen, so it is not a valid
|
|
||||||
Python package name and cannot be imported via ``import
|
|
||||||
adapters.kyverno-json``. The ``PolicyEngineRegistry`` loads the engine
|
|
||||||
by file path (``importlib.util.spec_from_file_location``). This
|
|
||||||
``__init__`` is a convenience for direct-script use and for ``pip
|
|
||||||
install -e .`` style discovery if the package is ever renamed.
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
def _load_engine():
|
|
||||||
import importlib.util
|
|
||||||
import os
|
|
||||||
engine_path = os.path.join(os.path.dirname(os.path.abspath(__file__)),
|
|
||||||
"kyverno_json_engine.py")
|
|
||||||
spec = importlib.util.spec_from_file_location("kyverno_json_engine", engine_path)
|
|
||||||
if spec is None or spec.loader is None:
|
|
||||||
raise ImportError(f"could not load {engine_path}")
|
|
||||||
mod = importlib.util.module_from_spec(spec)
|
|
||||||
spec.loader.exec_module(mod)
|
|
||||||
return mod.KyvernoJsonEngine
|
|
||||||
|
|
||||||
|
|
||||||
KyvernoJsonEngine = _load_engine()
|
|
||||||
|
|
||||||
__all__ = ["KyvernoJsonEngine"]
|
|
||||||
@@ -1,470 +0,0 @@
|
|||||||
"""Nova KyvernoJsonEngine (REQ-293, v1.25; fixed v1.26 P3 W0.5).
|
|
||||||
|
|
||||||
Implements the ``PolicyEngine`` protocol (``core/policy_engine.py``)
|
|
||||||
by shelling to the ``kj`` CLI (``kyverno-json``). Translates native
|
|
||||||
kyverno-json scan output to Nova ``PolicyCheckResult`` dicts
|
|
||||||
(``schemas/policy_check_result.schema.json``).
|
|
||||||
|
|
||||||
Engine enum reuse (D-116): records carry ``engine: "kyverno"`` (no new
|
|
||||||
enum value). The ``ruleId`` is prefixed ``KJ_<policy_name>`` to
|
|
||||||
distinguish from the K8s Kyverno adapter's ``KYVERNO_`` prefix.
|
|
||||||
|
|
||||||
Severity (RESEARCH §2.6, G-Q10a): kyverno-json does not natively assign
|
|
||||||
severities. Each Nova policy declares its severity via a
|
|
||||||
``metadata.annotations["nova.cloudinit.dev/severity"]`` field. The
|
|
||||||
engine reads this annotation from the loaded policy file (not from the
|
|
||||||
scan result — the result carries the policy spec but the annotation is
|
|
||||||
read here from disk) and applies it to every result that policy
|
|
||||||
produces. Default when absent: ``"info"``.
|
|
||||||
|
|
||||||
Graceful degradation (D-120): ``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.
|
|
||||||
|
|
||||||
Defensive parsing: any kyverno-json output that doesn't match the
|
|
||||||
expected shape produces an ``error`` PCR, never an exception. The
|
|
||||||
engine is read-only against a local policy dir + a temp payload file.
|
|
||||||
|
|
||||||
v1.26 P3 W0.5 fix — three substrate bugs uncovered once ``kj`` was
|
|
||||||
actually installed (the v1.25 test suite ``pytest.skip``-masked them):
|
|
||||||
|
|
||||||
1. **``.json`` policy files are not loaded by ``kj`` v0.0.3.** The
|
|
||||||
upstream policy loader (``pkg/policy/load.go``) uses
|
|
||||||
``fileinfo.IsYaml()`` which only matches ``.yaml``/``.yml``
|
|
||||||
extensions — ``.json`` files are silently skipped, yielding
|
|
||||||
``evaluating N resources against 0 policies``. Nova policies are
|
|
||||||
authored as ``.json`` (the ``TestPolicyFilesExist`` tests assert the
|
|
||||||
``.json`` filenames). Fix: ``evaluate()`` materializes a temp policy
|
|
||||||
dir that mirrors the source tree with every ``.json`` policy copied
|
|
||||||
to a ``.yaml`` twin (JSON is a valid YAML subset — verified against
|
|
||||||
``kj`` v0.0.3). The source ``.json`` files remain untouched.
|
|
||||||
|
|
||||||
2. **Bare-list output format.** ``kj scan --output json`` emits a bare
|
|
||||||
JSON list at the top level (NOT ``{"results": [...]}``). Each entry
|
|
||||||
has ``resource`` (the evaluated payload) + ``results`` (list of
|
|
||||||
per-policy result objects, each carrying ``policy.metadata.name``,
|
|
||||||
``rules[]`` with ``rule.name``, ``violations[]`` (present on fail),
|
|
||||||
``error`` (string, present on policy-evaluation error)). The v1.25
|
|
||||||
``_translate`` did ``out.get("results", [])`` on a dict — but
|
|
||||||
``out`` is a list → returned ``[]`` → emitted a single
|
|
||||||
``KJ_NO_RESULTS`` pass PCR. **This is why all failing fixtures showed
|
|
||||||
0 fails.** Fix: ``_translate`` handles list (v0.0.3) and dict
|
|
||||||
(future-proof) shapes.
|
|
||||||
|
|
||||||
3. **``validate`` wrapper + check syntax.** Documented in the policy
|
|
||||||
files themselves (see the W0.5 policy edits). The engine itself does
|
|
||||||
not enforce policy shape — it only translates ``kj`` output — so
|
|
||||||
this fix lives in the policy ``.json`` files.
|
|
||||||
"""
|
|
||||||
|
|
||||||
import datetime
|
|
||||||
import json
|
|
||||||
import os
|
|
||||||
import shutil
|
|
||||||
import subprocess
|
|
||||||
import sys
|
|
||||||
import tempfile
|
|
||||||
from pathlib import Path
|
|
||||||
from typing import Any, Union
|
|
||||||
|
|
||||||
import yaml
|
|
||||||
|
|
||||||
|
|
||||||
Payload = Union[dict, list, str]
|
|
||||||
|
|
||||||
SEVERITY_DEFAULT = "info"
|
|
||||||
SEVERITY_ANNOTATION = "nova.cloudinit.dev/severity"
|
|
||||||
|
|
||||||
RESULT_MAP = {
|
|
||||||
"pass": "pass",
|
|
||||||
"fail": "fail",
|
|
||||||
"error": "error",
|
|
||||||
"skip": "skipped",
|
|
||||||
"skipped": "skipped",
|
|
||||||
"warn": "skipped",
|
|
||||||
"warning": "skipped",
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
def _iso8601_now() -> str:
|
|
||||||
return datetime.datetime.now(datetime.timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ")
|
|
||||||
|
|
||||||
|
|
||||||
def _which_kj() -> str | None:
|
|
||||||
"""Return the path to ``kj`` if on PATH, else ``None``."""
|
|
||||||
return shutil.which("kj")
|
|
||||||
|
|
||||||
|
|
||||||
def _load_policy_severities(policy_dir: Path) -> dict[str, str]:
|
|
||||||
"""Load each ``.json``/``.yaml``/``.yml`` policy in ``policy_dir``
|
|
||||||
(non-recursive) and return ``{policy_name: severity}``.
|
|
||||||
|
|
||||||
kyverno-json policies are Kubernetes-style ``ValidatingPolicy``
|
|
||||||
resources. The severity is read from
|
|
||||||
``metadata.annotations["nova.cloudinit.dev/severity"]``. Policies
|
|
||||||
in subdirectories (e.g. ``contract/``, ``stack-ir/``) are loaded
|
|
||||||
when the caller passes that subdirectory as ``policy_dir``.
|
|
||||||
"""
|
|
||||||
severities: dict[str, str] = {}
|
|
||||||
if not policy_dir.is_dir():
|
|
||||||
return severities
|
|
||||||
for entry in sorted(os.listdir(policy_dir)):
|
|
||||||
if entry.startswith("_") or entry.startswith("."):
|
|
||||||
continue
|
|
||||||
full = policy_dir / entry
|
|
||||||
if not full.is_file():
|
|
||||||
continue
|
|
||||||
if entry.endswith((".json", ".yaml", ".yml")):
|
|
||||||
try:
|
|
||||||
with open(full, "r", encoding="utf-8") as fh:
|
|
||||||
doc = yaml.safe_load(fh)
|
|
||||||
if not isinstance(doc, dict):
|
|
||||||
continue
|
|
||||||
name = doc.get("metadata", {}).get("name") or entry.rsplit(".", 1)[0]
|
|
||||||
ann = doc.get("metadata", {}).get("annotations", {}) or {}
|
|
||||||
sev = ann.get(SEVERITY_ANNOTATION, SEVERITY_DEFAULT)
|
|
||||||
severities[name] = str(sev).lower()
|
|
||||||
except Exception:
|
|
||||||
continue
|
|
||||||
return severities
|
|
||||||
|
|
||||||
|
|
||||||
def _materialize_yaml_policy_dir(src: Path) -> tuple[Path, bool]:
|
|
||||||
"""Mirror ``src`` (recursively) into a temp dir, copying every
|
|
||||||
``.json`` policy to a ``.yaml`` twin and copying ``.yaml``/``.yml``
|
|
||||||
files verbatim. Returns ``(temp_dir, created)``.
|
|
||||||
|
|
||||||
``kj`` v0.0.3's policy loader (``pkg/policy/load.go``) only matches
|
|
||||||
``.yaml``/``.yml`` extensions — ``.json`` files are silently
|
|
||||||
skipped. Nova policies are authored as ``.json`` (the
|
|
||||||
``TestPolicyFilesExist`` tests assert the ``.json`` filenames, so
|
|
||||||
they cannot be renamed in-place). JSON is a valid YAML subset, so
|
|
||||||
a byte-for-byte copy with a ``.yaml`` extension loads cleanly.
|
|
||||||
|
|
||||||
``created`` is ``False`` when ``src`` contains no policy files at
|
|
||||||
all (empty dir) — in that case the temp dir is still returned (the
|
|
||||||
caller invokes ``kj`` against it and gets the no-results path).
|
|
||||||
"""
|
|
||||||
tmp = Path(tempfile.mkdtemp(prefix="nova-kj-pol-"))
|
|
||||||
any_policy = False
|
|
||||||
if src.is_dir():
|
|
||||||
for root, _dirs, files in os.walk(src):
|
|
||||||
rel = Path(root).relative_to(src)
|
|
||||||
dest_root = tmp / rel
|
|
||||||
dest_root.mkdir(parents=True, exist_ok=True)
|
|
||||||
for fn in files:
|
|
||||||
if fn.startswith(".") or fn.startswith("_"):
|
|
||||||
continue
|
|
||||||
src_file = Path(root) / fn
|
|
||||||
if fn.endswith(".json"):
|
|
||||||
dest_file = dest_root / (fn.rsplit(".", 1)[0] + ".yaml")
|
|
||||||
shutil.copy2(src_file, dest_file)
|
|
||||||
any_policy = True
|
|
||||||
elif fn.endswith((".yaml", ".yml")):
|
|
||||||
shutil.copy2(src_file, dest_root / fn)
|
|
||||||
any_policy = True
|
|
||||||
return tmp, any_policy
|
|
||||||
|
|
||||||
|
|
||||||
def _skipped_not_configured(contract_id: str) -> dict:
|
|
||||||
return {
|
|
||||||
"contractId": contract_id,
|
|
||||||
"evaluatedAt": _iso8601_now(),
|
|
||||||
"engine": "kyverno",
|
|
||||||
"ruleId": "KJ_ENGINE_NOT_CONFIGURED",
|
|
||||||
"severity": "info",
|
|
||||||
"result": "skipped",
|
|
||||||
"message": (
|
|
||||||
"kyverno-json engine not configured — `which kj` returned no path. "
|
|
||||||
"Install via scripts/install-kyverno-json.sh. The platform proceeds "
|
|
||||||
"with a neutral SKIPPED policy input (is_configured() guard, D-120)."
|
|
||||||
),
|
|
||||||
"evidence": {},
|
|
||||||
"resourceRef": "",
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
def _error_pcr(contract_id: str, message: str) -> dict:
|
|
||||||
return {
|
|
||||||
"contractId": contract_id,
|
|
||||||
"evaluatedAt": _iso8601_now(),
|
|
||||||
"engine": "kyverno",
|
|
||||||
"ruleId": "KJ_ENGINE_ERROR",
|
|
||||||
"severity": "info",
|
|
||||||
"result": "error",
|
|
||||||
"message": message,
|
|
||||||
"evidence": {},
|
|
||||||
"resourceRef": "",
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
def _no_results_pass(contract_id: str) -> dict:
|
|
||||||
"""No result entries — emit a single pass PCR so the confidence
|
|
||||||
signal's policy input is non-empty (a non-empty list of passes →
|
|
||||||
score 1.0)."""
|
|
||||||
return {
|
|
||||||
"contractId": contract_id,
|
|
||||||
"evaluatedAt": _iso8601_now(),
|
|
||||||
"engine": "kyverno",
|
|
||||||
"ruleId": "KJ_NO_RESULTS",
|
|
||||||
"severity": "info",
|
|
||||||
"result": "pass",
|
|
||||||
"message": "kyverno-json scan produced no result entries (all policies passed or no match).",
|
|
||||||
"evidence": {},
|
|
||||||
"resourceRef": "",
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
class KyvernoJsonEngine:
|
|
||||||
"""``PolicyEngine`` impl that shells to the ``kj`` CLI."""
|
|
||||||
|
|
||||||
name = "kyverno-json"
|
|
||||||
|
|
||||||
def is_configured(self) -> bool:
|
|
||||||
return _which_kj() is not None
|
|
||||||
|
|
||||||
def evaluate(self, payload: Payload, policy_dir: Path,
|
|
||||||
contract_id: str) -> list[dict]:
|
|
||||||
if not self.is_configured():
|
|
||||||
return [_skipped_not_configured(contract_id)]
|
|
||||||
kj = _which_kj()
|
|
||||||
policy_dir = Path(policy_dir)
|
|
||||||
if not policy_dir.is_dir():
|
|
||||||
return [_error_pcr(
|
|
||||||
contract_id,
|
|
||||||
f"kyverno-json policy dir not found: {policy_dir}",
|
|
||||||
)]
|
|
||||||
severities = _load_policy_severities(policy_dir)
|
|
||||||
# kj v0.0.3 only loads .yaml/.yml policy files. Mirror the tree
|
|
||||||
# to a temp dir with .json policies copied to .yaml twins.
|
|
||||||
yaml_dir, _any_policy = _materialize_yaml_policy_dir(policy_dir)
|
|
||||||
# Write payload to temp file (kj scan --payload expects a file path).
|
|
||||||
payload_tmp = tempfile.NamedTemporaryFile(
|
|
||||||
mode="w", suffix=".json", delete=False, encoding="utf-8"
|
|
||||||
)
|
|
||||||
try:
|
|
||||||
json.dump(payload, payload_tmp)
|
|
||||||
payload_tmp.flush()
|
|
||||||
payload_tmp.close()
|
|
||||||
cmd = [
|
|
||||||
kj, "scan",
|
|
||||||
"--policy", str(yaml_dir),
|
|
||||||
"--payload", payload_tmp.name,
|
|
||||||
"--output", "json",
|
|
||||||
]
|
|
||||||
try:
|
|
||||||
proc = subprocess.run(
|
|
||||||
cmd, capture_output=True, text=True, timeout=60,
|
|
||||||
)
|
|
||||||
except subprocess.TimeoutExpired:
|
|
||||||
return [_error_pcr(contract_id, "kyverno-json scan timed out (60s)")]
|
|
||||||
if proc.returncode not in (0, 1):
|
|
||||||
return [_error_pcr(
|
|
||||||
contract_id,
|
|
||||||
f"kyverno-json scan exited {proc.returncode}: {proc.stderr[:200]}",
|
|
||||||
)]
|
|
||||||
try:
|
|
||||||
out = json.loads(proc.stdout) if proc.stdout.strip() else []
|
|
||||||
except json.JSONDecodeError as e:
|
|
||||||
return [_error_pcr(
|
|
||||||
contract_id,
|
|
||||||
f"kyverno-json output not JSON: {e}",
|
|
||||||
)]
|
|
||||||
return self._translate(out, contract_id, severities)
|
|
||||||
finally:
|
|
||||||
try:
|
|
||||||
os.unlink(payload_tmp.name)
|
|
||||||
except OSError:
|
|
||||||
pass
|
|
||||||
shutil.rmtree(yaml_dir, ignore_errors=True)
|
|
||||||
|
|
||||||
def _translate(self, out: Any, contract_id: str,
|
|
||||||
severities: dict[str, str]) -> list[dict]:
|
|
||||||
# kj v0.0.3 emits a BARE JSON LIST at the top level: each entry
|
|
||||||
# has `resource` (the evaluated payload) + `results` (list of
|
|
||||||
# per-policy result objects). Future-proof: also accept the
|
|
||||||
# legacy {"results": [...]} dict shape.
|
|
||||||
if isinstance(out, list):
|
|
||||||
entries = out
|
|
||||||
elif isinstance(out, dict):
|
|
||||||
entries = out.get("results", [])
|
|
||||||
if not isinstance(entries, list):
|
|
||||||
entries = []
|
|
||||||
else:
|
|
||||||
entries = []
|
|
||||||
pcrs: list[dict] = []
|
|
||||||
for entry in entries:
|
|
||||||
if not isinstance(entry, dict):
|
|
||||||
continue
|
|
||||||
resource = entry.get("resource", {})
|
|
||||||
results = entry.get("results", [])
|
|
||||||
if not isinstance(results, list):
|
|
||||||
results = []
|
|
||||||
for pol_result in results:
|
|
||||||
if not isinstance(pol_result, dict):
|
|
||||||
continue
|
|
||||||
policy_obj = pol_result.get("policy", {}) or {}
|
|
||||||
policy_name = (
|
|
||||||
policy_obj.get("metadata", {}).get("name") if isinstance(policy_obj, dict)
|
|
||||||
else None
|
|
||||||
) or "UNKNOWN"
|
|
||||||
severity = severities.get(policy_name, SEVERITY_DEFAULT)
|
|
||||||
rules = pol_result.get("rules", [])
|
|
||||||
if not isinstance(rules, list):
|
|
||||||
rules = []
|
|
||||||
for rule_entry in rules:
|
|
||||||
if not isinstance(rule_entry, dict):
|
|
||||||
continue
|
|
||||||
rule_obj = rule_entry.get("rule", {}) or {}
|
|
||||||
rule_name = rule_obj.get("name", "") if isinstance(rule_obj, dict) else ""
|
|
||||||
rule_id = f"KJ_{policy_name}"
|
|
||||||
if rule_name:
|
|
||||||
rule_id = f"{rule_id}/{rule_name}"
|
|
||||||
violations = rule_entry.get("violations")
|
|
||||||
error_str = rule_entry.get("error")
|
|
||||||
if isinstance(violations, list) and violations:
|
|
||||||
# Fail: build a message from the violations' errors.
|
|
||||||
msg_parts: list[str] = []
|
|
||||||
for v in violations:
|
|
||||||
if not isinstance(v, dict):
|
|
||||||
continue
|
|
||||||
for err in v.get("errors", []) or []:
|
|
||||||
if not isinstance(err, dict):
|
|
||||||
continue
|
|
||||||
field = err.get("field", "")
|
|
||||||
detail = err.get("detail", "")
|
|
||||||
value = err.get("value", "")
|
|
||||||
msg_parts.append(
|
|
||||||
f"{field}: value={value!r} detail={detail}"
|
|
||||||
)
|
|
||||||
message = "; ".join(msg_parts) if msg_parts else "policy rule failed"
|
|
||||||
pcrs.append({
|
|
||||||
"contractId": contract_id,
|
|
||||||
"evaluatedAt": _iso8601_now(),
|
|
||||||
"engine": "kyverno",
|
|
||||||
"ruleId": rule_id,
|
|
||||||
"severity": severity,
|
|
||||||
"result": "fail",
|
|
||||||
"message": message,
|
|
||||||
"evidence": {
|
|
||||||
"resource": resource,
|
|
||||||
"policy": policy_name,
|
|
||||||
"rule": rule_name,
|
|
||||||
"violations": violations,
|
|
||||||
},
|
|
||||||
"resourceRef": _resource_ref(resource),
|
|
||||||
})
|
|
||||||
elif isinstance(error_str, str) and error_str:
|
|
||||||
# Policy-evaluation error (e.g. bad JMESPath).
|
|
||||||
pcrs.append({
|
|
||||||
"contractId": contract_id,
|
|
||||||
"evaluatedAt": _iso8601_now(),
|
|
||||||
"engine": "kyverno",
|
|
||||||
"ruleId": rule_id,
|
|
||||||
"severity": severity,
|
|
||||||
"result": "error",
|
|
||||||
"message": error_str,
|
|
||||||
"evidence": {
|
|
||||||
"resource": resource,
|
|
||||||
"policy": policy_name,
|
|
||||||
"rule": rule_name,
|
|
||||||
},
|
|
||||||
"resourceRef": _resource_ref(resource),
|
|
||||||
})
|
|
||||||
else:
|
|
||||||
# Pass: no violations, no error.
|
|
||||||
pcrs.append({
|
|
||||||
"contractId": contract_id,
|
|
||||||
"evaluatedAt": _iso8601_now(),
|
|
||||||
"engine": "kyverno",
|
|
||||||
"ruleId": rule_id,
|
|
||||||
"severity": severity,
|
|
||||||
"result": "pass",
|
|
||||||
"message": "",
|
|
||||||
"evidence": {
|
|
||||||
"resource": resource,
|
|
||||||
"policy": policy_name,
|
|
||||||
"rule": rule_name,
|
|
||||||
},
|
|
||||||
"resourceRef": _resource_ref(resource),
|
|
||||||
})
|
|
||||||
if not pcrs:
|
|
||||||
pcrs.append(_no_results_pass(contract_id))
|
|
||||||
return pcrs
|
|
||||||
|
|
||||||
|
|
||||||
def _resource_ref(resource: Any) -> str:
|
|
||||||
"""Best-effort resource ref from the evaluated payload."""
|
|
||||||
if isinstance(resource, dict):
|
|
||||||
for key in ("id", "name", "address"):
|
|
||||||
v = resource.get(key)
|
|
||||||
if isinstance(v, str) and v:
|
|
||||||
return v
|
|
||||||
return ""
|
|
||||||
|
|
||||||
|
|
||||||
# --- Legacy _to_pcr kept for the existing TestToPcr unit tests ---
|
|
||||||
# (test_kyverno_json_engine.py::TestToPcr constructs flat `entry`
|
|
||||||
# dicts with `policy`/`rule`/`result`/`message`/`resource` keys and
|
|
||||||
# asserts the translated PCR shape. The production _translate path no
|
|
||||||
# longer calls this helper — it inlines the translation against the
|
|
||||||
# real kj v0.0.3 nested output — but the unit tests pin the helper's
|
|
||||||
# contract, so it stays.)
|
|
||||||
|
|
||||||
|
|
||||||
def _to_pcr(entry: dict, contract_id: str, severity: str) -> dict:
|
|
||||||
"""Translate a flat kyverno-json scan result entry to a PCR dict.
|
|
||||||
|
|
||||||
Legacy shape (kept for unit-test backwards compatibility): the
|
|
||||||
entry is a flat dict with ``policy``/``rule``/``result``/``message``/
|
|
||||||
``resource`` string keys. The production ``_translate`` path no
|
|
||||||
longer calls this — it inlines translation against the real kj
|
|
||||||
v0.0.3 nested ``resource``+``results``+``rules`` shape — but the
|
|
||||||
``TestToPcr`` unit tests pin this contract.
|
|
||||||
"""
|
|
||||||
policy_name = entry.get("policy", "") or "UNKNOWN"
|
|
||||||
rule_name = entry.get("rule", "") or ""
|
|
||||||
rule_id = f"KJ_{policy_name}"
|
|
||||||
if rule_name:
|
|
||||||
rule_id = f"{rule_id}/{rule_name}"
|
|
||||||
result_raw = entry.get("result", "skip")
|
|
||||||
result = RESULT_MAP.get(str(result_raw).lower(), "error")
|
|
||||||
message = entry.get("message", "") or ""
|
|
||||||
resource = entry.get("resource", "")
|
|
||||||
if not resource and entry.get("name"):
|
|
||||||
kind = entry.get("kind", "")
|
|
||||||
ns = entry.get("namespace", "")
|
|
||||||
resource = f"{kind}/{ns}/{entry.get('name')}" if kind else entry.get("name", "")
|
|
||||||
return {
|
|
||||||
"contractId": contract_id,
|
|
||||||
"evaluatedAt": _iso8601_now(),
|
|
||||||
"engine": "kyverno",
|
|
||||||
"ruleId": rule_id,
|
|
||||||
"severity": severity,
|
|
||||||
"result": result,
|
|
||||||
"message": message,
|
|
||||||
"evidence": {
|
|
||||||
"resource": resource,
|
|
||||||
"policy": policy_name,
|
|
||||||
"rule": rule_name,
|
|
||||||
"namespace": entry.get("namespace", ""),
|
|
||||||
"kind": entry.get("kind", ""),
|
|
||||||
"name": entry.get("name", ""),
|
|
||||||
},
|
|
||||||
"resourceRef": resource,
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
if len(sys.argv) < 4:
|
|
||||||
print(
|
|
||||||
"usage: kyverno_json_engine.py <payload.json> <policy_dir> <contract-id>",
|
|
||||||
file=sys.stderr,
|
|
||||||
)
|
|
||||||
sys.exit(2)
|
|
||||||
with open(sys.argv[1], "r", encoding="utf-8") as fh:
|
|
||||||
pl = json.load(fh)
|
|
||||||
engine = KyvernoJsonEngine()
|
|
||||||
out = engine.evaluate(pl, Path(sys.argv[2]), sys.argv[3])
|
|
||||||
print(json.dumps(out, indent=2))
|
|
||||||
@@ -1,29 +0,0 @@
|
|||||||
{
|
|
||||||
"apiVersion": "json.kyverno.io/v1alpha1",
|
|
||||||
"kind": "ValidatingPolicy",
|
|
||||||
"metadata": {
|
|
||||||
"name": "require-contract-id",
|
|
||||||
"annotations": {
|
|
||||||
"nova.cloudinit.dev/severity": "high",
|
|
||||||
"title.policy.kyverno.io": "Require contract id"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"spec": {
|
|
||||||
"rules": [
|
|
||||||
{
|
|
||||||
"name": "require-id",
|
|
||||||
"assert": {
|
|
||||||
"all": [
|
|
||||||
{
|
|
||||||
"check": {
|
|
||||||
"id": {
|
|
||||||
"(regex_match('^[a-z][a-z0-9-]{2,5}$', @))": true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,30 +0,0 @@
|
|||||||
{
|
|
||||||
"apiVersion": "json.kyverno.io/v1alpha1",
|
|
||||||
"kind": "ValidatingPolicy",
|
|
||||||
"metadata": {
|
|
||||||
"name": "forbid-unknown-fields",
|
|
||||||
"annotations": {
|
|
||||||
"nova.cloudinit.dev/severity": "low",
|
|
||||||
"title.policy.kyverno.io": "Contract has only schema-allowed fields"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"spec": {
|
|
||||||
"rules": [
|
|
||||||
{
|
|
||||||
"name": "no-unknown-fields",
|
|
||||||
"assert": {
|
|
||||||
"all": [
|
|
||||||
{
|
|
||||||
"check": {
|
|
||||||
"(length(keys(@)) == `4`)": true,
|
|
||||||
"keys(@)": {
|
|
||||||
"(contains(['id','name','environment','infrastructure'], @))": true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,29 +0,0 @@
|
|||||||
{
|
|
||||||
"apiVersion": "json.kyverno.io/v1alpha1",
|
|
||||||
"kind": "ValidatingPolicy",
|
|
||||||
"metadata": {
|
|
||||||
"name": "require-env-in-enum",
|
|
||||||
"annotations": {
|
|
||||||
"nova.cloudinit.dev/severity": "high",
|
|
||||||
"title.policy.kyverno.io": "Contract environment is one of dev/qa/prod/dr"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"spec": {
|
|
||||||
"rules": [
|
|
||||||
{
|
|
||||||
"name": "env-enum",
|
|
||||||
"assert": {
|
|
||||||
"all": [
|
|
||||||
{
|
|
||||||
"check": {
|
|
||||||
"environment": {
|
|
||||||
"(contains(['dev','qa','prod','dr'], @))": true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,29 +0,0 @@
|
|||||||
{
|
|
||||||
"apiVersion": "json.kyverno.io/v1alpha1",
|
|
||||||
"kind": "ValidatingPolicy",
|
|
||||||
"metadata": {
|
|
||||||
"name": "require-id-pattern",
|
|
||||||
"annotations": {
|
|
||||||
"nova.cloudinit.dev/severity": "high",
|
|
||||||
"title.policy.kyverno.io": "Contract id matches operational acronym pattern"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"spec": {
|
|
||||||
"rules": [
|
|
||||||
{
|
|
||||||
"name": "id-pattern",
|
|
||||||
"assert": {
|
|
||||||
"all": [
|
|
||||||
{
|
|
||||||
"check": {
|
|
||||||
"id": {
|
|
||||||
"(regex_match('^[a-z][a-z0-9-]{2,5}$', @))": true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,29 +0,0 @@
|
|||||||
{
|
|
||||||
"apiVersion": "json.kyverno.io/v1alpha1",
|
|
||||||
"kind": "ValidatingPolicy",
|
|
||||||
"metadata": {
|
|
||||||
"name": "require-infrastructure-min-1",
|
|
||||||
"annotations": {
|
|
||||||
"nova.cloudinit.dev/severity": "medium",
|
|
||||||
"title.policy.kyverno.io": "Contract declares at least one infrastructure entry"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"spec": {
|
|
||||||
"rules": [
|
|
||||||
{
|
|
||||||
"name": "infra-min-1",
|
|
||||||
"assert": {
|
|
||||||
"all": [
|
|
||||||
{
|
|
||||||
"check": {
|
|
||||||
"infrastructure": {
|
|
||||||
"(length(keys(@)) > `0`)": true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,27 +0,0 @@
|
|||||||
{
|
|
||||||
"apiVersion": "json.kyverno.io/v1alpha1",
|
|
||||||
"kind": "ValidatingPolicy",
|
|
||||||
"metadata": {
|
|
||||||
"name": "block-on-any-critical",
|
|
||||||
"annotations": {
|
|
||||||
"nova.cloudinit.dev/severity": "critical",
|
|
||||||
"title.policy.kyverno.io": "Block on any critical-fail policy result (declarative source of truth)"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"spec": {
|
|
||||||
"rules": [
|
|
||||||
{
|
|
||||||
"name": "no-critical-fail",
|
|
||||||
"assert": {
|
|
||||||
"all": [
|
|
||||||
{
|
|
||||||
"check": {
|
|
||||||
"(severity == 'critical' && result == 'fail')": false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,32 +0,0 @@
|
|||||||
{
|
|
||||||
"apiVersion": "json.kyverno.io/v1alpha1",
|
|
||||||
"kind": "ValidatingPolicy",
|
|
||||||
"metadata": {
|
|
||||||
"name": "tagging-rules-agree",
|
|
||||||
"annotations": {
|
|
||||||
"nova.cloudinit.dev/severity": "medium",
|
|
||||||
"title.policy.kyverno.io": "Checkov NOVA_TAG_NAMING and kj KJ_REQUIRE_TAGGING_STANDARD agree per resource"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"spec": {
|
|
||||||
"rules": [
|
|
||||||
{
|
|
||||||
"name": "no-tagging-divergence",
|
|
||||||
"assert": {
|
|
||||||
"all": [
|
|
||||||
{
|
|
||||||
"check": {
|
|
||||||
"(ruleId == 'NOVA_TAG_NAMING' && result == 'fail')": false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"check": {
|
|
||||||
"(ruleId == 'KJ_REQUIRE_TAGGING_STANDARD' && result == 'fail')": false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,27 +0,0 @@
|
|||||||
{
|
|
||||||
"apiVersion": "json.kyverno.io/v1alpha1",
|
|
||||||
"kind": "ValidatingPolicy",
|
|
||||||
"metadata": {
|
|
||||||
"name": "no-placeholder-account",
|
|
||||||
"annotations": {
|
|
||||||
"nova.cloudinit.dev/severity": "critical",
|
|
||||||
"title.policy.kyverno.io": "Env does not use a placeholder AWS account id"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"spec": {
|
|
||||||
"rules": [
|
|
||||||
{
|
|
||||||
"name": "no-placeholder-account",
|
|
||||||
"assert": {
|
|
||||||
"all": [
|
|
||||||
{
|
|
||||||
"check": {
|
|
||||||
"(account_id == '000000000000')": false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,51 +0,0 @@
|
|||||||
{
|
|
||||||
"apiVersion": "json.kyverno.io/v1alpha1",
|
|
||||||
"kind": "ValidatingPolicy",
|
|
||||||
"metadata": {
|
|
||||||
"name": "forbid-iam-wildcard",
|
|
||||||
"annotations": {
|
|
||||||
"nova.cloudinit.dev/severity": "high",
|
|
||||||
"title.policy.kyverno.io": "No IAM wildcard Actions or Resources"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"spec": {
|
|
||||||
"rules": [
|
|
||||||
{
|
|
||||||
"name": "no-wildcard-action",
|
|
||||||
"assert": {
|
|
||||||
"all": [
|
|
||||||
{
|
|
||||||
"check": {
|
|
||||||
"planned_values": {
|
|
||||||
"root_module": {
|
|
||||||
"~.resources": {
|
|
||||||
"(type == 'aws_iam_policy' && contains(values.policy_document.Statement[].Action, '*'))": false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "no-wildcard-resource",
|
|
||||||
"assert": {
|
|
||||||
"all": [
|
|
||||||
{
|
|
||||||
"check": {
|
|
||||||
"planned_values": {
|
|
||||||
"root_module": {
|
|
||||||
"~.resources": {
|
|
||||||
"(type == 'aws_iam_policy' && contains(values.policy_document.Statement[].Resource, '*'))": false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,33 +0,0 @@
|
|||||||
{
|
|
||||||
"apiVersion": "json.kyverno.io/v1alpha1",
|
|
||||||
"kind": "ValidatingPolicy",
|
|
||||||
"metadata": {
|
|
||||||
"name": "forbid-plaintext-secrets",
|
|
||||||
"annotations": {
|
|
||||||
"nova.cloudinit.dev/severity": "high",
|
|
||||||
"title.policy.kyverno.io": "No plaintext secrets in the terraform plan"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"spec": {
|
|
||||||
"rules": [
|
|
||||||
{
|
|
||||||
"name": "no-plaintext-db-password",
|
|
||||||
"assert": {
|
|
||||||
"all": [
|
|
||||||
{
|
|
||||||
"check": {
|
|
||||||
"planned_values": {
|
|
||||||
"root_module": {
|
|
||||||
"~.resources": {
|
|
||||||
"(type == 'aws_db_instance' && contains(keys(values), 'password') && !contains(['${...}', ''], values.password))": false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,33 +0,0 @@
|
|||||||
{
|
|
||||||
"apiVersion": "json.kyverno.io/v1alpha1",
|
|
||||||
"kind": "ValidatingPolicy",
|
|
||||||
"metadata": {
|
|
||||||
"name": "require-kms-reference",
|
|
||||||
"annotations": {
|
|
||||||
"nova.cloudinit.dev/severity": "medium",
|
|
||||||
"title.policy.kyverno.io": "KMS keys referenced by alias, not inline key material"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"spec": {
|
|
||||||
"rules": [
|
|
||||||
{
|
|
||||||
"name": "kms-by-alias",
|
|
||||||
"assert": {
|
|
||||||
"all": [
|
|
||||||
{
|
|
||||||
"check": {
|
|
||||||
"planned_values": {
|
|
||||||
"root_module": {
|
|
||||||
"~.resources": {
|
|
||||||
"(type == 'aws_kms_key' && !contains(keys(values), 'key_id') && !contains(keys(values), 'kms_key_id'))": false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,27 +0,0 @@
|
|||||||
{
|
|
||||||
"apiVersion": "json.kyverno.io/v1alpha1",
|
|
||||||
"kind": "ValidatingPolicy",
|
|
||||||
"metadata": {
|
|
||||||
"name": "cap-013-adapter-dedup",
|
|
||||||
"annotations": {
|
|
||||||
"nova.cloudinit.dev/severity": "medium",
|
|
||||||
"title.policy.kyverno.io": "No duplicate adapter registrations (CAP-013 declarative mirror)"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"spec": {
|
|
||||||
"rules": [
|
|
||||||
{
|
|
||||||
"name": "no-duplicate-adapters",
|
|
||||||
"assert": {
|
|
||||||
"all": [
|
|
||||||
{
|
|
||||||
"check": {
|
|
||||||
"(max(map(&length(@), values(group_by(adapters, &@)))) == `1`)": true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,29 +0,0 @@
|
|||||||
{
|
|
||||||
"apiVersion": "json.kyverno.io/v1alpha1",
|
|
||||||
"kind": "ValidatingPolicy",
|
|
||||||
"metadata": {
|
|
||||||
"name": "cap-023-metrics-collector",
|
|
||||||
"annotations": {
|
|
||||||
"nova.cloudinit.dev/severity": "medium",
|
|
||||||
"title.policy.kyverno.io": "Every metric has a grounded/derived/deferred status (CAP-023 declarative mirror)"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"spec": {
|
|
||||||
"rules": [
|
|
||||||
{
|
|
||||||
"name": "every-metric-has-status",
|
|
||||||
"assert": {
|
|
||||||
"all": [
|
|
||||||
{
|
|
||||||
"check": {
|
|
||||||
"~.metrics": {
|
|
||||||
"(contains(['grounded','derived','deferred'], status))": true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,32 +0,0 @@
|
|||||||
{
|
|
||||||
"apiVersion": "json.kyverno.io/v1alpha1",
|
|
||||||
"kind": "ValidatingPolicy",
|
|
||||||
"metadata": {
|
|
||||||
"name": "cap-024-deck-structure",
|
|
||||||
"annotations": {
|
|
||||||
"nova.cloudinit.dev/severity": "low",
|
|
||||||
"title.policy.kyverno.io": "Deck structure matches the documented 4-beat arc (CAP-024 declarative mirror)"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"spec": {
|
|
||||||
"rules": [
|
|
||||||
{
|
|
||||||
"name": "deck-has-4-beats",
|
|
||||||
"assert": {
|
|
||||||
"all": [
|
|
||||||
{
|
|
||||||
"check": {
|
|
||||||
"deck": {
|
|
||||||
"beats": {
|
|
||||||
"(length(@) >= `4`)": true,
|
|
||||||
"(contains(@, 'Problem') && contains(@, 'Solution') && contains(@, 'Proof') && contains(@, 'Roadmap+Ask'))": true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,27 +0,0 @@
|
|||||||
{
|
|
||||||
"apiVersion": "json.kyverno.io/v1alpha1",
|
|
||||||
"kind": "ValidatingPolicy",
|
|
||||||
"metadata": {
|
|
||||||
"name": "all-matches-committed",
|
|
||||||
"annotations": {
|
|
||||||
"nova.cloudinit.dev/severity": "critical",
|
|
||||||
"title.policy.kyverno.io": "All settlement matches are committed (finalized)"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"spec": {
|
|
||||||
"rules": [
|
|
||||||
{
|
|
||||||
"name": "all-matches-committed",
|
|
||||||
"assert": {
|
|
||||||
"all": [
|
|
||||||
{
|
|
||||||
"check": {
|
|
||||||
"(all_committed)": true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,30 +0,0 @@
|
|||||||
{
|
|
||||||
"apiVersion": "json.kyverno.io/v1alpha1",
|
|
||||||
"kind": "ValidatingPolicy",
|
|
||||||
"metadata": {
|
|
||||||
"name": "forbid-public-ingress",
|
|
||||||
"annotations": {
|
|
||||||
"nova.cloudinit.dev/severity": "high",
|
|
||||||
"title.policy.kyverno.io": "No resource has public ingress enabled"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"spec": {
|
|
||||||
"rules": [
|
|
||||||
{
|
|
||||||
"name": "no-public-ingress",
|
|
||||||
"identifier": "id",
|
|
||||||
"assert": {
|
|
||||||
"all": [
|
|
||||||
{
|
|
||||||
"check": {
|
|
||||||
"~.resources": {
|
|
||||||
"(inputs.public_ingress || `false`)": false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,45 +0,0 @@
|
|||||||
{
|
|
||||||
"apiVersion": "json.kyverno.io/v1alpha1",
|
|
||||||
"kind": "ValidatingPolicy",
|
|
||||||
"metadata": {
|
|
||||||
"name": "require-encryption-by-default",
|
|
||||||
"annotations": {
|
|
||||||
"nova.cloudinit.dev/severity": "high",
|
|
||||||
"title.policy.kyverno.io": "S3 buckets and EBS volumes carry encryption config"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"spec": {
|
|
||||||
"rules": [
|
|
||||||
{
|
|
||||||
"name": "s3-encryption",
|
|
||||||
"identifier": "id",
|
|
||||||
"assert": {
|
|
||||||
"all": [
|
|
||||||
{
|
|
||||||
"check": {
|
|
||||||
"~.resources": {
|
|
||||||
"(type == 'aws:s3:bucket' && !(contains(keys(inputs), 'bucket_encryption') || contains(keys(inputs), 'kms_key_id')))": false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "ebs-encryption",
|
|
||||||
"identifier": "id",
|
|
||||||
"assert": {
|
|
||||||
"all": [
|
|
||||||
{
|
|
||||||
"check": {
|
|
||||||
"~.resources": {
|
|
||||||
"(type == 'aws:ebs:volume' && !(contains(keys(inputs), 'encrypted') || contains(keys(inputs), 'kms_key_id')))": false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,33 +0,0 @@
|
|||||||
{
|
|
||||||
"apiVersion": "json.kyverno.io/v1alpha1",
|
|
||||||
"kind": "ValidatingPolicy",
|
|
||||||
"metadata": {
|
|
||||||
"name": "require-tagging-standard",
|
|
||||||
"annotations": {
|
|
||||||
"nova.cloudinit.dev/severity": "medium",
|
|
||||||
"title.policy.kyverno.io": "All resources carry required Nova tags"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"spec": {
|
|
||||||
"rules": [
|
|
||||||
{
|
|
||||||
"name": "require-nova-tags",
|
|
||||||
"identifier": "id",
|
|
||||||
"assert": {
|
|
||||||
"all": [
|
|
||||||
{
|
|
||||||
"check": {
|
|
||||||
"~.resources": {
|
|
||||||
"(contains(keys(inputs.tags || `{}`), 'nova:owner'))": true,
|
|
||||||
"(contains(keys(inputs.tags || `{}`), 'nova:contract'))": true,
|
|
||||||
"(contains(keys(inputs.tags || `{}`), 'nova:environment'))": true,
|
|
||||||
"(contains(keys(inputs.tags || `{}`), 'nova:cost-center'))": true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
# Kyverno Adapter
|
# Kyverno Adapter
|
||||||
|
|
||||||
The Kyverno adapter translates Kyverno `PolicyReport` results to the
|
The Kyverno adapter translates Kyverno `PolicyReport` results to the
|
||||||
normalized Nova
|
normalized ACDL
|
||||||
[`PolicyCheckResult`](../../schemas/policy_check_result.schema.json) schema
|
[`PolicyCheckResult`](../../schemas/policy_check_result.schema.json) schema
|
||||||
(engine: `"kyverno"`), mirroring the Checkov/Wiz adapter pattern.
|
(engine: `"kyverno"`), mirroring the Checkov/Wiz adapter pattern.
|
||||||
|
|
||||||
@@ -15,7 +15,7 @@ publishes results to `PolicyReport` resources.
|
|||||||
## When to use it
|
## When to use it
|
||||||
|
|
||||||
Kyverno is the right engine **when the platform emits Kubernetes
|
Kyverno is the right engine **when the platform emits Kubernetes
|
||||||
manifests** (a K8s-native stack). The Nova platform today emits Terraform
|
manifests** (a K8s-native stack). The ACDL platform today emits Terraform
|
||||||
only (D-053), so this adapter is **ready but inactive**: it ships now so
|
only (D-053), so this adapter is **ready but inactive**: it ships now so
|
||||||
the schema path, severity/result mapping and sample policies are in place
|
the schema path, severity/result mapping and sample policies are in place
|
||||||
ahead of the GitOps reconciler that will emit K8s manifests (roadmap).
|
ahead of the GitOps reconciler that will emit K8s manifests (roadmap).
|
||||||
@@ -55,8 +55,8 @@ manifests (documentation-only today — the platform does not run them):
|
|||||||
|
|
||||||
- `disallow-privileged-containers.yml` — fail pods with
|
- `disallow-privileged-containers.yml` — fail pods with
|
||||||
`securityContext.privileged: true`.
|
`securityContext.privileged: true`.
|
||||||
- `require-resource-labels.yml` — require `nova:owner` and
|
- `require-resource-labels.yml` — require `acdl:owner` and
|
||||||
`nova:environment` labels on all pods (mirrors the Nova tagging standard
|
`acdl:environment` labels on all pods (mirrors the ACDL tagging standard
|
||||||
in [`schemas/tagging-standard.json`](../../schemas/tagging-standard.json)).
|
in [`schemas/tagging-standard.json`](../../schemas/tagging-standard.json)).
|
||||||
- `require-image-digests.yml` — require container images to reference a
|
- `require-image-digests.yml` — require container images to reference a
|
||||||
digest (`image@sha256:...`), not a mutable tag.
|
digest (`image@sha256:...`), not a mutable tag.
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
"""Kyverno adapter — translate Kyverno PolicyReport results to Nova PolicyCheckResult records.
|
"""Kyverno adapter — translate Kyverno PolicyReport results to ACDL PolicyCheckResult records.
|
||||||
|
|
||||||
Kyverno is a Kubernetes-native policy engine. It evaluates K8s manifests
|
Kyverno is a Kubernetes-native policy engine. It evaluates K8s manifests
|
||||||
and produces PolicyReport resources. This adapter translates those results
|
and produces PolicyReport resources. This adapter translates those results
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ kind: ClusterPolicy
|
|||||||
metadata:
|
metadata:
|
||||||
name: require-resource-labels
|
name: require-resource-labels
|
||||||
annotations:
|
annotations:
|
||||||
policies.kyverno.io/title: Require Nova Resource Labels
|
policies.kyverno.io/title: Require ACDL Resource Labels
|
||||||
policies.kyverno.io/category: Governance
|
policies.kyverno.io/category: Governance
|
||||||
policies.kyverno.io/severity: medium
|
policies.kyverno.io/severity: medium
|
||||||
policies.kyverno.io/subject: Pod
|
policies.kyverno.io/subject: Pod
|
||||||
@@ -11,27 +11,27 @@ spec:
|
|||||||
validationFailureAction: audit
|
validationFailureAction: audit
|
||||||
background: true
|
background: true
|
||||||
rules:
|
rules:
|
||||||
- name: require-nova-owner-label
|
- name: require-acdl-owner-label
|
||||||
match:
|
match:
|
||||||
any:
|
any:
|
||||||
- resources:
|
- resources:
|
||||||
kinds:
|
kinds:
|
||||||
- Pod
|
- Pod
|
||||||
validate:
|
validate:
|
||||||
message: "Pods must carry the nova:owner label (Nova tagging standard)."
|
message: "Pods must carry the acdl:owner label (ACDL tagging standard)."
|
||||||
pattern:
|
pattern:
|
||||||
metadata:
|
metadata:
|
||||||
labels:
|
labels:
|
||||||
nova:owner: "?*"
|
acdl:owner: "?*"
|
||||||
- name: require-nova-environment-label
|
- name: require-acdl-environment-label
|
||||||
match:
|
match:
|
||||||
any:
|
any:
|
||||||
- resources:
|
- resources:
|
||||||
kinds:
|
kinds:
|
||||||
- Pod
|
- Pod
|
||||||
validate:
|
validate:
|
||||||
message: "Pods must carry the nova:environment label (Nova tagging standard)."
|
message: "Pods must carry the acdl:environment label (ACDL tagging standard)."
|
||||||
pattern:
|
pattern:
|
||||||
metadata:
|
metadata:
|
||||||
labels:
|
labels:
|
||||||
nova:environment: "?*"
|
acdl:environment: "?*"
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
"""Nova Terraform adapter — stateless assembler (v1.11 RESTART, P56a).
|
"""ACDL Terraform adapter — stateless assembler (v1.11 RESTART, P56a).
|
||||||
|
|
||||||
A STATELESS ASSEMBLER. It owns no module content — no resource shape, no
|
A STATELESS ASSEMBLER. It owns no module content — no resource shape, no
|
||||||
nested HCL blocks, no defaults, no type-specific logic. It reads the
|
nested HCL blocks, no defaults, no type-specific logic. It reads the
|
||||||
@@ -10,10 +10,9 @@ lives in the per-module terraform/ subdir, NOT in this file.
|
|||||||
CLI: adapter.py <instance.json> <out_dir>
|
CLI: adapter.py <instance.json> <out_dir>
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import json, os, sys
|
import json
|
||||||
_R = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
import os
|
||||||
sys.path.insert(0, _R) if _R not in sys.path else None
|
import sys
|
||||||
from core import env
|
|
||||||
|
|
||||||
|
|
||||||
def _load_registry(repo_root):
|
def _load_registry(repo_root):
|
||||||
@@ -30,37 +29,6 @@ def _module_name(resource):
|
|||||||
return resource.get("module", "").split("@")[0]
|
return resource.get("module", "").split("@")[0]
|
||||||
|
|
||||||
|
|
||||||
def _load_env_json(env_name, repo_root):
|
|
||||||
"""Load core/environments/<env_name>.json → dict (P03 W3, REQ-319).
|
|
||||||
|
|
||||||
Returns {} if the file is absent (the adapter falls back to the
|
|
||||||
computed state-bucket name). Sources env.state_backend.bucket +
|
|
||||||
env.account_id + env.region for the S3 backend block.
|
|
||||||
"""
|
|
||||||
env_path = os.path.join(repo_root, "core", "environments", f"{env_name}.json")
|
|
||||||
if not os.path.isfile(env_path):
|
|
||||||
return {}
|
|
||||||
with open(env_path, "r") as fh:
|
|
||||||
return json.load(fh)
|
|
||||||
|
|
||||||
|
|
||||||
def _resolve_state_bucket(env_json, region):
|
|
||||||
"""Resolve the S3 state-backend bucket name (P03 W3, REQ-319).
|
|
||||||
|
|
||||||
Precedence: (1) env.state_backend.bucket when present + non-empty;
|
|
||||||
(2) nova-tfstate-{account_id}-{region} from env.account_id + region
|
|
||||||
(backwards-compat); (3) nova-tfstate-581513795199-{region} when
|
|
||||||
account_id is absent (the only real account — bootstrap bucket).
|
|
||||||
The env JSON is authoritative; NOVA_AWS_ACCOUNT_ID is no longer
|
|
||||||
consulted for the bucket name.
|
|
||||||
"""
|
|
||||||
bucket = (env_json.get("state_backend") or {}).get("bucket")
|
|
||||||
if bucket:
|
|
||||||
return bucket
|
|
||||||
account_id = env_json.get("account_id") or "581513795199"
|
|
||||||
return f"nova-tfstate-{account_id}-{region}"
|
|
||||||
|
|
||||||
|
|
||||||
def _ref_expr(value, data_source_names=None, id_remap=None):
|
def _ref_expr(value, data_source_names=None, id_remap=None):
|
||||||
"""Translate `ref:<rid>.<output>` → `module.<rid>.<output>` (or
|
"""Translate `ref:<rid>.<output>` → `module.<rid>.<output>` (or
|
||||||
`data.terraform_remote_state.platform.outputs.<output>` for data
|
`data.terraform_remote_state.platform.outputs.<output>` for data
|
||||||
@@ -139,23 +107,13 @@ def adapt(stack_instance, out_dir):
|
|||||||
resources = stack_instance.get("resources", [])
|
resources = stack_instance.get("resources", [])
|
||||||
stack_outputs = stack_instance.get("outputs", {})
|
stack_outputs = stack_instance.get("outputs", {})
|
||||||
|
|
||||||
stack_name = stack.get("name", "spike")
|
region = next((r["inputs"]["region"] for r in resources if "region" in r.get("inputs", {})), "us-east-1")
|
||||||
environment = stack.get("environment", "dev")
|
|
||||||
# P03 W3 (REQ-319): state backend bucket + account_id + region come
|
|
||||||
# from the env onboarding JSON (source of truth post-REQ-319). Bucket
|
|
||||||
# = env.state_backend.bucket when present (fallback to the computed
|
|
||||||
# nova-tfstate-{account_id}-{region} pattern for backwards compat).
|
|
||||||
env_json = _load_env_json(environment, repo_root)
|
|
||||||
region = env_json.get("region") or next(
|
|
||||||
(r["inputs"]["region"] for r in resources if "region" in r.get("inputs", {})),
|
|
||||||
"us-east-1",
|
|
||||||
)
|
|
||||||
state_bucket = _resolve_state_bucket(env_json, region)
|
|
||||||
providers_tf = f'provider "aws" {{\n region = "{region}"\n}}\n'
|
providers_tf = f'provider "aws" {{\n region = "{region}"\n}}\n'
|
||||||
|
|
||||||
# State key is env-scoped (v1.24 REQ-287): the {environment} segment lets
|
stack_name = stack.get("name", "spike")
|
||||||
# the env-transition detect-and-destroy step target the PRIOR env's state
|
environment = stack.get("environment", "dev")
|
||||||
# without affecting the new env. No orphan path on environment promotion.
|
account_id = os.environ.get("ACDL_AWS_ACCOUNT_ID", "581513795199")
|
||||||
|
state_bucket = f"acdl-tfstate-{account_id}-us-east-1"
|
||||||
terraform_tf = (
|
terraform_tf = (
|
||||||
'terraform {\n'
|
'terraform {\n'
|
||||||
' required_version = ">= 1.9, < 1.10"\n'
|
' required_version = ">= 1.9, < 1.10"\n'
|
||||||
@@ -168,7 +126,7 @@ def adapt(stack_instance, out_dir):
|
|||||||
' backend "s3" {\n'
|
' backend "s3" {\n'
|
||||||
f' bucket = "{state_bucket}"\n'
|
f' bucket = "{state_bucket}"\n'
|
||||||
f' key = "spike/{stack_name}/{environment}/terraform.tfstate"\n'
|
f' key = "spike/{stack_name}/{environment}/terraform.tfstate"\n'
|
||||||
f' region = "{region}"\n'
|
' region = "us-east-1"\n'
|
||||||
' }\n'
|
' }\n'
|
||||||
'}\n'
|
'}\n'
|
||||||
)
|
)
|
||||||
@@ -176,14 +134,14 @@ def adapt(stack_instance, out_dir):
|
|||||||
data_source_names = stack_instance.get("data_sources", [])
|
data_source_names = stack_instance.get("data_sources", [])
|
||||||
parts = []
|
parts = []
|
||||||
if data_source_names:
|
if data_source_names:
|
||||||
remote_state_key = env.get_env("REMOTE_STATE_KEY", "platform/terraform.tfstate")
|
remote_state_key = os.environ.get("ACDL_REMOTE_STATE_KEY", "platform/terraform.tfstate")
|
||||||
parts.append(
|
parts.append(
|
||||||
'data "terraform_remote_state" "platform" {\n'
|
'data "terraform_remote_state" "platform" {\n'
|
||||||
' backend = "s3"\n'
|
' backend = "s3"\n'
|
||||||
' config = {\n'
|
' config = {\n'
|
||||||
f' bucket = "{state_bucket}"\n'
|
f' bucket = "{state_bucket}"\n'
|
||||||
f' key = "{remote_state_key}"\n'
|
f' key = "{remote_state_key}"\n'
|
||||||
f' region = "{region}"\n'
|
' region = "us-east-1"\n'
|
||||||
' }\n'
|
' }\n'
|
||||||
'}\n'
|
'}\n'
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
"""Translate Checkov JSON output to Nova PolicyCheckResult records.
|
"""Translate Checkov JSON output to ACDL PolicyCheckResult records.
|
||||||
|
|
||||||
Reads Checkov's JSON output (one framework key, e.g. terraform_plan),
|
Reads Checkov's JSON output (one framework key, e.g. terraform_plan),
|
||||||
emits a list of PolicyCheckResult dicts conforming to
|
emits a list of PolicyCheckResult dicts conforming to
|
||||||
@@ -6,23 +6,16 @@ schemas/policy_check_result.schema.json. Run Checkov with --soft-fail so
|
|||||||
Checkov never exits non-zero; the confidence signal decides the gate, not
|
Checkov never exits non-zero; the confidence signal decides the gate, not
|
||||||
Checkov's exit code.
|
Checkov's exit code.
|
||||||
|
|
||||||
The Nova tagging standard (D-054, D-043 closure, D-109 hard mode in P3)
|
The ACDL tagging standard (D-054, D-043 closure) is enforced by a custom
|
||||||
is enforced by a custom Checkov rule at
|
Checkov rule at adapters/terraform/policy/custom_rules/acdl_tagging.py,
|
||||||
adapters/terraform/policy/custom_rules/nova_tagging.py, loaded via
|
loaded via --external-checks-dir. The adapter therefore maps
|
||||||
--external-checks-dir. The adapter therefore maps NOVA_TAG_NAMING as a
|
ACDL_TAG_NAMING as a real rule (no synthetic SKIPPED record is emitted).
|
||||||
real rule (no synthetic SKIPPED record is emitted). Renamed from
|
|
||||||
ACDL_TAG_NAMING in P2 (REQ-158); the rule is in hard mode as of P3
|
|
||||||
(REQ-162: hard-fail on missing nova:* or acdl:*-only tags).
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import datetime
|
import datetime
|
||||||
import json
|
import json
|
||||||
import os
|
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))))
|
|
||||||
from core.metrics.event_envelope import emit
|
|
||||||
|
|
||||||
|
|
||||||
RULE_MAP = {
|
RULE_MAP = {
|
||||||
"CKV_AWS_41": ("secrets-in-plaintext", "high"),
|
"CKV_AWS_41": ("secrets-in-plaintext", "high"),
|
||||||
@@ -36,12 +29,10 @@ RULE_MAP = {
|
|||||||
"CKV_AWS_40": ("iam-wildcard", "medium"),
|
"CKV_AWS_40": ("iam-wildcard", "medium"),
|
||||||
"CKV_AWS_7": ("kms-key-reference", "medium"),
|
"CKV_AWS_7": ("kms-key-reference", "medium"),
|
||||||
"CKV_AWS_33": ("kms-key-reference", "medium"),
|
"CKV_AWS_33": ("kms-key-reference", "medium"),
|
||||||
# D-054 / D-043 closure, D-109 hard mode (P3): NOVA_TAG_NAMING is a real
|
# D-054 / D-043 closure: ACDL_TAG_NAMING is now a real custom Checkov
|
||||||
# custom Checkov rule (adapters/terraform/policy/custom_rules/nova_tagging.py),
|
# rule (adapters/terraform/policy/custom_rules/acdl_tagging.py), loaded
|
||||||
# loaded via --external-checks-dir. No synthetic SKIPPED record is emitted.
|
# via --external-checks-dir. No synthetic SKIPPED record is emitted.
|
||||||
# Renamed from ACDL_TAG_NAMING in P2 (REQ-158). Hard mode as of P3
|
"ACDL_TAG_NAMING": ("tagging-standard", "medium"),
|
||||||
# (REQ-162: hard-fail on missing nova:* or acdl:*-only tags).
|
|
||||||
"NOVA_TAG_NAMING": ("tagging-standard", "medium"),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_RESULT_MAP = {"PASSED": "pass", "FAILED": "fail", "SKIPPED": "skipped"}
|
_RESULT_MAP = {"PASSED": "pass", "FAILED": "fail", "SKIPPED": "skipped"}
|
||||||
@@ -75,7 +66,7 @@ def _to_pcr(checkov_record, contract_id, result_str):
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
def adapt(checkov_json_path, contract_id, run_id=None, environment="dev"):
|
def adapt(checkov_json_path, contract_id):
|
||||||
with open(checkov_json_path, "r", encoding="utf-8") as fh:
|
with open(checkov_json_path, "r", encoding="utf-8") as fh:
|
||||||
data = json.load(fh)
|
data = json.load(fh)
|
||||||
out = []
|
out = []
|
||||||
@@ -89,25 +80,6 @@ def adapt(checkov_json_path, contract_id, run_id=None, environment="dev"):
|
|||||||
out.append(_to_pcr(rec, contract_id, "FAILED"))
|
out.append(_to_pcr(rec, contract_id, "FAILED"))
|
||||||
for rec in results.get("skipped_checks", []):
|
for rec in results.get("skipped_checks", []):
|
||||||
out.append(_to_pcr(rec, contract_id, "SKIPPED"))
|
out.append(_to_pcr(rec, contract_id, "SKIPPED"))
|
||||||
|
|
||||||
# Emit nova.policy.evaluated event (REQ-187).
|
|
||||||
if run_id:
|
|
||||||
passed = sum(1 for p in out if p["result"] == "pass")
|
|
||||||
failed = sum(1 for p in out if p["result"] == "fail")
|
|
||||||
skipped = sum(1 for p in out if p["result"] == "skipped")
|
|
||||||
severity_breakdown = {}
|
|
||||||
for p in out:
|
|
||||||
sev = p.get("severity", "info")
|
|
||||||
severity_breakdown[sev] = severity_breakdown.get(sev, 0) + 1
|
|
||||||
try:
|
|
||||||
emit("nova.policy.evaluated", run_id, environment, {
|
|
||||||
"passed": passed, "failed": failed, "skipped": skipped,
|
|
||||||
"severity_breakdown": severity_breakdown,
|
|
||||||
"rule_count": len(out),
|
|
||||||
}, contract_id=contract_id)
|
|
||||||
except Exception:
|
|
||||||
pass # metrics emission must never break the policy adapter
|
|
||||||
|
|
||||||
return out
|
return out
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,24 +1,16 @@
|
|||||||
# Nova Custom Checkov Rules
|
# ACDL Custom Checkov Rules
|
||||||
|
|
||||||
This directory holds Nova-authored Checkov custom rules, written in the
|
This directory holds ACDL-authored Checkov custom rules, written in the
|
||||||
[Checkov Python custom-rule framework](https://www.checkov.io/4.Contributing/Custom%20Policies.html).
|
[Checkov Python custom-rule framework](https://www.checkov.io/4.Contributing/Custom%20Policies.html).
|
||||||
|
|
||||||
## Files
|
## Files
|
||||||
|
|
||||||
- `nova_tagging.py` — `NOVA_TAG_NAMING` (D-054, D-109 warn mode in P2):
|
- `acdl_tagging.py` — `ACDL_TAG_NAMING` (D-054): ensures every taggable AWS
|
||||||
ensures every taggable AWS resource carries the four required Nova tags
|
resource carries the four required ACDL tags
|
||||||
(`nova:owner`, `nova:contract`, `nova:environment`, `nova:cost-center`).
|
(`acdl:owner`, `acdl:contract`, `acdl:environment`, `acdl:cost-center`).
|
||||||
This rule replaces the synthetic SKIPPED `NOVA_TAG_NAMING` record that the
|
This rule replaces the synthetic SKIPPED `ACDL_TAG_NAMING` record that the
|
||||||
Checkov adapter previously emitted (D-043 closure). Renamed from
|
Checkov adapter previously emitted (D-043 closure). The canonical tag set
|
||||||
`acdl_tagging.py` / `ACDL_TAG_NAMING` in P2 (REQ-158). The canonical tag
|
is declared in [`schemas/tagging-standard.json`](../../../schemas/tagging-standard.json).
|
||||||
set is declared in [`schemas/tagging-standard.json`](../../../schemas/tagging-standard.json).
|
|
||||||
|
|
||||||
**P2 warn mode (D-109):** existing resources still carry `acdl:*` tag-key
|
|
||||||
values (left for P3). When a resource has only `acdl:*`-style tags and no
|
|
||||||
`nova:*` tags, the rule logs a WARNING instead of failing, so the
|
|
||||||
regression gate stays green during the parallel-tag transition window.
|
|
||||||
P3 flips to hard-fail once `nova:*` tags are emitted in parallel and the
|
|
||||||
ABAC policy is swapped.
|
|
||||||
|
|
||||||
## How Checkov loads them
|
## How Checkov loads them
|
||||||
|
|
||||||
@@ -31,12 +23,12 @@ checkov -f terraform/spike/main.tf --framework terraform -o json --soft-fail \
|
|||||||
```
|
```
|
||||||
|
|
||||||
Checkov imports each `*.py` file in the directory and instantiates the
|
Checkov imports each `*.py` file in the directory and instantiates the
|
||||||
module-level `check` object (see the `check = NovaTaggingStandard()` line at
|
module-level `check` object (see the `check = AcdlTaggingStandard()` line at
|
||||||
the bottom of `nova_tagging.py`).
|
the bottom of `acdl_tagging.py`).
|
||||||
|
|
||||||
## Severity / result mapping
|
## Severity / result mapping
|
||||||
|
|
||||||
The Checkov adapter (`adapters/terraform/policy/checkov_adapter.py`)
|
The Checkov adapter (`adapters/terraform/policy/checkov_adapter.py`)
|
||||||
maps `NOVA_TAG_NAMING` to `(tagging-standard, medium)` in `RULE_MAP`. The
|
maps `ACDL_TAG_NAMING` to `(tagging-standard, medium)` in `RULE_MAP`. The
|
||||||
custom rule therefore produces real `PASS`/`FAIL` PolicyCheckResult records,
|
custom rule therefore produces real `PASS`/`FAIL` PolicyCheckResult records,
|
||||||
feeding the confidence signal instead of the old SKIPPED placeholder.
|
feeding the confidence signal instead of the old SKIPPED placeholder.
|
||||||
@@ -0,0 +1,54 @@
|
|||||||
|
"""ACDL tagging standard custom Checkov rule (D-054).
|
||||||
|
|
||||||
|
Checks that all taggable AWS resources have the required ACDL tags:
|
||||||
|
acdl:owner, acdl:contract, acdl:environment, acdl:cost-center
|
||||||
|
|
||||||
|
Fails (severity medium) when any required tag is missing.
|
||||||
|
Closes the D-043 deferral (the SKIPPED ACDL_TAG_NAMING placeholder
|
||||||
|
becomes a real check).
|
||||||
|
"""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from checkov.terraform.checks.resource.base_resource_check import BaseResourceCheck
|
||||||
|
from checkov.common.models.enums import CheckResult, CheckCategories
|
||||||
|
|
||||||
|
REQUIRED_TAGS = ("acdl:owner", "acdl:contract", "acdl:environment", "acdl:cost-center")
|
||||||
|
|
||||||
|
# Resources that support tags (exclude resources that have no tags attribute)
|
||||||
|
NON_TAGGABLE_TYPES = (
|
||||||
|
"aws_cloudfront_origin_access_control",
|
||||||
|
"aws_lambda_function_url",
|
||||||
|
"aws_route_table_association",
|
||||||
|
"aws_internet_gateway",
|
||||||
|
)
|
||||||
|
|
||||||
|
class AcdlTaggingStandard(BaseResourceCheck):
|
||||||
|
def __init__(self):
|
||||||
|
name = "Ensure all taggable AWS resources have required ACDL tags"
|
||||||
|
check_id = "ACDL_TAG_NAMING"
|
||||||
|
supported_resources = ["*"] # all resources
|
||||||
|
categories = [CheckCategories.GENERAL_SECURITY]
|
||||||
|
super().__init__(name=name, check_id=check_id, categories=categories, supported_resources=supported_resources)
|
||||||
|
|
||||||
|
def scan_resource_conf(self, conf, entity_type):
|
||||||
|
# Skip non-taggable resources
|
||||||
|
if entity_type in NON_TAGGABLE_TYPES:
|
||||||
|
return CheckResult.PASSED
|
||||||
|
# Check for a tags block
|
||||||
|
tags = conf.get("tags")
|
||||||
|
if not tags:
|
||||||
|
return CheckResult.FAILED
|
||||||
|
tag_keys = set()
|
||||||
|
if isinstance(tags, list) and tags:
|
||||||
|
tag_block = tags[0]
|
||||||
|
if isinstance(tag_block, dict):
|
||||||
|
tag_keys = set(tag_block.keys())
|
||||||
|
elif isinstance(tags, dict):
|
||||||
|
tag_keys = set(tags.keys())
|
||||||
|
missing = [t for t in REQUIRED_TAGS if t not in tag_keys]
|
||||||
|
if missing:
|
||||||
|
return CheckResult.FAILED
|
||||||
|
return CheckResult.PASSED
|
||||||
|
|
||||||
|
check = AcdlTaggingStandard()
|
||||||
@@ -1,82 +0,0 @@
|
|||||||
"""Nova tagging standard custom Checkov rule (D-054, D-109 hard mode).
|
|
||||||
|
|
||||||
Checks that all taggable AWS resources have the required Nova tags:
|
|
||||||
nova:owner, nova:contract, nova:environment, nova:cost-center
|
|
||||||
|
|
||||||
In **hard mode** (P3, REQ-162): the rule hard-fails when a taggable resource
|
|
||||||
is missing any required `nova:*` tag, OR when a resource carries only the
|
|
||||||
legacy `acdl:*` tag keys (and no `nova:*` keys). P2 shipped warn mode
|
|
||||||
(`_WARN_MODE = True`) so the regression gate stayed green during the
|
|
||||||
parallel-tag transition window; P3 flips to hard-fail (`_WARN_MODE = False`)
|
|
||||||
once `nova:*` tags are emitted in terraform and the ABAC policy is swapped
|
|
||||||
to match `nova:*`. P5 keeps hard mode and additionally hard-fails on any
|
|
||||||
`acdl:*` tag key present at all (no legacy tolerated post-cutoff).
|
|
||||||
|
|
||||||
Closes the D-043 deferral (the SKIPPED NOVA_TAG_NAMING placeholder
|
|
||||||
becomes a real check). Renamed from acdl_tagging.py in P2 (REQ-158);
|
|
||||||
the Checkov rule ID ACDL_TAG_NAMING → NOVA_TAG_NAMING.
|
|
||||||
"""
|
|
||||||
|
|
||||||
from __future__ import annotations
|
|
||||||
|
|
||||||
import sys
|
|
||||||
|
|
||||||
from checkov.terraform.checks.resource.base_resource_check import BaseResourceCheck
|
|
||||||
from checkov.common.models.enums import CheckResult, CheckCategories
|
|
||||||
|
|
||||||
REQUIRED_TAGS = ("nova:owner", "nova:contract", "nova:environment", "nova:cost-center")
|
|
||||||
|
|
||||||
# Legacy acdl:* tag keys — the parallel-tag period (P3) emits both nova:*
|
|
||||||
# and acdl:*; P2 warn mode treats acdl:*-only tags as a warning, not a
|
|
||||||
# failure. The acdl:* VALUES in tagging-standard.json are left for P3.
|
|
||||||
LEGACY_TAGS = ("acdl:owner", "acdl:contract", "acdl:environment", "acdl:cost-center")
|
|
||||||
|
|
||||||
# Resources that support tags (exclude resources that have no tags attribute)
|
|
||||||
NON_TAGGABLE_TYPES = (
|
|
||||||
"aws_cloudfront_origin_access_control",
|
|
||||||
"aws_lambda_function_url",
|
|
||||||
"aws_route_table_association",
|
|
||||||
"aws_internet_gateway",
|
|
||||||
)
|
|
||||||
|
|
||||||
# P5 hard mode (D-109, REQ-164): `_WARN_MODE = False` (set in P3) AND
|
|
||||||
# any `acdl:*` tag key present at all is a hard FAIL (P5 tightens from
|
|
||||||
# P3's "acdl:*-only fails" to "any acdl:* key fails"). The legacy tag
|
|
||||||
# keys are fully removed from terraform (P3); any remaining `acdl:*` key
|
|
||||||
# is a rebrand regression.
|
|
||||||
_WARN_MODE = False
|
|
||||||
|
|
||||||
|
|
||||||
class NovaTaggingStandard(BaseResourceCheck):
|
|
||||||
def __init__(self):
|
|
||||||
name = "Ensure all taggable AWS resources have required Nova tags"
|
|
||||||
check_id = "NOVA_TAG_NAMING"
|
|
||||||
supported_resources = ["*"] # all resources
|
|
||||||
categories = [CheckCategories.GENERAL_SECURITY]
|
|
||||||
super().__init__(name=name, check_id=check_id, categories=categories, supported_resources=supported_resources)
|
|
||||||
|
|
||||||
def scan_resource_conf(self, conf, entity_type):
|
|
||||||
# Skip non-taggable resources
|
|
||||||
if entity_type in NON_TAGGABLE_TYPES:
|
|
||||||
return CheckResult.PASSED
|
|
||||||
# Check for a tags block
|
|
||||||
tags = conf.get("tags")
|
|
||||||
if not tags:
|
|
||||||
return CheckResult.FAILED
|
|
||||||
tag_keys = set()
|
|
||||||
if isinstance(tags, list) and tags:
|
|
||||||
tag_block = tags[0]
|
|
||||||
if isinstance(tag_block, dict):
|
|
||||||
tag_keys = set(tag_block.keys())
|
|
||||||
elif isinstance(tags, dict):
|
|
||||||
tag_keys = set(tags.keys())
|
|
||||||
# P5 (REQ-164): any legacy acdl:* tag key present = hard FAIL.
|
|
||||||
legacy_present = tag_keys & set(LEGACY_TAGS)
|
|
||||||
if legacy_present:
|
|
||||||
return CheckResult.FAILED
|
|
||||||
missing = [t for t in REQUIRED_TAGS if t not in tag_keys]
|
|
||||||
if not missing:
|
|
||||||
return CheckResult.PASSED
|
|
||||||
return CheckResult.FAILED
|
|
||||||
|
|
||||||
check = NovaTaggingStandard()
|
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
"""Wiz adapter — translate Wiz API results to Nova PolicyCheckResult records.
|
"""Wiz adapter — translate Wiz API results to ACDL PolicyCheckResult records.
|
||||||
|
|
||||||
Wiz is a SaaS security platform with a GraphQL API. This adapter
|
Wiz is a SaaS security platform with a GraphQL API. This adapter
|
||||||
translates Wiz issue records to the normalized PolicyCheckResult schema
|
translates Wiz issue records to the normalized PolicyCheckResult schema
|
||||||
@@ -186,37 +186,8 @@ def is_configured():
|
|||||||
return bool(os.environ.get("WIZ_API_TOKEN") and os.environ.get("WIZ_API_URL"))
|
return bool(os.environ.get("WIZ_API_TOKEN") and os.environ.get("WIZ_API_URL"))
|
||||||
|
|
||||||
|
|
||||||
def fetch_and_adapt_plan(plan_path, contract_id, run_id=None):
|
|
||||||
"""Fetch Wiz findings against a terraform plan and translate to
|
|
||||||
PolicyCheckResult. REQ-250 (v1.21): Wiz scans the terraform plan
|
|
||||||
output. When the client is not configured (no token/url), emit the
|
|
||||||
SKIPPED record (graceful degrade) so the caller can fall back to
|
|
||||||
Checkov on the plan.
|
|
||||||
"""
|
|
||||||
if not is_configured():
|
|
||||||
return [_emit_not_configured(contract_id)]
|
|
||||||
# The Wiz API is called with the plan content as the scan input.
|
|
||||||
client = WizClient()
|
|
||||||
issues = client.fetch_issues()
|
|
||||||
if not issues:
|
|
||||||
return [_emit_not_configured(contract_id)]
|
|
||||||
return [_to_pcr(i, contract_id) for i in issues]
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
import argparse
|
if len(sys.argv) != 3:
|
||||||
parser = argparse.ArgumentParser(description="Wiz adapter (REQ-250: plan-mode supported)")
|
print("usage: wiz_adapter.py <wiz_issues.json> <contract-id>", file=sys.stderr)
|
||||||
parser.add_argument("wiz_json", nargs="?", help="wiz_issues.json (legacy positional mode)")
|
sys.exit(2)
|
||||||
parser.add_argument("contract_id_pos", nargs="?", help="contract-id (legacy positional mode)")
|
print(json.dumps(adapt(sys.argv[1], sys.argv[2]), indent=2))
|
||||||
parser.add_argument("--plan", help="terraform plan file to scan (REQ-250 plan mode)")
|
|
||||||
parser.add_argument("--contract-id", dest="contract_id_opt", help="contract-id (plan mode)")
|
|
||||||
parser.add_argument("--run-id", help="run-id for the plan scan (plan mode)")
|
|
||||||
args = parser.parse_args()
|
|
||||||
if args.plan:
|
|
||||||
cid = args.contract_id_opt or ""
|
|
||||||
out = fetch_and_adapt_plan(args.plan, cid, run_id=args.run_id)
|
|
||||||
print(json.dumps(out, indent=2))
|
|
||||||
elif args.wiz_json and args.contract_id_pos:
|
|
||||||
print(json.dumps(adapt(args.wiz_json, args.contract_id_pos), indent=2))
|
|
||||||
else:
|
|
||||||
parser.error("either --plan <file> --contract-id <id> OR <wiz_issues.json> <contract-id>")
|
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
# Nova sample consumer contract — microservice module (dev)
|
# ACDL sample consumer contract — microservice module (dev)
|
||||||
# Per-environment contract (REQ-105). Promotion = running the dev job;
|
# Per-environment contract (REQ-105). Promotion = running the dev job;
|
||||||
# no environment field editing. Interpolation resolves against dev.json.
|
# no environment field editing. Interpolation resolves against dev.json.
|
||||||
id: msvc
|
id: msvc
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
# Nova sample consumer contract — microservice module (dr)
|
# ACDL sample consumer contract — microservice module (dr)
|
||||||
# Per-environment contract (REQ-105). Promotion = running the dr job;
|
# Per-environment contract (REQ-105). Promotion = running the dr job;
|
||||||
# no environment field editing. Interpolation resolves against dr.json.
|
# no environment field editing. Interpolation resolves against dr.json.
|
||||||
id: msvc
|
id: msvc
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
# Nova sample consumer contract — microservice module (prod)
|
# ACDL sample consumer contract — microservice module (prod)
|
||||||
# Per-environment contract (REQ-105). Promotion = running the prod job;
|
# Per-environment contract (REQ-105). Promotion = running the prod job;
|
||||||
# no environment field editing. Interpolation resolves against prod.json.
|
# no environment field editing. Interpolation resolves against prod.json.
|
||||||
id: msvc
|
id: msvc
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
# Nova sample consumer contract — microservice module (qa)
|
# ACDL sample consumer contract — microservice module (qa)
|
||||||
# Per-environment contract (REQ-105). Promotion = running the qa job;
|
# Per-environment contract (REQ-105). Promotion = running the qa job;
|
||||||
# no environment field editing. Interpolation resolves against qa.json.
|
# no environment field editing. Interpolation resolves against qa.json.
|
||||||
id: msvc
|
id: msvc
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
# Nova sample consumer contract — microservice module (dev)
|
# ACDL sample consumer contract — microservice module (dev)
|
||||||
#
|
#
|
||||||
# Reference example for an ECS Fargate microservice deployment.
|
# Reference example for an ECS Fargate microservice deployment.
|
||||||
# Interpolation (D-081): bucket_name uses the naming pattern that includes
|
# Interpolation (D-081): bucket_name uses the naming pattern that includes
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
# Nova sample consumer contract — static-assets module (dev)
|
# ACDL sample consumer contract — static-assets module (dev)
|
||||||
# Per-environment contract (REQ-105). The dev default
|
# Per-environment contract (REQ-105). The dev default
|
||||||
# (contracts/static-assets.yml) remains for backwards compat; this file
|
# (contracts/static-assets.yml) remains for backwards compat; this file
|
||||||
# is the explicit per-env dev contract. Interpolation resolves against dev.json.
|
# is the explicit per-env dev contract. Interpolation resolves against dev.json.
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
# Nova sample consumer contract — static-assets module (dr)
|
# ACDL sample consumer contract — static-assets module (dr)
|
||||||
# Per-environment contract (REQ-105). Promotion = running the dr job;
|
# Per-environment contract (REQ-105). Promotion = running the dr job;
|
||||||
# no environment field editing. Interpolation resolves against dr.json.
|
# no environment field editing. Interpolation resolves against dr.json.
|
||||||
id: assets
|
id: assets
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
# Nova sample consumer contract — static-assets module (prod)
|
# ACDL sample consumer contract — static-assets module (prod)
|
||||||
# Per-environment contract (REQ-105). Promotion = running the prod job;
|
# Per-environment contract (REQ-105). Promotion = running the prod job;
|
||||||
# no environment field editing. Interpolation resolves against prod.json.
|
# no environment field editing. Interpolation resolves against prod.json.
|
||||||
id: assets
|
id: assets
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
# Nova sample consumer contract — static-assets module (qa)
|
# ACDL sample consumer contract — static-assets module (qa)
|
||||||
# Per-environment contract (REQ-105). Promotion = running the qa job;
|
# Per-environment contract (REQ-105). Promotion = running the qa job;
|
||||||
# no environment field editing. Interpolation resolves against qa.json.
|
# no environment field editing. Interpolation resolves against qa.json.
|
||||||
id: assets
|
id: assets
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
# Nova sample consumer contract — static-assets module (dev)
|
# ACDL sample consumer contract — static-assets module (dev)
|
||||||
#
|
#
|
||||||
# This is the reference example for a consumer contract. It declares:
|
# This is the reference example for a consumer contract. It declares:
|
||||||
# id: short operational acronym (becomes stack.name for state, tags, evidence)
|
# id: short operational acronym (becomes stack.name for state, tags, evidence)
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user