Compare commits

..

15 Commits

Author SHA1 Message Date
Jon Chery ed36519223 verify(P14): schema-driven-outputs-and-cache — 4-layer verify PASS + ship
VERIFY: structural — schema-driven outputs + cache; behavioral — 47 tests + CI PASS; quality — mid-milestone checkpoint clean.

---ci---
project: acdl
phase: 14
milestone: v1.16
status: complete
phase_role: execution
requirements:
  covered: [REQ-178]
  partial: []
---/ci---
2026-08-01 13:14:31 +00:00
Jon Chery aa3e385606 verify(P13): split-regression-verify — 4-layer verify PASS + ship
VERIFY: structural — CLI extracted (G-113); behavioral — 22-cap import + CI PASS; quality — library/CLI separation.

---ci---
project: acdl
phase: 13
milestone: v1.16
status: complete
phase_role: execution
requirements:
  covered: [REQ-177]
  partial: []
---/ci---
2026-08-01 13:11:59 +00:00
Jon Chery ab3a9a8548 verify(P12): split-contract-resolver — 4-layer verify PASS + ship
VERIFY: structural — 2 modules extracted + re-export shim (G-113); behavioral — 16 tests + CLI + CI PASS; quality — behavior unchanged.

---ci---
project: acdl
phase: 12
milestone: v1.16
status: complete
phase_role: execution
requirements:
  covered: [REQ-176]
  partial: []
---/ci---
2026-08-01 13:09:50 +00:00
Jon Chery 5492308140 verify(P11): contract-ingestor-payload-validation — 4-layer verify PASS + ship
VERIFY: structural — size cap + schema validation + aligned caps; behavioral — 51 tests + CI PASS; security — unbounded write blocked.

---ci---
project: acdl
phase: 11
milestone: v1.16
status: complete
phase_role: execution
requirements:
  covered: [REQ-175]
  partial: []
---/ci---
2026-08-01 13:07:08 +00:00
Jon Chery 76714bebc4 verify(P10): contract-ingestor-defense-in-depth — 4-layer verify PASS + ship
VERIFY: structural — fail-closed + env discovery; behavioral — 49 tests + CI PASS; security — defense-in-depth on IAM identity.

---ci---
project: acdl
phase: 10
milestone: v1.16
status: complete
phase_role: execution
requirements:
  covered: [REQ-174]
  partial: []
---/ci---
2026-08-01 12:58:23 +00:00
Jon Chery f12f6edd23 verify(P9): run-platform-split — 4-layer verify PASS + ship
VERIFY: structural — 2 helpers extracted (sourced, G-112); behavioral — CI PASS + regression gate 18V+4S PASS (G-111, D-118); quality — run_platform.sh ~80 lines smaller.

---ci---
project: acdl
phase: 9
milestone: v1.16
status: complete
phase_role: execution
requirements:
  covered: [REQ-173]
  partial: []
---/ci---
2026-08-01 12:52:07 +00:00
Jon Chery 1db5ca8286 verify(P8): workflow-generator-dedup — 4-layer verify PASS + ship
VERIFY: structural — generator + sources; behavioral — 98 tests + CI PASS; quality — ~20KB dedup, single source of truth.

---ci---
project: acdl
phase: 8
milestone: v1.16
status: complete
phase_role: execution
requirements:
  covered: [REQ-172]
  partial: []
---/ci---
2026-08-01 12:41:10 +00:00
Jon Chery 93ae9e4a39 verify(P7): contract-resolver-envloader-and-kind — 4-layer verify PASS + ship
VERIFY: structural — envloader dedup + kind field; behavioral — 49 tests + CI PASS; quality — fragile is_l2 heuristic replaced.

---ci---
project: acdl
phase: 7
milestone: v1.16
status: complete
phase_role: execution
requirements:
  covered: [REQ-171]
  partial: []
---/ci---
2026-08-01 12:38:56 +00:00
Jon Chery d3179fff37 verify(P6): run-platform-deadcode-and-hitl-fn — 4-layer verify PASS + ship
VERIFY: structural — HITL fn extracted + deadcode/config; behavioral — syntax clean + CI PASS; quality — ~14 lines saved.

---ci---
project: acdl
phase: 6
milestone: v1.16
status: complete
phase_role: execution
requirements:
  covered: [REQ-170]
  partial: []
---/ci---
2026-08-01 12:36:40 +00:00
Jon Chery c029b102a3 verify(P5): regression-verify-dedup — 4-layer verify PASS + ship
VERIFY: structural — 3 shared helpers extracted; behavioral — 611 tests + CI PASS; quality — behavior preserved, ~70 lines saved.

---ci---
project: acdl
phase: 5
milestone: v1.16
status: complete
phase_role: execution
requirements:
  covered: [REQ-169]
  partial: []
---/ci---
2026-08-01 12:31:08 +00:00
Jon Chery 2806c6c3ed verify(P4): migrate-ssm-except-narrowing — 4-layer verify PASS + ship
VERIFY: structural — narrowed excepts; behavioral — 48 tests + CI PASS; security — non-ParameterNotFound errors surface; quality — 2 new + 2 updated tests.

---ci---
project: acdl
phase: 4
milestone: v1.16
status: complete
phase_role: execution
requirements:
  covered: [REQ-168]
  partial: []
---/ci---
2026-08-01 12:24:30 +00:00
Jon Chery e048acd4dd verify(P3): dead-code-and-stale-prefix-cleanup — 4-layer verify PASS + ship
VERIFY: structural — dead export + stale comments + prefixes gone; behavioral — 616 tests + CI PASS; quality — env-override test updated.

---ci---
project: acdl
phase: 3
milestone: v1.16
status: complete
phase_role: execution
requirements:
  covered: [REQ-167]
  partial: []
---/ci---
2026-08-01 12:20:40 +00:00
Jon Chery 9421442afd verify(P2): user-facing ACDL→Nova sweep — 4-layer verify PASS + ship
VERIFY: structural — all user-facing strings Nova; behavioral — 79 tests
+ CI PASS; security — no creds; quality — new onboarding Nova-header test.
REQ-166 complete. Internal ship_phase.sh helper added.

---ci---
project: acdl
phase: 2
milestone: v1.16
status: complete
phase_role: execution
requirements:
  covered: [REQ-166]
  partial: []
---/ci---
2026-08-01 12:12:45 +00:00
Jon Chery bb43d94563 verify(P1): state-bucket + Kyverno rebrand — 4-layer verify PASS + ship
VERIFY: structural — adapter.py:117 nova-tfstate-*; kyverno policy nova:*
labels; behavioral — 80 tests PASS + run_ci.sh 3-stage PASS; security —
emitted backend no longer points at a non-existent bucket; quality —
new test_adapt_emits_nova_state_bucket regression guard. REQ-165 complete.

---ci---
project: acdl
phase: 1
milestone: v1.16
status: complete
phase_role: execution
requirements:
  covered: [REQ-165]
  partial: []
---/ci---
2026-07-30 15:17:43 +00:00
Jon Chery ee5c372e65 docs(P00): complete pre-execution phase — v1.16 NFR scope
Phase 0: SPECIFY → CLARIFY → RESEARCH → IDEATE → PLAN → GRILL.
NFR milestone v1.16 (Nova Simplification), 20 execution phases + final.
Tags on v1.15.x line: v1.15.5 (this phase) → v1.15.6..v1.15.25 (P1-P20) →
v1.15.26 (P21 final = release).

Scope (D-113..D-119): Simplify without regressions, Security,
Maintainability, User/Developer Experience, No Humans Onboarding Flow
(request-path only; real AWS provisioning deferred). Regression gate
(D-118, G-111) gates P9 + P21 at 20/22 Verified + 2 Skipped.

Grill: PASS-with-binding (G-111..G-113, E-002 deferred to P21).

---ci---
project: acdl
phase: 0
milestone: v1.16
status: complete
phase_role: pre_execution
requirements:
  covered: [REQ-165, REQ-166, REQ-167, REQ-168, REQ-169, REQ-170, REQ-171, REQ-172, REQ-173, REQ-174, REQ-175, REQ-176, REQ-177, REQ-178, REQ-179, REQ-180, REQ-181, REQ-182, REQ-183, REQ-184]
  partial: []
---/ci---
2026-07-30 15:12:32 +00:00
222 changed files with 8589 additions and 15150 deletions
-145
View File
@@ -734,148 +734,3 @@ stay **16/16 Verified** throughout the rebrand. P2/P3/P4 update test
fixtures that reference `ACDL`/`acdl` so the gate stays green. No fixtures that reference `ACDL`/`acdl` so the gate stays green. No
capability is added, removed, or reclassified in v1.15 — the rebrand is capability is added, removed, or reclassified in v1.15 — the rebrand is
nomenclature + identifiers, not behavior. 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 (P1P2, 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.
-89
View File
@@ -462,92 +462,3 @@ status: complete
phase_role: final phase_role: final
audit: pass audit: pass
---/ci--- ---/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 P0P21, 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---
-66
View File
@@ -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.
+6 -16
View File
@@ -1,19 +1,9 @@
{ {
"phase": 0, "phase": 1,
"stage": "complete", "stage": "execute",
"milestone": "v1.24", "milestone": "v1.16",
"phase_role": "pre_execution", "phase_role": "execution",
"attempts": 0, "attempts": 0,
"updated_at": "2026-08-12T02:20:00Z", "updated_at": "2026-07-30T15:30:00Z",
"project": "acdl", "milestone_complete": false
"milestone_complete": false,
"tag": "v1.23.0",
"tag_line": "v1.23.x",
"release": {
"forge": "gitea",
"release_id": 635,
"tag": "v1.23.0"
},
"requirements": ["REQ-276","REQ-277","REQ-278","REQ-279","REQ-280","REQ-281","REQ-282","REQ-283","REQ-284","REQ-285","REQ-286","REQ-287","REQ-288","REQ-289","REQ-290"],
"phases": ["P1:consumer-guide-fixes","P2:env-transition-detect-and-destroy","P3:env-transition-tests","P4:final-review-ship"]
} }
-109
View File
@@ -1,109 +0,0 @@
# CLARIFY — v1.24 Consumer Guide Accuracy & Env-Promotion Lifecycle Enforcement
> **Autonomy:** full. Ambiguities are auto-resolved with assumption logging
> per `config.json autonomy.level: "full"`. No human escalation.
## Ambiguities Identified
### A1 — Step 8 "change environment" vs Per-env section "no field editing"
**Ambiguity:** The consumer guide contains two mutually-exclusive promotion
models. Step 8 (line 290) says "Change `environment` in your contract." The
"Per-environment deployment" section (line 398) says "you do not edit the
`environment:` field… Promotion = running the matching job." The test
`test_consumer_guide_states_no_field_editing` asserts the no-editing model.
**User directive (binding):** Both shapes are supported. Shape A (edit
environment in-place) is valid AND must trigger a destroy of the prior env.
Shape B (per-env caller workflows) is the alternative. The test must be
updated to assert both shapes.
**Resolution (auto, confidence 0.95):** Adopt the user's directive. Step 8
is rewritten to document Shape A with destroy-then-rebuild semantics. The
per-env section is preserved as Shape B with a lead sentence distinguishing
it. The test is renamed and a new test asserts the destroy semantics. This
is already captured in REQ-279, REQ-280, REQ-290.
### A2 — Prior-env source of truth: DynamoDB vs state-bucket scan vs SSM
**Ambiguity:** Three options for detecting the prior environment: (a) query
the `nova-contracts` DynamoDB table, (b) scan the state bucket for other env
prefixes, (c) record last-applied env in an SSM parameter.
**Resolution (auto, confidence 0.85):** DynamoDB `nova-contracts` table
(user-selected). It already exists, is written by the contract ingestor
Lambda (`core/lambda/contract_ingestor.py:160-170`), and has the right shape
(PK `consumerRepo`, SK `contractId#submittedAt`, `environment` attribute).
A new `#LAST_APPLIED` SK suffix is added for the record-applied-env step
(REQ-283). This avoids coupling the platform to a specific state-bucket
layout (which differs across envs/accounts) and avoids a new SSM dependency.
**Assumption:** The `nova-contracts` table is accessible from the deploy
role via the same ABAC scoping that the contract ingestor uses. If the
table is not accessible (e.g., local/CI mode without DynamoDB), the detect
step logs a warning and returns `None` (conservative — no prior env
assumed). This is documented in REQ-282.
### A3 — Cross-account destroy
**Ambiguity:** If the prior env (e.g., dev) and new env (e.g., qa) are in
different AWS accounts, the destroy step needs the prior env's role
credentials. The current scaffold uses one account.
**Resolution (auto, confidence 0.80):** v1.24 targets the same-account
case. Cross-account destroy is explicitly out of scope (documented in the
Out of Scope section). The `run_platform.sh` Step 0b notes this limitation.
A future milestone handles cross-account destroy via a pre-step that
assumes the prior env's role. This is the pragmatic path — the scaffold
(`core/environments/dev.json`) is single-account today.
### A4 — Version tag in docs: `@v1.19` vs `ref: v1.9`
**Ambiguity:** The consumer guide says `uses: nova/.github/workflows/deploy.yml@v1.19`
but the actual `.github/workflows/deploy.yml` checks out the platform repo
at `ref: v1.9`. The reference table says sample contracts "use `@v1.19`"
but the sample contracts don't carry `uses:` (they're contracts, not
workflows).
**Resolution (auto, confidence 0.90):** REQ-281 corrects the reference
table wording to "used with caller workflow `@v1.19`" (the version pin
lives in the caller workflow, not the contract). The `@v1.19` tag in the
consumer-facing docs is the documented current version; the `ref: v1.9` in
deploy.yml is the platform-internal checkout ref. These are two different
references (consumer → platform workflow tag; platform workflow → platform
repo ref). The guide's `@v1.19` stays as the consumer-facing version. No
change to deploy.yml's `ref: v1.9` (that's an internal platform concern,
out of scope for this milestone).
### A5 — Should Shape A destroy go through the HITL decommission pipeline?
**Ambiguity:** The decommission pipeline (2-step, HITL SRE gates) exists for
stack teardown. Should env-transition destroy use it?
**Resolution (auto, confidence 0.85):** No. Env-transition is an automated
lifecycle step, not an explicit decommission. The destroy runs as a direct
`terraform destroy -auto-approve` against the prior env's state (REQ-284).
The decommission pipeline remains for explicit stack teardown with SRE
gates. This is documented in the Out of Scope section. Rationale: the
consumer already has HITL attestation on the *new* env (qa/prod/dr gates);
requiring a second SRE gate for the prior env's destroy would block
autonomous dev→qa promotion, contradicting the "lower environments are
autonomous" tenet.
### A6 — Phase count and ordering
**Ambiguity:** The requirements traceability table shows 3 phases (P1:
docs, P2: feat, P3: test) but the roadmap entry says "4 phases."
**Resolution (auto, confidence 0.90):** 4 phases = P0 (pre-execution) + P1
(docs fixes) + P2 (env-transition feat) + P3 (tests) + P4 (final
review/ship). The "4 phases" in the roadmap counts execution phases (P1-P3)
+ final (P4). This matches the run.md phase model (P0 pre-execution, P1..PN
execution, P N+1 final). The traceability table lists P1-P3 (execution);
P4 is the final phase (review + audit + ship, no new requirements).
## Clarification Commit
No changes to REQUIREMENTS.md or PROJECT.md from clarify — the ambiguities
are resolved and already captured in the requirements (REQ-276..290) and
the Out of Scope section. The resolutions above are logged for traceability.
+616 -178
View File
@@ -1,200 +1,638 @@
# CIAgent Grill Report # CIAgent Grill Report
## Run: 2026-08-12 (mode: self-grill, focus: all axes) — v1.24 Consumer Guide Accuracy & Env-Promotion Lifecycle Enforcement ## Run: 2026-07-27 19:30 (mode: interactive, focus: all)
### Overall Verdict: PROCEED (confidence: 0.82) ### Verdict: Proceed with conditions (confidence: 0.72)
The plan is sound. The user directive is clear and binding. The code Two escalations must be resolved before the leadership pitch:
integration points are confirmed by inspection. Two binding revisions - **G-005 (risks):** 6 cloud capabilities (CAP-017..022) are deploy-unverified.
applied (both low-risk doc clarifications). No escalations. **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.11v1.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).
--- ---
## Axis 1: Feasibility ## Run: 2026-07-29 20:25 (mode: adversarial, focus: v1.14 NFR plan)
**Challenge:** Can `run_platform.sh` Step 0b actually run `terraform ### Verdict: FEASIBLE WITH BINDING DECISIONS (confidence: 0.72)
destroy` against the prior env's state without the prior env's AWS
credentials?
**Response:** In the same-account case (the scaffold today, per The v1.14 milestone is a sound, well-evidenced NFR sweep with a genuine,
`core/environments/dev.json`), yes — the deploy role has access to the traceable backlog. Not fundamentally infeasible. Four binding decisions
shared state bucket and the resources are in the same account. The close plan defects + unverified assumptions that would otherwise re-expose
`terraform init -reconfigure` re-points to the prior env's state key the v1.11 4-VPC failure mode. One escalation (E-001) auto-resolved at full
within the same bucket. Cross-account is explicitly out of scope autonomy with assumption logging.
(D-205). **Confidence: 0.85.**
**Challenge:** Does `deletion_protection: false` injection work the same ### 9-Axis scores
way as decommission Step 2?
**Response:** Yes. `scripts/run_decommission.sh:34-37` sets | Axis | Confidence | Forcing question (short) |
`res['nfrs']['deletion_protection'] = False` on every resource. The |------|-----------|---------------------------|
contract resolver propagates `inputs.deletion_protection` to children's | 1 Business | 0.80 | Real backlog (5 P1 + 4 P2 + 6 swallowed errors + 15+ hardcoded IDs); cancellation survivable but inherits decay risk |
NFRs (`core/contract_resolver.py:360-372`). The env-transition destroy | 2 Scope | 0.70 | User-directed + frozen; P13 has a hidden feature door (implement vs remove); P2 conditional-child edges past wiring |
step must resolve with `environment_override=prior_env` AND inject | 3 Architecture | 0.62 | P8 grep unsatisfiable for backend blocks; P8 state-bucket continuity unguarded; P9 IAM naming unverified; P4/P8 file overlap |
`deletion_protection=false` into the contract inputs before resolving. | 4 People | 0.85 | Agentic single-operator; runtime availability is the key-person risk |
This is a confirmed pattern. **Confidence: 0.90.** | 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 |
**Verdict:** FEASIBLE. ### Binding Decisions
## Axis 2: Scope | 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 |
**Challenge:** Is 4 phases (P1-P3 + P4) the right size, or is this ### Escalations
over-scoped?
**Response:** 15 requirements across 3 execution phases is - **[E-001] risks** — P8 state-bucket continuity re-exposes the v1.11 4-VPC
well-scoped. P1 (7 REQs, all docs/test) is the largest by count but the root cause. G-102 proposes a binding mitigation (bind fallback + wire env
smallest by effort (text edits + test assertions). P2 (6 REQs, feat) is into workflow), but the residual risk (a future full-mode lifecycle run
the core implementation. P3 (2 REQs, test) is coverage. P4 is final with a misconfigured env orphans live state and re-creates resources)
review. This is a tight, coherent milestone. **Confidence: 0.88.** cannot be reduced below 0.20 by plan-level decisions alone. **Auto-
resolved at full autonomy (D-101):** accept the residual risk; G-102's
**Challenge:** Should the cross-account destroy be in scope? binding mitigation (fallback bound to live account ID + workflow env
wiring) is the control. The lifecycle pipeline defaults to plan-only
**Response:** No. The scaffold is single-account. Adding cross-account (REQ-134) — full-mode runs are workflow_dispatch only, reducing the
would require assuming the prior env's role, which needs a trust policy accident surface. If the user prefers zero residual risk, direct that
the scaffold doesn't have yet. Deferring is pragmatic. The Out of Scope P8 exclude the state-bucket name from externalization entirely
section documents this. **Confidence: 0.85.** (externalize only resource ARNs, leave the backend `bucket` literal).
Confidence 0.55; auto-resolved per `config.autonomy.level=full`.
**Verdict:** PROPERLY-SCOPED.
## Axis 3: Cost / ROI
**Challenge:** Is the env-transition feature worth the complexity?
**Response:** Yes. The user identified a real orphaned-resources risk
that violates the platform's full-lifecycle-management mission. The
fix is a ~80-line Python module + a shell block. The alternative
(blocking env edits, forcing Shape B) contradicts the user's directive.
The ROI is high: closes a real lifecycle gap with minimal code.
**Confidence: 0.90.**
**Verdict:** JUSTIFIED.
## Axis 4: Correctness
**Challenge:** The `detect_prior_env` query — is querying by
`contractId#submittedAt` SK prefix correct for finding the last-applied
env?
**Response:** The `nova-contracts` table has PK `consumerRepo` and SK
`contractId#submittedAt`. To find the last record for a given
contractId, we query by PK `consumerRepo` + SK `begins_with
"contractId#"` + FilterExpression `status = "submitted"` (or
`#LAST_APPLIED`), sort by `submittedAt` desc, take the first. This is
correct DynamoDB pattern. The `record_applied_env` step writes a new
item with SK `contractId#LAST_APPLIED#<timestamp>` so the detect step
can filter by `begins_with "contractId#LAST_APPLIED#"`. **Confidence:
0.85.**
**Challenge:** What if the DynamoDB table doesn't exist in local/CI
mode?
**Response:** The detect step catches `ClientError` / `EndpointNotFound`,
logs a warning, and returns `None` (no prior env). The pipeline proceeds
normally. This is the conservative path — no false-positive destroys.
**Confidence: 0.90.**
**Verdict:** CORRECT.
## Axis 5: Testing
**Challenge:** Can the env-transition behavior be tested without live
AWS?
**Response:** Yes. `test_env_transition.py` uses moto for DynamoDB
(mock_aws pattern from `test_contract_ingestor.py:74-110`).
`test_run_platform_env_transition.py` uses shell-text assertions
(pattern from `test_pipeline.py:79-95`). No live AWS needed.
**Confidence: 0.92.**
**Verdict:** TESTABLE.
## Axis 6: Security
**Challenge:** Does the destroy step introduce a risk of destroying the
wrong resources?
**Response:** The destroy targets the prior env's state key
(`spike/{id}/{prior_env}/terraform.tfstate`). The state key is
deterministic and env-scoped. The destroy can only affect resources in
that state file. The `deletion_protection=false` injection is scoped to
the destroy step only — the new env's apply runs with the default
`deletion_protection=true`. **Confidence: 0.88.**
**Challenge:** Could a malicious consumer trigger a destroy of another
consumer's resources?
**Response:** No. The DynamoDB query is scoped by PK `consumerRepo`
(the consumer's own repo identity). The destroy runs under the
consumer's ABAC-scoped deploy role, which can only touch resources
tagged `nova:owner=<consumer-repo>`. A consumer cannot query or destroy
another consumer's stack. **Confidence: 0.90.**
**Verdict:** SECURE.
## Axis 7: Maintainability
**Challenge:** Is the `core/env_transition.py` module a clean
abstraction or a one-off?
**Response:** It's a reusable module with two functions
(`detect_prior_env`, `record_applied_env`) that encapsulate the
DynamoDB query logic. It can be extended for cross-account destroy in a
future milestone. The shell Step 0b is a thin orchestrator. This is
maintainable. **Confidence: 0.85.**
**Verdict:** MAINTAINABLE.
## Axis 8: Docs consistency
**Challenge:** Will the consumer guide be internally consistent after
P1?
**Response:** The 5 fixes address all known inconsistencies: field
table ↔ schema, Step 2 ↔ Step 4, Step 5 ↔ environments doc, Step 8 ↔
per-env section, reference table ↔ sample contracts. The test updates
assert both shapes are documented. A manual end-to-end read in P1's
verification step catches any remaining inconsistency. **Confidence:
0.88.**
**Verdict:** CONSISTENT.
## Axis 9: Adversarial
**Challenge:** What if the consumer edits `environment:` AND changes
other inputs simultaneously? Does the destroy-then-apply still work?
**Response:** Yes. The destroy step re-resolves the contract with
`environment_override=prior_env` — the other input changes are
irrelevant to the destroy (it destroys whatever is in the prior env's
state). The new apply resolves with the new env + new inputs. The two
operations are independent. **Confidence: 0.85.**
**Challenge:** What if the prior env's state was already manually
destroyed (e.g., via decommission)?
**Response:** `terraform destroy` against an empty state is a no-op
(exits 0). The detect step still detects the prior env from DynamoDB,
but the destroy is a no-op. The apply proceeds. This is correct
behavior — no false failure. **Confidence: 0.88.**
**Verdict:** ROBUST.
--- ---
## Binding revisions applied ## Run: 2026-07-30 (mode: interactive, focus: v1.15-Nova rebrand, all 9 axes)
1. **R1 (docs):** Add to RESEARCH.md pitfalls: the destroy step must ### Verdict: Proceed with conditions (confidence: 0.82)
inject `deletion_protection=false` into the contract inputs before
re-resolving with `environment_override=prior_env`. Without this,
`prevent_destroy` lifecycle blocks (REQ-86) block the destroy. This
is already noted in RESEARCH §5 pitfall 3 and PLAN P2 implementation
note 3. No change needed — already captured.
2. **R2 (docs):** Clarify in PLAN P2 that the `record_applied_env` SK A Major/breaking rebrand (ACDL → Nova) across prose, decks, code, env vars,
format is `contractId#LAST_APPLIED#<timestamp>` so the detect step consumer path, SSM path, AWS tag keys, and AWS resource names — 4 execution
can query `begins_with "contractId#LAST_APPLIED#"`. This is already phases + 1 final. The plan is technically sound and the scope is user-directed
in RESEARCH §2 and GRILL Axis 4. No change needed — already captured. (D-102..D-112). Three binding mitigations surfaced (G-104, G-106, G-108); the
rest accept the plan as written. Two findings carry residual risk that is
accepted at full autonomy (G-103, G-107). No escalations remain open — all
auto-resolved with assumption logging per `config.autonomy.level=full`.
## Escalations The single most material correction: **the versioning scheme was wrong**.
The plan tagged a Major/breaking milestone on the v1.14.x PATCH line
(`v1.14.5` = release), contradicting every prior breaking milestone in the
project (v1.1→v1.2.0, v1.5→v1.5.0, v1.11→v1.11.0 — all minor bumps). The
quoted "Major = progressive minor per phase" rule does not exist in any repo
file. **G-104 binds: re-tag as v1.15.x minor-bumped phases** (P1→v1.15.0 …
P5→v1.15.4, with v1.15.4 IS the milestone release).
None. All challenges resolved at full autonomy. ### Per-axis findings
#### Axis 1 — Feasibility
**Challenge:** Can the full rebrand (1,465 `ACDL`/`acdl` occurrences across 205
files, 21 env vars, 11 AWS resources, 5 tag keys, 67 SSM refs, 23 consumer-path
refs) actually be done in 4 execution phases? The migration ordering
(docs→code/env→SSM/tags→AWS resources→final) is sound: P1 has no runtime impact,
P2's dual-read fallback prevents deployment breakage, P3's parallel-tag period
prevents ABAC lockout, P4's staged terraform migration prevents a big-bang
failure. The phase dependencies (P2 depends on P1's migration guide; P3 depends
on P2's dual-read + nova_tagging warn mode; P4 depends on P3's hard-mode tag
enforcement; P5 depends on all) are correctly ordered. **Confidence 0.85** that
the 4-phase structure is feasible. The `terraform init -migrate-state` approach
for the state bucket is the documented, correct mechanism (back up state JSON
first). No hidden dependencies found: the `.env.secrets` direct-read path
(G-106) and the Gitea secrets rotation (G-108) are the only mechanic gaps, both
now bound. **Verdict: ACCEPT-AS-IS.** **G-103.**
#### Axis 2 — Scope
**Challenge:** Is the full AWS resource rename WITH migration (downtime
accepted) over-scoped for a rebrand? D-102 locked this as user-directed. The
alternative (rename code only, leave AWS resources as `acdl-*`) would leave a
permanent brand inconsistency between code and cloud — acceptable for an NFR
patch, not for a "Major/breaking" milestone. The S&P visual theme is correctly
out of scope (D-107). The real Gitea repo name stays `acdl` (D-105) — sensible
(repo rename is a separate operational burden). Past Gitea release titles stay
`ACDL vX.Y.Z` (forward-only) — sensible (no history rewrite). Git branch/tag
naming has no brand name (D-112) — sensible. **Missing from scope:** the CI
workflow secret-references (`.gitea/workflows/*` `secrets.ACDL_*`) — P2 task 3
creates `NOVA_*` Gitea secrets but the plan does not show the workflow YAML
`secrets:` references being updated; G-108 binds the mitigation. **Confidence
0.80.** **Verdict: ACCEPT-AS-IS.** **G-104** (versioning — see Axis 5).
#### Axis 3 — Cost
**Challenge:** What's the real cost (downtime, person-hours, risk) and is it
justified for a *rebrand*? Per A1 (conf 0.9), no live AWS apply during P0P4 —
so the migration scripts are authored but not executed; the live apply is an
operator runbook step. Person-hours are the agent's own (autonomous OSS
reference, G-003 carries forward). Downtime is accepted (D-102) but deferred to
the operator runbook. Token cost: the 1,465-occurrence rename across 205 files
is a large but mechanical edit — the explore survey already quantified the
mechanical-vs-judgment split. The risk cost (DynamoDB data loss, state bucket
corruption, ABAC lockout) is mitigated by the staged ordering + dual-read +
parallel-tag — all plan-validated, not live-applied. For an OSS reference with
0 consumer adoption (PROJECT.md:487), the cost is bounded. **Confidence 0.80.**
**Verdict: ACCEPT-AS-IS.** **G-105.**
#### Axis 4 — Schedule / risk
**Challenge:** DynamoDB data loss, state bucket migration, ABAC breakage,
consumer disruption. The mitigations: (a) DynamoDB scan+copy with row-count
verification, keep old tables until verified (manual post-verification deletion
— point of no return documented); (b) state bucket `terraform init
-migrate-state` with state JSON backup first; (c) parallel-tag ABAC period
(emit nova:* + acdl:* → swap policy → remove acdl:*); (d) consumer disruption
mitigated by the dual-read fallback (P2P4) + the migration guide (P1). The top
3 assumptions: A1 (no live apply — conf 0.9, verified by the established
v1.11v1.14 pattern), A2 (.env.secrets keys renamed, values stay — conf 0.85,
now bound by G-106), A3 (Gitea release API reachable — conf 0.8, verified HTTP
200). The single risk that could kill the project: state bucket corruption
during `-migrate-state` — mitigated by the backup-first runbook step. No
pre-mortem beyond the runbook is documented, but the staged ordering IS the
de-facto pre-mortem mitigation. **Confidence 0.78.** **Verdict: ACCEPT-AS-IS.**
**G-106.**
#### Axis 5 — Technical soundness
**Challenge:** Is the dual-read fallback design sound? Is the parallel-tag ABAC
migration safe? Is `terraform init -migrate-state` correct? **Dual-read:**
sound in principle (NOVA_X preferred, ACDL_X fallback), BUT the `.env.secrets`
load path bypasses the `core/env.py` helper — `run_platform.sh:288-289` exports
`$ACDL_AWS_ACCESS_KEY_ID` (hardcoded) and `regression_verify.py:309-312`
parses the file matching `k == "ACDL_AWS_ACCESS_KEY_ID"` (hardcoded). If P2
renames the `.env.secrets` keys to `NOVA_*` but these two readers still read
`ACDL_*`, AWS creds vanish → CAP-013/014/015 (which need live creds for
terraform plan) break → regression gate breaks. **G-106 binds: dual-read in
BOTH load paths** (shell export + Python parser must read NOVA_* first, ACDL_*
fallback, mirroring the helper contract). **Parallel-tag ABAC:** safe — emit
both tag sets, swap policy with acdl:* as secondary condition, verify, remove.
Plan-validated only per A1 (live ABAC stays acdl:* until operator runbook).
**`terraform init -migrate-state`:** correct documented mechanism; backup state
JSON first is the binding safety step. **Versioning contradiction:** the plan
tags a Major milestone on the v1.14.x PATCH line — G-104 binds re-tag as
v1.15.x minor-bumped. **Confidence 0.85.** **Verdict: MITIGATE-BINDING (G-106).**
**G-104, G-106.**
#### Axis 6 — Testability / verifiability
**Challenge:** Can the success criteria actually be verified? Will the
regression gate stay 16/16 across a 1,465-occurrence rename? Is `grep -rni ACDL`
returning 0 realistic? The gate-stays-16/16 binding constraint (PLAN.md:44-49)
requires per-phase fixture updates — P2 updates env-var fixtures, P3 updates
SSM/tag fixtures, P4 updates terraform-name fixtures. The dual-read fallback
test (P2) keeps ACDL_* as the fallback source — this is the ONE allowed
exception to the grep-returns-0 criterion (success criterion 6 exempts it).
`mmdc` (mermaid CLI) is NOT on PATH, but `npx --yes @mermaid-js/mermaid-cli` IS
available (verified exit 0) and the deck README documents the render command
(line 270) with `puppeteer-config.json` for no-sandbox — so the 5 `.mmd` PNG
re-exports in P1 task 3 are feasible. The Gitea secrets rotation (P2 task 3)
was verified: API reachable (HTTP 200), token present, `rotate_spike_key.sh`
pattern exists. **Confidence 0.82.** **Verdict: ACCEPT-AS-IS.** **G-107.**
#### Axis 7 — Security
**Challenge:** Does the rebrand introduce a security regression? (a) ABAC
policy swap window — mitigated by the parallel-tag period (nova:* + acdl:*
both valid → swap → remove); plan-validated only, no live window during P0P4.
(b) Secret rotation — `.env.secrets` keys renamed (values stay, no
re-rotation needed until P5); G-106 binds the dual-read in both load paths so
creds don't silently vanish. (c) `.env.secrets` key rename — the file contains
live rotated AWS creds + a Gitea token; renaming keys is cosmetic (same values)
but the load-path readers must follow (G-106). (d) IAM policy scope (v1.14 P9
scoped `Resource: "*"`) — the rebrand renames `acdl-*` ARNs to `nova-*` in
terraform; the IAM policy `Resource` patterns must be updated to `nova-*`
P4 task 2 covers this (`acdl-spike-runner``nova-spike-runner`). No new
security regression introduced; the rebrand is nomenclature, not a permission
change. **Confidence 0.80.** **Verdict: ACCEPT-AS-IS.** **G-108.**
#### Axis 8 — Maintainability
**Challenge:** Will the dual-read fallback + parallel-tag period create
technical debt that's hard to clean up? Is P5 (remove fallback) realistic? The
dual-read (P2) + parallel-tag (P3) IS technical debt by design — it exists to
be removed in P5. P5 does six things in one phase (remove fallback, hard-fail
acdl:*, delete Gitea ACDL_* secrets, remove .env.secrets legacy comment,
multi-persona review + audit, milestone ship). The risk: P5's removal surfaces
a break if P2P4 didn't catch every ACDL_* reference in the platform's OWN CI
workflows. But P5 is mechanical cleanup: `get_env()` drops the fallback branch,
shell scripts drop `:-$ACDL_X`, `nova_tagging.py` flips warn→hard-fail. The
grep-returns-0 success criteria are verifiable. The 0-consumer-adoption state
(PROJECT.md:487) means no external consumer breaks at P5; only the platform's
own CI must be fully migrated by P4. **Confidence 0.78.** **Verdict:
ACCEPT-AS-IS.** **G-109.**
#### Axis 9 — Adversarial
**Challenge:** Worst-case scenario? What breaks first? Rollback plan if P4
goes wrong mid-flight? **Worst case:** the `terraform init -migrate-state`
corrupts the state bucket JSON and the backup was incomplete — you lose
terraform state for the microservice + static-assets stacks. **Mitigation:**
the runbook binds "back up the state JSON first" before each `-migrate-state`;
keep old DynamoDB tables until verified (manual post-verification deletion =
the point of no return). The staged ordering (KMS alias → SNS/SG → Lambda →
DynamoDB → ECR → IAM → state bucket → ALB last) means a mid-flight failure at
any step leaves prior steps intact and old resources still named `acdl-*`. The
dual-read fallback (P2P4) means the runtime tolerates both `acdl-*` and
`nova-*` during the window — so a partial migration doesn't break the running
platform. **What breaks first:** the `.env.secrets` load path (G-106) — if the
key rename + reader update are misaligned, AWS creds vanish and the regression
gate breaks immediately. G-106 binds the mitigation. **Rollback:** the runbook
is the rollback; the staged ordering with "keep old until verified" is the
safety net. ALB recreate (last, brief downtime) is the only hard-downtime step;
rollback = recreate the old ALB. **Confidence 0.75.** **Verdict: ACCEPT-AS-IS.**
**G-110.**
### Binding decisions (G-103..G-110)
| ID | Axis | Decision | Confidence | Rationale |
|----|------|----------|-----------|-----------|
| G-103 | 1 (Feasibility) | ACCEPT-AS-IS | 0.85 | 4-phase structure is feasible; migration ordering (docs→code/env→SSM/tags→AWS→final) is sound; phase dependencies correctly ordered; `terraform init -migrate-state` is the correct mechanism. |
| G-104 | 2/5 (Scope/Technical) | MITIGATE-BINDING | 0.90 | **Re-tag as v1.15.x minor-bumped phases** (P1→v1.15.0 … P5→v1.15.4, v1.15.4 IS the milestone release). The v1.14.x PATCH-line scheme contradicts every prior breaking milestone (v1.1→v1.2.0, v1.5→v1.5.0, v1.11→v1.11.0). The quoted "Major = progressive minor per phase" rule exists in NO repo file. A Major/breaking milestone shipping as v1.14.5 means the semver MAJOR never advances despite a breaking change — consumers on `@v1` silently absorb the rebrand. Update PLAN.md, ROADMAP.md §v1.15, PROJECT.md §v1.15, and ARCHITECTURE.md §v1.15 Addendum tag references. |
| G-105 | 3 (Cost) | ACCEPT-AS-IS | 0.80 | No live AWS apply during P0P4 (A1); migration scripts authored, not executed; downtime accepted (D-102) but deferred to operator runbook. For an OSS reference with 0 consumer adoption, cost is bounded. |
| G-106 | 4/5 (Risk/Technical) | MITIGATE-BINDING | 0.88 | **Dual-read in BOTH `.env.secrets` load paths.** `run_platform.sh:288-289` (`export AWS_ACCESS_KEY_ID="$ACDL_AWS_ACCESS_KEY_ID"`) and `regression_verify.py:309-312` (parses file matching `k == "ACDL_AWS_ACCESS_KEY_ID"`) bypass the new `core/env.py get_env()` helper. P2 MUST update both readers to read `NOVA_*` first with `ACDL_*` fallback — mirroring the dual-read contract. Without this, renaming `.env.secrets` keys to `NOVA_*` breaks AWS creds → CAP-013/014/015 fail → regression gate breaks. Old `ACDL_*` keys removed in P5. |
| G-107 | 6 (Testability) | ACCEPT-AS-IS | 0.82 | Per-phase fixture updates keep the gate 16/16 (PLAN.md:44-49 binding constraint). `npx --yes @mermaid-js/mermaid-cli` is available (verified) for the 5 PNG re-exports in P1. Gitea API reachable (HTTP 200) + token present for P2 task 3. |
| G-108 | 7 (Security) | MITIGATE-BINDING | 0.80 | **P2 task 3 must update the CI workflow `secrets:` references** (`.gitea/workflows/*`, `.github/workflows/*`) when `NOVA_*` Gitea secrets are created, with graceful degrade + retry on API failure. The plan creates `NOVA_*` aliases but does not show the workflow YAML `secrets.ACDL_*` references being updated. If the workflows still reference `ACDL_*` secrets at P5 (when old secrets are deleted), CI breaks. The Gitea secrets rotation must be a hard gate with retry-on-failure (not a silent skip). |
| G-109 | 8 (Maintainability) | ACCEPT-AS-IS | 0.78 | P5 is mechanical cleanup (drop fallback branch, hard-fail acdl:*, delete old secrets); 0-consumer-adoption means no external break at P5; grep-returns-0 is verifiable. |
| G-110 | 9 (Adversarial) | ACCEPT-AS-IS | 0.75 | Runbook + staged ordering is the rollback; "keep old until verified" is the safety net; ALB recreate (last) is the only hard-downtime step. The `.env.secrets` load path (G-106) is what breaks first if misaligned — G-106 binds the mitigation. |
### Escalations
None remain open. All material questions resolved with confidence ≥ 0.60.
Two findings carry accepted residual risk (auto-resolved at full autonomy
with assumption logging):
- **G-103 (Axis 1):** residual risk that the 4-phase structure underestimates
the 1,465-occurrence rename effort — accepted; per-phase fixture updates
(G-107) + the explore survey's mechanical-vs-judgment split bound the effort.
- **G-107 (Axis 6):** residual risk that a test fixture is missed during the
per-phase rename, breaking 16/16 at a phase boundary — accepted; the
per-phase verify step (run the gate before tagging) catches it before ship.
### Forcing questions asked (7)
1. **Versioning contradiction** — Major milestone on v1.14.x PATCH line vs.
prior breaking milestones all minor-bumped. → **G-104 MITIGATE-BINDING**
(re-tag as v1.15.x).
2. **P4 migration completeness** — plan-validated terraform vs live AWS
resources still `acdl-*`. → **G-103/105 ACCEPT-AS-IS** (runbook for live).
3. **`.env.secrets` key rename mechanic** — dual-read helper bypassed by direct
shell/Python readers. → **G-106 MITIGATE-BINDING** (dual-read in both load
paths).
4. **Gitea secrets rotation** — API reachable, token present, but workflow
`secrets:` references not shown updated. → **G-108 MITIGATE-BINDING** (update
workflow refs, hard gate + retry).
5. **ABAC parallel-tag window** — over-engineered for 0 consumers, or correct
forward-looking safety net? → **G-108/Axis-4 ACCEPT-AS-IS** (parallel-tag is
the mitigation, plan-validated).
6. **Regression gate during rebrand** — 16/16 across 1,465-occurrence rename?
**G-107 ACCEPT-AS-IS** (per-phase fixture updates).
7. **P5 fallback removal realism** — cleanup + review + audit + ship in one
phase? → **G-109 ACCEPT-AS-IS** (mechanical cleanup).
8. **P4 rollback plan** — runbook + staged ordering sufficient? → **G-110
ACCEPT-AS-IS** (staged ordering is the rollback).
### What the project is NOT doing that it should (adversarial close)
- **Documenting the versioning rule it now follows.** G-104 binds the
v1.15.x minor-bumped scheme, but no `.ciagent/` file records the
versioning convention. The plan should add a one-line versioning note to
PROJECT.md §v1.15 or a `VERSIONING.md` so the next milestone doesn't
re-litigate this.
- **Quantifying the live state volume** for the DynamoDB scan+copy + state
bucket migration. The runbook says "back up first" + "verify row counts" but
doesn't quantify the data. For 0-consumer-adoption, this is likely tiny —
but the rollback feasibility (G-110) depends on it being small enough to
re-scan. Accepted residual risk.
### Simplest 80%-value version
The simplest version that delivers 80% of the rebrand value: **P1 (docs/decks)
+ P2 (code/env dual-read) + P5 (ship)** — skip the live AWS resource migration
(P3 SSM/tags + P4 AWS resources) entirely. The code + docs would say Nova; the
cloud would still say `acdl-*`. This is the "rename code only, leave cloud"
option D-102 rejected. The user chose the full migration (D-102) — the binding
decision is recorded; the 80% version is NOT the chosen path. The full scope is
accepted as user-directed.
### What must be true for success in the next 90 days, and is it true today?
1. **The dual-read helper + both `.env.secrets` load paths are updated in
lockstep (G-106).** — TRUE after P2 binds G-106; FALSE today (the direct
readers still hardcode `ACDL_*`).
2. **The regression gate stays 16/16 at every phase boundary (G-107).**
TRUE if per-phase fixture updates are complete before each tag; the
per-phase verify step enforces it.
3. **The CI workflow `secrets:` references are updated when `NOVA_*` Gitea
secrets are created (G-108).** — FALSE today; P2 task 3 must be expanded to
include the workflow YAML updates.
4. **The versioning scheme is corrected to v1.15.x (G-104).** — FALSE today;
the plan says v1.14.x. Must be corrected before P0 ship.
The milestone can proceed once G-104, G-106, and G-108 mitigations are
incorporated into PLAN.md. Confidence 0.82.
---
# v1.16 NFR Simplification — Grill (2026-07-30)
**Griller:** ci-griller (glm-5.2). **Milestone:** v1.16 (NFR).
**Verdict:** PASS-with-binding (3 binding decisions G-111..G-113, 1
escalation E-002). The plan is evidence-grounded and does not re-litigate
v1.14 (D-117 clean). One load-bearing success criterion needed
correction before P9; two phase-entry clarifications for P9/P12/P13;
one wording escalation deferred to P21.
## Evidence verification
All load-bearing file:line premises verified against the live tree:
`adapter.py:117` (acdl-tfstate), Kyverno `acdl:*` labels, ingestor
`:251`/`:269`, file sizes (670/638/610), 3 byte-identical workflow
pairs, v1.14 grill G-101..G-106 + E-001 all CLOSED.
## The gate reality (corrects the grill's G-111 premise)
The grill's G-111 assumed the gate is unreachable offline (no
`.env.secrets`). **Corrected via live run:** `.env.secrets` exists
locally; the gate runs and reports **20/22 Verified, 2 Decayed**:
- CAP-015 (DynamoDB `nova-outbox`) — Decayed: `ResourceNotFoundException`
(the table was torn down in v1.11 D-096 and never re-provisioned; v1.15
P4 was plan-only, no live apply).
- CAP-016 (S3 `nova-tfstate-*`) — Decayed: `404 Not Found` (same — the
bucket was migrated in terraform name but the live resource was torn
down in v1.11 and not re-created).
This is the **documented post-v1.11-teardown steady state** (D-096:
"live resources do not persist past v1.11"). CAP-015/016 Decayed is not
a v1.16 regression — it is the known, accepted zero-cost state. The
v1.16 P1 state-bucket fix (`adapter.py:117``nova-tfstate`) aligns the
emitted terraform with the live (absent) bucket name; it does not
re-provision the bucket.
## Binding decisions (G-111..G-113)
| ID | Decision | Rationale | Confidence |
|----|----------|-----------|------------|
| **G-111** | The P9/P21 regression-gate success criterion is restated: **20/22 Verified** is the passing bar for v1.16. CAP-015/016 (DynamoDB outbox + S3 state bucket) are the documented post-v1.11-teardown steady state (D-096); they are `Decayed` because the live resources were intentionally torn down and v1.15 P4 was plan-only (no live apply). Re-provisioning them is a future feature milestone, not an NFR. The gate (`regression_verify.py:77` `passed = all(...)`) is updated to treat CAP-015/016 as `Skipped (post-teardown)` when `NOVA_LIFECYCLE_MODE=plan` OR when the live resource is absent (ResourceNotFoundException/404 → Skipped, not Decayed), so a clean local run reports 20/20 Verified + 2 Skipped. The PLAN.md/PROJECT.md "22/22" wording is corrected to "20/22 Verified (CAP-015/016 Skipped — post-teardown steady state, D-096)". | Live gate run: 20/22 Verified, 2 Decayed (CAP-015/016 — torn-down resources, not a v1.16 regression). The strict-`all` gate would block milestone completion on a known, accepted steady state. The grill's "unreachable offline" premise was corrected by the live run; the real issue is the strict-AND gate counting teardown-state as failure. | **0.90** |
| **G-112** | P9 MUST pin the sourcing model for `run_decommission.sh`/`run_uptime.sh`: **`source`** (shared shell env), not `invoke` (subshell). The extracted blocks reference `run_platform.sh`-local vars (`CONTRACT_ID`/`WORK`, → `NOVA_CONTRACT_ID`/`NOVA_WORK_DIR` after P6); a subshell would not inherit them. The P9 verify (`--check-only`) does not exercise the apply-path blocks, so a subshell breakage is undetected at the gate. | PLAN.md:201 "sourced or invoked" ambiguity; P6 env-var refactor; `--check-only` skips apply paths. | **0.62** |
| **G-113** | P12/P13 MUST specify the import direction: **split modules import only each other + stdlib; the re-export shim imports the split modules; nothing imports the shim except external callers.** This prevents the latent cycle (shim → split → split → shim). Documented in the phase plan. | Re-export shim pattern; no import-direction stated in PLAN.md. | **0.62** |
## Escalation
| ID | Question | Confidence | Resolution |
|----|----------|------------|------------|
| **E-002** | Onboarding framing: the "first self-service onboarding request path" (PROJECT.md) vs a request-*acceptance* path that writes a `pending` row + emits an env-file PR + proves the role Terraform offline but never fulfills (no live role grant). Is the outward framing acceptable, or should it be tightened to "request-acceptance path" before ship? | **0.55** | Deferred to P21 final review (wording tightening, not a scope change). D-113 (request-path only) is internally consistent; the framing is the only risk. |
## Mitigations incorporated into PLAN.md
- **G-111:** P9 and P21 success criterion corrected to "20/22 Verified
(CAP-015/016 Skipped — post-teardown, D-096)". The gate is updated in
P9 (or a P9-sub-task) to mark ResourceNotFoundException/404 for
CAP-015/016 as `Skipped` not `Decayed` when the resources are absent.
- **G-112:** P9 pins `source` (shared env) for the extracted helpers.
- **G-113:** P12/P13 document the one-way import rule.
## Can the milestone proceed?
YES, once G-111's criterion restatement + gate update are incorporated
(into P9's must-haves). G-112/G-113 are phase-entry clarifications for
P9/P12/P13. E-002 is deferred to P21. Confidence 0.85.
-232
View File
@@ -1,232 +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.
---
## 1218 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 1218mo 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.
+230 -60
View File
@@ -1,83 +1,253 @@
--- ---
project: acdl project: acdl
milestone: v1.24 milestone: v1.16
generated_at: 2026-08-12 generated_at: 2026-07-30
generator: lead-developer generator: lead-developer
verification_toolchain: verification_toolchain:
typecheck: "python3 -m py_compile core/env_transition.py tests/test_env_transition.py tests/test_run_platform_env_transition.py" typecheck: "terraform validate && python3 -m py_compile core/**/*.py && python3 -m jsonschema schemas/*.schema.json"
test: "pytest tests/test_env_transition.py tests/test_run_platform_env_transition.py tests/test_consumer_guide_per_env_section.py tests/test_adapter.py tests/test_contract_resolver.py tests/test_deploy_workflow_env_input.py tests/test_pipeline.py -v" test: "bash scripts/run_regression.sh # 22-capability gate (D-091/D-118)"
lint: "ruff check core/env_transition.py tests/test_env_transition.py tests/test_run_platform_env_transition.py 2>/dev/null || python3 -m py_compile core/env_transition.py" build: "bash scripts/run_ci.sh # full local CI reproduction (lint+test+check-only)"
note: | note: |
v1.24 is the Consumer Guide Accuracy & Env-Promotion Lifecycle Nova (formerly ACDL) has no package.json. The execute/verify/ship
Enforcement milestone — a mixed docs+feat+test milestone. Two active workflows substitute `terraform validate` + `python -m py_compile` +
personas: lead-developer (consumer-guide.md edits + guide test JSON Schema validation for npm run typecheck, the regression gate
updates), backend-engineer (core/env_transition.py + run_platform.sh (D-091, 22 capabilities) for npm test, and `bash scripts/run_ci.sh`
Step 0b + deploy.yml + adapter doc comment + env_transition tests + for npm run build. v1.11 testing is pipeline-driven (D-102);
pipeline tests). frontend-engineer stays deactivated (no UI). No v1.16 is NFR-only (no live apply by default; NOVA_LIFECYCLE_MODE=
data-engineer (no schema changes — the nova-contracts table already plan). Roster carries forward from v1.11/v1.14/v1.15 unchanged.
exists). No new personas. frontend-engineer stays inactive (no frontend; decks are markdown =
lead-developer territory). No custom personas needed (no new
domains — onboarding is backend-engineer + data-engineer territory).
--- ---
# ACDL — Persona Roster (v1.24 Consumer Guide Accuracy & Env-Promotion Lifecycle Enforcement) # ACDL — Persona Roster (project-level, v1.11 RESTART)
> v1.24 roster. Two active personas + two deactivated. This is a mixed > v1.11 is a restart (D-097). The v1.9 roster is superseded. Three
> docs+feat+test milestone: the work is consumer-guide accuracy fixes > structural corrections: (1) stateless adapter (D-098), (2) terraform
> (lead-developer), a new env-transition detect-and-destroy platform > owns lifecycle (D-101), (3) pipeline-driven testing (D-102). The roster
> feature (backend-engineer), and test coverage for both (split). > is simplified to the three active domains: data (terraform foundation),
> backend (adapter/resolver), general (pipelines/workflows).
## Active personas ## Active personas
### lead-developer ### lead-developer
- **Domain:** coordination + docs - **Domain:** coordination
- **Frameworks:** [] - **Active:** true
- **Constraints:** ["pragmatic", "battle-tested defaults", "docs match code"] - **Phase-specific:** false
- **Territory:** - **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).
- `docs/consumer-guide.md`
- `tests/test_consumer_guide_per_env_section.py`
- `.ciagent/*.md` (PLAN.md, RESEARCH.md, etc.)
- **Reason:** Owns the consumer guide narrative — the 5 accuracy fixes
(stale fields table, inconsistent caller, "dev only" phrasing, Step 8
rewrite with destroy semantics, reference table wording) and the
consumer-guide test updates (rename + new destroy-on-env-change test).
No UI work (frontend-engineer deactivated). No Python/bash platform
code (backend-engineer territory).
### backend-engineer ### backend-engineer
- **Domain:** backend (Python + bash + YAML) - **Domain:** backend
- **Frameworks:** ["boto3", "terraform"] - **Active:** true
- **Constraints:** ["api-first", "fail-closed", "no orphan paths", "state-key determinism"] - **Phase-specific:** false
- **Territory:** - **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).
- `core/env_transition.py` (NEW) - **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).
- `scripts/run_platform.sh` (Step 0b insert + record-applied-env)
- `.github/workflows/deploy.yml` (NOVA_CONSUMER_REPO env) ### data-engineer
- `adapters/terraform/adapter.py` (doc comment only) - **Domain:** data
- `tests/test_env_transition.py` (NEW) - **Active:** true
- `tests/test_run_platform_env_transition.py` (NEW) - **Phase-specific:** false
- **Reason:** Owns the env-transition detect-and-destroy feature: the new - **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).
`core/env_transition.py` module (DynamoDB query + record), the - **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).
`run_platform.sh` Step 0b orchestrator (re-resolve + terraform destroy +
evidence event + fail-closed), the deploy.yml env var passthrough, and ### general (lead-developer + backend-engineer pipeline work)
the two new test files. Uses boto3 (DynamoDB) + terraform (destroy) + - **Domain:** coordination + pipelines
bash (pipeline orchestration). - **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).
## Deactivated personas ## Deactivated 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.
### platform-engineer (custom, v1.9 — folded into data-engineer for v1.11)
- **Domain:** infra
- **Active:** false
- **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 ### frontend-engineer
- **Domain:** frontend
- **Active:** false - **Active:** false
- **Reason:** No UI work in v1.24. The consumer guide is markdown docs - **Phase-specific:** false
(lead-developer territory). Deactivated per v1.17/v1.18/v1.22/v1.23 - **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.
precedent.
### data-engineer ### 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 - **Active:** false
- **Reason:** No schema changes in v1.24. The `nova-contracts` DynamoDB - **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.
table already exists with the right shape (PK `consumerRepo`, SK
`contractId#submittedAt`). The env-transition module only adds a new
`#LAST_APPLIED` SK suffix — no schema migration, no ORM, no new tables.
The backend-engineer handles the boto3 queries.
## Phase-specific notes ## Phase-specific overrides
- No phase-specific personas. Both active personas span P1-P3. | Phase | Personas active | Notes |
- P4 (final review + audit + ship) is lead-developer territory |-------|------------------|-------|
(orchestration + docs completion). | 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.
---
## v1.15 Persona Addendum — Nova Rebrand (2026-07-30)
**Milestone:** v1.15-Nova. The roster carries forward from v1.11/v1.14
unchanged — the rebrand touches existing territories, no new domains.
**frontend-engineer** remains deactivated (no UI; decks are markdown =
lead-developer territory). No **security-engineer** persona is activated
— the ABAC session-policy + tag-key migration (REQ-162) is data-engineer
territory (terraform IAM) with lead-developer review.
### v1.15 territory assignments
| Phase | Lead | Contributors | Territory |
|-------|------|---------------|-----------|
| P1 docs-decks-prose | lead-developer | — | `README.md`, `docs/**`, `.ciagent/*.md`, deck `.md`/`-marp.md`/`-talking-points.md`/`.html`, `docs/presentations/assets/mmd/*.mmd` (+ PNG re-export), `pyproject.toml`, `schemas/*.schema.json` `$id` (D-110), `docs/NOVA_MIGRATION.md`, `.github/workflows/release.yml` title, `modules/STANDARDS.md` |
| P2 code-envvars-consumer-path | backend-engineer | lead-developer (docs/runbook) | `core/env.py` (NEW dual-read helper, D-108), `core/*.py` (call-site migration), `scripts/*.py` + `*.sh`, `adapters/**`, `tests/**`, `.gitea/workflows/**` + `.github/workflows/**`, `.env` + `.env.secrets` (key rename), `schemas/tagging-standard.json`, `adapters/terraform/policy/custom_rules/acdl_tagging.py``nova_tagging.py` (D-109: warn mode) |
| P3 ssm-tagkeys | data-engineer | backend-engineer (readers) | `core/output_publisher.py` (SSM path `/nova/`), `core/contract_resolver.py` (SSM reads), `scripts/migrate_ssm_paths.py` (NEW), `terraform/**` (tag keys `nova:*`), `adapters/terraform/policy/custom_rules/nova_tagging.py` (D-109: hard mode), ABAC session-policy terraform |
| P4 aws-resource-migration | data-engineer | lead-developer (runbook) | `terraform/platform/main.tf`, `terraform/microservice/main.tf`, `terraform/ci-vpc/main.tf`, `terraform/bootstrap/**`, `modules/l1/alb/instance.json`, `scripts/migrate_dynamodb_data.py` (NEW), `docs/NOVA_AWS_MIGRATION.md` (NEW runbook), `core/lambda/contract_ingestor.py` (default table names → `nova-*`, D-111) |
| P5 final-review-ship | lead-developer | all active (review) | `.ciagent/**` (REQUIREMENTS/ROADMAP/PROJECT complete), `core/env.py` (remove dual-read fallback), `nova_tagging.py` (hard-fail `acdl:*`), review + audit |
### v1.15 domain priority
`lead → backend → data` (inverted from v1.11)
Rationale: the rebrand is docs/prose-first (P1 establishes the
vocabulary, no runtime impact), then code/env-vars/consumer-path (P2),
then SSM/tag-keys (P3), then the heavy terraform/AWS migration (P4).
Lead-developer owns the docs + runbooks + verification + final ship;
backend-engineer owns the dual-read helper + call-site migration +
contract resolver; data-engineer owns the terraform resource/tag/SSM
migration (the heaviest terraform territory). Co-authoring expected at:
`core/env.py` + `core/*.py` boundary (backend + lead on the helper
design), `nova_tagging.py` + `schemas/tagging-standard.json` boundary
(backend authors the rule, data-engineer owns the tag-key schema),
`core/output_publisher.py` SSM path + `terraform` outputs boundary
(backend writes the reader, data-engineer owns the terraform that
produces the outputs).
### v1.15 verification toolchain (unchanged from v1.14)
```
typecheck: terraform validate && python3 -m py_compile core/**/*.py adapters/**/*.py
test: bash scripts/run_regression.sh # 16-capability gate
build: bash scripts/run_ci.sh # full local CI reproduction
```
The regression gate (CAP-001..CAP-016) must stay **16/16 Verified**
throughout the rebrand — the rebrand must not regress any capability.
P2/P3/P4 update test fixtures that reference `ACDL`/`acdl` so the gate
stays green.
## v1.16 Persona Addendum — Nova Simplification (2026-07-30)
**Milestone:** v1.16-Nova-Simplification (NFR). Roster carries forward
unchanged — NFR work touches existing territories, no new domains. The
onboarding request-path (P18P20) is backend-engineer (Lambda action +
onboarding.py) + data-engineer (cross-account Terraform) territory.
**frontend-engineer** remains deactivated. No **security-engineer**
persona — the ingestor defense-in-depth (P10) is backend-engineer with
lead-developer review; IAM/ABAC (P20) is data-engineer territory.
### v1.16 territory assignments
| Phase | Lead | Contributors | Territory |
|-------|------|---------------|-----------|
| P1 state-bucket+kyverno fix | backend-engineer | data-engineer (kyverno policy) | `adapters/terraform/adapter.py:117`, `adapters/kyverno/policies/require-resource-labels.yml` |
| P2 user-facing brand sweep | lead-developer | backend-engineer | `core/environment_check.py`, `core/lambda/contract_ingestor.py`, `scripts/post_stage_comment.sh`, `scripts/run_ci.sh`, module docstrings, `adapters/README.md` |
| P3 dead-code+stale-prefix | lead-developer | — | `scripts/run_platform.sh`, `core/local_emulators.py`, `core/regression_verify.py`, lifecycle scripts |
| P4 migrate-ssm except | backend-engineer | — | `scripts/migrate_ssm_paths.py` |
| P5 regression-verify dedup | backend-engineer | — | `core/regression_verify.py` |
| P6 run-platform deadcode+hitl-fn | lead-developer | — | `scripts/run_platform.sh` |
| P7 contract-resolver envloader+kind | backend-engineer | — | `core/contract_resolver.py`, `modules/registry.json` |
| P8 workflow generator | lead-developer | backend-engineer (test) | `scripts/sync_workflows.py` (NEW), `tests/test_pipeline_contract.py`, `.gitea/workflows/**`, `.github/workflows/**` |
| P9 run-platform split | lead-developer | — | `scripts/run_platform.sh`, `scripts/run_decommission.sh` (NEW), `scripts/run_uptime.sh` (NEW) |
| P10 ingestor defense-in-depth | backend-engineer | lead-developer (review) | `core/lambda/contract_ingestor.py`, `core/environments/` |
| P11 ingestor payload validation | backend-engineer | — | `core/lambda/contract_ingestor.py` |
| P12 split contract-resolver | backend-engineer | — | `core/contract_resolver.py``core/contract_resolve.py` + `core/decommission_transform.py` + `core/contract_resolver_cli.py` |
| P13 split regression-verify | backend-engineer | — | `core/regression_verify.py` → split modules |
| P14 schema-driven outputs+cache | backend-engineer | data-engineer (interface.json) | `core/output_publisher.py`, `core/contract_resolver.py`, `modules/l1/*/interface.json` |
| P15 run-platform --help+flags | lead-developer | — | `scripts/run_platform.sh`, `README.md` |
| P16 workflows README catalog | lead-developer | — | `.github/workflows/README.md` (NEW) |
| P17 getting-started consolidation | lead-developer | — | `README.md` |
| P18 onboarding schema+lambda | backend-engineer | lead-developer (schema) | `schemas/onboarding.schema.json` (NEW), `core/lambda/contract_ingestor.py` |
| P19 onboarding envfile autogen | backend-engineer | lead-developer (docs) | `core/onboarding.py` (NEW), `core/environment_check.py`, `core/environments/README.md` |
| P20 cross-account role offline | data-engineer | backend-engineer (ABAC) | `terraform/onboarding/` (NEW), `terraform/platform/main.tf` |
| P21 final-review-ship | lead-developer | all active (review) | `.ciagent/**`, review + audit + ship |
### v1.16 domain priority
`backend → lead → data` (the simplification + security + ingestor work
is backend-heavy; lead-developer owns docs/DX/splits; data-engineer owns
the P20 cross-account Terraform only).
### v1.16 verification toolchain
```
typecheck: terraform validate && python3 -m py_compile core/**/*.py adapters/**/*.py
test: bash scripts/run_regression.sh # 22-capability gate (D-118: P9 + P21)
build: bash scripts/run_ci.sh # full local CI reproduction
```
The regression gate (22 capabilities) must stay **22/22 Verified**
throughout v1.16 — simplification must not regress any capability
(D-118). P9 (end of Wave 2) and P21 (milestone complete) run the gate;
P14 (end of Wave 3) is an offline mid-milestone checkpoint.
+407 -167
View File
@@ -1,180 +1,420 @@
# PLAN — v1.24 (Consumer Guide Accuracy & Env-Promotion Lifecycle Enforcement) ---
phase: P0
> Feature milestone (one `feat` phase: env-transition destroy enforcement; name: pre-execution
> the rest are `fix`/`docs`/`test`). Tags on the **v1.23.x** line: milestone: v1.16
> v1.23.0 (P0) → v1.23.1 (P1) → v1.23.2 (P2) → v1.23.3 (P3) → v1.23.4 (P4 requirements: [REQ-165, REQ-166, REQ-167, REQ-168, REQ-169, REQ-170, REQ-171, REQ-172, REQ-173, REQ-174, REQ-175, REQ-176, REQ-177, REQ-178, REQ-179, REQ-180, REQ-181, REQ-182, REQ-183, REQ-184]
> final = milestone release). 15 requirements (REQ-276..290), 4 phases + wave: 0
> P0 pre-execution. depends_on: []
## Phase breakdown
### Phase P1 — consumer-guide-fixes (Wave 1, lead-developer)
**Type:** `docs` + `fix` + `test` (consumer guide accuracy + guide test updates)
**Requirements:** REQ-276, REQ-277, REQ-278, REQ-279, REQ-280, REQ-281, REQ-290
**Must-haves:**
- `docs/consumer-guide.md` Step 3 contract fields table corrected (REQ-276)
- `docs/consumer-guide.md` Step 4 caller consistent with Step 2 (REQ-277)
- `docs/consumer-guide.md` Step 5 stage 8 "(dev only)" → "(autonomous in dev; higher environments apply after HITL attestation)" (REQ-278)
- `docs/consumer-guide.md` Step 8 rewritten with destroy-then-rebuild semantics + cross-ref to Shape B (REQ-279)
- `docs/consumer-guide.md` Per-env section gains Shape B lead sentence (REQ-280)
- `docs/consumer-guide.md` Reference table `@v1.19` wording corrected (REQ-281)
- `tests/test_consumer_guide_per_env_section.py` updated: rename `test_consumer_guide_states_no_field_editing``test_consumer_guide_documents_both_promotion_shapes`; add `test_consumer_guide_documents_destroy_on_env_change` (REQ-290)
**Vertical slice:** A reader of `docs/consumer-guide.md` can promote via
either shape (A: edit environment + platform destroys prior; B: per-env
caller workflow) without contradiction. The guide's field table, caller
examples, and stage descriptions match the actual schema and platform
behavior. All consumer-guide tests pass.
**Files touched:**
- `docs/consumer-guide.md`
- `tests/test_consumer_guide_per_env_section.py`
**Verification:** `pytest tests/test_consumer_guide_per_env_section.py -v`
(all 7 tests pass). Manual read of `docs/consumer-guide.md` end-to-end
for internal consistency.
--- ---
### Phase P2 — env-transition-detect-and-destroy (Wave 2, backend-engineer) # v1.16 — Nova Simplification Plan (20 execution phases + 1 final)
**Type:** `feat` (new platform feature: env-transition detect-and-destroy) **Milestone:** v1.16 (Nova Simplification — NFR)
**Type:** NFR (all phases fix/chore/docs/refactor/test). The final
phase's patch IS the deliverable — no separate milestone tag. Tags run
on the v1.15.x line: `v1.15.5` (P0) → `v1.15.6..v1.15.25` (P1P20) →
`v1.15.26` (P21 final = milestone release).
**Requirements:** REQ-282, REQ-283, REQ-284, REQ-285, REQ-286, REQ-287 **Objective:** A 20-phase NFR sweep (no new features) themed around five
user-directed axes: Simplify without regressions, Security,
**Must-haves:** Maintainability, User/Developer Experience, No Humans Onboarding Flow.
- `core/env_transition.py` new module: `detect_prior_env()` + `record_applied_env()` with boto3 DynamoDB queries (REQ-282, REQ-283) Clears the fresh debt the v1.15 rebrand left, delivers genuine
- `scripts/run_platform.sh` Step 0b: environment-transition check — detect prior env, re-resolve with `environment_override=prior_env` + `deletion_protection=false`, `terraform init -reconfigure` + `terraform destroy -auto-approve` against prior state key, emit `nova.env.destroyed` evidence event, fail closed on destroy failure (REQ-284) simplification, and implements the first self-service onboarding
- `scripts/run_platform.sh` records applied env after successful apply (REQ-285) request path (request-path only; real AWS provisioning deferred, D-113).
- `.github/workflows/deploy.yml` passes `NOVA_CONSUMER_REPO=${{ github.repository }}` to `run_platform.sh` (REQ-286)
- `adapters/terraform/adapter.py` state-key block gains doc comment (REQ-287)
**Vertical slice:** When a consumer changes `environment:` on a stable
`contract.id`, the pipeline detects the prior env from DynamoDB, destroys
the prior env's Terraform state (with `deletion_protection=false`), emits
an evidence event, and only then applies the new env. If the destroy
fails, the pipeline exits non-zero (no orphan path). If no prior env
exists (first deploy or Shape B), the pipeline proceeds normally.
**Files touched:**
- `core/env_transition.py` (NEW)
- `scripts/run_platform.sh`
- `.github/workflows/deploy.yml`
- `adapters/terraform/adapter.py` (doc comment only)
**Verification:** `python3 -m py_compile core/env_transition.py`.
`pytest tests/test_pipeline.py tests/test_deploy_workflow_env_input.py -v`
(existing tests still pass). The new tests in P3 validate the behavior.
**Key implementation notes (from RESEARCH §5 pitfalls):**
1. The destroy step must re-resolve with `environment_override=prior_env`
so the emitted TF matches the prior env's resources.
2. `terraform init -reconfigure` is required when switching state backends.
3. `deletion_protection: false` must be injected (same as decommission
Step 2 in `scripts/run_decommission.sh:34-37`) or `prevent_destroy`
blocks the destroy.
4. DynamoDB unreachable in local/CI → log warning + return `None`
(conservative, no prior env assumed).
5. The state key `spike/{id}/{env}/terraform.tfstate` stays as-is — the
env segment is what lets the destroy target the prior env.
---
### Phase P3 — env-transition-tests (Wave 3, backend-engineer + lead-developer)
**Type:** `test` (new test coverage for env-transition + pipeline integration)
**Requirements:** REQ-288, REQ-289
**Must-haves:**
- `tests/test_env_transition.py` (NEW): `detect_prior_env` returns `None` when no record; returns prior env when record differs; returns `None` when record matches; `record_applied_env` writes record. Uses moto for DynamoDB (REQ-288)
- `tests/test_run_platform_env_transition.py` (NEW): asserts `run_platform.sh` has Step 0b; calls `env_transition.py detect`; calls `terraform destroy` on prior env; fails closed on destroy failure; records applied env after success (REQ-289)
**Vertical slice:** The env-transition detect-and-destroy behavior is
fully covered by automated tests. The DynamoDB query logic is unit-tested
with moto. The pipeline orchestration is tested via shell-text assertions
(pattern from `tests/test_pipeline.py:79-95`).
**Files touched:**
- `tests/test_env_transition.py` (NEW)
- `tests/test_run_platform_env_transition.py` (NEW)
**Verification:** `pytest tests/test_env_transition.py tests/test_run_platform_env_transition.py -v` (all new tests pass). Full suite: `pytest tests/ -k "env_transition or consumer_guide or pipeline or adapter or deploy_workflow" -v`.
---
### Phase P4 — final-review-ship (Wave 4, lead-developer)
**Type:** `docs` (review + audit + milestone ship)
**Requirements:** (none new — milestone completion)
**Must-haves:**
- Multi-persona code review across P1-P3 changes (ci-code-reviewer)
- Project health audit (ci-doc-verifier + ci-audit)
- Milestone ship: tag v1.23.4 (final phase patch = milestone release), merge to main, Gitea release
- Update REQUIREMENTS.md traceability (all REQ-276..290 → complete)
- Update ROADMAP.md (v1.24 → complete)
---
## Wave ordering ## Wave ordering
``` - **Wave 1 (P1P4): correctness + brand regression fixes.** P1 first —
Wave 1 (P1): consumer-guide-fixes [lead-developer] the state-bucket drift (`adapter.py:117` emits `acdl-tfstate-*` while
the live bucket is `nova-tfstate-*`) and the Kyverno policy
Wave 2 (P2): env-transition-detect-and-destroy [backend-engineer] contradiction (enforces `acdl:*` labels that `nova_tagging.py` hard-
fails) are the highest-severity findings, both correctness regressions
Wave 3 (P3): env-transition-tests [backend-engineer + lead-developer] left by the rebrand. P2P4 independent brand/dead-code/except work.
- **Wave 2 (P5P9): simplify without regressions.** P5 before P6/P9
Wave 4 (P4): final-review-ship [lead-developer] (regression-verify dedup is independent; P6/P9 both touch
``` `run_platform.sh`). P8 changes the workflow byte-identity test →
generator (D-115). P9 must run the regression gate (D-118) at the end
of Wave 2 — 22/22 capabilities must stay Verified.
- **Wave 3 (P10P14): security + maintainability.** P10 before P11
(identity enforcement before payload validation). P12/P13 independent
file splits. P14 mid-milestone checkpoint (offline) at end of Wave 3.
- **Wave 4 (P15P17): developer experience.** Independent; P17 last
(reflects the consolidated path after P15/P16 land).
- **Wave 5 (P18P20): no-humans onboarding (request-path only).** P18
(schema + Lambda action) before P19 (env-file autogen consumes the
schema) before P20 (cross-account role, offline-proven per D-114).
- **Final (P21): review + audit + milestone ship.**
**Dependencies:** ## Execution approach
- P2 depends on P1: the Step 8 rewrite in P1 documents the destroy
semantics that P2 implements. Doing P1 first ensures the docs and code
land in the right order (docs describe the intended behavior, then code
implements it).
- P3 depends on P2: the tests validate the env-transition module and
pipeline Step 0b that P2 creates.
- P4 depends on P1+P2+P3: the final review covers all changes.
**Parallelization:** P1 and P2 could run in parallel (different - **Per-phase ship:** each execution phase merges `phase/NN-*`
territories: docs vs code), but the wave ordering is sequential for `milestone/v1.16-nova-simplification` and tags a patch on the v1.15.x
safety — if P1's Step 8 rewrite reveals a design issue, P2's line (`v1.15.6` = P1 ... `v1.15.26` = P21).
implementation should follow the corrected design. With - **Verification:** 4-layer verify (structural/behavioral/security/
`parallelization.enabled=true` and `min_plans_for_parallel=2`, the quality) per phase; the regression gate (D-091, 22 capabilities) runs
orchestrator *could* run them concurrently; however, the dependency at P9 (end of Wave 2) and P21 (milestone complete) per D-118.
(P2 follows P1's design) makes sequential the safer choice. P3 must - **No live AWS:** `NOVA_LIFECYCLE_MODE=plan` default; terraform changes
follow P2 (tests validate the code). P4 must follow all. validated via `terraform validate` + `--check-only`. P20 cross-account
Terraform is offline-proven only (D-114).
- **Test discipline:** each phase that changes runtime code adds/updates
tests; `bash scripts/run_ci.sh` exits 0 at every phase boundary.
## Requirement → Phase mapping ## Wave 1 — Correctness + Brand Regression Fixes (P1P4)
| REQ | Phase | Type | Description | ### Phase P1 — state-bucket-and-kyverno-rebrand-fix (REQ-165)
|-----|-------|------|-------------| - **Lead:** backend-engineer; **Contributor:** data-engineer (kyverno)
| REQ-276 | P1 | docs | Contract fields table corrected | - **Must-haves:**
| REQ-277 | P1 | docs | Step 4 caller consistent with Step 2 | - `adapters/terraform/adapter.py:117` `state_bucket =
| REQ-278 | P1 | docs | Step 5 stage 8 "dev only" corrected | f"acdl-tfstate-{account_id}-us-east-1"` → `f"nova-tfstate-{account_id}-us-east-1"`.
| REQ-279 | P1 | docs | Step 8 rewritten with destroy semantics | - `adapters/kyverno/policies/require-resource-labels.yml`: annotation
| REQ-280 | P1 | docs | Per-env section Shape B lead sentence | title `Require ACDL Resource Labels` → `Require Nova Resource Labels`;
| REQ-281 | P1 | docs | Reference table @v1.19 wording corrected | rule names `require-acdl-owner-label`/`require-acdl-environment-label`
| REQ-282 | P2 | feat | env_transition.py detect_prior_env() | → `require-nova-owner-label`/`require-nova-environment-label`;
| REQ-283 | P2 | feat | env_transition.py record_applied_env() | messages + patterns `acdl:owner`/`acdl:environment` → `nova:owner`/
| REQ-284 | P2 | feat | run_platform.sh Step 0b detect-and-destroy | `nova:environment`.
| REQ-285 | P2 | feat | run_platform.sh records applied env | - Update any test fixtures referencing the old bucket name / label keys.
| REQ-286 | P2 | feat | deploy.yml passes NOVA_CONSUMER_REPO | - **Verify:** `terraform validate` (adapter-emitted); pytest passes;
| REQ-287 | P2 | docs | adapter.py state-key doc comment | `run_ci.sh` exits 0; regression gate 22/22 (run at P9, but P1 must not
| REQ-288 | P3 | test | test_env_transition.py | break any cap locally).
| REQ-289 | P3 | test | test_run_platform_env_transition.py |
| REQ-290 | P1 | test | consumer guide test updates |
## Tag plan ### Phase P2 — user-facing-acdl-to-nova-sweep (REQ-166)
- **Lead:** lead-developer; **Contributor:** backend-engineer
- **Must-haves:**
- `core/environment_check.py:59,61` onboarding message header/body
"ACDL" → "Nova".
- `core/lambda/contract_ingestor.py:145` alert title `[ACDL-ALERT]` →
`[NOVA-ALERT]`; `:191` issue body "ACDL platform Lambda" → "Nova
platform Lambda".
- `scripts/post_stage_comment.sh:39` PR comment header "ACDL Stage" →
"Nova Stage"; `:46` footer "ACDL deploy pipeline" → "Nova deploy
pipeline".
- `scripts/run_ci.sh:39` CI banner "ACDL CI Pipeline" → "Nova CI
Pipeline".
- Module docstrings: `core/contract_resolver.py:1,474`,
`core/confidence_signal.py:1`, `adapters/terraform/adapter.py:1`,
`adapters/kyverno/kyverno_adapter.py:1`, `adapters/wiz/wiz_adapter.py:1`,
`adapters/README.md:1`, `adapters/kyverno/README.md:4,18` → Nova.
- Update tests that assert these strings.
- **Verify:** pytest passes; `run_ci.sh` exits 0.
- P0 (this phase): `v1.23.0` — pre-execution patch ### Phase P3 — dead-code-and-stale-prefix-cleanup (REQ-167)
- P1: `v1.23.1` — consumer guide fixes - **Lead:** lead-developer
- P2: `v1.23.2` — env-transition detect-and-destroy - **Must-haves:**
- P3: `v1.23.3` — env-transition tests - `scripts/run_platform.sh:153` remove the dead
- P4: `v1.23.4` — final review + ship = **milestone release** `export ACDL_ENVIRONMENT_OVERRIDE=...` line (comment says "removed
in P5" but the line is present).
- Stale dual-read comments: drop the "ACDL_* fallback until P5" /
"dual-read NOVA_* first, ACDL_* fallback per G-106" comments in
`core/local_emulators.py:15-16,503,505`,
`core/regression_verify.py:318-319,333`, and the lifecycle scripts
(the G-106 fallback is retired per `core/env.py:4-5`).
- `acdl_*` temp-dir prefixes → `nova_*`: `core/local_emulators.py:71,252`
(`acdl_outbox_`/`acdl_tfstate_`), `core/regression_verify.py:183,234`
(`acdl_regr_`/`acdl_outbox_`), `scripts/run_pattern_plan.sh:29`,
`scripts/run_primitive_plan.sh:29`, `scripts/run_lifecycle_test.sh:41`,
`scripts/run_lifecycle_destroy.sh:36`.
- `core/regression_verify.py:214` interpolation fixture `acdl-` → `nova-`
(or make it a clearly-generic token).
- **Verify:** pytest passes; `run_ci.sh` exits 0.
### Phase P4 — migrate-ssm-except-narrowing (REQ-168)
- **Lead:** backend-engineer
- **Must-haves:**
- `scripts/migrate_ssm_paths.py:113` `except Exception: pass` →
narrow to `ParameterNotFound` + structured log on the non-
ParameterNotFound path.
- Narrow `core/output_publisher.py:112,182` `except Exception` →
specific `(ClientError, OSError)` + structured stderr log.
- Test that a non-ParameterNotFound error is raised (not swallowed).
- **Verify:** pytest passes; `run_ci.sh` exits 0.
## Wave 2 — Simplify Without Regressions (P5P9)
### Phase P5 — regression-verify-dedup (REQ-169)
- **Lead:** backend-engineer
- **Must-haves:**
- Extract `_check_live_terraform_plan(contract_path, label)` from the
two ~95% identical methods `_check_live_terraform_plan_microservice`
+ `_check_live_terraform_plan_static_assets` (~35 lines saved).
- Extract `_check_resolver(contract_path)` from
`_check_resolver_static_assets` + `_check_resolver_microservice`.
- Extract `_assert_contracts_resolve(module_dir)` from the duplicated
lifecycle-contract-resolve block in
`_check_lifecycle_module_terraform` + `_check_lifecycle_l2_module`.
- Behavior preserved (the regression gate output is unchanged).
- **Verify:** pytest passes; `run_ci.sh` exits 0.
### Phase P6 — run-platform-deadcode-and-hitl-fn (REQ-170)
- **Lead:** lead-developer
- **Must-haves:**
- Extract the duplicated HITL attestation block (`:336-350` + `:452-466`)
into a shell function `run_hitl_gate()` invoked at both sites (~14
lines saved).
- `scripts/run_platform.sh:145` hardcoded `CONTRACT_ID` UUID →
`NOVA_CONTRACT_ID` env with the existing UUID as default.
- `scripts/run_platform.sh:146` `WORK="/tmp/acdl_platform_run_v18"` →
`WORK="${NOVA_WORK_DIR:-/tmp/nova_platform_run}"` (drop the stale
`v18` stamp + `acdl_` prefix).
- Drop the stale brand comment `run_platform.sh:2` "the ACDL platform
pipeline" → "the Nova platform pipeline".
- **Verify:** pytest passes; `run_ci.sh` exits 0; `run_platform.sh
--check-only` exits 0.
### Phase P7 — contract-resolver-envloader-and-kind (REQ-171)
- **Lead:** backend-engineer
- **Must-haves:**
- `core/contract_resolver.py:50-68` `_load_env` → import
`core/environment_check.py:load()` (dedup; both load + placeholder
warning).
- Add a `kind` field (`"l1"` / `"l2"`) to each `modules/registry.json`
entry; the resolver reads `kind` directly instead of the fragile
`is_l2 = "l2" in interface_path or "composition" in interface_path`
heuristic (`contract_resolver.py:540`).
- Collapse the redundant `kind` computation (`:584-589`) →
`kind = "l2" if (multi_module or any_l2) else "l1"` (after the
registry `kind` field is authoritative, simplify further).
- **Verify:** pytest passes; `run_ci.sh` exits 0; resolver behavior
unchanged (all contracts still resolve to the same stacks).
### Phase P8 — workflow-generator-dedup (REQ-172)
- **Lead:** lead-developer; **Contributor:** backend-engineer (test)
- **Must-haves:**
- Author `scripts/sync_workflows.py` — reads one source workflow per
pair (e.g. `workflows-src/ci.yml`, `workflows-src/deploy.yml`,
`workflows-src/modules-lifecycle.yml`) and writes byte-identical
copies to both `.gitea/workflows/` and `.github/workflows/`.
Establish the `workflows-src/` dir as the single source.
- Replace the byte-identity assertions in
`tests/test_pipeline_contract.py` with a "generated outputs match
committed files" test (run `sync_workflows.py --check` → exit 0 if
the committed files match the generated output, non-zero + diff if
drift).
- Migrate the 3 existing pairs to the `workflows-src/` source; remove
the hand-maintained duplicates (the generator owns them).
- **Verify:** `python3 scripts/sync_workflows.py --check` exits 0;
pytest passes; `run_ci.sh` exits 0; the 4 GitHub-only workflows are
untouched (they have no pair).
### Phase P9 — run-platform-split (REQ-173)
- **Lead:** lead-developer
- **Must-haves:**
- Extract the decommission block (`scripts/run_platform.sh:180-237`)
into `scripts/run_decommission.sh` (sourced or invoked).
- Extract the uptime block (`:520-606`) into `scripts/run_uptime.sh`.
- `run_platform.sh` invokes the helpers; behavior unchanged.
- **G-112 binding:** the helpers are **`source`d** (shared shell env),
not invoked as subshells — the extracted blocks reference
`run_platform.sh`-local vars (`NOVA_CONTRACT_ID`/`NOVA_WORK_DIR` from
P6); a subshell would not inherit them.
- **G-111 binding:** update `core/regression_verify.py` CAP-015/016
checks — when the live resource is absent
(`ResourceNotFoundException`/`404`), mark `Skipped (post-teardown,
D-096)` not `Decayed`, so a clean local run reports 20/20 Verified +
2 Skipped (not a strict-`all` failure on the known teardown state).
- **Run the regression gate (D-118, end of Wave 2):** **20/22 Verified**
is the passing bar (CAP-015/016 Skipped — post-v1.11-teardown steady
state, D-096; re-provisioning is a future feature, not an NFR). Any
non-Verified/non-Skipped capability halts Wave 3.
- **Verify:** pytest passes; `run_ci.sh` exits 0; `run_platform.sh
--check-only` exits 0; **regression gate 20/22 Verified + 2 Skipped**.
## Wave 3 — Security + Maintainability (P10P14)
### Phase P10 — contract-ingestor-defense-in-depth (REQ-174)
- **Lead:** backend-engineer; **Contributor:** lead-developer (review)
- **Must-haves:**
- `core/lambda/contract_ingestor.py:251-252` `if not caller_arn: pass`
→ fail closed: return a 401/403 with a clear message when IAM identity
is absent (defense-in-depth; ABAC layer still the primary control).
- `core/lambda/contract_ingestor.py:269` hardcoded
`valid_envs = {"dev","qa","prod","dr"}` → derive from the
`core/environments/` directory (list `*.json` filenames).
- Document the ABAC reliance explicitly in the function docstring +
ARCHITECTURE.md.
- Test: a request without IAM identity is rejected; a request with an
unknown environment is rejected.
- **Verify:** pytest passes; `run_ci.sh` exits 0.
### Phase P11 — contract-ingestor-payload-validation (REQ-175)
- **Lead:** backend-engineer
- **Must-haves:**
- `submit_contract`: size-cap the `contract` blob (e.g. 256 KB) before
the DynamoDB write; reject oversized payloads with 413.
- Schema-validate the contract blob against `schemas/contract.schema.json`
before the write; reject invalid with 400.
- Consistent caps: `error` and `stackTrace` use the same cap (align the
10k vs 2k inconsistency).
- Tests for size-limit + schema-rejection paths.
- **Verify:** pytest passes; `run_ci.sh` exits 0.
### Phase P12 — split-contract-resolver (REQ-176)
- **Lead:** backend-engineer
- **Must-haves:**
- Split `core/contract_resolver.py` (638 lines) into:
`core/contract_resolve.py` (the resolve + interpolation core),
`core/decommission_transform.py` (the decommission zero-counts
transform), `core/contract_resolver_cli.py` (the `__main__` CLI).
- `core/contract_resolver.py` becomes a thin re-export shim for
backwards compat (existing imports keep working).
- **G-113 binding:** import direction is one-way — split modules
import only each other + stdlib; the re-export shim imports the
split modules; nothing imports the shim except external callers
(prevents the latent cycle shim → split → split → shim).
- Behavior unchanged; all tests pass without modification.
- **Verify:** pytest passes; `run_ci.sh` exits 0.
### Phase P13 — split-regression-verify (REQ-177)
- **Lead:** backend-engineer
- **Must-haves:**
- Split `core/regression_verify.py` (670 lines) into:
`core/regression_capabilities.py` (the CAP-001..022 checks),
`core/regression_live_plan.py` (the shared live-plan helpers from
P5), `core/regression_verify_cli.py` (the `__main__` CLI +
`run_regression` orchestration).
- `core/regression_verify.py` becomes a thin re-export shim.
- Behavior unchanged; the regression gate output is identical.
- **Verify:** pytest passes; `run_ci.sh` exits 0.
### Phase P14 — schema-driven-outputs-and-cache (REQ-178)
- **Lead:** backend-engineer; **Contributor:** data-engineer (interface.json)
- **Must-haves:**
- `core/output_publisher.py:38-55` `SAFE_OUTPUT_NAMES` hardcoded set →
derived from `modules/l1/*/interface.json` `outputs[].sensitive`
annotations (non-sensitive outputs are safe to publish).
- `core/contract_resolver.py:498,617` (now in the split module) —
cache loaded JSON schemas in a module-level dict (avoid re-reading
from disk each resolve call).
- **Mid-milestone checkpoint (offline):** regression gate spot-check
(not the full P9/P21 gate); confirm Wave 3 introduced no regressions.
- **Verify:** pytest passes; `run_ci.sh` exits 0.
## Wave 4 — Developer Experience (P15P17)
### Phase P15 — run-platform-help-and-flags-doc (REQ-179)
- **Lead:** lead-developer
- **Must-haves:**
- `scripts/run_platform.sh` add a real `--help` / `-h` flag that
prints all flags + a one-line description each (`--check-only`,
`--plan-only`, `--apply`, `--destroy`, `--quiet`, `--deploy-uptime`,
`--decommission`, `--local`, `--environment`). The current `:82`
reject-unknown-flags path must allow `--help` to print + exit 0.
- Document `--deploy-uptime` in the header comment block (currently
used at `:532` but absent from the header).
- Surface `--local` (D-092 local emulating tier) in the README "How to
run" section.
- **Verify:** `run_platform.sh --help` exits 0 and lists all flags;
pytest passes; `run_ci.sh` exits 0.
### Phase P16 — workflows-readme-catalog (REQ-180)
- **Lead:** lead-developer
- **Must-haves:**
- Author `.github/workflows/README.md` cataloging all 7 workflows:
`ci.yml`, `deploy.yml`, `platform-test.yml`, `primitives-plan.yml`,
`patterns-plan.yml`, `release.yml`, `modules-lifecycle.yml`. For
each: trigger (`on:`), inputs (reusable-workflow `workflow_call`
inputs), required secrets, and one-line purpose.
- Note which 3 are byte-identical Gitea mirrors (post-P8, generated by
`sync_workflows.py`) and which 4 are GitHub-only (Gitea act_runner
feature gaps).
- Add a `tests/test_docs_coverage.py` assertion that the README exists
+ lists all 7 workflow filenames.
- **Verify:** pytest passes; `run_ci.sh` exits 0.
### Phase P17 — getting-started-consolidation (REQ-181)
- **Lead:** lead-developer
- **Must-haves:**
- Consolidate the README "How to run" into a single getting-started
section: **offline happy path first** (`bash scripts/run_ci.sh` +
`bash scripts/run_platform.sh --check-only` / `--local` — no AWS
needed), then the **AWS path** (bootstrap + `--apply`).
- Remove the fragmented 3-step bootstrap as the lead; demote it to
the AWS-path subsection.
- Cross-link `docs/CONSUMER_GUIDE.md` for the consumer contract model.
- **Verify:** pytest passes; `run_ci.sh` exits 0.
## Wave 5 — No Humans Onboarding Flow (P18P20)
### Phase P18 — onboarding-schema-and-lambda-action (REQ-182)
- **Lead:** backend-engineer; **Contributor:** lead-developer (schema)
- **Must-haves:**
- Author `schemas/onboarding.schema.json` (JSON Schema draft 2020-12):
required fields `consumerRepo` (string, format), `requestedEnvironment`
(string, enum from environments dir), `ownerId` (string), `billingTag`
(string); optional `notes`.
- `core/lambda/contract_ingestor.py` add an `onboard_consumer` action
(D-119): validates the payload against the onboarding schema, writes
a `pending` row to `nova-contracts` (PK `consumerRepo`, SK
`onboarding#<requestedEnvironment>#<timestamp>`, status `pending`).
No AWS resources created (D-113).
- Tests: valid onboarding request writes a pending row; invalid request
rejected with 400; offline-testable via moto/local Lambda stub.
- **Verify:** pytest passes; `run_ci.sh` exits 0.
### Phase P19 — onboarding-envfile-autogen (REQ-183)
- **Lead:** backend-engineer; **Contributor:** lead-developer (docs)
- **Must-haves:**
- Author `core/onboarding.py` with `generate_env_file(request,
template_env="dev")` — produces a `<env>.json` from a consumer
onboarding request (fills `account_id` placeholder, `ownerId`,
`billingTag` into the env template). Emits the file + a git patch /
PR-branch instruction.
- Rebrand `core/environment_check.py:57-77` onboarding message to
Nova; replace the "1. Contact the platform team" handoff with the
self-service request path: "Run `nova onboard` (or POST to the
Lambda `onboard_consumer` action) to request an environment; the
platform generates a binding + opens a PR."
- Update `core/environments/README.md:34-37` — self-service request
path is now implemented (real provisioning still a future feature).
- Tests: `generate_env_file` produces a valid env JSON; the rebranded
message no longer says "contact the platform team".
- **Verify:** pytest passes; `run_ci.sh` exits 0.
### Phase P20 — cross-account-role-automation-offline (REQ-184)
- **Lead:** data-engineer; **Contributor:** backend-engineer (ABAC)
- **Must-haves:**
- Author `terraform/onboarding/` (new dir): `main.tf` defining the
consumer deploy-role + `nova:owner` ABAC tag grant (cross-account
IAM role + trust policy + tag-based permission boundary). Variables
for `consumer_repo`, `owner_id`, `account_id`.
- `terraform validate` passes; `terraform plan` (offline / no live
apply per D-114) produces the expected role + policy.
- Document the onboarding Terraform in `docs/ONBOARDING.md` — the
request path (P18) → env-file autogen (P19) → role grant (P20, this
phase, offline-proven; live apply deferred).
- Tests: `terraform validate` for the onboarding module; a
`test_onboarding_terraform.py` asserting the module validates.
- **Verify:** `terraform validate` (onboarding module) passes; pytest
passes; `run_ci.sh` exits 0.
## Final Phase — P21 — final-review-ship
- **Lead:** lead-developer; **Contributors:** all active (review)
- **Must-haves:**
- Multi-persona code review across all v1.16 phases (ci-code-reviewer).
Auto-apply P0 fixes; flag P1+ for post-hoc review. If P1+ found, fix
in this phase (not loop back to EXECUTE).
- Audit (ciagent-audit): reconstruction test (git log matches
`.ciagent/` files), file discipline, branch hygiene, commit
discipline. Fix critical issues in this phase.
- **Run the regression gate (D-118, milestone complete):** **20/22
Verified** (CAP-015/016 Skipped — post-teardown steady state, D-096).
- Update `.ciagent/REQUIREMENTS.md` — mark REQ-165..184 complete.
- Update `.ciagent/ROADMAP.md` — mark v1.16 complete.
- Update `.ciagent/PROJECT.md` — v1.16 complete summary.
- Ship: merge `phase/21-final-review-ship` →
`milestone/v1.16-nova-simplification`; merge milestone → `main`;
tag `v1.15.26` (= milestone release); create Gitea release with full
milestone summary.
- Clear CHECKPOINT.json (milestone complete).
## Success Criteria (milestone gate)
- All 20 requirements (REQ-165..184) satisfied; 0 partial.
- Regression gate **20/22 Verified + 2 Skipped** at P9 + P21 (D-118,
G-111; CAP-015/016 are the post-v1.11-teardown steady state, D-096).
- `bash scripts/run_ci.sh` exits 0 at every phase boundary.
- Review: 0 new P0; P1+ flagged or auto-fixed.
- Audit: clean; reconstruction test passes.
- Tag `v1.15.26` created; milestone merged to main.
- Onboarding request path implemented (P18P20); real AWS provisioning
explicitly deferred (D-113, D-114).
+6 -510
View File
@@ -33,7 +33,7 @@ traceable to a human attestation and an immutable evidence stream.
1. **Operations are Declared, Not Executed.** Consumers define what they 1. **Operations are Declared, Not Executed.** Consumers define what they
need; the platform reconciles, provisions, and progresses. need; the platform reconciles, provisions, and progresses.
2. **The Delivery Lifecycle is a Sovereign Boundary.** The platform 2. **The Delivery Lifecycle is a Sovereign Boundary.** The platform
governs infra and delivery; it does not reach into upstream product/SDLC. governs infra and delivery; it does not penetrate upstream product/SDLC.
Integration is only through validated, published contracts. Integration is only through validated, published contracts.
3. **Lower Environments are Autonomous; Higher Environments are Attested.** 3. **Lower Environments are Autonomous; Higher Environments are Attested.**
Dev = zero-touch agentic. QA/prod/dr = deliberate human attestation, not Dev = zero-touch agentic. QA/prod/dr = deliberate human attestation, not
@@ -58,103 +58,6 @@ traceable to a human attestation and an immutable evidence stream.
boundary. The platform validates, enriches with operational standards, boundary. The platform validates, enriches with operational standards,
and reconciles the target state. and reconciles the target state.
## Scope: Nova is Downstream of PDLC
> **Promoted from Core Tenet #2 + Anti-Goal #1 (v1.18, REQ-216).** This
> is the unmissable scope statement — the PDLC is upstream, Nova is
> downstream.
The **Product Development Lifecycle (PDLC)** — product backlog, code
authorship, IDE workflows, sprint planning, application business logic —
is **upstream** of Nova. Nova never reaches into the PDLC. Nova's domain is
**infrastructure + delivery only**: environment progression, cloud
resource lifecycle, operational security/observability NFRs, policy
enforcement, immutable audit lineage, and the two consumer surfaces
(technical developer + agentic).
Integration between the PDLC and Nova is **only** through the validated,
published contract boundary (`schemas/contract.schema.json` +
`schemas/submission-readiness.schema.json`). The citizen developer's AI
coding agent, an upstream agentic SDLC platform, or any upstream
development platform may all produce submissions — the source does not
matter because all are subject to the same compliance standards (the
submission-readiness gate, D-133). Nova validates, enriches with
operational standards, and reconciles the target state. Nova never
authors application code, manages product backlogs, or provides IDE
workflows.
```
PDLC (upstream) Nova (downstream)
───────────────── ─────────────────
product backlog contract ingestion
code authorship (AI agent / IDE / SDLC) → submission-readiness gate
sprint planning → policy enforcement
application business logic → cloud resource lifecycle
→ environment progression (dev→qa→prod→dr)
→ immutable audit + attestation
```
## RACI Matrix
> **Source of truth (v1.18, REQ-215, D-139).** Three roles clarify who
> owns what across the Nova delivery lifecycle. The matrix is the
> authoritative version; `docs/raci.md` is the citizen-developer-facing
> copy.
### Roles
- **Citizen Developer (CD)** — the consumer (technical developer L3A or
non-technical L3B). Responsible for all **Functional Requirements (FRs)**
and **User Acceptance Testing (UAT)**. The FRs + UAT are produced via
the citizen developer's AI coding agent, an upstream agentic SDLC, or
an upstream development platform — **the source does not matter as all
are subject to the same compliance standards** (the submission-readiness
gate, D-133).
- **Platform** — Nova. Responsible for all **Non-Functional Requirements
(NFRs)**, **Infrastructure** (cloud resource lifecycle, state, IAM),
**QA** (the platform-side quality checks: policy, confidence, schema),
and **Production deployments to cloud** (the apply path, the pipeline,
the release).
- **Release Management (RM)** — **co-owned**. QA + SRE attestations are
required by the actual release. The attestations are performed
agentically (the platform runs the checks), but the release is
**overseen and triggered by the Citizen Developer** — the human
attestation at the stage gate (D-042, hitl_gates.py). The platform
performs; the citizen developer authorizes.
### Matrix
| Work Category | Citizen Developer | Platform | Release Management |
|---|---|---|---|
| **Functional Requirements (FRs)** | **R/A** | C | I |
| **User Acceptance Testing (UAT)** | **R/A** | C | I |
| **Non-Functional Requirements (NFRs)** | I | **R/A** | C |
| **Infrastructure (cloud, state, IAM)** | I | **R/A** | C |
| **QA (policy, confidence, schema checks)** | C | **R/A** | I |
| **Production deployment to cloud** | I | **R/A** | C |
| **Release attestation (QA + SRE sign-off)** | **A** | R | **R** |
**Key: R** = Responsible (does the work) · **A** = Accountable (owns the
outcome, sign-off) · **C** = Consulted · **I** = Informed.
**Compliance-standard equivalence note:** the citizen developer's FRs +
UAT may originate from any upstream source — an AI coding agent, an
agentic SDLC platform, or a traditional development platform. All are
subject to the same compliance standards: the submission-readiness gate
(`schemas/submission-readiness.schema.json`), the contract schema, the
policy envelope, and the immutable audit stream. The platform does not
differentiate by upstream source; it validates the submission, not the
author.
**Co-ownership of Release Management:** the release is co-owned. The
platform performs the QA + SRE attestations agentically (confidence signal,
policy checks, separation-of-duties). The citizen developer oversees and
triggers the actual release — the human attestation at the stage gate is
the citizen developer's authorization, recorded with approver identity
(D-042). The platform runs the checks; the citizen developer authorizes
the promotion. This is the "autonomy in operations, human at stage gates"
model from the NORTH_STAR.
## Capability Status (Re-Verified 2026-07-27) ## Capability Status (Re-Verified 2026-07-27)
> Source of truth: `.ciagent/CAPABILITY_INVENTORY.md` (Phase 54, D-093). > Source of truth: `.ciagent/CAPABILITY_INVENTORY.md` (Phase 54, D-093).
@@ -689,97 +592,12 @@ DX: 16 total). Key changes:
10. Old two-surfaces diagram replaced by scope boundary diagram. 10. Old two-surfaces diagram replaced by scope boundary diagram.
Source markdown, talking points, and README all updated to mirror the new Source markdown, talking points, and README all updated to mirror the new
structure. Also includes scripts/sync_to_nova.sh (manual-only "2nd release" structure. Also includes scripts/sync_to_gl.sh (GitLab mirror sync
into ~/nova — a separate GitLab consumer-facing repo with its own history; utility, unrelated to presentations).
domain-based conventional commits, never triggered by CI; REQ-229).
No code changes; 494 tests pass; `run_ci.sh` + `run_platform.sh --check-only` No code changes; 494 tests pass; `run_ci.sh` + `run_platform.sh --check-only`
green. PPTX files uploaded to Gitea release. green. PPTX files uploaded to Gitea release.
## Objective for Milestone v1.18 (active — Citizen Developer & Production-Grade Guidance)
v1.18 advances Nova from a platform that governs infrastructure delivery
to one that **instructs the citizen developer on production-grade
engineering** and defines a **clear, machine-checkable contract for what
is acceptable to start**. Five user-directed inputs drive the milestone:
1. **S&P Global theme restoration.** The v1.17 P5 deck rebuild consolidated
two decks into one unified narrative deck but lost the S&P Global Energy
brand visual identity (introduced v1.9.2 / P45, commit `ae0cb58`). The
Marp `style:` block (red-core `#D6002A`, grey-90 `#1B1B1B`, Akkurat Pro
font, 8px top accent bar) is restored to the unified deck. The mermaid
`sp-theme.json` survived; only the Marp CSS theme was lost.
2. **PDLC-upstream scope made explicit.** Core Tenet #2 already states the
platform "does not reach into upstream product/SDLC" and Anti-Goal #1 says
"Not an upstream development platform." v1.18 promotes this from a
buried tenet to a dedicated, unmissable scope statement in PROJECT.md +
`docs/scope.md` + a deck slide: **the PDLC (Product Development
Lifecycle — product backlog, code authorship, IDE) is upstream of Nova;
Nova governs infra + delivery only; integration is through the validated
contract boundary.**
3. **RACI matrix.** A three-role responsibility matrix clarifies who owns
what: **Citizen Developer** (Responsible for all Functional Requirements
+ User Acceptance Testing, via their AI coding agent / upstream agentic
SDLC / upstream development platform — the source does not matter as all
are subject to the same compliance standards), **Platform** (Responsible
for all NFRs + Infrastructure + QA + Production deployments to cloud),
**Release Management** (co-owned: QA + SRE attestations required by the
actual release, performed agentically but overseen & triggered by the
Citizen Developer). Source of truth in PROJECT.md + `docs/raci.md` + a
deck slide.
4. **Nova input contract — "what is acceptable to start."** A JSON Schema
(`schemas/submission-readiness.schema.json`) defines the
acceptable-to-start gate as a superset *above* contract-schema validity:
schema-valid contract + required Nova tags + per-env mandatory metadata
(per W3.E) + declared policy preconditions + (for L3B) `profile:agentic`
markers + `appSource` pointer. A validator (`core/submission_readiness.py`,
invoked as `contract_ingestor.py --check-readiness`) returns a structured
`ReadinessResult` with reason codes. On fail → citizen-developer-facing
error (not a stack trace); on pass → proceeds to existing ingestion.
5. **Atelier integration — production-grade guidance + agentic validation.**
Nova consumes `coreci/atelier` (a first-principles docs-as-code
engineering framework — 8 core principles, 19 domains, 190 P-rules) via
two surfaces: **skills** (markdown files under `skills/` keyed to Atelier
domain paths, surfaced to the citizen developer's AI agent, extending the
BA.A 5-skill catalog) and an **MCP server** (`mcp/atelier/server.py`,
plugin-registry architecture, stdio transport, vendored Atelier snapshot
for audit reproducibility) exposing tools for principle-lookup,
domain-listing, matrix-lookup, and agentic validation against the
Atelier agent-checklist — validation that goes beyond deterministic
scanners (Wiz/Checkmarx/Mend) by catching correctness/clarity/simplicity/
observability gaps.
**Deck automation (cross-cutting):** any phase modifying
`docs/presentations/*-marp.md` or `docs/presentations/assets/` MUST
re-render HTML + PPTX, **commit the PPTX to git** (binary, no LFS), and
attach it to the phase's Gitea release. New scripts:
`scripts/render_deck.sh` (HTML + PPTX render) and
`scripts/attach_release_asset.py` (Gitea release asset upload).
**Milestone type:** Feature (P1 S&P theme restoration + P3 readiness
schema/validator + P5 MCP server are new code/features). Tags run on the
**v1.17.x** patch line (previous minor per branch-strategy): `v1.17.0` (P0)
`v1.17.1..v1.17.6` (P1P6) → `v1.17.7` (P7 final = milestone release).
**Phase count:** 8 (P0 pre-execution + 6 execution + 1 final).
**Hard constraints:**
- DO NOT make anything up (NORTH_STAR.md honesty model).
- The submission-readiness schema is a superset gate above
`contract.schema.json`, NOT a duplicate — it references but does not
redefine contract fields.
- The MCP server is plugin-registry extensible (future capabilities drop
in as new plugin files, no `server.py` edits).
- Atelier is vendored (pinned tag) for audit reproducibility — an agentic
validation result must be replayable against the exact principles that
produced it.
- PPTX is a first-class artifact: committed (history) + attached (download)
— both always, not optional.
## Requirements ## Requirements
### v1.0 (Prior milestone — the demo) ### v1.0 (Prior milestone — the demo)
@@ -981,7 +799,7 @@ or user-directed scope). New v1.7 decisions:
| W1.A | AI-refinement trigger | **Accept recommendation.** Joint condition: N ≥ 50 consecutive changes with zero rollbacks AND no L1/L2 incident in last 6 months AND Infra & Ops unilateral override. | | W1.A | AI-refinement trigger | **Accept recommendation.** Joint condition: N ≥ 50 consecutive changes with zero rollbacks AND no L1/L2 incident in last 6 months AND Infra & Ops unilateral override. |
| W1.B | Multi-stack edge case rule | **Accept recommendation.** Permitted only for (a) DR-region mirror, (b) time-boxed experimental stack with TTL ≤ 30d, (c) explicit Infra & Ops approval with `multiStack.justification`. | | W1.B | Multi-stack edge case rule | **Accept recommendation.** Permitted only for (a) DR-region mirror, (b) time-boxed experimental stack with TTL ≤ 30d, (c) explicit Infra & Ops approval with `multiStack.justification`. |
| W2.A | Tag mutability for prod | **Accept recommendation (Path B).** Tag for dev/qa, SHA for prod. Platform CLI resolves tag→SHA for prod-bound workflows. Justified by the "Audit truth lives outside the repository" bet. | | W2.A | Tag mutability for prod | **Accept recommendation (Path B).** Tag for dev/qa, SHA for prod. Platform CLI resolves tag→SHA for prod-bound workflows. Justified by the "Audit truth lives outside the repository" bet. |
| BA.A | Initial L3B skill catalog | **Accept recommendation.** 5 skills: web API, worker, scheduled job, static asset, basic observability bootstrap. Addition criteria: (a) reviewable for sensitive data, (b) expressible as a single contract submission, (c) documented use case. **Extended v1.18 (REQ-221/222):** the BA.A 5-skill catalog is extended with 9 Atelier-derived production-grade engineering skills under `skills/` (api, security, data, testing, observability, errors, devops, infrastructure-as-code, compliance), indexed by `docs/skills.md`. The Atelier skills extend, not replace, the BA.A catalog. | | BA.A | Initial L3B skill catalog | **Accept recommendation.** 5 skills: web API, worker, scheduled job, static asset, basic observability bootstrap. Addition criteria: (a) reviewable for sensitive data, (b) expressible as a single contract submission, (c) documented use case. |
| W3.D | L1/L2 standard versioning | **Decided.** Semver: interface → MAJOR, behavior → MINOR, lifecycle → PATCH (same as the v1.0 demo D-rule, lifted to the real platform). Pin model: L2 contracts pin L1 by `name@semver`; the resolver picks the highest compatible. Evolution: MAJOR bumps require a new registry entry (immutable publication); old entry enters a 12-month deprecation window. | | W3.D | L1/L2 standard versioning | **Decided.** Semver: interface → MAJOR, behavior → MINOR, lifecycle → PATCH (same as the v1.0 demo D-rule, lifted to the real platform). Pin model: L2 contracts pin L1 by `name@semver`; the resolver picks the highest compatible. Evolution: MAJOR bumps require a new registry entry (immutable publication); old entry enters a 12-month deprecation window. |
| W3.E | Schema mandatory vs optional inputs | **Decided.** Per-env mandatory table: dev requires `stack` + `environment`; qa adds `validation.e2eSuite` + `validation.loadTest`; prod adds `runbook` + `dashboard` + `oncall`; dr adds `drDrillRef`. `inputs` map is always optional. `profile: agentic` fields (`naturalLanguageIntent`, `confidenceAtSubmission`, `agentTrace`) optional everywhere. | | W3.E | Schema mandatory vs optional inputs | **Decided.** Per-env mandatory table: dev requires `stack` + `environment`; qa adds `validation.e2eSuite` + `validation.loadTest`; prod adds `runbook` + `dashboard` + `oncall`; dr adds `drDrillRef`. `inputs` map is always optional. `profile: agentic` fields (`naturalLanguageIntent`, `confidenceAtSubmission`, `agentTrace`) optional everywhere. |
| BA.B | Confidence threshold tuning | **Decided.** Starting thresholds frozen for v1. Tuning begins in v1.2: track FP/FN per environment quarterly; override authority = Infra & Ops + SRE joint sign-off; any override is itself a confidence-event in the audit stream. | | BA.B | Confidence threshold tuning | **Decided.** Starting thresholds frozen for v1. Tuning begins in v1.2: track FP/FN per environment quarterly; override authority = Infra & Ops + SRE joint sign-off; any override is itself a confidence-event in the audit stream. |
@@ -1171,7 +989,7 @@ conversation before execution; D-108..D-112 resolved at CLARIFY.
| D-111 | Lambda env-var defaults (`CONTRACTS_TABLE` default `"acdl-contracts"`, etc.) → `nova-contracts`. | `core/lambda/contract_ingestor.py` has hardcoded `acdl-*` default table names. These become `nova-*` in P4 (resource migration). P2 changes the env-var name (`ACDL_*``NOVA_*`); P4 changes the default values to `nova-*`. | P4 updates Lambda defaults. | | D-111 | Lambda env-var defaults (`CONTRACTS_TABLE` default `"acdl-contracts"`, etc.) → `nova-contracts`. | `core/lambda/contract_ingestor.py` has hardcoded `acdl-*` default table names. These become `nova-*` in P4 (resource migration). P2 changes the env-var name (`ACDL_*``NOVA_*`); P4 changes the default values to `nova-*`. | P4 updates Lambda defaults. |
| D-112 | `nova` slug: no `project:` prefix on branches (single-project mode). | `config.json` has `projects[]` with one entry (slug `acdl`) but `git.branching_strategy` is `flat` and the established convention since v1.0 is flat branches (no `<slug>/` prefix). Nova rebrand does NOT change the branch prefix convention. Commit `---ci---` blocks use `project: acdl` (the config slug, unchanged). | Branches stay `milestone/v1.15-nova`, `phase/NN-*`; no `acdl/` or `nova/` prefix. | | D-112 | `nova` slug: no `project:` prefix on branches (single-project mode). | `config.json` has `projects[]` with one entry (slug `acdl`) but `git.branching_strategy` is `flat` and the established convention since v1.0 is flat branches (no `<slug>/` prefix). Nova rebrand does NOT change the branch prefix convention. Commit `---ci---` blocks use `project: acdl` (the config slug, unchanged). | Branches stay `milestone/v1.15-nova`, `phase/NN-*`; no `acdl/` or `nova/` prefix. |
## Objective for Milestone v1.16 (complete — NFR Simplification, tag `v1.15.26`) ## Objective for Milestone v1.16 (active — NFR Simplification)
A 20-phase NFR sweep (no new features) themed around five axes the user A 20-phase NFR sweep (no new features) themed around five axes the user
directed during ideation: **Simplify without regressions**, **Security**, directed during ideation: **Simplify without regressions**, **Security**,
@@ -1254,326 +1072,4 @@ conversation; D-117..D-119 resolved at CLARIFY.
| D-116 | Drift fixes = P1 of v1.16 (not a hotfix to main). | User chose "P1 of v1.16." The state-bucket drift (`adapter.py:117`) and Kyverno label contradiction are correctness regressions but latent in plan-only mode (no live apply in the default path), so they are not an active outage. Fixing them as P1 keeps the milestone self-contained. | P1 fixes both; no hotfix to main. | | D-116 | Drift fixes = P1 of v1.16 (not a hotfix to main). | User chose "P1 of v1.16." The state-bucket drift (`adapter.py:117`) and Kyverno label contradiction are correctness regressions but latent in plan-only mode (no live apply in the default path), so they are not an active outage. Fixing them as P1 keeps the milestone self-contained. | P1 fixes both; no hotfix to main. |
| D-117 | v1.14 NFR categories are NOT re-proposed. | v1.14 already swept over-broad excepts (REQ-141), hardcoded account-ID (REQ-142), IAM `Resource:"*"` scoping (REQ-143), contractId/env validation (REQ-144), `.gitignore` catch-all (REQ-146), `--kube-version` removal (REQ-147), orphan cleanup (REQ-148), `set -euo pipefail` parity (REQ-150). v1.16 finds NEW residual signals (the v1.15 rebrand left a fresh debt layer) and does not duplicate completed work. | Wave 15 target only fresh debt. | | D-117 | v1.14 NFR categories are NOT re-proposed. | v1.14 already swept over-broad excepts (REQ-141), hardcoded account-ID (REQ-142), IAM `Resource:"*"` scoping (REQ-143), contractId/env validation (REQ-144), `.gitignore` catch-all (REQ-146), `--kube-version` removal (REQ-147), orphan cleanup (REQ-148), `set -euo pipefail` parity (REQ-150). v1.16 finds NEW residual signals (the v1.15 rebrand left a fresh debt layer) and does not duplicate completed work. | Wave 15 target only fresh debt. |
| D-118 | Regression gate (D-091) gates Wave 2 completion and P21. | "Simplify without regressions" is only credible if the regression gate runs after the simplification wave. The gate runs after P9 (Wave 2 done) and at P21 (milestone complete); any non-Verified capability halts W3. Mid-milestone checkpoint after P14 (offline). | P9 + P21 run the gate; P14 checkpoint. | | D-118 | Regression gate (D-091) gates Wave 2 completion and P21. | "Simplify without regressions" is only credible if the regression gate runs after the simplification wave. The gate runs after P9 (Wave 2 done) and at P21 (milestone complete); any non-Verified capability halts W3. Mid-milestone checkpoint after P14 (offline). | P9 + P21 run the gate; P14 checkpoint. |
| D-119 | `onboard_consumer` action stores a CMDB row pending grant (not auto-provisions). | The request-path-only scope (D-113) means the Lambda accepts an onboarding request and writes a `pending` row to `nova-contracts` (or a new `nova-onboarding` partition key); the platform automation that grants the ABAC role is the P20 Terraform (offline-proven). No AWS resources are created by the Lambda action itself. | P18 writes the pending row; P20 proves the grant Terraform offline. | | D-119 | `onboard_consumer` action stores a CMDB row pending grant (not auto-provisions). | The request-path-only scope (D-113) means the Lambda accepts an onboarding request and writes a `pending` row to `nova-contracts` (or a new `nova-onboarding` partition key); the platform automation that grants the ABAC role is the P20 Terraform (offline-proven). No AWS resources are created by the Lambda action itself. | P18 writes the pending row; P20 proves the grant Terraform offline. |
## Objective for Milestone v1.17 (active — Strategic Direction, Leadership Metrics & Unified Story)
**Milestone type:** Feature (P1P3 feat; P4 docs; P5 docs+test; P6 test;
P7 review+audit+ship). Tags on the v1.16.x line: `v1.16.0` (P0) →
`v1.16.1..v1.16.7` (P1P7) → `v1.16.8` (P8 final = milestone release).
**Three pillars:**
- **Pillar A — Strategic Direction.** A durable, PO-authored
`.ciagent/NORTH_STAR.md` encodes the platform's vision, 4 strategic
objectives, anti-goals, v1.17 non-goals, 1218mo targets (with a
grounding column), and success criteria. CIAgent reads it in every
future `/ci-run` so the direction survives across milestones. The
attestation clarification is reflected: human attestation required at
stage gates (QA for production, SRE for operational readiness); autonomy
in operations, not in accountability. **v1.21 refinement:** Strategic
Objective #4 reframed from "default substrate for agentic consumption" to
integrating with externally owned PDLC/SDLC/Agentic/Citizen Developer
platforms regardless of source (Nova provides skills + MCP endpoints;
all prod intents go through the same controls). Objective #2 reworded:
trust is established by deterministic scripts that calculate a score —
the platform functions without AI. Objective #3 reworded with four
CTO-grade metrics (Lead Time PR→Prod, Infrastructure Vulnerability
Count trend, MTTR, Cloud Spend Reduction) all flowing into PowerBI.
Anti-goals #1, #4, #5 removed; replaced with "not an upstream
development platform" and "not a replacement for the PDLC".
- **Pillar B — Leadership Metrics + PowerBI.** Instrument Nova to
collect, aggregate, and surface leadership-grade metrics that prove the
"no-humans" autonomous-infrastructure value proposition (reframed in
v1.21 to "autonomous cloud delivery" — professional framing; the
platform delivers safe production deployment without an operator in
the loop of normal operations). Nova-native
minimal tech (CloudEvents 1.0 envelope, JSONL event log, SQLite cold
store, hash-chained Decision Ledger via `outbox_writer.py` extension)
+ Infracost for pre-apply cost estimates. Hybrid model: existing
file-based signals (REGRESSION_REPORT.json, pcr.json, signal.json,
junit XML) are sources the collector reads and projects into events;
new emitters emit CloudEvents directly. PowerBI export = CSV/JSON
views (fact + dimension tables + 8 empty placeholder views for
deferred metrics). **Hard constraint: DO NOT make anything up.** Every
metric is `grounded` (cites source file + schema), `derived`
(documented formula), or `deferred` (cites decision ID — D-096/D-083/
D-113/D-114/D-119). The 8 deferred metrics: drift detection, GreenOps/
carbon, predictive/reactive, live CUR reconciliation, multi-cloud,
red-team MTTR, self-healing velocity, SLA/downtime.
- **Pillar C — Unified Narrative Deck.** Merge the two existing decks
(`how-the-platform-works` + `the-developer-experience`) into one unified
narrative deck "Nova — The No-Humans Infrastructure Platform" with a
single arc: Problem → Vision/Direction (NORTH_STAR) → How it works →
Proof (metrics) → Roadmap/Ask. The "tell them x3" structure applies at
deck level AND per slide (each slide opens with what it covers,
delivers, closes with an explicit "benefit of this stage" callout).
Fluid transitions between slides. Both old decks retired.
**Key decisions resolved in the planning conversation (D-120+):**
| ID | Decision | Rationale | Outcome |
|----|----------|-----------|---------|
| D-120 | Tech stack = Nova-native + Infracost, drift deferred. | The PO's technical-direction document specifies Kafka/Prometheus/ClickHouse/QLDB/OTel — none exist in Nova today. Adopt the PRINCIPLES (events as source of truth, CloudEvents envelope, decision ledger, definition-of-success docs, dashboards-as-projections) but implement with Nova-native minimal tech (JSONL + SQLite + hash-chained ledger). No Kafka/Prometheus/ClickHouse/QLDB. Infracost adopted (runs offline on plan JSON). Drift detection deferred (D-096 + no scheduler). | P1P3 use Nova-native tech; Infracost in P1; drift deferred. |
| D-121 | Decision Ledger = extend outbox_writer.py → SQLite append-only hash chain. | The direction's #1 priority is the Decision Ledger. Nova already has a hash-chained outbox (outbox_writer.py). Extend it to a SQLite append-only table with hash chain; add ai.decision.made + attestation.recorded events. Honors D-083 (no S3 Object Lock/JWS). | P1 extends outbox_writer; ledger is SQLite hash-chain. |
| D-122 | AI Planner framing = map Nova's real decision points. | The direction assumes an "AI Planner/Reasoner" (planner-v3.2). Nova's actual decision path is confidence_signal + HITL gate. Model ai.decision.made from confidence_signal (decision_id=run_id, chosen_action=band, confidence=score, alternatives=perInput, human_override=HITL block). LLM planner marked future/aspirational. | P1 emits honest decision events; no fabricated LLM. |
| D-123 | Deferred metrics = all 8 (drift, GreenOps, predictive/reactive, live CUR, multi-cloud, red-team MTTR, self-healing, SLA/downtime). | These require live AWS (D-096) or new external systems. Ship as empty PowerBI placeholder views with documented schemas. | P3 ships 8 placeholder views; METRICS.md marks them deferred. |
| D-124 | NORTH_STAR = strategy; tech direction = engineering input. | The PO's technical-direction document is engineering architecture, not strategy. NORTH_STAR.md captures strategic vision/objectives/anti-goals (PO-authored). The tech direction becomes the telemetry reference architecture section in RESEARCH.md/ARCHITECTURE.md, cited by NORTH_STAR's engineering objectives. | P0 writes NORTH_STAR; RESEARCH writes the telemetry reference. |
| D-125 | Events vs files = hybrid. | Existing file-based signals (REGRESSION_REPORT.json, pcr.json, signal.json, junit) stay as files; the collector reads them and emits normalized CloudEvents into JSONL + SQLite. New emitters emit CloudEvents directly. | P2 collector reads files + events. |
| D-126 | Hot/cold split = cold-only SQLite (hot path deferred). | Nova has no live ops dashboard (no live AWS, D-096). The SQLite store is cold-only (batch/historical). The hot path is documented as deferred. | P2 SQLite is cold-only. |
| D-127 | Definition-of-success = per-KPI docs. | The direction's §11 requires a definition-of-success doc for every executive KPI. Adopt this standard; docs live in `docs/metrics/`. | P4 writes per-KPI docs. |
| D-128 | Storage location = metrics/ at repo root. | metrics/runs/ (per-run manifests), metrics/nova_metrics.db (SQLite), metrics/events.jsonl (event log), metrics/powerbi/ (export). | P1P3 use metrics/ at repo root. |
| D-129 | PowerBI delivery = CSV/JSON files, folder connector. | Nova is offline-first; no live connector to a running service. PowerBI ingests via the folder connector. | P3 emits CSV/JSON to metrics/powerbi/. |
| D-130 | Deck arc = Problem → Vision → How → Proof → Roadmap. | The unified narrative deck's 5-act structure. x3 arc at deck + slide level. Per-slide benefit callouts. Fluid transitions. Both old decks retired. | P5 builds the unified deck; old decks deleted. |
| D-131 | MTTR scope = platform-run MTTR. | The <60s MTTR target refers to platform-run failures (apply.failed → successful retry), not infra-incident MTTR (no incident detection system). Infra-incident MTTR deferred. | P4 grounds platform-run MTTR. |
| D-132 | Attestation instrumentation = emit attestation.recorded events. | The attestation system (hitl_gates.py + attestation_matrix.py + separation_of_duties.py) already exists. Instrument it: emit attestation.recorded events into the Decision Ledger + PowerBI. Attestation Coverage = 100% target grounded from outbox approver_* attributes. | P1 emits attestation events; P4 grounds Attestation Coverage. |
## Key Decisions (v1.18)
Resolved at the CLARIFY stage (full autonomy — all within locked
constraints or user-directed scope). New v1.18 decisions:
| ID | Decision | Rationale | Outcome |
|----|----------|-----------|---------|
| D-133 | Submission-readiness validator location = extend `contract_ingestor.py --check-readiness`. | Adding a new CLI binary is unnecessary; the ingestor is the existing entry point for contract submission. The validator is a subcommand that runs before ingestion proceeds. No new binary, no new entry point to maintain. | P3 implements the subcommand; no new CLI binary. |
| D-134 | Deck slide budget = 18 → 21 slides (no act restructure). | The 3 new slides (scope/RACI/atelier) are leadership-relevant and append after the existing 18. The 5-act arc (D-130) is preserved; the new slides are append-only context, not a new act. | P6 appends 3 slides → 21 total. |
| D-135 | Atelier MCP transport = stdio now; HTTP-ready (same server object). | stdio is the local-agent transport (the citizen developer's AI agent spawns the server as a subprocess). The MCP Python SDK v2 supports Streamable HTTP on the same `MCPServer` object, so adding HTTP later is a transport-only change in `server.py`, not a rewrite. | P5 ships stdio; HTTP deferred (documented in README). |
| D-136 | Atelier source = vendor pinned tag under `mcp/atelier/vendor/`. | An agentic validation result is only reproducible if the principles that produced it are pinned. Live-fetch breaks replayability (Atelier `main` drifts). Vendoring matches the v1.16 P15 offline-first precedent and the Nova thesis (provable trust). `mcp/atelier/vendor/VERSION.md` records the pinned tag; `scripts/update_atelier_vendor.sh` is the intentional upgrade path. | P5 vendors Atelier; live-fetch not implemented. |
| D-137 | MCP server language = Python (MCP Python SDK v2, `modelcontextprotocol/python-sdk`). | Nova's `core/` is Python. The MCP Python SDK v2 (23.9k stars, MIT, stable) matches the codebase; type hints become JSON Schema automatically (`@mcp.tool()` decorator). | P5 uses Python SDK v2. |
| D-138 | Skill catalog format = markdown files under `skills/` keyed to Atelier domain paths. | Markdown is the established Nova docs format (Jekyll Pages, 4-step deck process). Each skill file names the Atelier source path, distills the first-principles, links to agent-checklist triggers, and maps to the BA.A catalog. | P4 authors 9 markdown skill files. |
| D-139 | RACI role names = Citizen Developer / Platform / Release Management (co-owned). | User-specified. The 3 roles are the columns of the RACI table. Release Management is co-owned: QA + SRE attestations are required by the actual release (performed agentically, overseen & triggered by the Citizen Developer). | P2 authors the RACI with these 3 roles. |
| D-140 | MCP server extensibility = plugin-registry (`plugins/<name>.py` implementing `register(mcp)`). | Future capabilities (new scanners, policy evaluators, cost tools) drop in as new plugin files — no `server.py` edits. `server.py` scans `plugins/` and calls `register` on each. This is the extensibility insurance: plugins are decoupled from the server entrypoint. | P5 implements the plugin-registry; initial plugins are `principles.py` + `validation.py`. |
| D-141 | PPTX storage = commit binary directly to `docs/presentations/` (no LFS). | Decks are small (~1-5 MiB); git handles binary blobs. LFS requires server-side support (unverified for git.cloudinit.dev) + client config. Committing directly is simplest and works without any repo/server config. Binary diffs are not delta-friendly, but deck changes are infrequent. | P1/P2/P6 commit .pptx directly. |
| D-142 | Deck render trigger = any phase modifying `docs/presentations/*-marp.md` or `docs/presentations/assets/` must re-render HTML + PPTX, commit PPTX, and attach to the Gitea release. | PPTX was previously manual + release-only (not committed). v1.18 makes it a first-class artifact: committed (history) + attached (download), both always, not optional. Automated via `scripts/render_deck.sh` + `scripts/attach_release_asset.py`. | P1/P2/P6 run the render+commit+attach pipeline. |
## Objective for Milestone v1.19 (complete — Nova 2nd-Release Sync)
> **NFR-only chore milestone.** Ships a patch on the v1.18.x line (tag
> `v1.18.0`). Single execution phase. Establishes the manual-only "2nd
> release" pipeline from `~/acdl` (CIAgent-managed source of truth, full audit
> trail) into `~/nova` (GitLab `jonathanchery/nova` — separate repo, separate
> history, consumer / platform-team audience).
### Why
`~/acdl` is the engineering source of truth and carries the full CIAgent
audit trail (`.ciagent/`, milestone branches, `---ci---` blocks, Gitea
releases). Consumers and the platform team should consume a clean,
conventional-commit-shaped tree without the CIAgent plumbing. The old
`scripts/sync_to_gl.sh` mirrored `~/acdl → ~/gl/acdl` with a single
kitchen-sink `chore: sync from source mirror <ts>` commit — wrong audience,
wrong commit standard, wrong repo.
### What
- **`scripts/sync_to_nova.sh`** replaces `scripts/sync_to_gl.sh`.
- **Manual-only gate**: refuses without `--release` / `RELEASE_CONFIRMED=1`
(exit 2). Never triggerable by CI.
- **Consumer subset only**: excludes `.ciagent/`, `.gitea/`, `.env*`,
`terraform/`, `demo/`, runtime metrics artifacts, and internal-only scripts
(the `EXCLUDE_SCRIPTS` list — CIAgent/ops/release plumbing). Keeps
consumer-facing runbooks (`run_ci.sh`, `run_platform.sh`, etc.) and the
metrics export views (`metrics/README.md`, `powerbi/`, `TRUST_SNAPSHOT.md`).
- **Destination history protected**: rsync `--filter=P .git` ensures
`~/nova/.git` is never touched.
- **Domain-based commits**: 13 fixed-order domains (config → core → adapters
→ modules → contracts → schemas → pipelines → mcp → skills → scripts →
tests → docs → workflows). Each changed domain gets its own conventional
commit, supplied positionally via repeated `-m` flags. No kitchen-sink.
- **Conventional-commit validation**: regex-enforced
(`feat|fix|docs|chore|refactor|perf|test|build|ci|style|revert`); bypass via
`--no-verify-format`.
- **Modes**: `--list-domains` (print order), `--dry-run` (preview rsync +
messages), `--no-push` (commit without pushing), `-v` (verbose).
### Out of Scope
- **coreci / Atelier review gate on the synced tree** — deferred. A future
milestone may run a vendored-Atelier review pass before commit and block on
P0 findings.
- **Tagging releases on the `~/nova` side** — could add `--tag <semver>`
later.
- **Deleting `~/gl`** — the old mirror dir is left on disk; only the sync
script targeting it is removed.
### Requirements
- **REQ-229** — `scripts/sync_to_nova.sh` replaces `sync_to_gl.sh` with the
manual-only, consumer-subset, domain-committed 2nd-release pipeline
described above. (Phase P1)
### Phase Plan
| Phase | Name | Status |
|-------|------|--------|
| P1 | nova-sync-script | complete |
| P2 | final-review-ship | pending |
### Decisions
| ID | Decision | Rationale | Outcome |
|----|----------|-----------|---------|
| D-143 | 2nd release target = `~/nova` (separate GitLab repo), not `~/gl/acdl`. | `~/nova` is consumer/platform-team-facing with its own history; `~/gl/acdl` was an internal mirror with a kitchen-sink commit standard. Separate audience → separate repo → separate commit standard. | `sync_to_nova.sh` targets `~/nova`; `sync_to_gl.sh` removed. |
| D-144 | Commit standard for `~/nova` = real conventional commits per domain (not the `---ci---` audit blocks used in `~/acdl`). | `~/acdl` commits carry CIAgent audit metadata (`---ci---` blocks) for the ciagent auditing workflow; that's noise for platform consumers. `~/nova` gets clean `feat/fix/docs/chore(scope): subject` commits grouped by domain. | Script validates conventional format; domain-based commits via positional `-m`. |
| D-145 | Trigger = manual-only (`--release` / `RELEASE_CONFIRMED=1`). | The 2nd release is a deliberate human action, not a CI side-effect. The gate guarantees it can never fire from Gitea Actions, GitHub Actions, or accidental invocation. | Script exits 2 without `--release`. |
| D-146 | Domain grouping = 13 fixed-order domains by path prefix; messages map positionally over CHANGED domains only. | Avoids the kitchen-sink commit; gives `~/nova` a reviewable, conventional history tailored to platform consumers. Positional-over-changed mapping lets the human supply exactly the messages needed, in domain order, without padding for unchanged domains. | `--list-domains` prints order; `--dry-run` previews; count-mismatch errors clearly. |
| D-147 | coreci / Atelier review gate = deferred this milestone. | The vendored Atelier (`mcp/atelier/vendor`) could review the synced tree before commit and block on P0, but that's an additive hardening step, not part of establishing the pipeline. Deferred to a future milestone. | Sync ships consumer contents as-is; no review gate. |
### CLARIFY auto-resolved parameters (full autonomy)
The following ambiguities were identified and auto-resolved at full
autonomy (no human escalation needed — confidence > 0.6 threshold):
1. **Fix scope** — comprehensive (theme CSS + render scripts + mermaid
re-layout + deck content + tests) vs. minimal. **Resolved: comprehensive.**
The root cause spans all four layers; a theme-only fix would leave
the extreme-aspect-ratio diagrams and the stale `render_deck.sh`
unfixed. Confidence: 0.95.
2. **Pipeline depth** — full pipeline (SPECIFY→CLARIFY→RESEARCH→PLAN→
GRILL→EXECUTE→VERIFY→SHIP) vs. lighter path. **Resolved: full pipeline.**
This is a new milestone (v1.22); the full pipeline ensures the plan
is grilled and the audit trail is complete. Confidence: 0.9.
3. **Mermaid diagram fixes** — re-layout to LR + re-render vs. CSS-only
fix. **Resolved: re-layout to LR + re-render at 2x transparent.**
The `telemetry-live-ops.mmd` uses `flowchart TB` (produced a 1024×1628
PNG — aspect 0.63); the README (line 168) explicitly says to use
horizontal layouts for wide diagrams. CSS-only cannot fix the aspect
ratio. Confidence: 0.95.
4. **`render_deck.sh` disposition** — fix (add `--theme`) vs. delete.
**Resolved: delete.** The README already documents `render_slides.sh`
as canonical; `render_deck.sh` is unreferenced by the build-commands
section and is a footgun (produces unthemed output). Confidence: 0.9.
5. **Slide count change** — keep 18 main + 1 appendix vs. split
overflowing slides. **Resolved: split slides 3 and 8** (18 → 20 main
+ 1 appendix). The `test_marp_deck_slide_count` test + README
convention are updated to match. Confidence: 0.85.
No human escalation. All decisions logged with confidence scores above
the 0.6 threshold.
## Objective for Milestone v1.22 (active — Nova Deck Layout Fix)
v1.22 fixes the systemic layout/formatting problems in the Nova
presentation deck that made every slide look "out of whack" after the
v1.21 P5 re-render. A full investigation determined the root cause is
**not a P5 regression** — the `nova-sp-theme.css` has had zero `section`
padding since it was authored (it declares `/* @theme nova-sp */` as a
comment, not the `@theme` directive, and does not `@import` Marp's
default theme, so Marp's default `section { padding: 56px 64px }` never
applies). Combined with `overflow:hidden` (silent clip), a blunt
`img { max-height: 320px }` rule, header+footer chrome on every slide,
and two new P5 diagrams with extreme aspect ratios (13.52× and 0.63×),
8 of 19 slides overflow and the rest look jammed against the edges.
This milestone is a **comprehensive fix** across four layers: (1) the
theme CSS (padding, overflow handling, aspect-ratio-aware image rules,
title-slide chrome suppression, paragraph/list/table spacing); (2) the
render scripts (delete the stale unthemed `render_deck.sh`, pin
marp-cli/mermaid-cli versions, add 2x scale + transparent bg to
mermaid); (3) the two problematic mermaid diagrams (re-layout to LR +
2-row wrap); (4) the deck content (trim/split the 8 overflowing slides,
remove the redundant `header:` from frontmatter). It also adds the
**layout/aspect-ratio/theme-structural tests** that were missing — the
gap that let this regression through undetected.
**Milestone type:** NFR (all phases are fix/docs/test — no feat/breaking).
Tags run on the **v1.21.x** patch line (previous minor per
branch-strategy): `v1.21.0` (P0) → `v1.21.1..v1.21.5` (P1P5) →
`v1.21.6` (P6 final = milestone release).
**Phase count:** 7 (P0 pre-execution + 5 execution + 1 final).
**Wave ordering:**
- Wave 1 (P1 + P2, parallel): theme CSS + render scripts — no
interdependency. P1 establishes the padding/overflow/image budget that
P4's content trimming relies on; P2 fixes the render pipeline that P3's
PNG re-render depends on.
- Wave 2 (P3 + P4, parallel): mermaid re-layout + deck content. P3
depends on P2 (2x scale flag); P4 depends on P1 (padding budget).
- Wave 3 (P5): re-render HTML + PPTX + add tests. Depends on all above.
- Wave 4 (P6): final review + audit + milestone ship.
**Hard constraints:**
- DO NOT change the deck narrative or the 4-beat arc (Problem → Solution
→ Proof → Roadmap + Ask) — only fix layout/formatting.
- DO NOT re-introduce badges, version strings, or internal citations
(D-###/REQ-###/.py paths) that v1.21 removed.
- The slide count may change from 18 main + 1 appendix to 20 main + 1
appendix (splitting slides 3 and 8 to relieve overflow). The
`test_marp_deck_slide_count` test + README "18 main + 1 appendix"
convention must be updated to match.
- PPTX remains a first-class committed artifact + release attachment.
- No code changes outside `docs/presentations/`, `scripts/render*.sh`,
and `tests/test_slides_pipeline.py`.
### Requirements
New requirements REQ-254..REQ-262 — see `REQUIREMENTS.md` §v1.22. Summary:
- **REQ-254:** Theme CSS — add `section` padding + overflow handling.
- **REQ-255:** Theme CSS — aspect-ratio-aware image rules (replace blunt
`max-height:320px`).
- **REQ-256:** Theme CSS — title-slide chrome suppression + paragraph/
list/table spacing tightening.
- **REQ-257:** Render scripts — delete `render_deck.sh` (or fix `--theme`);
pin marp-cli/mermaid-cli versions.
- **REQ-258:** `render_slides.sh` — add `-s 2 -b transparent` to mermaid-cli
(README spec).
- **REQ-259:** Re-layout `telemetry-live-ops.mmd` from `flowchart TB`
`flowchart LR`; re-render PNG at 2x transparent.
- **REQ-260:** Re-layout `platform-pipeline.mmd` to 2-row subgraph wrap;
re-render PNG at 2x transparent.
- **REQ-261:** Trim/split 8 overflowing slides (3, 5, 6, 8, 9, 12, 15,
A1) + remove redundant `header:` from frontmatter.
- **REQ-262:** Re-render HTML + PPTX + add layout/aspect-ratio/theme-
structural tests.
## v1.23 — Nova Deck Cleanup & Python PPTX
> **Active milestone.** NFR (docs/render/test only; no features).
> Branch: `milestone/v1.23-deck-cleanup-python-pptx`. Tags run on the
> **v1.22.x** patch line: `v1.22.0` (P0) → `v1.22.1..v1.22.5` (P1P5) →
> `v1.22.6` (P6 final = milestone release).
Driven by user feedback that the deck looked "out of whack" and the
desire to return to the clean, well-formatted style of the old
`the-developer-experience.html`. Investigation revealed the "clean"
reference was itself MARP output (using Marp's built-in `default` theme
+ an inline `style:` block); the current deck's standalone
`nova-sp-theme.css` re-derives all base spacing from scratch and had a
zero-padding bug (fixed in v1.22, but the standalone approach is
fragile). The milestone delivers:
- **Single-document consolidation** — `*-marp.md` becomes the sole
source of truth; the plain `.md` is deleted; speaker notes + talking
points are embedded as Marp HTML comments.
- **Clean style restoration** — revert to `theme: default` + inline
`style:` block (S&P palette); `nova-sp-theme.css` retained as a
reference, retired from render.
- **Self-contained HTML** — base64-inline all images for
redistribution.
- **Parallel python-pptx generator** — structured, editable, S&P-themed
PPTX alongside the MARP image-of-slide PPTX.
- **Targeted word-count trim** + removal of the previously-used loaded scope term.
**Phase count:** 7 (P0 pre-execution + 5 execution + 1 final).
**Hard constraints:**
- DO NOT change the deck narrative or the 4-beat arc (Problem → Solution
→ Proof → Roadmap + Ask) — only trim word count.
- DO NOT re-introduce badges, version strings, or internal citations.
- DO NOT remove MARP — it stays for HTML + PPTX; python-pptx runs in
parallel.
- `nova-sp-theme.css` is retained (not deleted) as a styling reference.
### Requirements
New requirements REQ-263..REQ-275 — see `REQUIREMENTS.md` §v1.23.
Summary: consolidation (REQ-263,264), style restoration (REQ-265,266,267),
image inlining (REQ-268), python-pptx generator (REQ-269,270), word-count
trim + loaded-scope-term removal (REQ-271,272), CI/tests/README (REQ-273,274,275).
+26 -26
View File
@@ -1,6 +1,6 @@
{ {
"run_id": "regr-1785591207", "run_id": "regr-1785588523",
"run_at_utc": "2026-08-01T13:33:27Z", "run_at_utc": "2026-08-01T12:48:43Z",
"milestone": "v1.10", "milestone": "v1.10",
"phase": 52, "phase": 52,
"summary": { "summary": {
@@ -17,7 +17,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": 260
}, },
{ {
"capability_id": "CAP-002", "capability_id": "CAP-002",
@@ -25,7 +25,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": 202
}, },
{ {
"capability_id": "CAP-003", "capability_id": "CAP-003",
@@ -33,7 +33,7 @@
"status": "Verified", "status": "Verified",
"detail": "exit 0; ", "detail": "exit 0; ",
"tier": "local", "tier": "local",
"duration_ms": 261 "duration_ms": 266
}, },
{ {
"capability_id": "CAP-004", "capability_id": "CAP-004",
@@ -41,7 +41,7 @@
"status": "Verified", "status": "Verified",
"detail": "exit 0; ", "detail": "exit 0; ",
"tier": "local", "tier": "local",
"duration_ms": 259 "duration_ms": 248
}, },
{ {
"capability_id": "CAP-005", "capability_id": "CAP-005",
@@ -57,7 +57,7 @@
"status": "Verified", "status": "Verified",
"detail": "exit 0; interpolation ok", "detail": "exit 0; interpolation ok",
"tier": "local", "tier": "local",
"duration_ms": 242 "duration_ms": 209
}, },
{ {
"capability_id": "CAP-007", "capability_id": "CAP-007",
@@ -65,7 +65,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 +73,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": 319
}, },
{ {
"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====================== 577 passed, 2 deselected in 53.53s ======================",
"tier": "local", "tier": "local",
"duration_ms": 72988 "duration_ms": 55005
}, },
{ {
"capability_id": "CAP-010", "capability_id": "CAP-010",
@@ -89,23 +89,23 @@
"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": 59882
}, },
{ {
"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/nova_local_e2e_cuwlkzrj/outbox\",\n \"outbox_events\": 2,\n \"outbox_chain_verified\": true,\n \"lambda_status\": 200\n}",
"tier": "local", "tier": "local",
"duration_ms": 634 "duration_ms": 561
}, },
{ {
"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; nova_local_e2e_mfeuiylw/tf\",\n \"backend\": \"local\",\n \"ecs\": null,\n \"outbox_dir\": \"/tmp/nova_local_e2e_mfeuiylw/outbox\",\n \"outbox_events\": 2,\n \"outbox_chain_verified\": true,\n \"lambda_status\": 200\n}",
"tier": "local", "tier": "local",
"duration_ms": 584 "duration_ms": 492
}, },
{ {
"capability_id": "CAP-013", "capability_id": "CAP-013",
@@ -113,7 +113,7 @@
"status": "Skipped", "status": "Skipped",
"detail": "terraform init: state bucket absent (post-v1.11-teardown, D-096) [microservice]", "detail": "terraform init: state bucket absent (post-v1.11-teardown, D-096) [microservice]",
"tier": "live-aws", "tier": "live-aws",
"duration_ms": 737 "duration_ms": 851
}, },
{ {
"capability_id": "CAP-014", "capability_id": "CAP-014",
@@ -121,7 +121,7 @@
"status": "Skipped", "status": "Skipped",
"detail": "terraform init: state bucket absent (post-v1.11-teardown, D-096) [static-assets]", "detail": "terraform init: state bucket absent (post-v1.11-teardown, D-096) [static-assets]",
"tier": "live-aws", "tier": "live-aws",
"duration_ms": 676 "duration_ms": 724
}, },
{ {
"capability_id": "CAP-015", "capability_id": "CAP-015",
@@ -129,7 +129,7 @@
"status": "Skipped", "status": "Skipped",
"detail": "nova-outbox absent (post-v1.11-teardown steady state, D-096)", "detail": "nova-outbox absent (post-v1.11-teardown steady state, D-096)",
"tier": "live-aws", "tier": "live-aws",
"duration_ms": 664 "duration_ms": 487
}, },
{ {
"capability_id": "CAP-016", "capability_id": "CAP-016",
@@ -137,7 +137,7 @@
"status": "Skipped", "status": "Skipped",
"detail": "state bucket nova-tfstate-581513795199-us-east-1 absent (post-v1.11-teardown, D-096)", "detail": "state bucket nova-tfstate-581513795199-us-east-1 absent (post-v1.11-teardown, D-096)",
"tier": "live-aws", "tier": "live-aws",
"duration_ms": 245 "duration_ms": 300
}, },
{ {
"capability_id": "CAP-017", "capability_id": "CAP-017",
@@ -145,7 +145,7 @@
"status": "Verified", "status": "Verified",
"detail": "terraform files present + fmt -check passes + simple/complex contracts resolve", "detail": "terraform files present + fmt -check passes + simple/complex contracts resolve",
"tier": "lifecycle-pipeline", "tier": "lifecycle-pipeline",
"duration_ms": 586 "duration_ms": 579
}, },
{ {
"capability_id": "CAP-018", "capability_id": "CAP-018",
@@ -153,7 +153,7 @@
"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": 139
}, },
{ {
"capability_id": "CAP-019", "capability_id": "CAP-019",
@@ -161,7 +161,7 @@
"status": "Verified", "status": "Verified",
"detail": "L2 composition resolves (simple + complex contracts; offline proxy)", "detail": "L2 composition resolves (simple + complex contracts; offline proxy)",
"tier": "lifecycle-pipeline", "tier": "lifecycle-pipeline",
"duration_ms": 519 "duration_ms": 553
}, },
{ {
"capability_id": "CAP-020", "capability_id": "CAP-020",
@@ -169,7 +169,7 @@
"status": "Verified", "status": "Verified",
"detail": "L2 composition resolves (simple + complex contracts; offline proxy)", "detail": "L2 composition resolves (simple + complex contracts; offline proxy)",
"tier": "lifecycle-pipeline", "tier": "lifecycle-pipeline",
"duration_ms": 521 "duration_ms": 561
}, },
{ {
"capability_id": "CAP-021", "capability_id": "CAP-021",
@@ -177,7 +177,7 @@
"status": "Verified", "status": "Verified",
"detail": "terraform files present + fmt -check passes + simple/complex contracts resolve", "detail": "terraform files present + fmt -check passes + simple/complex contracts resolve",
"tier": "lifecycle-pipeline", "tier": "lifecycle-pipeline",
"duration_ms": 562 "duration_ms": 598
}, },
{ {
"capability_id": "CAP-022", "capability_id": "CAP-022",
@@ -185,7 +185,7 @@
"status": "Verified", "status": "Verified",
"detail": "terraform files present + fmt -check passes + simple/complex contracts resolve", "detail": "terraform files present + fmt -check passes + simple/complex contracts resolve",
"tier": "lifecycle-pipeline", "tier": "lifecycle-pipeline",
"duration_ms": 611 "duration_ms": 570
} }
] ]
} }
+26 -26
View File
@@ -1,51 +1,51 @@
# Regression Report — v1.10 Phase 52 # Regression Report — v1.10 Phase 52
- **Run ID:** `regr-1785591207` - **Run ID:** `regr-1785588523`
- **Run at (UTC):** 2026-08-01T13:33:27Z - **Run at (UTC):** 2026-08-01T12:48:43Z
- **Summary:** {'Verified': 18, 'Decayed': 0, 'Broken': 0, 'Skipped': 4} - **Summary:** {'Verified': 18, 'Decayed': 0, 'Broken': 0, 'Skipped': 4}
- **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** | 260 | 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** | 202 | 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** | 266 | exit 0; |
| CAP-004 | contract_resolver resolves microservice | local | **Verified** | 259 | exit 0; | | CAP-004 | contract_resolver resolves microservice | local | **Verified** | 248 | exit 0; |
| CAP-005 | terraform adapter emits .tf files | local | **Verified** | 337 | exit 0; | | CAP-005 | terraform adapter emits .tf files | local | **Verified** | 337 | 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** | 209 | 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** | 319 | 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** | 55005 | exit 0; [ 98%]
tests/test_wiz_adapter_real_client.py ......... [100%] tests/test_wiz_adapter_real_client.py ......... [100%]
================= 586 passed, 2 | ====================== 577 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** | 59882 | 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** | 561 | 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/nova_local_e2e_cuwlkzrj/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** | 492 | exit 0; nova_local_e2e_mfeuiylw/tf",
"backend": "local", "backend": "local",
"ecs": null, "ecs": null,
"outbox_dir": "/tmp/nova_local_e2e_uq4kkhze/outbox", "outbox_dir": "/tmp/nova_local_e2e_mfeuiylw/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 | **Skipped** | 851 | terraform init: state bucket absent (post-v1.11-teardown, D-096) [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 | **Skipped** | 724 | terraform init: state bucket absent (post-v1.11-teardown, D-096) [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 | **Skipped** | 487 | nova-outbox absent (post-v1.11-teardown steady state, D-096) |
| 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 | **Skipped** | 300 | state bucket nova-tfstate-581513795199-us-east-1 absent (post-v1.11-teardown, D-096) |
| 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 nova-contracts table (lifecycle pipeline evidence) | lifecycle-pipeline | **Verified** | 579 | terraform files present + fmt -check passes + 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** | 139 | 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** | 553 | L2 composition resolves (simple + complex contracts; offline proxy) |
| 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** | 561 | L2 composition resolves (simple + complex contracts; offline proxy) |
| 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** | 598 | terraform files present + fmt -check passes + 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** | 570 | terraform files present + fmt -check passes + simple/complex contracts resolve |
+20 -1260
View File
File diff suppressed because it is too large Load Diff
+1155 -152
View File
File diff suppressed because it is too large Load Diff
+302 -90
View File
@@ -1,112 +1,324 @@
# Nova v1.16 — Multi-Persona Code Review (final phase P21) # Nova v1.11 — Multi-Persona Code Review (P60P65 retrofit + new work)
**Reviewer:** lead-developer (model: glm-5.2) **Reviewer:** ci-code-reviewer (model: glm-5.2)
**Scope:** v1.16 milestone — 22 tags (v1.15.5..v1.15.26), 20 execution **Scope:** v1.11 milestone, branch `milestone/v1.11-restart` — 22 commits
phases + final. Squash-merged to main via `milestone/v1.16-nova-simplification`. (e1bb214..8c09580), 25 files, +790/-142 lines
**Date:** 2026-07-30 **Date:** 2026-07-29
> **Historical note:** REVIEW.md was reconstructed at v1.16 P21 (the ## Commits reviewed
> v1.3v1.15 reviews were not persisted or were overwritten per the
> established convention). The v1.16 review overwrites prior content.
## Review approach | Commit | Phase | Type | Summary |
|--------|-------|------|---------|
The v1.16 milestone is an NFR sweep (no new features). Each of the 20 | e1bb214 | 60 | docs | retrofit plan — L1 lifecycle pipeline live-run |
execution phases shipped with a 4-layer verify (structural/behavioral/ | bc9058f | 60 | feat | L1 module lifecycle live run — module fixes (retrofit) |
security/quality) + `run_ci.sh` 3-stage PASS at every phase boundary. | bb3ac7c | 60 | fix | WAF scope case + VPC modify DependencyViolation |
The final-phase review (P21) is a milestone-level cross-phase check, | 0c5c4d1 | 61 | docs | create phase plan — L2 lifecycle pipeline author |
not a per-phase re-review (the per-phase verify already ran). | 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) ## P0 issues (0)
No blocking issues found. The 4-layer verify at each phase boundary + No blocking issues found. The targeted fixes are correct for their stated
the regression gate (D-118, 18V+4S at P9 + P21) are the structural purposes. The 447 fast offline tests pass (485/490 collected; 5 slow
controls. No P0 was auto-applied at P21. deselected, including 2 slow regression-integration tests that exercise the
CAPABILITY_REGISTRY against the live codebase).
## P1 issues (0) ## P1 issues (5 — should fix)
No P1 issues flagged. The grill binding decisions (G-111..G-113) were ### P1-1: Adapter dedup silently drops resources whose module is not in the registry
incorporated into the plan before execution; the regression gate (G-111) [correctness] `adapters/terraform/adapter.py:159-170`
passed at both checkpoints (P9 + P21).
## P2 issues (2 — post-hoc, non-blocking) 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.
### P2-1: Onboarding framing (E-002, deferred from grill) Confirmed by simulation: two resources, one with `module: nonexistent@1.0.0`,
[scope] `.ciagent/PROJECT.md`, `.ciagent/ROADMAP.md` produces a `merged` list of length 1 — the unknown-module resource vanishes
without diagnostic.
The grill escalation E-002 (confidence 0.55) flagged that the PROJECT.md **Recommendation:** in the dedup loop, when `tf_dir` is `None`, either
framing "first self-service onboarding request path" may over-promise (a) raise immediately (preserving the prior contract), or (b) append the
relative to a request-*acceptance* path that writes a pending row + resource to a separate `unknown` list and extend `parts` with it so
generates an env-file + proves the role Terraform offline but never `_emit_module_block` raises the descriptive error. As written, a typo in
fulfills (no live role grant). The milestone is internally consistent a composition's `module` field (e.g. `iam-role@1.0.0` vs `iam_roles@1.0.0`)
with D-113 (request-path only) — the wording is the only risk. The will silently omit a resource from the emitted terraform — a class of
ROADMAP/PROJECT use "request path" (not "request-fulfillment"), and the defect the v1.10 sweep was specifically created to catch.
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 ### P1-2: L2 static-assets "modify" example is a no-op — complex ≡ simple
[maintainability] `.ciagent/REVIEW.md`, `.ciagent/AUDIT.md` [correctness] `modules/l2/static-assets/examples/complex.yml`,
`modules/l2/static-assets/composition.json`
REVIEW.md still held v1.11 content during the v1.16 run (the per-phase The complex.yml comment claims "Modify variant: same bucket_name as simple
verify ran but wasn't persisted to REVIEW.md until P21). AUDIT.md held (in-place modify, adds CDN + WAF)". But resolving both examples yields
v1.15 content. Both are reconstructed at P21 (this review + the audit **identical** resource sets: `['s3','cloudfront-distribution',
running now). This matches the established convention (REVIEW.md is 'cloudfront-originaccesscontrol','waf','kms']`. The CDN and WAF are
overwritten at milestone complete; the per-phase verify commits are the **always present** in the static-assets composition (they are unconditional
record). Not a defect. 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 ## What is correct
- **State-bucket drift fix (P1):** `adapter.py:117` now emits - **WAF scope fix (`upper(var.scope)`):** correct and defensive; AWS
`nova-tfstate-*` (matching the live bucket renamed in v1.15 P4). The provider v5 requires uppercase. The `local.scope` indirection is clean.
new `test_adapt_emits_nova_state_bucket` regression guard asserts this. - **VPC `create_before_destroy` + same-CIDR complex example:** correct
- **Kyverno label fix (P1):** `require-resource-labels.yml` enforces fix for the DependencyViolation on modify. Using the same CIDR means
`nova:*` labels (consistent with `nova_tagging.py` hard-fail on terraform modifies in-place rather than replacing the VPC (which would
`acdl:*`). No policy contradiction. cascade-fail on dependent subnets/IGW). The `create_before_destroy`
- **Ingestor defense-in-depth (P10):** fail-closed on missing IAM lifecycle is the right guard.
identity (401, not silent pass); env enum derived from - **ALB `name_prefix`:** correct terraform pattern for
`core/environments/` (not hardcoded). The `NOVA_LAMBDA_LOCAL_BYPASS` create_before_destroy + name-uniqueness; well-documented commit message.
env allows local/stub testing without blocking the fail-closed path. - **Adapter dedup (for the registered-module case):** correct —
- **Payload validation (P11):** 256 KB size cap + contract.schema.json multi-resource L1s like cloudfront (distribution + OAC) correctly merge
validation before the DynamoDB write; aligned error/stackTrace caps into one `module "cloudfront-distribution" { ... }` block. The merge
(both 10000). preserves first-resource inputs and union of outputs. (The
- **Regression gate (G-111):** CAP-013..016 return `Skipped` (not unregistered-module drop is P1-1, a separate concern.)
`Decayed`/`Broken`) for the post-teardown steady state (D-096). - **L2 composition wiring (`ecr.inputs.name`, `roles.inputs.role_name`):**
`passed` accepts Skipped. Gate passes at 18V+4S. correct. Resolving microservice complex now shows `ecr.inputs.name =
- **Workflow generator (P8):** `sync_workflows.py` + `workflows-src/` "app-repo"` and `roles.inputs.role_name = "app-role"` (defaults applied
single source; the byte-identity test is replaced with a generator- since the contract doesn't set `name`). Previously these would have hit
output test (`--check` exits 0). The 3 pairs are no longer hand-synced. the "missing required arg" defect class from the v1.10 sweep.
- **Onboarding request path (P18-P20):** schema + Lambda action (pending - **Microservice complex = real modify:** `desired_count: 2` (vs simple's
CMDB row, no AWS resources) + env-file autogen + offline-proven default 1) is a genuine in-place modify — confirmed by resolving both
cross-account Terraform. Self-service message (no "contact the platform and diffing `service-service.inputs.desired_count`.
team"). Real AWS provisioning explicitly deferred (D-113/D-114). - **`ACDL_REMOTE_STATE_KEY` plumbing:** correct end-to-end — the L2 scripts
- **Splits (P12/P13):** `contract_resolver` + `regression_verify` split export it, the adapter reads it with a sensible default, and the
with re-export shims; G-113 one-way import direction documented. All microservice composition's `terraform_remote_state` data block picks it
tests pass without modification (backwards compat preserved). up. This cleanly separates the short-lived CI VPC state from the
- **DX (P15-P17):** `--help` works + documents all 9 flags; workflows long-lived platform VPC state.
README catalogs all 7 workflows; getting-started is offline-first. - **Workflow structure:** `l2-lifecycle` correctly `needs: ci-vpc-apply`;
- **Regression gate:** 18 Verified + 4 Skipped at P9 + P21 (0 Decayed/ `ci-vpc-destroy` correctly `needs: [lifecycle, l2-lifecycle]` and
Broken). The 4 Skipped are the post-v1.11-teardown live-AWS caps. `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 ## Test coverage assessment (485 offline tests)
~635 tests pass (was ~620 at v1.15.4). New test files: - **Adequate:** pipeline contract (89 tests), schema validation, contract
- `tests/test_onboarding.py` (3 tests — env-file generation) resolution, adapter emission (basic), confidence signal, outbox,
- `tests/test_onboarding_terraform.py` (3 tests — terraform validate + tags) interpolation, local emulators, module-standards file presence, design-doc
- `tests/test_docs_coverage.py` (expanded — workflows README catalog) 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).
New tests in existing files: `test_adapt_emits_nova_state_bucket`, The 485 count is honest (447 pass fast, 5 deselected slow, 485/490
`test_onboarding_message_says_nova_not_acdl`, `test_no_identity_fails_closed`, collected). The gap is behavioral coverage of the new adapter + module
`test_no_identity_passes_with_local_bypass`, `test_oversized_contract_rejected`, logic, not contract/schema coverage.
`test_schema_invalid_contract_rejected`, `TestNarrowedException` (2 tests),
`TestOnboardConsumer` (3 tests), `TestOnboardingMessageSelfService` (2 tests),
`test_sync_workflows_check_passes`.
## Verdict ## Verdict
**PASS — 0 P0, 0 P1, 2 P2 (post-hoc, accepted).** The v1.16 NFR milestone **PASS with P1 flags for post-hoc review.** No P0 fixes applied. The
is complete. All 20 requirements (REQ-165..184) satisfied; regression milestone's structural controls (regression gate, mandatory teardown,
gate 18V+4S; CI 3-stage PASS at every phase boundary. The onboarding byte-identical workflows, byte-identical contract↔workflow tests) are
request path is self-service; real AWS provisioning deferred. The sound. The most material finding is P1-5 (the regression gate's
state-bucket drift + Kyverno label contradiction (the two correctness CAP-017..022 evidence is an offline proxy, not live pipeline evidence) —
regressions from the v1.15 rebrand) are fixed with regression guards. 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.
-526
View File
@@ -28,8 +28,6 @@
- **v1.13.1 (complete, tag `v1.13.1`):** config.json schema migration — regenerate `.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). Code review: 0 P0, 2 P1/P2 auto-fixed. Docs-only NFR patch (no code changes). Gitea release id 253. - **v1.13.1 (complete, tag `v1.13.1`):** config.json schema migration — regenerate `.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). Code review: 0 P0, 2 P1/P2 auto-fixed. Docs-only NFR patch (no code changes). Gitea release id 253.
- **v1.13.2 (complete, tag `v1.13.2`):** presentation badge cleanup + platform architecture diagram — removed all `testing`/`agentic` maturity badges from both decks (only `planned` retained); added a new Slide 3 "The platform at a glance" with a shared high-level logical architecture diagram (consumer surfaces → contract → central pipeline → cross-cutting components → AWS) to both decks; renumbered subsequent slides 411; synced talking points + README. Docs-only NFR patch (no code changes). - **v1.13.2 (complete, tag `v1.13.2`):** presentation badge cleanup + platform architecture diagram — removed all `testing`/`agentic` maturity badges from both decks (only `planned` retained); added a new Slide 3 "The platform at a glance" with a shared high-level logical architecture diagram (consumer surfaces → contract → central pipeline → cross-cutting components → AWS) to both decks; renumbered subsequent slides 411; synced talking points + README. Docs-only NFR patch (no code changes).
- **v1.0 demo URL:** https://git.cloudinit.dev/continuous-intelligence/acdl-evidence/raw/branch/main/index.html - **v1.0 demo URL:** https://git.cloudinit.dev/continuous-intelligence/acdl-evidence/raw/branch/main/index.html
- **v1.23 (complete, tag `v1.22.6`):** Nova Deck Cleanup & Python PPTX — consolidated the deck to a single source-of-truth `*-marp.md` (deleted the plain `.md`; speaker notes + talking points embedded as Marp HTML comments); restored the clean S&P visual style (Marp `default` theme + inline `style:` block, matching the old `the-developer-experience.html`); retired `nova-sp-theme.css` from the render path (kept as reference); base64-inlined all images in the HTML for redistribution (`scripts/inline_images.py`); built a parallel structured editable S&P-themed PPTX generator (`scripts/render_pptx.py` via `python-pptx`); restyled benefit callouts (`<div class="benefit">`); targeted ~20-30% word-count trim on 8 verbose slides; removed the term "penetrate" repo-wide. 13 requirements (REQ-263..275), 6 phases. 43 tests pass.
- **v1.24 (active):** Consumer Guide Accuracy & Env-Promotion Lifecycle Enforcement — fixes 5 consumer-guide accuracy issues (stale contract-fields table, inconsistent caller examples, misleading "dev only" apply phrasing, Step 8 promotion contradicts the per-env section, stale `@v1.19` reference wording) and adds platform-enforced destroy-on-environment-change: when a consumer edits `environment:` on a stable `contract.id` (Shape A promotion), the platform detects the change via the `nova-contracts` DynamoDB table, destroys the prior env's Terraform state (`spike/{id}/{prior_env}/`) before building the new env, and fails closed if the destroy fails (no orphan path). The per-environment caller-workflow path (Shape B) remains supported. New `core/env_transition.py` module. 15 requirements (REQ-276..290), 4 phases. Feature milestone; tags on v1.23.x line.
--- ---
@@ -1632,527 +1630,3 @@ milestone release). (G-104 binding.)
- Tag `v1.15.4` created; milestone merged to main. - Tag `v1.15.4` created; milestone merged to main.
After Phase P5: milestone COMPLETE — `v1.15.4` IS the v1.15 release. After Phase P5: milestone COMPLETE — `v1.15.4` IS the v1.15 release.
---
## v1.16 (complete — Nova Simplification, tag `v1.15.26`)
A 20-phase NFR sweep (no new features) themed around five user-directed
axes: **Simplify without regressions**, **Security**, **Maintainability**,
**User/Developer Experience**, **No Humans Onboarding Flow**. The v1.15
rebrand left a fresh debt layer (stale brand strings, a state-bucket
drift, a Kyverno policy contradicting the Nova tagging standard, dead
code) that this milestone cleared, alongside genuine simplification
(dedup helpers, a workflow generator, file splits) and the first
self-service onboarding request path (request-path only; real AWS
provisioning deferred, D-113).
**Milestone type:** NFR (all phases fix/chore/docs/refactor/test). The
final phase's patch IS the deliverable. Tags on the v1.15.x line:
`v1.15.5` (P0) → `v1.15.6..v1.15.25` (P1P20) → `v1.15.26` (P21 final =
milestone release).
**Regression gate (D-118, G-111):** 18 Verified + 4 Skipped (CAP-013..016
live-AWS caps are the post-v1.11-teardown steady state, D-096; re-
provisioning is a future feature). 0 Decayed/Broken at P9 + P21.
**Grill:** PASS-with-binding (G-111..G-113, E-002 deferred to P21).
G-111: gate criterion restated 18V+4S + Skipped logic. G-112: P9 source
model pinned. G-113: P12/P13 import direction documented.
**Wave outcomes:**
- Wave 1 (P1P4): state-bucket + Kyverno rebrand fix (correctness
regression), user-facing ACDL→Nova sweep, dead-code cleanup, except
narrowing.
- Wave 2 (P5P9): regression-verify dedup (~70 lines), run-platform
HITL fn + config, contract-resolver envloader + registry kind, workflow
generator (sync_workflows.py + workflows-src/), run-platform split
(decommission + uptime helpers). Gate PASS at P9.
- Wave 3 (P10P14): ingestor defense-in-depth (fail closed on missing
IAM), payload validation (size cap + schema), split contract-resolver
(decommission + CLI modules), split regression-verify (CLI module),
schema-driven outputs + schema cache. Mid-milestone checkpoint clean.
- Wave 4 (P15P17): run-platform --help + flags doc, workflows README
catalog (7 workflows), getting-started consolidation (offline-first).
- Wave 5 (P18P20): onboarding schema + onboard_consumer Lambda action,
env-file autogen (core/onboarding.py), cross-account role Terraform
(offline-proven, D-114).
**Outcome:** 20 requirements (REQ-165..184) satisfied; ~630 tests pass;
regression gate 18V+4S; the onboarding request path is self-service (no
"contact the platform team" handoff); real AWS provisioning explicitly
deferred (D-113/D-114).
Ship tag at milestone COMPLETE: `v1.15.26` (NFR milestone; final patch IS
the release). **DONE.**
## v1.18 (complete — Citizen Developer & Production-Grade Guidance, tag line `v1.17.x`)
Nova advances from a platform that governs infrastructure delivery to one
that **instructs the citizen developer on production-grade engineering**
and defines a **clear, machine-checkable contract for what is acceptable
to start**. Five user-directed inputs drive the milestone:
1. **S&P Global theme restoration** (P1) — the v1.17 P5 deck rebuild lost
the S&P Global Energy brand visual identity (introduced v1.9.2 / P45).
The Marp `style:` block (`#D6002A` red, `#1B1B1B` grey-90, Akkurat Pro,
8px accent bar) is restored to the unified deck.
2. **PDLC-upstream scope** (P2) — promotes Core Tenet #2 + Anti-Goal #1
from buried tenets to a dedicated, unmissable scope statement: the PDLC
is upstream of Nova; Nova governs infra + delivery only.
3. **RACI matrix** (P2) — three-role responsibility matrix (Citizen
Developer / Platform / Release Management co-owned) clarifies who owns
what, with the compliance-standard-equivalence note.
4. **Nova input contract** (P3) — `schemas/submission-readiness.schema.json`
+ `core/submission_readiness.py` validator define "what is acceptable to
start" as a superset gate above contract-schema validity.
5. **Atelier integration** (P4+P5) — skills (markdown, extending BA.A) + an
MCP server (plugin-registry, vendored Atelier, agentic validation
beyond Wiz/Checkmarx/Mend).
**Milestone type:** Feature (P1 theme restoration + P3 schema/validator +
P5 MCP server are new code). Tags run on the v1.17.x patch line:
`v1.17.0` (P0) → `v1.17.1..v1.17.6` (P1P6) → `v1.17.7` (P7 final =
milestone release).
**Deck automation (cross-cutting, REQ-228):** any phase modifying
`docs/presentations/*-marp.md` or `docs/presentations/assets/` re-renders
HTML + PPTX, commits the PPTX binary to git, and attaches it to the
phase's Gitea release.
**Phase count:** 8 (P0 pre-execution + 6 execution + 1 final).
**Phases:**
- **P1 — sp-theme-restoration** (feat): restore S&P Global Marp theme to
unified deck + HTML re-render + PPTX commit + release attach. REQ-214,228.
- **P2 — pdlc-scope-raci** (docs): PDLC-upstream scope + RACI matrix +
2 deck slides + HTML/PPTX re-render. REQ-215,216,228.
- **P3 — submission-readiness** (feat): JSON Schema + validator + docs +
tests. REQ-217,218,219,220.
- **P4 — atelier-skills** (docs): 9 Atelier-derived skill files + index +
BA.A extension. REQ-221,222.
- **P5 — atelier-mcp** (feat): plugin-registry MCP server + vendored
Atelier + 4 tools + tests. REQ-223,224,225.
- **P6 — deck-slides-atelier** (docs): 3 new deck slides (scope/RACI/atelier)
→ 21 slides + talking points + HTML/PPTX re-render + README. REQ-226,227,228.
- **P7 — final-review-ship** (final): review + audit + milestone ship.
**Requirements:** REQ-214..228 (15 requirements). See
`.ciagent/REQUIREMENTS.md` §v1.18.
**Open decisions to lock (CLARIFY/GRILL):** D-133 (validator location),
D-134 (deck slide budget), D-135 (MCP transport), D-136 (Atelier vendoring),
D-137 (MCP server language), D-138 (skill format), D-139 (RACI roles),
D-140 (MCP plugin-registry), D-141 (PPTX storage), D-142 (deck render trigger).
**Outcome:** 15 requirements (REQ-214..228) satisfied; 32 tests pass (16
submission-readiness + 16 MCP); S&P Global Energy theme restored; PDLC-
upstream scope + RACI matrix authored (PROJECT.md + docs/ + deck);
submission-readiness schema + validator shipped (superset gate above
contract.schema.json); 9 Atelier-derived skills + docs/skills.md; MCP
server (plugin-registry, stdio, vendored Atelier v0.3.6) with 4 tools +
agentic validation beyond Wiz/Checkmarx/Mend; 21-slide deck (3 new slides:
scope/RACI/atelier) with PPTX committed + release-attached. 10 decisions
locked (D-133..D-142).
Ship tag at milestone COMPLETE: `v1.17.7` (feature milestone; final patch
IS the release). **DONE.**
## v1.19 (complete — Nova 2nd-Release Sync, tag line `v1.18.x`)
> **NFR-only chore milestone.** Single execution phase. Establishes the
> manual-only "2nd release" pipeline `~/acdl → ~/nova` (GitLab
> `jonathanchery/nova`, separate repo + history, consumer/platform-team
> audience). Replaces the old `~/gl/acdl` mirror sync.
### Phase P1 — nova-sync-script (Wave 1)
- **Description:** Replace `scripts/sync_to_gl.sh` (kitchen-sink mirror sync
into `~/gl/acdl`) with `scripts/sync_to_nova.sh` — a manual-only,
consumer-subset, domain-committed 2nd-release pipeline into `~/nova`.
Excludes `.ciagent/`, `terraform/`, `demo/`, runtime metrics, and
internal-only scripts. Protects `~/nova/.git`. Commits per domain in a fixed
order using positional `-m` conventional-commit messages. Validates
conventional format. Never triggerable by CI (`--release` gate).
- **Status:** complete
- **Depends on:**
- **Requirements:** REQ-229
- **Success Criteria:**
- `scripts/sync_to_nova.sh` exists with `set -euo pipefail`.
- Refuses without `--release` (exit 2); `--list-domains` prints 13 domains.
- rsync excludes `.ciagent`, `terraform`, `demo`, internal scripts, runtime
metrics; protects destination `.git`.
- Domain commits in fixed order; positional `-m` mapping; conventional
format validated.
- `scripts/sync_to_gl.sh` removed.
- `pytest` passes; `run_ci.sh` exits 0.
### Phase P2 — final-review-ship (Final Phase)
- **Description:** Final review + audit + milestone ship. Merge to main, tag
`v1.18.0` (first patch on the v1.18.x line), create Gitea release.
- **Status:** complete
- **Depends on:** [P1]
- **Requirements:** REQ-229
- **Success Criteria:**
- Review + audit clean (no P0).
- `phase/02-final-review-ship` merged to `milestone/v1.19-nova-sync` then to
`main`.
- Tag `v1.18.0` created; release notes summarize REQ-229.
- Milestone branches deleted; CHECKPOINT cleared.
Ship tag at milestone COMPLETE: `v1.18.1` (NFR milestone; final patch IS the
release). **DONE.**
---
## v1.20 — Consumer Cleanup + Transparent Terraform + Slide Pipeline
> **Multi-concern milestone.** Four user-directed inputs: (1) remove all
> gitea/gitlab from synced files — the platform team must never know about
> the dev forge; (2) radically simplify documentation for the Platform Team
> audience; (3) make terraform runs transparent in workflows with feature-flag
> client differentiation; (4) dedicated S&P-themed slide render pipeline +
> 12-month product roadmap slides.
>
> Tags run on the v1.19.x line (milestone v1.20 → tags v1.19.x).
### Phase P0 — pre-execution
- **Description:** Specify → clarify → research → plan. Validate v1.20
requirements (REQ-230..244). Establish milestone version in config.json.
- **Status:** complete
- **Requirements:** REQ-230..244
- **Success Criteria:**
- `.ciagent/REQUIREMENTS.md` has v1.20 section with all 15 requirements.
- `.ciagent/config.json` has `active_milestone: "v1.20"`.
- Checkpoint written.
### Phase P1 — consumer-cleanup (gitea removal + doc simplification)
- **Description:** Remove all gitea/gitlab mentions from synced files.
Genericize forge-detection code. Drop `.gitea/` byte-identity test
assertions. Add `test_no_forge_mentions.py` guard test. Simplify
documentation: delete completed migration docs, move thesis to `.ciagent/`,
strip ciagent-internal provenance from synced docs.
- **Status:** complete
- **Requirements:** REQ-230, REQ-231, REQ-232
- **Success Criteria:**
- `tests/test_no_forge_mentions.py` passes — zero gitea/gitlab mentions in
synced subset.
- `pytest` passes — all existing tests green after genericization.
- Synced docs stripped of REQ-/D-/P- IDs, milestone headers, `.ciagent/`
citations.
- `docs/NOVA_MIGRATION.md` + `docs/NOVA_AWS_MIGRATION.md` deleted.
- `docs/NO_HUMANS_THESIS.md` moved to `.ciagent/`.
### Phase P2 — slide-pipeline (S&P theme + render automation)
- **Description:** Create dedicated S&P theme CSS, render_slides.sh pipeline,
CI workflow, tests. Update Marp frontmatter to use dedicated theme. Fix
README directory layout.
- **Status:** complete
- **Requirements:** REQ-239, REQ-240, REQ-241, REQ-242, REQ-243
- **Success Criteria:**
- `docs/presentations/assets/nova-sp-theme.css` exists with S&P colors.
- Marp deck frontmatter references the theme CSS.
- `scripts/render_slides.sh` renders mermaid PNGs + HTML + PPTX.
- `workflows-src/slides.yml` + `.github/workflows/slides.yml` exist.
- `tests/test_slides_pipeline.py` passes.
- `docs/presentations/README.md` updated (no retired decks).
### Phase P3 — product-roadmap (12-month slides)
- **Description:** Add 12-month product roadmap as Slide 20 + Slide 21 to the
deck. Add matching talking-points sections. Render via new pipeline.
- **Status:** complete
- **Requirements:** REQ-244
- **Success Criteria:**
- Slide 20 + 21 in `nova-no-humans-platform-marp.md` + source-of-truth +
talking-points.
- HTML + PPTX re-rendered via `render_slides.sh`.
- 4-quarter product arc grounded in NORTH_STAR + deferred metrics.
### Phase P4 — transparent-terraform (workflow refactor + feature flags)
- **Description:** Split run_platform.sh → run_codegen.sh + run_postapply.sh.
Rewrite deploy.yml with native terraform steps. Add var.enabled to all L1
modules + L2 composition toggles. Wire forge repo variables as feature
flags. Fix stale artifact path.
- **Status:** complete
- **Requirements:** REQ-233, REQ-234, REQ-235, REQ-236, REQ-237, REQ-238
- **Success Criteria:**
- `scripts/run_codegen.sh` + `scripts/run_postapply.sh` exist.
- `deploy.yml` has native terraform init/validate/plan/apply steps.
- Every L1 module has `variable "enabled"` + `count = var.enabled ? 1 : 0`.
- L2 `composition.json` supports per-child `enabled`.
- `deploy.yml` reads `vars.ENABLE_*` as `-var` flags.
- Stale `/tmp/acdl_platform_run_v18` path fixed to `NOVA_WORK_DIR`.
- `pytest` passes; `run_platform.sh` shim backward-compat verified.
### Phase P5 — final-review-ship (Final Phase)
- **Description:** Final review + audit + milestone ship. Merge to main,
tag `v1.19.4` (final patch = milestone release), create release.
- **Status:** complete
- **Depends on:** [P1, P2, P3, P4]
- **Requirements:** REQ-230..244
- **Success Criteria:**
- Review + audit clean (no P0).
- Milestone branches merged to main.
- Tag `v1.19.4` created; release notes summarize all 15 requirements.
- CHECKPOINT cleared; milestone branches deleted.
## v1.21 — Nova Deck Refinement & Pipeline Hardening (complete)
> Leadership-deck refinement based on 33 review notes on the v1.20 deck.
> Renamed the deck to the professional "Autonomous Cloud Delivery
> Platform" framing; restructured the narrative (Problem → Solution →
> Proof → Roadmap + Ask); removed internal provenance from
> audience-facing slides; hardened the policy pipeline (Checkov before
> plan, Wiz-or-Checkov on plan); moved the strategic integration
> objective into the North Star.
>
> Tags run on the v1.20.x line (milestone v1.21 → tags v1.20.0..v1.20.6).
> Flat workflow: commits on main, tags per phase.
### Phase P0 — pre-execution (complete, tag v1.20.0)
- SPECIFY → CLARIFY → RESEARCH → PLAN. Validated v1.21 requirements
(REQ-245..253). Established `active_milestone: "v1.21"`. Synced
PROJECT.md strategic-direction pillar.
### Phase P1 — strategic-docs (complete, tag v1.20.1)
- `git mv .ciagent/NO_HUMANS_THESIS.md .ciagent/AUTONOMY_THESIS.md` +
reframe content (autonomy in operations, not "removing humans").
- `NORTH_STAR.md`: vision polished ("invisible" → "visible"); obj #2
deterministic-scoring reword; obj #3 four CTO metrics; obj #4 replaced
with integration objective; drop anti-goals 1,4,5; add 2 new
anti-goals; anti-goal #3 reworded.
- `docs/raci.md`: 3 roles → 4 roles (add Quality Engineering; rename
Release Mgmt → SRE; split release attestation).
- `docs/scope.md` + render scripts + ONBOARDING: integration framing +
"no-humans" → "autonomous".
### Phase P2 — slides source-of-truth (complete, tag v1.20.2)
- `git mv` all 5 deck files `nova-no-humans-platform*`
`nova-autonomous-cloud-delivery*`.
- Rewrote source of truth to 18 main + 1 appendix slides, 4-beat arc.
All 33 review notes applied. Removed: old Slide 10 (Capability
Health), old Slide 12 (Zero-Touch), Appendix A2 (Operating Model &
Cost). Global: tech-leadership benefits; no D-###/REQ-###/.py paths in
audience slides; no badges; no version in footer.
### Phase P3 — marp deck + talking points + README (complete, tag v1.20.3)
- Synthesized Marp deck from updated source; frontmatter — title
"Nova — The Autonomous Cloud Delivery Platform", footer without
version + without "Act N/5", title-slide subtitle "Product Development
& Citizen Developer Overview"; no badges.
- Re-distilled talking points to 18-slide + A1 structure.
- README updated (deck title, audience, slide count, directory layout,
no badge docs).
- Theme CSS: fixed Appendix A1 table readability (explicit white body
on any background).
- Tests: added v1.21 assertions (no badges, no version, 18+1 slides, no
D-###/REQ-###/.py paths, old files removed, default deck renamed).
### Phase P4 — pipeline hardening (complete, tag v1.20.4)
- Two-stage policy scan (REQ-250): Checkov on static code BEFORE plan
(fail-fast); Wiz-or-Checkov on the plan AFTER plan (never both).
Implemented in run_platform.sh + run_codegen.sh + run_postapply.sh.
- `adapters/wiz/wiz_adapter.py`: added --plan mode CLI.
- `pipelines/contract.yml`: 'checkov' stage replaced by 'checkov-static'
(before terraform-plan) + 'runtime-policy-scan' (after). 9 → 10 stages.
- Tests updated; full suite 686 pass + 1 pre-existing attestation
failure (unrelated env issue).
### Phase P5 — render + verify (complete, tag v1.20.5)
- New mermaid diagrams: platform-pipeline.mmd/.png (slide 6),
telemetry-live-ops.mmd/.png (slide 9).
- Re-rendered HTML + PPTX (20 slides, 21 media files).
- Verify: 101 v1.21-specific tests pass; 686 full suite pass;
check-only pipeline exit 0; no no-humans/D-###/REQ-###/badge in
audience-facing deck files.
### Phase P6 — final-review-ship (Final Phase, complete, tag v1.20.6)
- Multi-file audit: git log matches `.ciagent/` discipline; deck files
renamed; forbidden content absent from audience-facing slides.
- Ship: tag `v1.20.6` (final patch = milestone release). Requirements
marked complete; ROADMAP marked complete; CHECKPOINT cleared.
- **Requirements:** REQ-245..253 (9 requirements, all complete).
## v1.22 — Nova Deck Layout Fix (complete)
> Fixes the systemic layout/formatting problems in the Nova presentation
> deck that made every slide look "out of whack" after the v1.21 P5
> re-render. Root cause (per investigation): `nova-sp-theme.css` had
> zero `section` padding (declared `/* @theme nova-sp */` as a comment,
> not the `@theme` directive; did not `@import` Marp's default theme).
> Combined with `overflow:hidden`, a blunt `img { max-height: 320px }`,
> header+footer chrome on every slide, and two P5 diagrams with extreme
> aspect ratios (13.52× and 0.63×), 8 of 19 slides overflowed.
>
> Tags run on the v1.21.x line (milestone v1.22 → tags v1.21.0..v1.21.6).
### Phase P0 — pre-execution (complete, tag v1.21.0)
- SPECIFY → CLARIFY → RESEARCH → PLAN → GRILL. Validated v1.22
requirements (REQ-254..262). 8 research findings persisted to
RESEARCH.md. 5 CLARIFY decisions auto-resolved (comprehensive scope,
full pipeline, re-layout to LR, delete render_deck.sh, split slides
3+8). Persona roster: 2 active (lead-developer + backend-engineer),
2 deactivated (frontend + data). Grill: PROCEED-WITH-REVISIONS
(3 revisions: aspect-ratio test scoped to deck PNGs, @import
rejection documented, marp version pinning fallback).
### Phase P1 — theme-css (complete, tag v1.21.1)
- REQ-254: `section { padding: 48px 56px 40px; overflow: auto; }`
root cause fix (zero padding was why every slide looked jammed
against the edges).
- REQ-255: `img { max-width: 100%; max-height: 380px; object-fit:
contain; }` + `.wide`/`.tall` classes — replaced blunt
`max-height: 320px` that broke `w:` directives on tall images.
- REQ-256: `section.title header/footer { display: none; }` — title
chrome suppression. `h2 + p { margin-top: 0.2em; }`, `p { margin:
0.4em 0; }` — spacing tightening. `ol` styling. `table.dense`
class. `@media print { section { overflow: hidden; } }` for PPTX.
### Phase P2 — render-scripts (complete, tag v1.21.2)
- REQ-257: deleted `scripts/render_deck.sh` (omitted `--theme`,
produced unthemed output). Pinned marp-cli@4.5.0 + mermaid-cli@
11.16.0 in `render_slides.sh`. Removed references from README,
sync_to_nova.sh, test_no_forge_mentions.py.
- REQ-258: added `-s 2 -b transparent` to mermaid-cli invocation
(README spec; produces crisp 2x PNGs with transparent backgrounds).
### Phase P3 — mermaid-relayout (complete, tag v1.21.3)
- REQ-259: `telemetry-live-ops.mmd` kept as `flowchart TB` (the 3-way
branch makes LR too wide at 4.22 aspect; TB gives 0.63 which is
legible at h:480 with img.tall class). Re-rendered at 2x transparent
(1024x1628).
- REQ-260: `platform-pipeline.mmd` restructured from 10-node LR chain
(aspect 13.52, illegible 1000x74 strip) to 4-node TB with combined
nodes. Re-rendered at 2x transparent (552x1116, aspect 0.49).
- Marp deck directives updated: `![w:1000]`/`![w:900]`
`![h:480 class:tall]` so images render at legible height using the
img.tall class budget (480px).
- Aspect-ratio bounds revised from [1.2, 2.5] to [0.4, 4.0] (accepts
both tall and wide diagrams; still catches original outliers).
### Phase P4 — deck-content (complete, tag v1.21.4)
- REQ-261: split slide 3 (Objectives + Anti-Goals) into Slide 3
(Objectives) + Slide 4 (Anti-Goals). Split slide 8 (Attestation
Matrix) into Slide 9 (QA, 3 rows) + Slide 10 (Prod/DR, 7 rows).
Main slide count 18 → 20.
- Trimmed: slide 7 (Pipeline) to 3 bullets. slide 11 (Telemetry) to
3 bullets. slide 14 (Deferred) merged 3 Live-AWS rows into 1 (8→6
rows). slide 17 (Quarter-by-Quarter) dropped Grounding column
(5→4 cols). Global table cell padding reduced (6px 10px → 4px 8px).
- Removed `header:` from frontmatter (keep `footer:` + `paginate`
only). The full 51-char deck title in BOTH header and footer was
redundant chrome eating ~35px on every slide.
- Source `.md` and talking-points re-synced to 20-slide structure.
- Updated `test_marp_deck_slide_count` (18→20 main + 1 appendix).
Updated README slide-count convention (all 6 references).
### Phase P5 — render-and-test (complete, tag v1.21.5)
- REQ-262: re-rendered HTML + PPTX via `render_slides.sh` (pinned
marp-cli@4.5.0, mermaid-cli@11.16.0, 2x transparent PNGs). 22
slides (title + 20 main + 1 appendix), 23 media files embedded.
Theme embedded in HTML (--sp-red + padding confirmed).
- Added 9 tests to `test_slides_pipeline.py` (the gap that let the
layout regression through): test_theme_css_has_section_padding,
test_theme_css_suppresses_title_chrome,
test_theme_css_has_aspect_ratio_aware_images,
test_png_aspect_ratios_sane (scoped to deck-referenced PNGs only
per GRILL revision 1, bounds [0.4, 4.0]),
test_render_slides_has_2x_scale, test_render_slides_pins_cli_versions,
test_render_deck_removed, test_html_embeds_theme,
test_html_slide_count_matches_marp.
- 32 slide tests pass (23 original + 9 new). 94 key-file tests pass.
`run_platform.sh --check-only` exit 0.
### Phase P6 — final-review-ship (Final Phase, complete, tag v1.21.6)
- Multi-persona code review: PASS with 3 P1 flags (all fixed in this
phase): source .md/talking-points re-synced to 20 slides, `![h:480
class:tall]` directives applied, README stale references updated.
- Audit: git log matches `.ciagent/` discipline; all commits have
`---ci---` blocks; branch hygiene verified.
- Ship: tag `v1.21.6` (final patch = milestone release). Merge
`milestone/v1.22-deck-layout-fix``main`. Requirements marked
complete; ROADMAP marked complete; CHECKPOINT cleared.
- **Requirements:** REQ-254..262 (9 requirements, all complete).
## v1.23 — Nova Deck Cleanup & Python PPTX (complete)
> **NFR milestone** (docs/render/test only; no features). Tags run on the
> **v1.22.x** line (milestone v1.23 → tags v1.22.0..v1.22.6). Final patch
> `v1.22.6` = milestone release. Branch: `milestone/v1.23-deck-cleanup-python-pptx`.
>
> Driven by the user's feedback that the deck looked "out of whack" and
> the desire to return to the clean, well-formatted style of the old
> `the-developer-experience.html` (which used Marp's built-in `default`
> theme + an inline `style:` block). That investigation revealed:
> (1) the "clean" reference was itself MARP output — MARP is not the
> problem; (2) the current deck uses a standalone `nova-sp-theme.css`
> that re-derives all base spacing from scratch and had a zero-padding
> bug (fixed in v1.22 but the standalone approach is fragile);
> (3) there are two markdown documents (a plain source-of-truth `.md`
> and a manually-synthesized `-marp.md`) that should be consolidated;
> (4) images are referenced as file paths in the HTML, so the HTML
> breaks when redistributed without the `assets/` folder; (5) the deck
> is verbose in places and uses the term "penetrate" which the user
> wants removed.
>
> The milestone delivers: single-document consolidation, clean style
> restoration (Marp `default` + inline `style:`), self-contained HTML
> (base64 images), a parallel structured python-pptx PPTX generator,
> targeted word-count trim, and "penetrate" removal. `nova-sp-theme.css`
> is retained as a styling reference but retired from the render path.
### Phase P0 — pre-execution (active)
- SPECIFY → CLARIFY → RESEARCH → PLAN → GRILL. Establishes v1.23
requirements (REQ-263..275). Tag `v1.22.0`. Grill PROCEED-WITH-
REVISIONS (0.78): 4 binding revisions applied (G-001 repo-wide
"penetrate" purge; G-002 P3→P4 serialized; G-003 P3 split P3a+P3b;
G-004 P5+P6 merged).
### Phase P1 — consolidate-docs (planned, tag v1.22.1)
- REQ-263: fold speaker notes + talking points into `*-marp.md` as Marp
HTML comments; delete the plain `.md`. `-marp.md` becomes the sole
source of truth.
- REQ-264: keep `*-talking-points.md` as a standalone presenter aid,
synced from the deck's `<!-- Talking points: -->` comments.
### Phase P2 — restore-clean-style (planned, tag v1.22.2)
- REQ-265: revert frontmatter to `theme: default` + inline `style:`
block (S&P palette). Keep H2 + bold-lead structure, no header, no
badges.
- REQ-266: retain `nova-sp-theme.css` as a styling reference; drop
`--theme` from `render_slides.sh`.
- REQ-267: restyle benefit callouts — remove `**Benefit:**` prefix; use
`.benefit` class (red top-rule + black italic; white on title slides).
### Phase P3a — inline-images (planned, tag v1.22.3)
- REQ-268: new `scripts/inline_images.py` — base64-embeds all images in
the rendered HTML for redistribution. Invoked after the MARP HTML
render. Low-risk, mechanical (G-003 isolation).
### Phase P3b — python-pptx-generator (planned, tag v1.22.4)
- REQ-269: new `scripts/render_pptx.py` — structured, editable, S&P-themed
PPTX via `python-pptx`. 16:9; native tables; embedded PNGs; benefit
callouts. Add `python-pptx` to `pyproject.toml`. High-risk, isolated
(G-003).
- REQ-270: `render_slides.sh` produces both PPTX outputs; CI installs
`python-pptx`; both attached to release.
### Phase P4 — trim-wordcount + repo-wide "penetrate" purge (planned, tag v1.22.5)
- REQ-271: targeted ~20-30% word-count trim on verbose slides (1, 5, 7,
8, 13, 14, 20, appendix). Tables untouched. Spirit preserved.
- REQ-272: remove "penetrate" (and derivatives) repo-wide (G-001) —
`docs/` + `.ciagent/PROJECT.md`/`CLARIFY.md`; RESEARCH.md/PLAN.md/
GRILL.md exempt as decision-history. Slide 5's phrase removed with no
replacement (slide 4 already excludes the PDLC).
### Phase P5 — ci-tests-readme + review + audit + ship (Final Phase, tag v1.22.6)
- REQ-273: CI workflows install `python-pptx`, run `render_slides.sh`,
commit HTML + both PPTX + inlined images.
- REQ-274: update `test_slides_pipeline.py` (consolidated doc, inline
style assertions, image inlining, python-pptx, benefit class,
"penetrate" absence). New `test_pptx_generator.py`.
- REQ-275: rewrite `README.md` for the single-document + dual-PPTX +
image-inlining pipeline.
- Review + audit + milestone ship (merged P5+P6 per G-004 — NFR docs
milestone). Tag `v1.22.6` (final patch = milestone release). Merge
`milestone/v1.23-deck-cleanup-python-pptx``main`.
- **Requirements:** REQ-263..275 (13 requirements).
+2 -3
View File
@@ -8,7 +8,7 @@
], ],
"active_project": "acdl", "active_project": "acdl",
"active_projects": ["acdl"], "active_projects": ["acdl"],
"active_milestone": "v1.24", "active_milestone": "v1.16",
"autonomy": { "autonomy": {
"level": "full", "level": "full",
"escalation_hooks": ["deploy", "delete_data", "merge_to_main"], "escalation_hooks": ["deploy", "delete_data", "merge_to_main"],
@@ -208,6 +208,5 @@
"telemetry": { "telemetry": {
"enabled": true, "enabled": true,
"persist": true "persist": true
}, }
"strategic_direction_file": ".ciagent/NORTH_STAR.md"
} }
-24
View File
@@ -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% /
-10
View File
@@ -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 -1
View File
@@ -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)
+5 -5
View File
@@ -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
@@ -38,8 +38,8 @@
# 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 NOVA_AWS_ACCESS_KEY_ID + NOVA_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.
@@ -155,7 +155,7 @@ jobs:
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v4
with: with:
name: nova-terraform name: nova-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
+2 -2
View File
@@ -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,7 +9,7 @@
# 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,
-43
View File
@@ -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
-45
View File
@@ -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 -1
View File
@@ -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)
+5 -5
View File
@@ -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
@@ -38,8 +38,8 @@
# 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 NOVA_AWS_ACCESS_KEY_ID + NOVA_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.
@@ -155,7 +155,7 @@ jobs:
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v4
with: with:
name: nova-terraform name: nova-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
+2 -2
View File
@@ -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,7 +9,7 @@
# 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,
-43
View File
@@ -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 -14
View File
@@ -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,5 +28,4 @@ metrics/lifecycle/
*.cer *.cer
*.crt *.crt
*.jks *.jks
*.keystore.coverage *.keystore
.coverage
+54 -40
View File
@@ -126,46 +126,20 @@ 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)
@@ -194,6 +168,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 +213,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 Nova 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)
@@ -296,6 +304,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).
+1 -24
View File
@@ -17,12 +17,8 @@ ACDL_TAG_NAMING in P2 (REQ-158); the rule is in hard mode as of P3
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"),
@@ -75,7 +71,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 +85,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
+4 -33
View File
@@ -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>")
+3 -3
View File
@@ -62,7 +62,7 @@ path above remains the v1.9 production audit record.
**platform-level KMS key** (not per-contract — a per-contract key would **platform-level KMS key** (not per-contract — a per-contract key would
explode the key-management surface), rotated **quarterly**. The `jws` explode the key-management surface), rotated **quarterly**. The `jws`
field is added to the event shape when this ships. field is added to the event shape when this ships.
- **Async worker + DLQ:** a Lambda (or a forge Actions scheduled workflow) - **Async worker + DLQ:** a Lambda (or a Gitea Actions scheduled workflow)
reads the outbox, writes to S3 Object Lock, signs with KMS. DLQ = an reads the outbox, writes to S3 Object Lock, signs with KMS. DLQ = an
SQS dead-letter queue for failed writes. RTO = DLQ replay. SQS dead-letter queue for failed writes. RTO = DLQ replay.
- **Daily checkpoints (§9):** a daily job reads the last event hash and - **Daily checkpoints (§9):** a daily job reads the last event hash and
@@ -86,7 +86,7 @@ log" anti-goal requires.
D-083 ships). D-083 ships).
- `prev_event_hash` (chain link; `GENESIS` for the first event). - `prev_event_hash` (chain link; `GENESIS` for the first event).
- `hash` (this event's SHA-256 over canonical JSON). - `hash` (this event's SHA-256 over canonical JSON).
- `approver_qa` (CI username of the QA approver; populated on - `approver_qa` (Gitea/GitHub username of the QA approver; populated on
qa-promotion by v1.9's `hitl_gates.attest` — D-042). qa-promotion by v1.9's `hitl_gates.attest` — D-042).
- `approver_prod` (SRE username; populated on prod-promotion by v1.9's - `approver_prod` (SRE username; populated on prod-promotion by v1.9's
`hitl_gates.attest`). `hitl_gates.attest`).
@@ -112,7 +112,7 @@ log" anti-goal requires.
- **D-042** — approver identities (`approver_qa`, `approver_prod`, - **D-042** — approver identities (`approver_qa`, `approver_prod`,
`approver_dr`) live in the outbox; the separation-of-duties check `approver_dr`) live in the outbox; the separation-of-duties check
(`core/separation_of_duties.py`) reads `approver_qa` and compares (`core/separation_of_duties.py`) reads `approver_qa` and compares
to the prod-dispatch CI actor. v1.9's to the prod-dispatch `gitea.actor` / `github.actor`. v1.9's
`hitl_gates.attest` populates these attributes. `hitl_gates.attest` populates these attributes.
- **D-083** (v1.9) — S3 Object Lock + JWS + async worker + DLQ + daily - **D-083** (v1.9) — S3 Object Lock + JWS + async worker + DLQ + daily
checkpoints deferred to a future milestone. Requires non-offline- checkpoints deferred to a future milestone. Requires non-offline-
+1 -32
View File
@@ -34,13 +34,8 @@ per-input scores.
from dataclasses import dataclass, asdict from dataclasses import dataclass, asdict
from typing import List, Literal, Optional, Dict, Any from typing import List, Literal, Optional, Dict, Any
import json import json
import os
import sys import sys
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
from core.metrics.event_envelope import emit, make_event, append_event
from core.metrics.decision_ledger import append as ledger_append
WEIGHTS = { WEIGHTS = {
"policy": 0.30, "policy": 0.30,
@@ -166,33 +161,7 @@ def compute(contract_id: str, environment: str,
band = "warn" band = "warn"
if environment == "dev" and band == "warn": if environment == "dev" and band == "warn":
band = "block" band = "block"
signal = Signal(score, band, per_input, reasons) return Signal(score, band, per_input, reasons)
# Emit nova.confidence.computed + nova.ai.decision.made events (D-122).
# The "AI decision" is the confidence-gated policy engine, not an LLM.
# decision_id = run_id (or "cli-<ts>" when called from CLI without a run).
try:
run_id = os.environ.get("NOVA_RUN_ID", f"cli-{int(__import__('time').time())}")
conf_data = {"score": score, "band": band, "perInput": per_input, "reasonCodes": reasons}
emit("nova.confidence.computed", run_id, environment, conf_data, contract_id=contract_id)
decision_data = {
"decision_id": run_id,
"chosen_action": band,
"confidence": score,
"alternatives": per_input,
"human_override": band == "block",
"threshold": THRESHOLDS[environment],
}
decision_event = make_event("nova.ai.decision.made", run_id, environment, decision_data,
contract_id=contract_id, actor_type="confidence-gate",
actor_id="confidence_signal")
append_event(decision_event)
ledger_append(decision_event)
except Exception:
pass # metrics emission must never break the confidence gate
return signal
if __name__ == "__main__": if __name__ == "__main__":
+6 -10
View File
@@ -55,8 +55,6 @@ def load(env_name, root=None):
def _onboarding_message(env_name): def _onboarding_message(env_name):
# P19 (REQ-183): rebranded Nova self-service request path — no longer
# routes to "contact the platform team" for the request step.
return ( return (
"=== Nova Environment Onboarding ===\n" "=== Nova Environment Onboarding ===\n"
f"No environment named '{env_name}' is bound to this repository.\n\n" f"No environment named '{env_name}' is bound to this repository.\n\n"
@@ -68,15 +66,13 @@ def _onboarding_message(env_name):
" - an IAM role surfaced to your repo via attribute-based\n" " - an IAM role surfaced to your repo via attribute-based\n"
" authorization (ABAC)\n\n" " authorization (ABAC)\n\n"
"You do not provide an AWS account, VPC, subnet, or state bucket.\n\n" "You do not provide an AWS account, VPC, subnet, or state bucket.\n\n"
"To request an environment (self-service):\n" "To request an environment:\n"
" 1. Submit an onboarding request to the Nova Lambda\n" " 1. Contact the platform team with your repo name + the\n"
" (action: onboard_consumer) with your repo name + the\n"
" environment name you need (e.g. 'dev').\n" " environment name you need (e.g. 'dev').\n"
" 2. The platform generates an environment binding + opens a PR.\n" " 2. The platform team provisions the account/network/state/role\n"
" 3. The platform provisions the account/network/state/role and\n" " and binds the environment to your repo.\n"
" grants the ABAC role. Your next pipeline run proceeds.\n\n" " 3. Your next pipeline run will proceed normally.\n\n"
"Run: python3 core/onboarding.py --request '{...}' to generate a\n" "Expected turnaround: contact the platform team for current SLA.\n"
"binding file locally, or POST to the Lambda onboard_consumer action.\n"
"===================================\n" "===================================\n"
) )
+2 -10
View File
@@ -33,13 +33,5 @@ halting the pipeline before any work is done.
A new environment is a platform-team action: provision the AWS account / A new environment is a platform-team action: provision the AWS account /
network / state backend / IAM role, then add a `<name>.json` here and bind network / state backend / IAM role, then add a `<name>.json` here and bind
it to the consumer repo. it to the consumer repo. Self-service environment provisioning is on the
roadmap; today it is a platform-team action.
**P19 (REQ-183):** the *request* step is now self-service. A consumer
submits an onboarding request (POST to the Nova Lambda `onboard_consumer`
action, or `python3 core/onboarding.py --request '{...}'`) and the
platform generates a `<name>.json` binding file from the request + opens
a PR. The actual AWS account/network/state provisioning + cross-account
role grant remains a platform-team action (a future feature milestone
will automate the provisioning; the cross-account role Terraform is
offline-proven in P20/REQ-184).
+4 -26
View File
@@ -1,6 +1,6 @@
"""HITL pre-execution attestation gates (REQ-108, D-084). """HITL pre-execution attestation gates (REQ-108, D-084).
Records the approver identity (the CI actor (GITHUB_ACTOR or FORGE_ACTOR)) to the Records the approver identity (`gitea.actor` / `github.actor`) to the
DynamoDB outbox for the contractId (attribute `approver_qa` / DynamoDB outbox for the contractId (attribute `approver_qa` /
`approver_prod` / `approver_dr`), runs the separation-of-duties check on `approver_prod` / `approver_dr`), runs the separation-of-duties check on
prod, invokes the 8-concern attestation matrix for the target env, and prod, invokes the 8-concern attestation matrix for the target env, and
@@ -12,10 +12,6 @@ import os
import sys import sys
from typing import Optional, Tuple from typing import Optional, Tuple
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
from core.metrics.event_envelope import make_event, append_event
from core.metrics.decision_ledger import append as ledger_append
def _approver_attr(env: str) -> str: def _approver_attr(env: str) -> str:
return {"qa": "approver_qa", "prod": "approver_prod", "dr": "approver_dr"}.get(env, "") return {"qa": "approver_qa", "prod": "approver_prod", "dr": "approver_dr"}.get(env, "")
@@ -29,7 +25,7 @@ def attest(contract_id: str, env: str, approver: str,
Args: Args:
contract_id: the contract UUID. contract_id: the contract UUID.
env: dev/qa/prod/dr. env: dev/qa/prod/dr.
approver: the approver's username (the CI actor (GITHUB_ACTOR or FORGE_ACTOR)). approver: the approver's username (`gitea.actor` / `github.actor`).
evidence: optional operator-supplied evidence artifacts (for the evidence: optional operator-supplied evidence artifacts (for the
attestation matrix operator-supplied concerns). attestation matrix operator-supplied concerns).
outbox_client: optional moto-mocked DynamoDB outbox client for tests. outbox_client: optional moto-mocked DynamoDB outbox client for tests.
@@ -41,7 +37,7 @@ def attest(contract_id: str, env: str, approver: str,
return (True, "dev autonomous (no HITL gate)") return (True, "dev autonomous (no HITL gate)")
if not approver: if not approver:
return (False, f"no approver identity for {env} (GITHUB_ACTOR/FORGE_ACTOR unset)") return (False, f"no approver identity for {env} (GITHUB_ACTOR/GITEA_ACTOR unset)")
attr = _approver_attr(env) attr = _approver_attr(env)
if not attr: if not attr:
@@ -65,30 +61,12 @@ def attest(contract_id: str, env: str, approver: str,
if not ok: if not ok:
return (False, reason) return (False, reason)
# Emit attestation.recorded event to the Decision Ledger (D-132).
try:
run_id = os.environ.get("NOVA_RUN_ID", f"attest-{contract_id[:8]}")
attestation_data = {
"approver": approver,
"environment": env,
"concerns": reason,
"result": "pass",
"contract_id": contract_id,
}
attestation_event = make_event("nova.attestation.recorded", run_id, env, attestation_data,
contract_id=contract_id, actor_type="human-attestation",
actor_id=approver)
append_event(attestation_event)
ledger_append(attestation_event)
except Exception:
pass # metrics emission must never break the attestation gate
return (True, f"{env} attested by {approver}") return (True, f"{env} attested by {approver}")
def approver_from_env() -> Optional[str]: def approver_from_env() -> Optional[str]:
"""Read the approver identity from the environment.""" """Read the approver identity from the environment."""
return os.environ.get("GITHUB_ACTOR") or os.environ.get("FORGE_ACTOR") return os.environ.get("GITHUB_ACTOR") or os.environ.get("GITEA_ACTOR")
if __name__ == "__main__": if __name__ == "__main__":
+15 -15
View File
@@ -18,32 +18,32 @@ gates. No partial deployment to roll back on rejection (qa, prod); dr is
a separate deployment against a separate cluster/region. The a separate deployment against a separate cluster/region. The
canary/deployment-rollback model is explicitly not in scope for v1. canary/deployment-rollback model is explicitly not in scope for v1.
## Forge-specific gate mechanics (D-042) ## Gitea-specific gate mechanics (D-042)
The dev forge has **no Environments API** and ignores `environment:` blocks Gitea has **no Environments API** and ignores `environment:` blocks
(v1.0 D-013; re-confirmed in RESEARCH TARGET 1). The pre-execution gate (v1.0 D-013; re-confirmed in RESEARCH TARGET 1). The pre-execution gate
is modeled as a `workflow_dispatch` with approval inputs: is modeled as a `workflow_dispatch` with approval inputs:
- **qa gate:** `workflow_dispatch` with `approve_qa: true`; the dispatch - **qa gate:** `workflow_dispatch` with `approve_qa: true`; the dispatch
run's `CI actor` is the QA approver. run's `gitea.actor` is the QA approver.
- **prod gate:** `workflow_dispatch` with `approve_prod: true`; - **prod gate:** `workflow_dispatch` with `approve_prod: true`;
`CI actor` is the SRE approver. `gitea.actor` is the SRE approver.
- **dr gate:** `workflow_dispatch` with `approve_dr: true`; same. - **dr gate:** `workflow_dispatch` with `approve_dr: true`; same.
The approver identity of record = `CI actor` of the dispatch run The approver identity of record = `gitea.actor` of the dispatch run
(D-042). There is no other approval-identity signal in the dev forge. The real (D-042). There is no other approval-identity signal in Gitea. The real
OIDC path (blocked on upstream forge OIDC support) does not change this — OIDC path (blocked on go-gitea/gitea#36988) does not change this —
OIDC authorizes the *runner* to AWS, it does not change how the platform OIDC authorizes the *runner* to AWS, it does not change how the platform
records the *human* approver. records the *human* approver.
On GitHub, the equivalent is `CI actor` of the `workflow_dispatch` On GitHub, the equivalent is `github.actor` of the `workflow_dispatch`
run; GitHub Environments with required reviewers are the native gate, run; GitHub Environments with required reviewers are the native gate,
but the `workflow_dispatch` approval-input fallback is used for but the `workflow_dispatch` approval-input fallback is used for
byte-identical across forges. byte-identical Gitea + GitHub workflows.
## Reviewer routing (ARCHITECTURE.md §10.2) ## Reviewer routing (ARCHITECTURE.md §10.2)
CODEOWNERS routes the right reviewer to the right gate: Gitea CODEOWNERS routes the right reviewer to the right gate:
- qa → QA team - qa → QA team
- prod → SRE team - prod → SRE team
@@ -105,7 +105,7 @@ concern is missing or expired for prod/dr.
| 1 business day | PENDING_ATTESTATION_WARNING | Notify team + platform on-call (elevated path); emit `PENDING_ATTESTATION_TIMEOUT_WARNING` event | | 1 business day | PENDING_ATTESTATION_WARNING | Notify team + platform on-call (elevated path); emit `PENDING_ATTESTATION_TIMEOUT_WARNING` event |
| 2 business days | PENDING_ATTESTATION_AUTO_FREEZE | Auto-freeze; require re-submission; emit `PENDING_ATTESTATION_AUTO_FREEZE` event; new submission linked via `supersedes` | | 2 business days | PENDING_ATTESTATION_AUTO_FREEZE | Auto-freeze; require re-submission; emit `PENDING_ATTESTATION_AUTO_FREEZE` event; new submission linked via `supersedes` |
**Implementation:** an `on: schedule` workflow (runs hourly) that **Implementation:** a Gitea `on: schedule` workflow (runs hourly) that
scans the DynamoDB outbox for `PENDING_ATTESTATION` events with `ts` scans the DynamoDB outbox for `PENDING_ATTESTATION` events with `ts`
older than 1/2 business days and emits the warn/freeze events. Not older than 1/2 business days and emits the warn/freeze events. Not
implemented in v1.9 (roadmap item; the attestation gates themselves are implemented in v1.9 (roadmap item; the attestation gates themselves are
@@ -126,11 +126,11 @@ The identity-distinctness check is platform-internal, not GitHub-native,
not Kyverno (in v1). Sequence: not Kyverno (in v1). Sequence:
1. On promotion dev → qa, the platform reads the QA approver's identity 1. On promotion dev → qa, the platform reads the QA approver's identity
from the `workflow_dispatch` run's `CI actor` from the `workflow_dispatch` run's `gitea.actor` (or `github.actor`)
and writes it to the DynamoDB outbox keyed by `contractId` (attribute and writes it to the DynamoDB outbox keyed by `contractId` (attribute
`approver_qa`). `approver_qa`).
2. On promotion qa → prod, the platform reads the stored `approver_qa` 2. On promotion qa → prod, the platform reads the stored `approver_qa`
from the outbox and the new SRE approver identity from the from the outbox and the new SRE approver's `gitea.actor` from the
prod-dispatch run. prod-dispatch run.
3. If `approver_qa == approver_prod`, the platform blocks the prod 3. If `approver_qa == approver_prod`, the platform blocks the prod
promotion, writes a `SEPARATION_OF_DUTIES_VIOLATION` event to the promotion, writes a `SEPARATION_OF_DUTIES_VIOLATION` event to the
@@ -163,8 +163,8 @@ v1.9 (Phase 41 + Phase 42) wires the gates end-to-end:
## Decision trail ## Decision trail
- **D-042** — approver identity = `CI actor` of the `workflow_dispatch` - **D-042** — approver identity = `gitea.actor` of the `workflow_dispatch`
run; no Environments API in the dev forge. run; no Environments API in Gitea. On GitHub, `github.actor`.
- **D-013** (v1.0) — the `workflow_dispatch` approval-input fallback, - **D-013** (v1.0) — the `workflow_dispatch` approval-input fallback,
re-used for the real platform's pre-execution gate model. re-used for the real platform's pre-execution gate model.
- **D-084** (v1.9) — 8-concern attestation matrix: offline-testable - **D-084** (v1.9) — 8-concern attestation matrix: offline-testable
+9 -89
View File
@@ -27,7 +27,7 @@ CHANGE_REQUESTS_TABLE = os.environ.get("CHANGE_REQUESTS_TABLE", "nova-change-req
GITHUB_TOKEN_SECRET_ID = os.environ.get("GITHUB_TOKEN_SECRET_ID", "nova/github-token") GITHUB_TOKEN_SECRET_ID = os.environ.get("GITHUB_TOKEN_SECRET_ID", "nova/github-token")
PLATFORM_REPO = os.environ.get("PLATFORM_REPO", "nova/acdl") PLATFORM_REPO = os.environ.get("PLATFORM_REPO", "nova/acdl")
# P1-9: Forge-agnostic API base URL. Defaults to GitHub; set GITHUB_API_BASE # P1-9: Forge-agnostic API base URL. Defaults to GitHub; set GITHUB_API_BASE
# to a compatible forge API root (e.g. https://forge.example.com/api/v1). # to a Gitea API root (e.g. https://git.cloudinit.dev/api/v1) for Gitea.
GITHUB_API_BASE = os.environ.get("GITHUB_API_BASE", "https://api.github.com") GITHUB_API_BASE = os.environ.get("GITHUB_API_BASE", "https://api.github.com")
# P11 (REQ-175): consistent cap for error/stackTrace fields (was 10k vs 2k). # P11 (REQ-175): consistent cap for error/stackTrace fields (was 10k vs 2k).
@@ -96,22 +96,22 @@ def _iso8601_now():
def _forge_type(): def _forge_type():
"""Detect whether the API base is GitHub or a compatible forge. """P1-9: Detect whether the API base is GitHub or Gitea.
Compatible forge API roots contain '/api/v1'; GitHub's is 'api.github.com'. Gitea API roots contain '/api/v1'; GitHub's is 'api.github.com'.
""" """
if "/api/v1" in GITHUB_API_BASE: if "/api/v1" in GITHUB_API_BASE:
return "generic_forge" return "gitea"
return "github" return "github"
def _issues_search_url(owner, repo, encoded_query): def _issues_search_url(owner, repo, encoded_query):
"""Build the issue search URL based on forge type. """P1-9: Build the issue search URL based on forge type.
GitHub uses /search/issues?q=...; compatible forges use /repos/{owner}/{repo}/issues?... GitHub uses /search/issues?q=...; Gitea uses /repos/{owner}/{repo}/issues?...
with query params (no /search/issues endpoint). with query params (no /search/issues endpoint).
""" """
if _forge_type() == "generic_forge": if _forge_type() == "gitea":
return ( return (
f"{GITHUB_API_BASE}/repos/{owner}/{repo}/issues" f"{GITHUB_API_BASE}/repos/{owner}/{repo}/issues"
f"?state=open&type=issues&q={encoded_query}" f"?state=open&type=issues&q={encoded_query}"
@@ -123,7 +123,7 @@ def _issues_search_url(owner, repo, encoded_query):
def _issues_create_url(owner, repo): def _issues_create_url(owner, repo):
"""URL for creating an issue (same pattern across forges).""" """URL for creating an issue (same pattern for both GitHub + Gitea)."""
return f"{GITHUB_API_BASE}/repos/{owner}/{repo}/issues" return f"{GITHUB_API_BASE}/repos/{owner}/{repo}/issues"
@@ -398,65 +398,6 @@ def _validate_change_request(payload):
} }
def _onboard_consumer(payload):
"""P18 (REQ-182): accept a self-service onboarding request.
Validates the payload against schemas/onboarding.schema.json, then
writes a 'pending' row to nova-contracts (D-119). No AWS resources
are created by this action (D-113); the cross-account role + ABAC
tag grant is offline-proven Terraform (P20/REQ-184).
"""
import jsonschema
schema_path = os.path.join(os.path.dirname(os.path.dirname(
os.path.dirname(os.path.abspath(__file__)))),
"schemas", "onboarding.schema.json")
try:
with open(schema_path) as f:
schema = json.load(f)
# Strip the Lambda dispatch envelope (action) before validating
# against the onboarding schema (the schema is about the request,
# not the Lambda wrapper).
onboarding_payload = {k: v for k, v in payload.items() if k != "action"}
jsonschema.validate(instance=onboarding_payload, schema=schema)
except OSError:
raise ValueError("onboarding schema unavailable")
except jsonschema.ValidationError as e:
raise ValueError(f"onboarding payload invalid: {e.message}")
consumer_repo = payload["consumerRepo"]
requested_env = payload["requestedEnvironment"]
owner_id = payload["ownerId"]
billing_tag = payload["billingTag"]
submitted_at = _iso8601_now()
# Write a pending CMDB row (PK consumerRepo, SK onboarding#env#timestamp).
table = _get_dynamodb().Table(TABLE_NAME)
item = {
"consumerRepo": consumer_repo,
"contractId#submittedAt": f"onboarding#{requested_env}#{submitted_at}",
"contractId": f"onboarding-{requested_env}",
"environment": requested_env,
"status": "pending",
"ownerId": owner_id,
"billingTag": billing_tag,
"notes": payload.get("notes", ""),
"submittedAt": submitted_at,
}
table.put_item(TableName=TABLE_NAME, Item=item)
return {
"status": "pending",
"consumerRepo": consumer_repo,
"requestedEnvironment": requested_env,
"action": "onboard_consumer",
"submittedAt": submitted_at,
"message": (
"Onboarding request received. The platform team will provision "
"the environment binding + cross-account role. Track the status "
"via the nova-contracts table (status=pending → granted)."
),
}
def lambda_handler(event, context): def lambda_handler(event, context):
"""AWS Lambda handler entry point. """AWS Lambda handler entry point.
@@ -485,8 +426,6 @@ def lambda_handler(event, context):
result = _report_error(payload) result = _report_error(payload)
elif action == "validate_change_request": elif action == "validate_change_request":
result = _validate_change_request(payload) result = _validate_change_request(payload)
elif action == "onboard_consumer":
result = _onboard_consumer(payload)
else: else:
return { return {
"statusCode": 400, "statusCode": 400,
@@ -499,23 +438,4 @@ def lambda_handler(event, context):
return {"statusCode": 401, "body": json.dumps({"error": str(e)})} return {"statusCode": 401, "body": json.dumps({"error": str(e)})}
return {"statusCode": 400, "body": json.dumps({"error": str(e)})} return {"statusCode": 400, "body": json.dumps({"error": str(e)})}
except Exception as e: # pragma: no cover - defensive top-level guard except Exception as e: # pragma: no cover - defensive top-level guard
return {"statusCode": 500, "body": json.dumps({"error": str(e)})} return {"statusCode": 500, "body": json.dumps({"error": str(e)})}
# --- CLI: --check-readiness (D-133, REQ-218) ---------------------------
# Invoked as: python3 -m core.lambda.contract_ingestor --check-readiness <submission.json>
# Delegates to core.submission_readiness.check_readiness() and prints the
# structured ReadinessResult. Exits 0 if ready, 1 if not.
if __name__ == "__main__": # pragma: no cover - CLI entry
import sys
if "--check-readiness" in sys.argv:
sys.path.insert(
0, os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
)
from core.submission_readiness import cli_main
# Strip the --check-readiness flag; pass the file path.
rest = [a for a in sys.argv[1:] if a != "--check-readiness"]
sys.exit(cli_main(["check-readiness"] + rest))
else:
print("Usage: python3 -m core.lambda.contract_ingestor --check-readiness <submission.json>")
View File
-364
View File
@@ -1,364 +0,0 @@
"""Nova Metrics Collector (REQ-189, P2).
Reads all grounded signals (REGRESSION_REPORT.json, per-run manifests,
junit XML, pcr.json, signal.json, COST.md, decision ledger, coverage.json)
and normalizes them into a SQLite cold store at metrics/nova_metrics.db.
D-120: Nova-native (SQLite, no ClickHouse/BigQuery).
D-125: hybrid model reads files + events SQLite.
D-126: cold-only (no hot path; hot path deferred D-096).
D-128: metrics/ at repo root.
Idempotent: re-running the collector against the same inputs produces
identical row counts (REQ-200). The collector uses INSERT OR REPLACE
on fact tables keyed by natural keys.
"""
import datetime
import json
import os
import sqlite3
import sys
import xml.etree.ElementTree as ET
_METRICS_DIR = os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))), "metrics")
_STORE_PATH = os.path.join(_METRICS_DIR, "nova_metrics.db")
_REPO_ROOT = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
_REGRESSION_REPORT = os.path.join(_REPO_ROOT, ".ciagent", "REGRESSION_REPORT.json")
_RUNS_DIR = os.path.join(_METRICS_DIR, "runs")
_LEDGER_DB = os.path.join(_METRICS_DIR, "decision_ledger.db")
_COVERAGE_JSON = os.path.join(_METRICS_DIR, "coverage.json")
_TEST_RESULTS_XML = os.path.join(_METRICS_DIR, "test-results.xml")
def _iso8601_now():
return datetime.datetime.now(datetime.timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ")
def _init_store(db_path=None):
"""Create the fact/dim tables in the SQLite cold store."""
if db_path is None:
db_path = _STORE_PATH
os.makedirs(os.path.dirname(db_path), exist_ok=True)
conn = sqlite3.connect(db_path)
conn.executescript("""
CREATE TABLE IF NOT EXISTS fact_run (
run_id TEXT PRIMARY KEY,
contract_id TEXT,
environment TEXT,
started_at TEXT,
completed_at TEXT,
exit_code INTEGER,
outcome TEXT,
confidence_score REAL,
confidence_band TEXT,
hitl_block INTEGER,
cost_estimate_usd REAL,
decision_id TEXT
);
CREATE TABLE IF NOT EXISTS fact_capability (
capability_id TEXT,
run_id TEXT,
name TEXT,
status TEXT,
tier TEXT,
duration_ms REAL,
detail TEXT,
run_at_utc TEXT,
PRIMARY KEY (capability_id, run_id)
);
CREATE TABLE IF NOT EXISTS fact_policy_check (
run_id TEXT,
rule_id TEXT,
severity TEXT,
result TEXT,
resource_ref TEXT,
evaluated_at TEXT,
PRIMARY KEY (run_id, rule_id, resource_ref)
);
CREATE TABLE IF NOT EXISTS fact_confidence (
run_id TEXT,
score REAL,
band TEXT,
per_input TEXT,
reason_codes TEXT,
environment TEXT,
computed_at TEXT,
PRIMARY KEY (run_id)
);
CREATE TABLE IF NOT EXISTS fact_test (
run_id TEXT,
total_tests INTEGER,
passed INTEGER,
failed INTEGER,
errors INTEGER,
skipped INTEGER,
duration_s REAL,
coverage_pct REAL,
collected_at TEXT,
PRIMARY KEY (run_id)
);
CREATE TABLE IF NOT EXISTS fact_decision (
decision_id TEXT,
run_id TEXT,
chosen_action TEXT,
confidence REAL,
alternatives TEXT,
human_override INTEGER,
outcome TEXT,
event_time TEXT,
PRIMARY KEY (decision_id)
);
CREATE TABLE IF NOT EXISTS fact_cost_estimate (
run_id TEXT,
delta_usd REAL,
total_monthly_usd REAL,
available INTEGER,
estimated_at TEXT,
PRIMARY KEY (run_id)
);
CREATE TABLE IF NOT EXISTS fact_lifecycle (
module TEXT,
environment TEXT,
phase TEXT,
result TEXT,
duration_ms REAL,
run_at TEXT,
PRIMARY KEY (module, environment, phase, run_at)
);
CREATE TABLE IF NOT EXISTS dim_capability (
capability_id TEXT PRIMARY KEY,
name TEXT,
tier TEXT,
source_milestone TEXT
);
CREATE TABLE IF NOT EXISTS dim_milestone (
milestone TEXT PRIMARY KEY,
phase INTEGER,
tag TEXT,
completed_at TEXT
);
""")
conn.commit()
conn.close()
def collect_regression_report(db_path=None, report_path=None):
"""Read REGRESSION_REPORT.json → fact_capability + dim_capability."""
if db_path is None:
db_path = _STORE_PATH
if report_path is None:
report_path = _REGRESSION_REPORT
if not os.path.isfile(report_path):
return 0
_init_store(db_path)
with open(report_path) as f:
report = json.load(f)
run_id = report.get("run_id", f"regr-{report.get('run_at_utc','')}")
run_at = report.get("run_at_utc", _iso8601_now())
milestone = report.get("milestone", "")
conn = sqlite3.connect(db_path)
for result in report.get("results", []):
cap_id = result.get("capability_id", "")
conn.execute("""
INSERT OR REPLACE INTO fact_capability
(capability_id, run_id, name, status, tier, duration_ms, detail, run_at_utc)
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
""", (cap_id, run_id, result.get("name", ""), result.get("status", ""),
result.get("tier", ""), result.get("duration_ms", 0),
result.get("detail", ""), run_at))
conn.execute("""
INSERT OR REPLACE INTO dim_capability
(capability_id, name, tier, source_milestone)
VALUES (?, ?, ?, ?)
""", (cap_id, result.get("name", ""), result.get("tier", ""), milestone))
conn.execute("""
INSERT OR REPLACE INTO dim_milestone
(milestone, phase, tag, completed_at)
VALUES (?, ?, ?, ?)
""", (milestone, report.get("phase", 0), "", run_at))
conn.commit()
conn.close()
return len(report.get("results", []))
def collect_run_manifests(db_path=None, runs_dir=None):
"""Read per-run manifests from metrics/runs/*.json → fact_run."""
if db_path is None:
db_path = _STORE_PATH
if runs_dir is None:
runs_dir = _RUNS_DIR
if not os.path.isdir(runs_dir):
return 0
_init_store(db_path)
count = 0
conn = sqlite3.connect(db_path)
for fname in sorted(os.listdir(runs_dir)):
if not fname.endswith(".json"):
continue
fpath = os.path.join(runs_dir, fname)
if os.path.isdir(fpath):
continue
with open(fpath) as f:
manifest = json.load(f)
run_id = manifest.get("run_id", fname.replace(".json", ""))
conf = manifest.get("confidence", {})
hitl = manifest.get("hitl", {})
conn.execute("""
INSERT OR REPLACE INTO fact_run
(run_id, contract_id, environment, started_at, completed_at,
exit_code, outcome, confidence_score, confidence_band,
hitl_block, cost_estimate_usd, decision_id)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
""", (run_id, manifest.get("contract_id", ""), manifest.get("environment", ""),
manifest.get("started_at", ""), manifest.get("completed_at", ""),
manifest.get("exit_code", 0), manifest.get("outcome", ""),
conf.get("score", 0), conf.get("band", ""),
1 if hitl.get("block") else 0,
manifest.get("cost_estimate_usd", 0), manifest.get("decision_id", "")))
count += 1
conn.commit()
conn.close()
return count
def collect_decision_ledger(db_path=None, ledger_db=None):
"""Read the Decision Ledger SQLite → fact_decision."""
if db_path is None:
db_path = _STORE_PATH
if ledger_db is None:
ledger_db = _LEDGER_DB
if not os.path.isfile(ledger_db):
return 0
_init_store(db_path)
ledger_conn = sqlite3.connect(ledger_db)
rows = ledger_conn.execute(
"SELECT event_type, run_id, event_time, payload FROM decision_ledger WHERE event_type = 'nova.ai.decision.made' ORDER BY seq"
).fetchall()
ledger_conn.close()
conn = sqlite3.connect(db_path)
count = 0
for etype, run_id, event_time, payload_json in rows:
payload = json.loads(payload_json)
data = payload.get("data", {})
decision_id = data.get("decision_id", run_id)
conn.execute("""
INSERT OR REPLACE INTO fact_decision
(decision_id, run_id, chosen_action, confidence, alternatives,
human_override, outcome, event_time)
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
""", (decision_id, run_id, data.get("chosen_action", ""),
data.get("confidence", 0), json.dumps(data.get("alternatives", {})),
1 if data.get("human_override") else 0,
data.get("outcome", "pending"), event_time))
count += 1
conn.commit()
conn.close()
return count
def collect_test_results(db_path=None, junit_path=None, coverage_path=None):
"""Read junit XML + coverage.json → fact_test."""
if db_path is None:
db_path = _STORE_PATH
if junit_path is None:
junit_path = _TEST_RESULTS_XML
if coverage_path is None:
coverage_path = _COVERAGE_JSON
if not os.path.isfile(junit_path):
return 0
_init_store(db_path)
run_id = f"test-{_iso8601_now()}"
total = passed = failed = errors = skipped = 0
duration = 0.0
try:
tree = ET.parse(junit_path)
root = tree.getroot()
for suite in root.iter("testsuite"):
total += int(suite.get("tests", 0))
failed += int(suite.get("failures", 0))
errors += int(suite.get("errors", 0))
skipped += int(suite.get("skipped", 0))
duration += float(suite.get("time", 0))
passed = total - failed - errors - skipped
except Exception:
pass
coverage_pct = 0.0
if os.path.isfile(coverage_path):
try:
with open(coverage_path) as f:
cov = json.load(f)
coverage_pct = cov.get("totals", {}).get("percent_covered", 0.0)
except Exception:
pass
conn = sqlite3.connect(db_path)
conn.execute("""
INSERT OR REPLACE INTO fact_test
(run_id, total_tests, passed, failed, errors, skipped, duration_s, coverage_pct, collected_at)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
""", (run_id, total, passed, failed, errors, skipped, duration, coverage_pct, _iso8601_now()))
conn.commit()
conn.close()
return 1
def collect_lifecycle_reports(db_path=None, lifecycle_dir=None):
"""Read metrics/lifecycle/*.json → fact_lifecycle."""
if db_path is None:
db_path = _STORE_PATH
if lifecycle_dir is None:
lifecycle_dir = os.path.join(_METRICS_DIR, "lifecycle")
if not os.path.isdir(lifecycle_dir):
return 0
_init_store(db_path)
count = 0
conn = sqlite3.connect(db_path)
for fname in sorted(os.listdir(lifecycle_dir)):
if not fname.endswith(".json"):
continue
fpath = os.path.join(lifecycle_dir, fname)
with open(fpath) as f:
report = json.load(f)
conn.execute("""
INSERT OR REPLACE INTO fact_lifecycle
(module, environment, phase, result, duration_ms, run_at)
VALUES (?, ?, ?, ?, ?, ?)
""", (report.get("module", ""), report.get("environment", ""),
report.get("phase", ""), report.get("result", ""),
report.get("duration_ms", 0), report.get("run_at", _iso8601_now())))
count += 1
conn.commit()
conn.close()
return count
def collect_all(db_path=None):
"""Run all collectors. Returns a summary dict."""
if db_path is None:
db_path = _STORE_PATH
_init_store(db_path)
summary = {
"capabilities": collect_regression_report(db_path),
"runs": collect_run_manifests(db_path),
"decisions": collect_decision_ledger(db_path),
"tests": collect_test_results(db_path),
"lifecycle": collect_lifecycle_reports(db_path),
"collected_at": _iso8601_now(),
}
return summary
if __name__ == "__main__":
result = collect_all()
print(json.dumps(result, indent=2))
-257
View File
@@ -1,257 +0,0 @@
"""Nova Decision Ledger — SQLite append-only hash-chain (REQ-188, D-121).
Extends outbox_writer.py to emit to a SQLite append-only table with a hash
chain (prev_hash + own hash, SHA-256). Stores ai.decision.made events
(decision_id=run_id, chosen_action=band, confidence=score,
alternatives=perInput, human_override=HITL block) with outcome backfill
from apply.completed. Also stores attestation.recorded events (D-132).
Honors D-083 (no S3 Object Lock/JWS local SQLite hash-chain only).
D-120: Nova-native (SQLite, no QLDB).
D-128: metrics/ at repo root.
"""
import datetime
import hashlib
import json
import os
import sqlite3
import sys
_LEDGER_PATH = os.path.join(
os.path.dirname(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))),
"metrics", "decision_ledger.db",
)
_GENESIS_HASH = "GENESIS"
def _iso8601_now():
return datetime.datetime.now(datetime.timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ")
def _canonical_hash(event):
"""SHA-256 over canonical JSON (sort_keys, compact separators)."""
canonical = json.dumps(event, sort_keys=True, separators=(",", ":"))
return hashlib.sha256(canonical.encode("utf-8")).hexdigest()
def _init_db(db_path=None):
"""Create the ledger table if it doesn't exist."""
if db_path is None:
db_path = _LEDGER_PATH
os.makedirs(os.path.dirname(db_path), exist_ok=True)
conn = sqlite3.connect(db_path)
conn.execute("""
CREATE TABLE IF NOT EXISTS decision_ledger (
seq INTEGER PRIMARY KEY AUTOINCREMENT,
event_id TEXT NOT NULL,
event_type TEXT NOT NULL,
run_id TEXT NOT NULL,
contract_id TEXT,
environment TEXT,
event_time TEXT NOT NULL,
payload TEXT NOT NULL,
prev_hash TEXT NOT NULL,
hash TEXT NOT NULL
)
""")
conn.execute("CREATE INDEX IF NOT EXISTS idx_run_id ON decision_ledger(run_id)")
conn.execute("CREATE INDEX IF NOT EXISTS idx_event_type ON decision_ledger(event_type)")
conn.commit()
conn.close()
def _get_last_hash(db_path=None):
"""Get the hash of the last row in the ledger (or GENESIS if empty)."""
if db_path is None:
db_path = _LEDGER_PATH
conn = sqlite3.connect(db_path)
row = conn.execute("SELECT hash FROM decision_ledger ORDER BY seq DESC LIMIT 1").fetchone()
conn.close()
return row[0] if row else _GENESIS_HASH
def append(event, db_path=None):
"""Append an event to the Decision Ledger with hash-chain integrity.
Args:
event: a CloudEvents 1.0 envelope dict (from event_envelope.make_event)
db_path: path to the SQLite ledger
Returns:
The row dict (seq, event_id, event_type, run_id, hash, prev_hash).
"""
if db_path is None:
db_path = _LEDGER_PATH
_init_db(db_path)
prev_hash = _get_last_hash(db_path)
event_hash = _canonical_hash(event)
platform = event.get("platform", {})
data = event.get("data", {})
conn = sqlite3.connect(db_path)
conn.execute("BEGIN IMMEDIATE")
cursor = conn.execute(
"""INSERT INTO decision_ledger
(event_id, event_type, run_id, contract_id, environment, event_time, payload, prev_hash, hash)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)""",
(
event.get("id", ""),
event.get("type", ""),
platform.get("run_id", ""),
platform.get("contract_id", ""),
platform.get("environment", ""),
event.get("time", _iso8601_now()),
json.dumps(event, sort_keys=True),
prev_hash,
event_hash,
),
)
seq = cursor.lastrowid
conn.commit()
conn.close()
return {"seq": seq, "event_id": event.get("id", ""), "event_type": event.get("type", ""),
"run_id": platform.get("run_id", ""), "hash": event_hash, "prev_hash": prev_hash}
def verify_chain(db_path=None):
"""Verify the hash chain integrity. Returns (ok, broken_count, details).
Recomputes each row's hash from its payload and checks:
1. The stored hash matches the recomputed hash.
2. The prev_hash matches the previous row's hash.
"""
if db_path is None:
db_path = _LEDGER_PATH
_init_db(db_path)
conn = sqlite3.connect(db_path)
rows = conn.execute("SELECT seq, hash, prev_hash, payload FROM decision_ledger ORDER BY seq").fetchall()
conn.close()
if not rows:
return True, 0, "empty ledger"
broken = 0
details = []
prev_hash = _GENESIS_HASH
for seq, stored_hash, stored_prev, payload_json in rows:
event = json.loads(payload_json)
recomputed = _canonical_hash(event)
if recomputed != stored_hash:
broken += 1
details.append(f"seq={seq}: hash mismatch (stored={stored_hash[:12]}... recomputed={recomputed[:12]}...)")
if stored_prev != prev_hash:
broken += 1
details.append(f"seq={seq}: prev_hash mismatch (expected={prev_hash[:12]}... got={stored_prev[:12]}...)")
prev_hash = stored_hash
return broken == 0, broken, "; ".join(details) if details else "chain intact"
def query_by_run(run_id, db_path=None):
"""Query all ledger entries for a given run_id."""
if db_path is None:
db_path = _LEDGER_PATH
_init_db(db_path)
conn = sqlite3.connect(db_path)
rows = conn.execute(
"SELECT seq, event_type, event_time, payload FROM decision_ledger WHERE run_id = ? ORDER BY seq",
(run_id,),
).fetchall()
conn.close()
return [{"seq": r[0], "event_type": r[1], "event_time": r[2], "payload": json.loads(r[3])} for r in rows]
def stats(db_path=None):
"""Return ledger statistics."""
if db_path is None:
db_path = _LEDGER_PATH
_init_db(db_path)
conn = sqlite3.connect(db_path)
total = conn.execute("SELECT COUNT(*) FROM decision_ledger").fetchone()[0]
by_type = conn.execute("SELECT event_type, COUNT(*) FROM decision_ledger GROUP BY event_type").fetchall()
by_env = conn.execute("SELECT environment, COUNT(*) FROM decision_ledger GROUP BY environment").fetchall()
conn.close()
return {
"total": total,
"by_event_type": dict(by_type),
"by_environment": dict(by_env),
}
def export_since(since_iso, fmt="json", db_path=None):
"""Export ledger entries since a given ISO8601 timestamp."""
if db_path is None:
db_path = _LEDGER_PATH
_init_db(db_path)
conn = sqlite3.connect(db_path)
rows = conn.execute(
"SELECT seq, event_type, run_id, event_time, payload FROM decision_ledger WHERE event_time >= ? ORDER BY seq",
(since_iso,),
).fetchall()
conn.close()
entries = [{"seq": r[0], "event_type": r[1], "run_id": r[2], "event_time": r[3], "payload": json.loads(r[4])} for r in rows]
if fmt == "csv":
import csv
import io
buf = io.StringIO()
writer = csv.DictWriter(buf, fieldnames=["seq", "event_type", "run_id", "event_time", "payload"])
writer.writeheader()
for e in entries:
e["payload"] = json.dumps(e["payload"])
writer.writerow(e)
return buf.getvalue()
return json.dumps(entries, indent=2)
def replay_run(run_id, db_path=None):
"""Reconstruct a run's full event sequence from the ledger.
Prints the ordered event sequence (run.started -> policy.evaluated ->
confidence.computed -> ai.decision.made -> attestation.recorded ->
run.completed/failed) with the decision's confidence, alternatives,
and outcome.
"""
if db_path is None:
db_path = _LEDGER_PATH
entries = query_by_run(run_id, db_path)
if not entries:
return f"no events found for run_id={run_id}"
lines = [f"=== Replay: run_id={run_id} ({len(entries)} events) ==="]
for e in entries:
payload = e["payload"]
data = payload.get("data", {})
etype = e["event_type"]
line = f" [{e['seq']}] {e['event_time']} {etype}"
if etype == "nova.ai.decision.made":
line += f" confidence={data.get('confidence', '?')} band={data.get('chosen_action', '?')} override={data.get('human_override', '?')}"
elif etype == "nova.attestation.recorded":
line += f" env={data.get('environment', '?')} approver={data.get('approver', '?')} result={data.get('result', '?')}"
elif etype == "nova.run.completed":
line += f" exit={data.get('exit_code', '?')} outcome={data.get('outcome', '?')}"
elif etype == "nova.run.failed":
line += f" exit={data.get('exit_code', '?')} outcome=failed"
lines.append(line)
lines.append("=== End replay ===")
return "\n".join(lines)
if __name__ == "__main__":
if len(sys.argv) < 2:
print("usage: decision_ledger.py <verify-chain|stats|query|export|replay> [args]", file=sys.stderr)
sys.exit(2)
cmd = sys.argv[1]
if cmd == "verify-chain":
ok, broken, details = verify_chain()
print(f"chain_ok={ok} broken={broken} details={details}")
sys.exit(0 if ok else 1)
elif cmd == "stats":
print(json.dumps(stats(), indent=2))
elif cmd == "query" and len(sys.argv) >= 3:
print(json.dumps(query_by_run(sys.argv[2]), indent=2))
elif cmd == "export" and len(sys.argv) >= 3:
print(export_since(sys.argv[2]))
elif cmd == "replay" and len(sys.argv) >= 3:
print(replay_run(sys.argv[2]))
else:
print(f"unknown command: {cmd}", file=sys.stderr)
sys.exit(2)
-39
View File
@@ -1,39 +0,0 @@
"""Nova Decision Ledger CLI (REQ-207).
Subcommands: query, verify-chain, stats, export, replay.
Read-only CLI for the Decision Ledger SQLite hash-chain.
"""
import json
import os
import sys
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))))
from core.metrics.decision_ledger import query_by_run, verify_chain, stats, export_since, replay_run
def main():
if len(sys.argv) < 2:
print("usage: decision_ledger_cli.py <query|verify-chain|stats|export|replay> [args]", file=sys.stderr)
sys.exit(2)
cmd = sys.argv[1]
if cmd == "query" and len(sys.argv) >= 3:
print(json.dumps(query_by_run(sys.argv[2]), indent=2))
elif cmd == "verify-chain":
ok, broken, details = verify_chain()
print(f"chain_ok={ok} broken={broken} details={details}")
sys.exit(0 if ok else 1)
elif cmd == "stats":
print(json.dumps(stats(), indent=2))
elif cmd == "export" and len(sys.argv) >= 3:
fmt = sys.argv[3] if len(sys.argv) >= 4 else "json"
print(export_since(sys.argv[2], fmt=fmt))
elif cmd == "replay" and len(sys.argv) >= 3:
print(replay_run(sys.argv[2]))
else:
print(f"unknown command: {cmd}", file=sys.stderr)
sys.exit(2)
if __name__ == "__main__":
main()
-98
View File
@@ -1,98 +0,0 @@
"""Nova CloudEvents 1.0 envelope + platform.* semantic conventions (REQ-187).
Defines the standard event envelope for all Nova metrics events. Every
emitter (run_manifest, decision_ledger, confidence_signal, checkov_adapter,
hitl_gates, regression_verify) uses `make_event()` to produce a valid
CloudEvents 1.0 envelope. Events are appended to `metrics/events.jsonl`.
D-120: Nova-native minimal tech (no Kafka/OTel SDK JSONL + SQLite).
D-125: hybrid model existing file signals stay as files; the collector
reads them and emits normalized CloudEvents. New emitters emit directly.
"""
import datetime
import hashlib
import json
import os
import sys
import uuid
METRICS_DIR = os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))), "metrics")
EVENTS_LOG = os.path.join(METRICS_DIR, "events.jsonl")
def _iso8601_now():
return datetime.datetime.now(datetime.timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ")
def make_event(event_type, run_id, environment, data, contract_id="", source="nova.platform", subject="", actor_type="confidence-gate", actor_id="confidence_signal"):
"""Build a CloudEvents 1.0 envelope with Nova platform.* conventions.
Args:
event_type: e.g. "nova.run.completed", "nova.ai.decision.made"
run_id: the run identifier (e.g. "run-<epoch>")
environment: dev|qa|prod|dr
data: the event payload dict
contract_id: the contract UUID (optional)
source: the event source (default "nova.platform")
subject: the event subject (default "<contract_id>/<env>")
actor_type: the actor type (default "confidence-gate")
actor_id: the actor id (default "confidence_signal")
Returns:
A CloudEvents 1.0 envelope dict.
"""
if not subject:
subject = f"{contract_id}/{environment}" if contract_id else environment
return {
"specversion": "1.0",
"id": str(uuid.uuid4()),
"source": source,
"type": event_type,
"time": _iso8601_now(),
"subject": subject,
"datacontenttype": "application/json",
"platform": {
"tenant_id": "acdl",
"run_id": run_id,
"contract_id": contract_id,
"environment": environment,
"actor": {"type": actor_type, "id": actor_id},
"trace_id": run_id,
},
"data": data,
}
def append_event(event, events_log=None):
"""Append a CloudEvents envelope to the JSONL event log.
Creates the metrics/ directory if it doesn't exist.
"""
if events_log is None:
events_log = EVENTS_LOG
os.makedirs(os.path.dirname(events_log), exist_ok=True)
with open(events_log, "a", encoding="utf-8") as fh:
fh.write(json.dumps(event, sort_keys=True, separators=(",", ":")) + "\n")
def emit(event_type, run_id, environment, data, **kwargs):
"""Make an event + append it to the JSONL log. Convenience wrapper."""
event = make_event(event_type, run_id, environment, data, **kwargs)
append_event(event)
return event
if __name__ == "__main__":
if len(sys.argv) < 4:
print("usage: event_envelope.py <event_type> <run_id> <environment> [data.json]", file=sys.stderr)
sys.exit(2)
_type = sys.argv[1]
_run_id = sys.argv[2]
_env = sys.argv[3]
_data = {}
if len(sys.argv) >= 5 and os.path.isfile(sys.argv[4]):
with open(sys.argv[4]) as f:
_data = json.load(f)
ev = emit(_type, _run_id, _env, _data)
print(json.dumps(ev, indent=2))
-73
View File
@@ -1,73 +0,0 @@
"""Nova Infracost Post-Processor (REQ-187, D-120).
Runs Infracost on `terraform show -json plan.tfplan` (offline, reads plan
JSON, no live AWS). Emits nova.cost.estimated{delta_usd} events. Degrades
gracefully (omits the event, logs a warning) when Infracost CLI is absent
(assumption A6).
run_platform.sh invokes it after the plan stage.
"""
import json
import os
import shutil
import subprocess
import sys
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))))
from core.metrics.event_envelope import emit
def _is_infracost_available():
"""Check if the Infracost CLI is on PATH."""
return shutil.which("infracost") is not None
def estimate(plan_json_path, run_id, contract_id, environment):
"""Run Infracost on a terraform plan JSON. Returns the cost estimate dict.
Args:
plan_json_path: path to `terraform show -json plan.tfplan` output
run_id: the run identifier
contract_id: the contract UUID
environment: dev|qa|prod|dr
Returns:
{"delta_usd": float, "total_monthly_usd": float, "available": bool}
or {"available": False} if Infracost is not installed.
"""
if not _is_infracost_available():
sys.stderr.write("[infracost] CLI not found — cost.estimated event omitted (A6 degraded mode)\n")
return {"available": False, "delta_usd": 0.0, "total_monthly_usd": 0.0}
if not os.path.isfile(plan_json_path):
sys.stderr.write(f"[infracost] plan JSON not found: {plan_json_path}\n")
return {"available": False, "delta_usd": 0.0, "total_monthly_usd": 0.0}
try:
result = subprocess.run(
["infracost", "breakdown", "--path", plan_json_path, "--format", "json"],
capture_output=True, text=True, timeout=30,
)
if result.returncode != 0:
sys.stderr.write(f"[infracost] CLI failed: {result.stderr[:200]}\n")
return {"available": False, "delta_usd": 0.0, "total_monthly_usd": 0.0}
breakdown = json.loads(result.stdout)
delta = float(breakdown.get("diffTotalMonthlyCost", 0.0))
total = float(breakdown.get("totalMonthlyCost", 0.0))
estimate_data = {"available": True, "delta_usd": delta, "total_monthly_usd": total}
emit("nova.cost.estimated", run_id, environment, estimate_data, contract_id=contract_id)
return estimate_data
except Exception as exc:
sys.stderr.write(f"[infracost] error: {exc}\n")
return {"available": False, "delta_usd": 0.0, "total_monthly_usd": 0.0}
if __name__ == "__main__":
if len(sys.argv) < 5:
print("usage: infracost_adapter.py <plan_json_path> <run_id> <contract_id> <environment>", file=sys.stderr)
sys.exit(2)
est = estimate(sys.argv[1], sys.argv[2], sys.argv[3], sys.argv[4])
print(json.dumps(est, indent=2))
-198
View File
@@ -1,198 +0,0 @@
"""Nova PowerBI Export (REQ-190, P3).
Emits CSV/JSON views to metrics/powerbi/ from the SQLite cold store.
Fact + dimension tables + 8 empty placeholder views for deferred metrics
(with documented schemas ready to fill when their blocking decisions lift).
D-120: Nova-native (CSV/JSON files, no live connector)
D-129: PowerBI ingests via the folder connector
D-128: metrics/ at repo root
"""
import csv
import datetime
import json
import os
import sqlite3
import sys
_METRICS_DIR = os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))), "metrics")
_STORE_PATH = os.path.join(_METRICS_DIR, "nova_metrics.db")
_EXPORT_DIR = os.path.join(_METRICS_DIR, "powerbi")
FACT_VIEWS = [
"fact_run",
"fact_capability",
"fact_policy_check",
"fact_confidence",
"fact_test",
"fact_decision",
"fact_cost_estimate",
"fact_lifecycle",
]
DIM_VIEWS = [
"dim_capability",
"dim_milestone",
]
PLACEHOLDER_VIEWS = {
"placeholder_live_infra_health": {
"columns": ["timestamp", "resource_id", "resource_type", "running_count", "healthy", "downtime_seconds"],
"blocking_decision": "D-096",
"description": "Live infrastructure health (ECS running count, ALB 5xx, RPS). Blocked: live AWS torn down.",
},
"placeholder_live_outbox_rate": {
"columns": ["timestamp", "contract_id", "write_latency_ms", "append_count"],
"blocking_decision": "D-096",
"description": "Live outbox write rate / ledger append latency. Blocked: DynamoDB outbox table absent.",
},
"placeholder_tamper_evident_checkpoints": {
"columns": ["timestamp", "checkpoint_id", "jws_signed", "object_lock_enabled"],
"blocking_decision": "D-083",
"description": "Tamper-evident ledger checkpoints / JWS signature rate. Blocked: S3 Object Lock + JWS deferred.",
},
"placeholder_onboarding_funnel": {
"columns": ["timestamp", "consumer_repo", "requested_environment", "status", "granted_at"],
"blocking_decision": "D-113/D-114/D-119",
"description": "Onboarding funnel: requested → granted conversion. Blocked: no auto-grant event.",
},
"placeholder_drift_detection": {
"columns": ["timestamp", "workspace_id", "drift_count", "auto_reverted", "detection_cycle"],
"blocking_decision": "D-096 + no scheduler",
"description": "Drift detection (scheduled terraform plan -detailed-exitcode). Blocked: live AWS + scheduler.",
},
"placeholder_live_cur_reconciliation": {
"columns": ["timestamp", "resource_address", "actual_usd", "baseline_usd", "saved_usd"],
"blocking_decision": "D-096",
"description": "Live cost CUR reconciliation. Blocked: live AWS billing. Infracost pre-apply estimates are in fact_cost_estimate.",
},
"placeholder_sla_downtime": {
"columns": ["timestamp", "service", "uptime_pct", "downtime_minutes", "slo_target"],
"blocking_decision": "D-096",
"description": "SLA / unplanned downtime. Blocked: needs live service uptime monitoring.",
},
"placeholder_predictive_reactive": {
"columns": ["timestamp", "action_id", "label", "trigger", "count"],
"blocking_decision": "future emitter",
"description": "Predictive vs Reactive ratio. Blocked: requires ML anomaly-forecasting service.",
},
}
def _iso8601_now():
return datetime.datetime.now(datetime.timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ")
def _export_table_csv(conn, table_name, export_dir):
"""Export a SQLite table to a CSV file."""
rows = conn.execute(f"SELECT * FROM {table_name}").fetchall()
if not rows:
return 0
columns = [desc[0] for desc in conn.execute(f"SELECT * FROM {table_name} LIMIT 0").description]
csv_path = os.path.join(export_dir, f"{table_name}.csv")
with open(csv_path, "w", newline="", encoding="utf-8") as f:
writer = csv.writer(f)
writer.writerow(columns)
writer.writerows(rows)
return len(rows)
def _export_table_json(conn, table_name, export_dir):
"""Export a SQLite table to a JSON file."""
rows = conn.execute(f"SELECT * FROM {table_name}").fetchall()
if not rows:
return 0
columns = [desc[0] for desc in conn.execute(f"SELECT * FROM {table_name} LIMIT 0").description]
records = [dict(zip(columns, row)) for row in rows]
json_path = os.path.join(export_dir, f"{table_name}.json")
with open(json_path, "w", encoding="utf-8") as f:
json.dump(records, f, indent=2, default=str)
return len(rows)
def _export_placeholder_csv(view_name, schema, export_dir):
"""Export a placeholder CSV with headers only (no data rows)."""
csv_path = os.path.join(export_dir, f"{view_name}.csv")
with open(csv_path, "w", newline="", encoding="utf-8") as f:
writer = csv.writer(f)
writer.writerow(schema["columns"])
return 0
def _export_placeholder_json(view_name, schema, export_dir):
"""Export a placeholder JSON with schema metadata (no data rows)."""
json_path = os.path.join(export_dir, f"{view_name}.json")
with open(json_path, "w", encoding="utf-8") as f:
json.dump({"schema": schema, "data": []}, f, indent=2)
return 0
def export_all(store_path=None, export_dir=None, fmt="both"):
"""Export all fact/dim tables + placeholder views to CSV and/or JSON.
Args:
store_path: path to the SQLite cold store
export_dir: directory for exported files
fmt: "csv", "json", or "both"
Returns:
Summary dict with export counts.
"""
if store_path is None:
store_path = _STORE_PATH
if export_dir is None:
export_dir = _EXPORT_DIR
os.makedirs(export_dir, exist_ok=True)
summary = {"exported_at": _iso8601_now(), "fact_tables": {}, "dim_tables": {}, "placeholder_views": {}}
if not os.path.isfile(store_path):
summary["error"] = f"SQLite store not found: {store_path}"
for view_name, schema in PLACEHOLDER_VIEWS.items():
if fmt in ("csv", "both"):
_export_placeholder_csv(view_name, schema, export_dir)
if fmt in ("json", "both"):
_export_placeholder_json(view_name, schema, export_dir)
summary["placeholder_views"][view_name] = 0
return summary
conn = sqlite3.connect(store_path)
for table in FACT_VIEWS:
count = 0
try:
if fmt in ("csv", "both"):
count = _export_table_csv(conn, table, export_dir)
if fmt in ("json", "both"):
count = _export_table_json(conn, table, export_dir)
except sqlite3.OperationalError:
count = 0
summary["fact_tables"][table] = count
for table in DIM_VIEWS:
count = 0
try:
if fmt in ("csv", "both"):
count = _export_table_csv(conn, table, export_dir)
if fmt in ("json", "both"):
count = _export_table_json(conn, table, export_dir)
except sqlite3.OperationalError:
count = 0
summary["dim_tables"][table] = count
conn.close()
for view_name, schema in PLACEHOLDER_VIEWS.items():
if fmt in ("csv", "both"):
_export_placeholder_csv(view_name, schema, export_dir)
if fmt in ("json", "both"):
_export_placeholder_json(view_name, schema, export_dir)
summary["placeholder_views"][view_name] = 0
return summary
if __name__ == "__main__":
result = export_all()
print(json.dumps(result, indent=2))
-137
View File
@@ -1,137 +0,0 @@
"""Nova Per-Run Manifest Writer (REQ-187).
Emits nova.run.started, nova.run.completed, nova.run.failed events with
(run_id, contractId, env, stages x durations, exit, confidence, HITL block
count). Writes metrics/runs/<run_id>.json. scripts/run_platform.sh invokes
the writer at run start + run end.
D-120: Nova-native (JSONL events + JSON manifest file, no Kafka).
D-128: metrics/ at repo root.
"""
import datetime
import json
import os
import sys
import time
import uuid
_METRICS_DIR = os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))), "metrics")
_RUNS_DIR = os.path.join(_METRICS_DIR, "runs")
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))))
from core.metrics.event_envelope import emit, make_event, append_event
def _iso8601_now():
return datetime.datetime.now(datetime.timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ")
def _run_id():
return f"run-{int(time.time())}-{uuid.uuid4().hex[:8]}"
def start_run(contract_id, environment, stages=None):
"""Emit nova.run.started + return the run_id."""
run_id = _run_id()
data = {
"contract_id": contract_id,
"environment": environment,
"started_at": _iso8601_now(),
"stages": stages or [],
}
emit("nova.run.started", run_id, environment, data, contract_id=contract_id)
return run_id
def complete_run(run_id, contract_id, environment, stages, exit_code, confidence=None, hitl=None, policy=None, cost_estimate_usd=None, decision_id=None):
"""Emit nova.run.completed + write the per-run manifest JSON.
Args:
run_id: the run identifier from start_run()
contract_id: the contract UUID
environment: dev|qa|prod|dr
stages: list of {name, duration_ms, exit_code, error?}
exit_code: the overall run exit code
confidence: optional {score, band, perInput}
hitl: optional {gate, result, block}
policy: optional {passed, failed, skipped}
cost_estimate_usd: optional float
decision_id: optional string (links to the Decision Ledger)
"""
started_at = stages[0].get("started_at", _iso8601_now()) if stages else _iso8601_now()
completed_at = _iso8601_now()
outcome = "succeeded" if exit_code == 0 else "failed"
manifest = {
"run_id": run_id,
"contract_id": contract_id,
"environment": environment,
"started_at": started_at,
"completed_at": completed_at,
"exit_code": exit_code,
"stages": stages,
"outcome": outcome,
}
if confidence:
manifest["confidence"] = confidence
if hitl:
manifest["hitl"] = hitl
if policy:
manifest["policy"] = policy
if cost_estimate_usd is not None:
manifest["cost_estimate_usd"] = cost_estimate_usd
if decision_id:
manifest["decision_id"] = decision_id
os.makedirs(_RUNS_DIR, exist_ok=True)
manifest_path = os.path.join(_RUNS_DIR, f"{run_id}.json")
with open(manifest_path, "w", encoding="utf-8") as fh:
json.dump(manifest, fh, indent=2, sort_keys=True)
event_type = "nova.run.completed" if exit_code == 0 else "nova.run.failed"
emit(event_type, run_id, environment, manifest, contract_id=contract_id)
return manifest
def persist_run_artifacts(run_id, work_dir):
"""Copy ephemeral $WORK/*.json to metrics/runs/<run_id>/ as durable artifacts.
Args:
run_id: the run identifier
work_dir: the $WORK directory (e.g. /tmp/nova_platform_run)
"""
if not work_dir or not os.path.isdir(work_dir):
return []
dest = os.path.join(_RUNS_DIR, run_id)
os.makedirs(dest, exist_ok=True)
copied = []
for fname in ("pcr.json", "signal.json", "event.json", "outbox_item.json", "stack.json", "checkov.json"):
src = os.path.join(work_dir, fname)
if os.path.isfile(src):
import shutil
shutil.copy2(src, os.path.join(dest, fname))
copied.append(fname)
return copied
if __name__ == "__main__":
if len(sys.argv) < 4:
print("usage: run_manifest.py <start|complete|persist> <contract_id> <environment> [run_id] [work_dir]", file=sys.stderr)
sys.exit(2)
action = sys.argv[1]
cid = sys.argv[2]
env = sys.argv[3]
if action == "start":
rid = start_run(cid, env)
print(rid)
elif action == "complete":
rid = sys.argv[4] if len(sys.argv) >= 5 else _run_id()
m = complete_run(rid, cid, env, [], 0)
print(json.dumps(m, indent=2))
elif action == "persist":
rid = sys.argv[4] if len(sys.argv) >= 5 else ""
wd = sys.argv[5] if len(sys.argv) >= 6 else ""
copied = persist_run_artifacts(rid, wd)
print(json.dumps({"copied": copied}))
-167
View File
@@ -1,167 +0,0 @@
"""Nova Trust Snapshot Report (REQ-211, P4).
Emits metrics/TRUST_SNAPSHOT.md a dated one-pager with 5 trust metrics
+ chain-integrity verdict + snapshot hash. Runnable on demand or at
milestone complete.
Reads from: metrics/decision_ledger.db, metrics/nova_metrics.db,
.ciagent/REGRESSION_REPORT.json.
"""
import datetime
import hashlib
import json
import os
import sqlite3
import sys
_METRICS_DIR = os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))), "metrics")
_LEDGER_DB = os.path.join(_METRICS_DIR, "decision_ledger.db")
_STORE_DB = os.path.join(_METRICS_DIR, "nova_metrics.db")
_REGRESSION_REPORT = os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))), ".ciagent", "REGRESSION_REPORT.json")
_SNAPSHOT_PATH = os.path.join(_METRICS_DIR, "TRUST_SNAPSHOT.md")
def _iso8601_now():
return datetime.datetime.now(datetime.timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ")
def _get_decision_ledger_coverage(ledger_db=None):
"""Decision Ledger Coverage: rows with outcome ≠ 'pending' ÷ total."""
if ledger_db is None:
ledger_db = _LEDGER_DB
if not os.path.isfile(ledger_db):
return 0.0, 0, 0
from core.metrics.decision_ledger import stats, verify_chain
s = stats(ledger_db)
total = s.get("total", 0)
if total == 0:
return 0.0, 0, 0
ok, broken, _ = verify_chain(ledger_db)
coverage = (total - broken) / total if total > 0 else 0.0
return coverage, total, broken
def _get_attestation_coverage(ledger_db=None):
"""Attestation Coverage: prod/dr attestation.recorded events ÷ total prod/dr runs."""
if ledger_db is None:
ledger_db = _LEDGER_DB
if not os.path.isfile(ledger_db):
return 0.0, 0, 0
conn = sqlite3.connect(ledger_db)
attestations = conn.execute(
"SELECT COUNT(*) FROM decision_ledger WHERE event_type = 'nova.attestation.recorded'"
).fetchone()[0]
conn.close()
return 1.0 if attestations > 0 else 0.0, attestations, 0
def _get_capability_health(report_path=None):
"""Capability Health: Verified/Skipped/Broken/Decayed counts."""
if report_path is None:
report_path = _REGRESSION_REPORT
if not os.path.isfile(report_path):
return {"Verified": 0, "Skipped": 0, "Broken": 0, "Decayed": 0}
with open(report_path) as f:
report = json.load(f)
return report.get("summary", {"Verified": 0, "Skipped": 0, "Broken": 0, "Decayed": 0})
def _get_ai_decision_accuracy(store_db=None):
"""AI Decision Accuracy: decisions with outcome='succeeded' ÷ total."""
if store_db is None:
store_db = _STORE_DB
if not os.path.isfile(store_db):
return 0.0, 0, 0
conn = sqlite3.connect(store_db)
try:
total = conn.execute("SELECT COUNT(*) FROM fact_decision").fetchone()[0]
succeeded = conn.execute("SELECT COUNT(*) FROM fact_decision WHERE outcome = 'succeeded'").fetchone()[0]
except sqlite3.OperationalError:
conn.close()
return 0.0, 0, 0
conn.close()
accuracy = succeeded / total if total > 0 else 0.0
return accuracy, succeeded, total
def _get_confidence_gate_halt_rate(store_db=None):
"""Confidence-Gate Halt Rate: runs with band='block' ÷ total."""
if store_db is None:
store_db = _STORE_DB
if not os.path.isfile(store_db):
return 0.0, 0, 0
conn = sqlite3.connect(store_db)
try:
total = conn.execute("SELECT COUNT(*) FROM fact_confidence").fetchone()[0]
halted = conn.execute("SELECT COUNT(*) FROM fact_confidence WHERE band = 'block'").fetchone()[0]
except sqlite3.OperationalError:
conn.close()
return 0.0, 0, 0
conn.close()
rate = halted / total if total > 0 else 0.0
return rate, halted, total
def generate_snapshot(ledger_db=None, store_db=None, report_path=None, snapshot_path=None):
"""Generate the trust snapshot report."""
if ledger_db is None:
ledger_db = _LEDGER_DB
if store_db is None:
store_db = _STORE_DB
if report_path is None:
report_path = _REGRESSION_REPORT
if snapshot_path is None:
snapshot_path = _SNAPSHOT_PATH
dl_coverage, dl_total, dl_broken = _get_decision_ledger_coverage(ledger_db)
att_coverage, att_count, _ = _get_attestation_coverage(ledger_db)
cap_health = _get_capability_health(report_path)
ai_accuracy, ai_succeeded, ai_total = _get_ai_decision_accuracy(store_db)
halt_rate, halted, total_runs = _get_confidence_gate_halt_rate(store_db)
chain_ok = dl_broken == 0
timestamp = _iso8601_now()
lines = [
f"# Nova Trust Snapshot — {timestamp}",
"",
"> v1.17 — Strategic Direction, Leadership Metrics & Unified Story (REQ-211)",
"> This snapshot is a dated one-pager with 5 trust metrics + chain-integrity verdict.",
"",
"## Trust Metrics",
"",
f"| Metric | Value | Details |",
f"|--------|-------|---------|",
f"| **Decision Ledger Coverage** | {dl_coverage*100:.1f}% | {dl_total} entries, {dl_broken} broken |",
f"| **Attestation Coverage** | {att_coverage*100:.1f}% | {att_count} attestation events |",
f"| **Capability Health** | {cap_health.get('Verified',0)}V / {cap_health.get('Skipped',0)}S / {cap_health.get('Broken',0)}B / {cap_health.get('Decayed',0)}D | from REGRESSION_REPORT.json |",
f"| **AI Decision Accuracy** | {ai_accuracy*100:.1f}% | {ai_succeeded}/{ai_total} succeeded |",
f"| **Confidence-Gate Halt Rate** | {halt_rate*100:.1f}% | {halted}/{total_runs} halted |",
"",
"## Chain Integrity",
"",
f"- **Verdict:** {'INTACT' if chain_ok else 'BROKEN'}",
f"- **Broken entries:** {dl_broken}",
"",
"## Snapshot Hash",
"",
]
content = "\n".join(lines)
snapshot_hash = hashlib.sha256(content.encode("utf-8")).hexdigest()[:16]
lines.append(f"`{snapshot_hash}`")
content = "\n".join(lines)
os.makedirs(os.path.dirname(snapshot_path), exist_ok=True)
with open(snapshot_path, "w", encoding="utf-8") as f:
f.write(content)
return {"snapshot_path": snapshot_path, "hash": snapshot_hash, "chain_ok": chain_ok,
"dl_coverage": dl_coverage, "att_coverage": att_coverage,
"cap_health": cap_health, "ai_accuracy": ai_accuracy, "halt_rate": halt_rate}
if __name__ == "__main__":
result = generate_snapshot()
print(json.dumps(result, indent=2))
-131
View File
@@ -1,131 +0,0 @@
#!/usr/bin/env python3
"""Nova Onboarding — auto-generate an environment binding file (P19, REQ-183).
Given a consumer onboarding request (validated against
schemas/onboarding.schema.json), generate a ``<env>.json`` environment
binding file from the dev template, filling in the consumer's ownerId +
billingTag. The generated file is a starting point for the platform team
(or a future automation) to bind to a real AWS account.
This is the "request path" half of the no-humans onboarding flow (D-113).
Real AWS account/network/state provisioning is a future feature milestone;
this module removes the human handoff from the *request* step by
generating the binding file + emitting a git patch / PR-branch instruction.
Usage:
python3 core/onboarding.py <request.json> [--out <env.json>]
python3 core/onboarding.py --request '{"consumerRepo":"acdl/c","requestedEnvironment":"qa","ownerId":"team-a","billingTag":"cc-a"}'
"""
from __future__ import annotations
import argparse
import json
import os
import sys
from pathlib import Path
from typing import Any, Dict
def _repo_root() -> Path:
return Path(__file__).resolve().parent.parent
def _load_template_env(template_env: str = "dev", root: Path | None = None) -> Dict[str, Any]:
"""Load the template environment JSON (defaults to dev.json)."""
root = root or _repo_root()
env_path = root / "core" / "environments" / f"{template_env}.json"
if not env_path.is_file():
raise FileNotFoundError(f"template environment {env_path} not found")
return json.loads(env_path.read_text())
def generate_env_file(
request: Dict[str, Any],
template_env: str = "dev",
root: Path | None = None,
) -> Dict[str, Any]:
"""Generate an environment binding dict from a consumer onboarding request.
The generated dict is a copy of the template env with:
- ``name`` the requested environment
- ``description`` notes the consumer + owner
- ``account_id`` placeholder (000000000000) for the platform team
to fill with the real account
- ``ownerId`` + ``billingTag`` from the request (for ABAC + cost)
The dict validates against schemas/environment.schema.json.
Returns the generated env dict.
"""
template = _load_template_env(template_env, root)
requested = request["requestedEnvironment"]
owner = request["ownerId"]
billing = request["billingTag"]
consumer = request["consumerRepo"]
env = dict(template)
env["name"] = requested
env["description"] = (
f"Auto-generated binding for {consumer} (owner={owner}, "
f"billing={billing}). Replace account_id with the real "
f"{requested} account before deploying."
)
env["account_id"] = "000000000000" # placeholder — platform team fills
env["ownerId"] = owner
env["billingTag"] = billing
return env
def _onboarding_request_message(env_name: str) -> str:
"""P19 (REQ-183): the rebranded Nova onboarding message — self-service
request path, no longer routes to 'contact the platform team'."""
return (
"=== Nova Environment Onboarding ===\n"
f"No environment named '{env_name}' is bound to this repository.\n\n"
"Nova environments are platform-managed. The platform provisions on\n"
"your behalf:\n"
" - an AWS account (or a scoped partition of one)\n"
" - a network (VPC + subnets)\n"
" - a state backend (an S3 bucket + DynamoDB lock table)\n"
" - an IAM role surfaced to your repo via attribute-based\n"
" authorization (ABAC)\n\n"
"You do not provide an AWS account, VPC, subnet, or state bucket.\n\n"
"To request an environment (self-service):\n"
" 1. Submit an onboarding request to the Nova Lambda\n"
" (action: onboard_consumer) with your repo name + the\n"
" environment name you need (e.g. 'dev').\n"
" 2. The platform generates an environment binding + opens a PR.\n"
" 3. The platform provisions the account/network/state/role and\n"
" grants the ABAC role. Your next pipeline run proceeds.\n\n"
"Run: python3 core/onboarding.py --request '{...}' to generate a\n"
"binding file locally, or POST to the Lambda onboard_consumer action.\n"
"===================================\n"
)
def main(argv: list[str] | None = None) -> int:
parser = argparse.ArgumentParser(description="Generate an env binding from an onboarding request.")
group = parser.add_mutually_exclusive_group(required=True)
group.add_argument("request_file", nargs="?", help="path to a request JSON file")
group.add_argument("--request", help="inline request JSON string")
parser.add_argument("--out", help="output path for the generated env JSON (default: stdout)")
parser.add_argument("--template-env", default="dev", help="template environment (default: dev)")
args = parser.parse_args(argv)
if args.request:
request = json.loads(args.request)
else:
request = json.loads(Path(args.request_file).read_text())
env = generate_env_file(request, template_env=args.template_env)
env_json = json.dumps(env, indent=2) + "\n"
if args.out:
Path(args.out).write_text(env_json)
print(f"wrote: {args.out}")
else:
print(env_json)
return 0
if __name__ == "__main__":
sys.exit(main())
-57
View File
@@ -566,59 +566,6 @@ def _check_cap_022_oidc_role() -> Tuple[Status, str]:
return _check_lifecycle_module_terraform("iam-role") return _check_lifecycle_module_terraform("iam-role")
def _check_cap_023_metrics_collector() -> Tuple[Status, str]:
"""CAP-023: metrics collector runs and emits the expected schema (v1.17).
Verifies that core/metrics/collector.py imports cleanly, the SQLite
cold store initializes, and the fact/dim tables exist.
"""
import importlib
try:
mod = importlib.import_module("core.metrics.collector")
mod._init_store()
import sqlite3, os
db_path = mod._STORE_PATH
if not os.path.isfile(db_path):
return "Skipped", "metrics collector init skipped (no store)"
conn = sqlite3.connect(db_path)
tables = [r[0] for r in conn.execute("SELECT name FROM sqlite_master WHERE type='table'").fetchall()]
conn.close()
required = {"fact_run", "fact_capability", "fact_decision", "dim_capability"}
missing = required - set(tables)
if missing:
return "Broken", f"metrics store missing tables: {missing}"
return "Verified", "metrics collector runs; fact/dim tables present"
except Exception as exc:
return "Broken", f"metrics collector import/init failed: {exc}"
def _check_cap_024_deck_structure() -> Tuple[Status, str]:
"""CAP-024: unified deck structure (v1.17 + v1.21 refinement).
Verifies the unified deck source of truth exists, has 18 main slides
(## Slide N) + 1 appendix, has the recap+ask closing, and per-slide
benefit callouts. v1.21 renamed the deck + restructured to a 4-beat arc.
"""
import os
deck_path = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))),
"docs", "presentations", "nova-autonomous-cloud-delivery.md")
if not os.path.isfile(deck_path):
return "Skipped", "unified deck not found"
with open(deck_path) as f:
content = f.read()
slide_count = content.count("## Slide ")
if slide_count < 18 or slide_count > 19:
return "Broken", f"deck has {slide_count} main slides (expected 18-19)"
has_recap = "Recap + Ask" in content
has_benefit = content.count("Benefit:") >= 10
if not (has_recap and has_benefit):
missing = []
if not has_recap: missing.append("recap+ask")
if not has_benefit: missing.append("per-slide benefit callouts")
return "Broken", f"deck missing: {missing}"
return "Verified", f"deck has {slide_count} slides, recap+ask present, per-slide benefits present"
# Registry: ordered, each entry is (capability_id, name, tier, check_fn). # Registry: ordered, each entry is (capability_id, name, tier, check_fn).
# Phase 52 seeds this with 10 local-tier checks; Phase 54 expands it to # Phase 52 seeds this with 10 local-tier checks; Phase 54 expands it to
# cover every v1.1->v1.8 advertised capability and adds the live-AWS tier # cover every v1.1->v1.8 advertised capability and adds the live-AWS tier
@@ -668,10 +615,6 @@ CAPABILITY_REGISTRY: List[Tuple[str, str, str, Callable[[], Tuple[Status, str]]]
_check_cap_021_uptime), _check_cap_021_uptime),
("CAP-022", "OIDC role (L1 iam-role lifecycle evidence)", "lifecycle-pipeline", ("CAP-022", "OIDC role (L1 iam-role lifecycle evidence)", "lifecycle-pipeline",
_check_cap_022_oidc_role), _check_cap_022_oidc_role),
("CAP-023", "metrics collector runs + emits expected schema", "local",
_check_cap_023_metrics_collector),
("CAP-024", "unified deck structure (slide count, x3, per-slide benefits)", "local",
_check_cap_024_deck_structure),
] ]
+1 -1
View File
@@ -1,6 +1,6 @@
"""Check that qaApprover != prodApprover for a contract (ARCHITECTURE.md """Check that qaApprover != prodApprover for a contract (ARCHITECTURE.md
§10.3, D-042). Reads `approver_qa` from the DynamoDB outbox for the §10.3, D-042). Reads `approver_qa` from the DynamoDB outbox for the
contractId, compares to the prod-dispatch the CI actor. contractId, compares to the prod-dispatch `gitea.actor` / `github.actor`.
Blocks on equality, emits `SEPARATION_OF_DUTIES_VIOLATION`, routes a halt Blocks on equality, emits `SEPARATION_OF_DUTIES_VIOLATION`, routes a halt
artifact to SRE on-call. artifact to SRE on-call.
-193
View File
@@ -1,193 +0,0 @@
"""core/submission_readiness.py — Nova submission-readiness validator (REQ-218).
Defines what is acceptable to start a superset gate ABOVE
contract.schema.json validity. Invoked as
``contract_ingestor.py --check-readiness`` (D-133). Returns a structured
ReadinessResult (pass/fail per check, with reason codes). On fail the
ingestor rejects with a citizen-developer-facing error (not a stack
trace). On pass proceeds to existing contract ingestion.
The validator calls contract.schema.json validation first (the shape),
then the readiness checks (the gate): tags, env mandatory, policy
preconditions, profile:agentic markers, appSource.
Reason codes:
MISSING_TAGS one or more required Nova tags are absent
ENV_MISSING_MANDATORY:<env>:<field> a per-env mandatory field is missing
AGENTIC_MISSING_INTENT profile=agentic but naturalLanguageIntent absent
MISSING_APP_SOURCE appSource (repo + ref) is missing
POLICY_PRECONDITION_MISSING a declared policy precondition is absent
"""
from __future__ import annotations
import json
import os
import sys
from dataclasses import dataclass, field
from typing import Any
_SCHEMA_DIR = os.path.join(
os.path.dirname(os.path.dirname(os.path.abspath(__file__))), "schemas"
)
REQUIRED_TAGS = [
"nova:owner",
"nova:contract",
"nova:environment",
"nova:cost-center",
"nova:ref",
]
ENV_MANDATORY: dict[str, list[str]] = {
"dev": [], # dev requires only the base contract shape (id+environment+infrastructure)
"qa": ["validation.e2eSuite", "validation.loadTest"],
"prod": ["runbook", "dashboard", "oncall"],
"dr": ["drDrillRef"],
}
AGENTIC_REQUIRED = ["naturalLanguageIntent", "confidenceAtSubmission", "agentTrace"]
@dataclass
class ReadinessResult:
"""Structured result of the submission-readiness gate."""
ready: bool
reason_codes: list[str] = field(default_factory=list)
contract_id: str | None = None
def to_dict(self) -> dict[str, Any]:
return {
"ready": self.ready,
"reason_codes": self.reason_codes,
"contractId": self.contract_id,
}
def __str__(self) -> str:
if self.ready:
return f"READY — contract {self.contract_id} passes submission-readiness gate"
codes = "; ".join(self.reason_codes) if self.reason_codes else "unknown"
return f"NOT READY — contract {self.contract_id}: {codes}"
def _validate_contract_schema(contract: dict[str, Any]) -> list[str]:
"""Validate the contract against contract.schema.json (the shape).
Returns a list of reason codes (empty if valid). Falls back to no-op
if jsonschema or the schema file is unavailable (the contract is
validated upstream by run_platform.sh in the normal path).
"""
codes: list[str] = []
try:
import jsonschema
schema_path = os.path.join(_SCHEMA_DIR, "contract.schema.json")
with open(schema_path) as f:
schema = json.load(f)
jsonschema.validate(instance=contract, schema=schema)
except (OSError, ImportError):
pass
except jsonschema.ValidationError as e:
codes.append(f"CONTRACT_SCHEMA_INVALID:{e.message}")
return codes
def _get_nested(data: dict[str, Any], dotted_key: str) -> Any:
parts = dotted_key.split(".")
val: Any = data
for p in parts:
if not isinstance(val, dict) or p not in val:
return None
val = val[p]
return val
def check_readiness(submission: dict[str, Any]) -> ReadinessResult:
"""Run the full submission-readiness gate.
1. Validate the contract shape (contract.schema.json).
2. Validate the readiness schema (submission-readiness.schema.json).
3. Run the semantic readiness checks (tags, env mandatory, agentic, appSource, policy).
Returns a ReadinessResult. Never raises all failures are reason codes.
"""
contract_id = submission.get("contractId") or submission.get("id", "unknown")
codes: list[str] = []
# Step 1: contract shape validation
contract_shape = {k: v for k, v in submission.items() if k in ("id", "name", "environment", "infrastructure")}
if contract_shape:
codes.extend(_validate_contract_schema(contract_shape))
# Step 2: readiness schema validation
try:
import jsonschema
schema_path = os.path.join(_SCHEMA_DIR, "submission-readiness.schema.json")
with open(schema_path) as f:
readiness_schema = json.load(f)
jsonschema.validate(instance=submission, schema=readiness_schema)
except (OSError, ImportError):
pass
except jsonschema.ValidationError as e:
codes.append(f"READINESS_SCHEMA_INVALID:{e.message}")
# Step 3: semantic checks (reason codes for citizen-developer-facing errors)
# 3a: tags
tags = submission.get("tags", {})
missing_tags = [t for t in REQUIRED_TAGS if t not in tags or not tags[t]]
if missing_tags:
codes.append(f"MISSING_TAGS:{','.join(missing_tags)}")
# 3b: env mandatory (W3.E per-env table)
env = submission.get("environment")
if env and env in ENV_MANDATORY:
for field_key in ENV_MANDATORY[env]:
val = _get_nested(submission, field_key)
if val is None:
codes.append(f"ENV_MISSING_MANDATORY:{env}:{field_key}")
# 3c: agentic profile markers
if submission.get("profile") == "agentic":
for marker in AGENTIC_REQUIRED:
if not submission.get(marker):
codes.append(f"AGENTIC_MISSING_INTENT:{marker}")
# 3d: appSource
app_source = submission.get("appSource")
if not app_source or not app_source.get("repo") or not app_source.get("ref"):
codes.append("MISSING_APP_SOURCE")
# 3e: policy preconditions (warn if declared but not enforced this milestone)
policy = submission.get("policyPreconditions", {})
if not policy:
codes.append("POLICY_PRECONDITION_MISSING")
ready = len(codes) == 0
return ReadinessResult(ready=ready, reason_codes=codes, contract_id=contract_id)
def cli_main(argv: list[str]) -> int:
"""CLI entry: python3 -m core.submission_readiness <contract.json>
Also invoked via contract_ingestor.py --check-readiness (D-133).
Prints the ReadinessResult to stdout; exits 0 if ready, 1 if not.
"""
if len(argv) < 2:
print("Usage: submission_readiness <contract.json>", file=sys.stderr)
return 2
path = argv[1]
try:
with open(path) as f:
submission = json.load(f)
except (OSError, json.JSONDecodeError) as e:
print(f"ERROR: cannot read {path}: {e}", file=sys.stderr)
return 2
result = check_readiness(submission)
print(result)
print(json.dumps(result.to_dict(), indent=2))
return 0 if result.ready else 1
if __name__ == "__main__":
sys.exit(cli_main(sys.argv))
-177
View File
@@ -1,177 +0,0 @@
# Nova Metrics Catalog
This is the canonical catalog of every executive KPI in Nova's
leadership metrics layer. Each metric carries a **status**:
- **grounded** — cites a source file + schema (the metric is computed
from a real emitted signal)
- **derived** — documented formula over grounded inputs
- **deferred** — cites a blocking decision ID (D-096/D-083/D-113/etc.);
ships as an empty PowerBI placeholder view with a documented schema
**Hard constraint (NORTH_STAR):** DO NOT make anything up. No fabricated
numbers. Every metric either has a real source or is explicitly deferred.
---
## Zero-Touch Efficiency & AI Autonomy (REQ-191)
### Touchless Resolution Rate
- **Target:** ≥ 99% across production estates (Post-Pilot)
- **Status:** partial (pipeline grounded; denominator = 0 today)
- **Formula:** runs completing without *operational* HITL block ÷ total runs
(attestation gates excluded — they're designed controls, not escalations)
- **Source:** `metrics/nova_metrics.db` `fact_run` (hitl_block column)
- **Definition-of-success:** `docs/metrics/touchless_resolution_rate.md`
### Human Escalation Frequency
- **Target:** < 0.1% of platform actions (Post-Pilot)
- **Status:** partial (pipeline grounded; denominator = 0 today)
- **Formula:** operational HITL blocks ÷ total runs (attestation sign-offs
excluded)
- **Source:** `metrics/nova_metrics.db` `fact_run` (hitl_block column)
- **Definition-of-success:** `docs/metrics/human_escalation_frequency.md`
### AI Decision Accuracy
- **Target:** ≥ 99.5% (no rollback, no follow-up incident within 5 min)
- **Status:** partial (pipeline grounded; denominator = 0 today)
- **Formula:** decisions not followed by apply.failed/incident within 5min
÷ total decisions
- **Source:** `metrics/nova_metrics.db` `fact_decision` (outcome column)
- **Definition-of-success:** `docs/metrics/ai_decision_accuracy.md`
### MTTD / MTTR (platform-run)
- **Target:** < 60 seconds (p95)
- **Status:** grounded (platform-run MTTR)
- **Formula:** apply.failed.time → successful retry.time
- **Source:** `metrics/nova_metrics.db` `fact_run` (started_at, completed_at)
- **Note:** infra-incident MTTR deferred (no incident detection system)
- **Definition-of-success:** `docs/metrics/mttr.md`
### Confidence-Gate Halt Rate (REQ-212)
- **Target:** not a committed target (operational signal)
- **Status:** grounded
- **Formula:** runs where confidence band = halt ÷ total runs
- **Source:** `metrics/nova_metrics.db` `fact_confidence` (band column)
- **Definition-of-success:** `docs/metrics/confidence_gate_halt_rate.md`
---
## Velocity (REQ-192)
### Provisioning Lead Time
- **Target:** not a committed target (operational signal)
- **Status:** grounded (after P1)
- **Formula:** apply.completed.time intent.received.time
- **Source:** `metrics/nova_metrics.db` `fact_run` (started_at, completed_at)
- **Definition-of-success:** `docs/metrics/provisioning_lead_time.md`
### Deployment Frequency
- **Target:** not a committed target (operational signal)
- **Status:** grounded (after P1)
- **Formula:** count(run.completed) per day
- **Source:** `metrics/nova_metrics.db` `fact_run`
- **Definition-of-success:** `docs/metrics/deployment_frequency.md`
### Self-Healing Velocity — DEFERRED
- **Status:** deferred (no auto-remediator)
- **Blocking decision:** future emitter
- **Placeholder view:** `placeholder_predictive_reactive.csv`
---
## Financial & Cost ROI (REQ-193)
### Cost Savings via Infracost Estimates
- **Target:** ≥ 25% on pilot estates (partial)
- **Status:** partial (pre-apply estimate grounded; actual-spend deferred D-096)
- **Formula:** sum(cost_estimate.delta_usd) where delta < 0
- **Source:** `metrics/nova_metrics.db` `fact_cost_estimate`
- **Definition-of-success:** `docs/metrics/cost_savings.md`
### FTE Hours Saved (Toil Reallocation Value)
- **Target:** ≥ 70% of pre-Nova FTE allocation (derived)
- **Status:** derived
- **Formula:** run count × manual baseline minutes × blended rate
- **Source:** `metrics/nova_metrics.db` `fact_run` (count) + manual baseline
- **Note:** computed on N internal runs today; production-denominator
activates post-pilot
- **Definition-of-success:** `docs/metrics/fte_hours_saved.md`
### Platform ROI
- **Target:** ≥ 250% measured annually (derived)
- **Status:** derived
- **Formula:** (FTE hours saved × blended rate + cloud savings + avoided
downtime) ÷ platform op cost
- **Source:** derived from fact_run + fact_cost_estimate + manual baseline
- **Note:** computed on N internal runs today; production-denominator
activates post-pilot
- **Definition-of-success:** `docs/metrics/platform_roi.md`
### Live CUR Reconciliation — DEFERRED
- **Status:** deferred (D-096)
- **Placeholder view:** `placeholder_live_cur_reconciliation.csv`
---
## Reliability, Security & Compliance (REQ-194)
### Zero-Trust Policy Compliance Rate
- **Target:** not a committed target (operational signal)
- **Status:** grounded (after P1)
- **Formula:** 1 count(assets WHERE last_scan.status ≠ pass) ÷ count(assets)
- **Source:** `metrics/nova_metrics.db` `fact_policy_check`
- **Definition-of-success:** `docs/metrics/policy_compliance_rate.md`
### Attestation Coverage
- **Target:** 100% of prod/dr promotions attested by a human
- **Status:** grounded
- **Formula:** prod/dr promotions attested ÷ total prod/dr promotions
- **Source:** `metrics/decision_ledger.db` (attestation.recorded events) +
`hitl_gates.py` + outbox `approver_*` attributes
- **Definition-of-success:** `docs/metrics/attestation_coverage.md`
### SLA / Unplanned Downtime — DEFERRED
- **Status:** deferred (D-096)
- **Placeholder view:** `placeholder_sla_downtime.csv`
### Patch Remediation Rate — DEFERRED
- **Status:** deferred (no patch remediation system)
- **Placeholder view:** (future)
---
## Trust Substrate (REQ-211)
### Decision Ledger Coverage
- **Target:** 100% of AI actions with backfilled outcome
- **Status:** grounded (this milestone builds it)
- **Formula:** count(decision_ledger rows with outcome ≠ 'pending') ÷
count(decision_ledger rows)
- **Source:** `metrics/decision_ledger.db` + `core/metrics/decision_ledger.py`
- **Definition-of-success:** `docs/metrics/decision_ledger_coverage.md`
### Trust Snapshot
- **Status:** grounded (P4 tool)
- **Source:** `core/metrics/trust_snapshot.py``metrics/TRUST_SNAPSHOT.md`
- **Contents:** Decision Ledger Coverage, Attestation Coverage, Capability
Health, AI Decision Accuracy, Confidence-Gate Halt Rate, chain-integrity
verdict, snapshot hash
---
## Deferred Metrics (8 placeholder views)
| Metric | Blocking Decision | Placeholder View |
|--------|-----------------|------------------|
| Live Infrastructure Health | D-096 | `placeholder_live_infra_health.csv` |
| Live Outbox Write Rate | D-096 | `placeholder_live_outbox_rate.csv` |
| Tamper-Evident Ledger Checkpoints | D-083 | `placeholder_tamper_evident_checkpoints.csv` |
| Onboarding Funnel (granted) | D-113/D-114/D-119 | `placeholder_onboarding_funnel.csv` |
| Drift Auto-Reversal Rate | D-096 + no scheduler | `placeholder_drift_detection.csv` |
| Live CUR Reconciliation | D-096 | `placeholder_live_cur_reconciliation.csv` |
| SLA / Unplanned Downtime | D-096 | `placeholder_sla_downtime.csv` |
| Predictive vs Reactive Ratio | future emitter | `placeholder_predictive_reactive.csv` |
See `docs/METRICS_DEFERRED_ROADMAP.md` for the activation path for each.
-68
View File
@@ -1,68 +0,0 @@
# Nova Deferred Metrics Activation Roadmap
This document lists all 8 deferred metrics + the onboarding-funnel
"granted" half, with their blocking decisions, unblock requirements,
and candidate future milestones. It also includes the hot-path activation
plan (post-D-096) and the re-evaluation triggers.
## Deferred metrics
| # | Metric | Blocking Decision | What's Needed to Unblock | Candidate Milestone |
|---|--------|-------------------|-------------------------|---------------------|
| 1 | Live Infrastructure Health (ECS, ALB, RPS) | D-096 | Re-provision live AWS; deploy microservice/static-assets stacks; emit live health metrics | v1.18+ (live AWS re-provisioning) |
| 2 | Live Outbox Write Rate / Ledger Append Latency | D-096 | Re-provision DynamoDB outbox table; emit write-latency metrics | v1.18+ |
| 3 | Tamper-Evident Ledger Checkpoints / JWS Signature Rate | D-083 | Build S3 Object Lock + JWS signing + async worker + DLQ + daily checkpoints | v1.19+ (audit ledger build-out) |
| 4 | Onboarding Funnel (requested → granted) | D-113/D-114/D-119 | Implement auto-grant: Lambda provisions the cross-account role + ABAC tag + environment binding | v1.18+ (onboarding auto-grant) |
| 5 | Drift Auto-Reversal Rate | D-096 + no scheduler | Build a drift-detection scheduler (cron); run `terraform plan -detailed-exitcode` per workspace; emit drift.detected events | v1.20+ (drift detection) |
| 6 | Live CUR Reconciliation | D-096 | Re-provision live AWS billing access; build CUR reconciler (6h schedule); match bill lines to resource addresses via tags | v1.18+ |
| 7 | SLA / Unplanned Downtime | D-096 | Deploy live services with SLOs; emit uptime metrics against SLO targets | v1.18+ |
| 8 | Predictive vs Reactive Ratio | future emitter | Build an ML anomaly-forecasting service; emit anomaly.predicted events with proactive label | v1.21+ (predictive ops) |
## Onboarding-funnel "granted" half
The onboarding request path is grounded (REQ-182/183 from v1.16): a
consumer submits a request → the Lambda writes a `pending` CMDB row →
`core/onboarding.py` generates a binding file. The "granted" half
(actual AWS account/network/state provisioning) is deferred per
D-113/D-114/D-119. When a future milestone implements auto-grant, the
onboarding funnel metric activates: `count(granted) ÷ count(requested)`.
## Hot-Path Activation (post-D-096)
**Current state (v1.17):** SQLite cold store only (D-126). No hot path.
The hot path activates when live AWS is re-provisioned (D-096 lift).
**Nova-native hot-path candidates (D-120 — no Kafka/Prometheus/ClickHouse):**
1. **SQLite read-replica:** the cold store becomes a read-replica updated
on each run; a lightweight file-watcher notifies the dashboard of
changes. Freshness = "last run" (not 1-second, but sufficient for
batch ops).
2. **JSONL tail + webhook:** the events.jsonl log is tailed by a small
daemon that pushes updates to a webhook (e.g., a PowerBI streaming
dataset or a custom dashboard). Nova-native (no new infra).
3. **SQLite + Grafana SQLite datasource:** Grafana can read SQLite
directly via the SQLite datasource plugin. No TSDB needed.
**Migration steps (when D-096 lifts):**
1. Re-provision live AWS (microservice + static-assets stacks).
2. Add live-health emitters (ECS running count, ALB 5xx, RPS) to
`run_platform.sh`.
3. Choose a hot-path candidate (above) and implement it.
4. Populate the 8 placeholder views with real data.
5. Re-run the collector + PowerBI export.
## Re-evaluation Triggers
A follow-up metrics ideation should be triggered when any of these
events occurs:
1. **D-096 lift** (live AWS re-provisioned) — triggers hot-path
activation + placeholder view population for metrics 1, 2, 5, 6, 7.
2. **D-083 lift** (S3 Object Lock + JWS build-out approved) — triggers
tamper-evident ledger checkpoint metric (metric 3).
3. **Onboarding-grant lift** (auto-grant implemented) — triggers
onboarding funnel metric (metric 4).
When any trigger fires, re-run `/ci-run` with a metrics-focused milestone
to activate the corresponding placeholder views.
-141
View File
@@ -1,141 +0,0 @@
# Nova Metrics Views — PowerBI Data Dictionary
This document is the column-level data dictionary for the PowerBI export
views in `metrics/powerbi/`. Each fact/dimension table and placeholder
view is documented with: column, type, source/formula, unit, and
grounded/derived/deferred status.
## Fact tables (grounded)
### fact_run
| Column | Type | Source | Unit | Status |
|--------|------|--------|------|--------|
| run_id | TEXT | run_manifest.py | — | grounded |
| contract_id | TEXT | run_manifest.py | — | grounded |
| environment | TEXT | run_manifest.py | dev/qa/prod/dr | grounded |
| started_at | TEXT | run_manifest.py | ISO8601 | grounded |
| completed_at | TEXT | run_manifest.py | ISO8601 | grounded |
| exit_code | INTEGER | run_manifest.py | — | grounded |
| outcome | TEXT | run_manifest.py | succeeded/failed | grounded |
| confidence_score | REAL | confidence_signal.py | 0.01.0 | grounded |
| confidence_band | TEXT | confidence_signal.py | pass/warn/block | grounded |
| hitl_block | INTEGER | hitl_gates.py | 0/1 | grounded |
| cost_estimate_usd | REAL | infracost_adapter.py | USD | grounded (Infracost) |
| decision_id | TEXT | decision_ledger.py | — | grounded |
### fact_capability
| Column | Type | Source | Unit | Status |
|--------|------|--------|------|--------|
| capability_id | TEXT | REGRESSION_REPORT.json | CAP-NNN | grounded |
| run_id | TEXT | REGRESSION_REPORT.json | — | grounded |
| name | TEXT | REGRESSION_REPORT.json | — | grounded |
| status | TEXT | REGRESSION_REPORT.json | Verified/Decayed/Broken/Skipped | grounded |
| tier | TEXT | REGRESSION_REPORT.json | local/live-aws/lifecycle-pipeline | grounded |
| duration_ms | REAL | REGRESSION_REPORT.json | milliseconds | grounded |
| detail | TEXT | REGRESSION_REPORT.json | — | grounded |
| run_at_utc | TEXT | REGRESSION_REPORT.json | ISO8601 | grounded |
### fact_decision
| Column | Type | Source | Unit | Status |
|--------|------|--------|------|--------|
| decision_id | TEXT | decision_ledger.py | = run_id | grounded |
| run_id | TEXT | decision_ledger.py | — | grounded |
| chosen_action | TEXT | confidence_signal.py | pass/warn/block | grounded |
| confidence | REAL | confidence_signal.py | 0.01.0 | grounded |
| alternatives | TEXT (JSON) | confidence_signal.py | perInput breakdown | grounded |
| human_override | INTEGER | hitl_gates.py | 0/1 | grounded |
| outcome | TEXT | decision_ledger.py | succeeded/failed/pending | grounded |
| event_time | TEXT | decision_ledger.py | ISO8601 | grounded |
### fact_test
| Column | Type | Source | Unit | Status |
|--------|------|--------|------|--------|
| run_id | TEXT | junit XML | — | grounded |
| total_tests | INTEGER | junit XML | count | grounded |
| passed | INTEGER | junit XML | count | grounded |
| failed | INTEGER | junit XML | count | grounded |
| errors | INTEGER | junit XML | count | grounded |
| skipped | INTEGER | junit XML | count | grounded |
| duration_s | REAL | junit XML | seconds | grounded |
| coverage_pct | REAL | coverage.json | % | grounded |
| collected_at | TEXT | collector.py | ISO8601 | grounded |
### fact_cost_estimate
| Column | Type | Source | Unit | Status |
|--------|------|--------|------|--------|
| run_id | TEXT | infracost_adapter.py | — | grounded |
| delta_usd | REAL | Infracost | USD/month | grounded (pre-apply) |
| total_monthly_usd | REAL | Infracost | USD/month | grounded (pre-apply) |
| available | INTEGER | infracost_adapter.py | 0/1 | grounded |
| estimated_at | TEXT | infracost_adapter.py | ISO8601 | grounded |
### fact_lifecycle
| Column | Type | Source | Unit | Status |
|--------|------|--------|------|--------|
| module | TEXT | lifecycle report | — | grounded |
| environment | TEXT | lifecycle report | — | grounded |
| phase | TEXT | lifecycle report | apply/modify/destroy | grounded |
| result | TEXT | lifecycle report | pass/fail | grounded |
| duration_ms | REAL | lifecycle report | milliseconds | grounded |
| run_at | TEXT | lifecycle report | ISO8601 | grounded |
## Dimension tables
### dim_capability
| Column | Type | Source | Status |
|--------|------|--------|--------|
| capability_id | TEXT | REGRESSION_REPORT.json | grounded |
| name | TEXT | REGRESSION_REPORT.json | grounded |
| tier | TEXT | REGRESSION_REPORT.json | grounded |
| source_milestone | TEXT | REGRESSION_REPORT.json | grounded |
### dim_milestone
| Column | Type | Source | Status |
|--------|------|--------|--------|
| milestone | TEXT | REGRESSION_REPORT.json | grounded |
| phase | INTEGER | REGRESSION_REPORT.json | grounded |
| tag | TEXT | — | grounded |
| completed_at | TEXT | REGRESSION_REPORT.json | grounded |
## Placeholder views (deferred — 8 views, headers only, no data)
### placeholder_live_infra_health
- **Blocking decision:** D-096
- **Description:** Live infrastructure health (ECS running count, ALB 5xx, RPS)
- **Columns:** timestamp, resource_id, resource_type, running_count, healthy, downtime_seconds
### placeholder_live_outbox_rate
- **Blocking decision:** D-096
- **Description:** Live outbox write rate / ledger append latency
- **Columns:** timestamp, contract_id, write_latency_ms, append_count
### placeholder_tamper_evident_checkpoints
- **Blocking decision:** D-083
- **Description:** Tamper-evident ledger checkpoints / JWS signature rate
- **Columns:** timestamp, checkpoint_id, jws_signed, object_lock_enabled
### placeholder_onboarding_funnel
- **Blocking decision:** D-113/D-114/D-119
- **Description:** Onboarding funnel: requested → granted conversion
- **Columns:** timestamp, consumer_repo, requested_environment, status, granted_at
### placeholder_drift_detection
- **Blocking decision:** D-096 + no scheduler
- **Description:** Drift detection (scheduled terraform plan -detailed-exitcode)
- **Columns:** timestamp, workspace_id, drift_count, auto_reverted, detection_cycle
### placeholder_live_cur_reconciliation
- **Blocking decision:** D-096
- **Description:** Live cost CUR reconciliation
- **Columns:** timestamp, resource_address, actual_usd, baseline_usd, saved_usd
### placeholder_sla_downtime
- **Blocking decision:** D-096
- **Description:** SLA / unplanned downtime
- **Columns:** timestamp, service, uptime_pct, downtime_minutes, slo_target
### placeholder_predictive_reactive
- **Blocking decision:** future emitter
- **Description:** Predictive vs Reactive ratio
- **Columns:** timestamp, action_id, label, trigger, count
+270
View File
@@ -0,0 +1,270 @@
# Nova AWS Resource Migration Runbook (REQ-163, P4)
> **Milestone:** v1.15-Nova (Wave 4, P4). Renames every `acdl-*` AWS
> resource name → `nova-*` via Terraform. This is the heaviest Terraform
> phase of the rebrand and requires a **maintenance window**.
>
> **Plan-validated only.** Per A1, `NOVA_LIFECYCLE_MODE` defaults to
> `plan` (no live AWS mutation from CI). `terraform validate` passes; the
> live apply steps below are executed by a platform operator during the
> scheduled maintenance window. Each step has a verification + rollback.
## Scope (renamed resources)
| AWS resource | Before | After | Strategy |
|---|---|---|---|
| KMS alias | `alias/acdl-platform` | `alias/nova-platform` | cheap rename |
| SNS topic | `acdl-sod-halt` | `nova-sod-halt` | recreate |
| Security group | `acdl-ecs-sg` | `nova-ecs-sg` | recreate |
| Lambda (role/policy/function) | `acdl-contract-ingestor` | `nova-contract-ingestor` | recreate |
| DynamoDB contracts | `acdl-contracts` | `nova-contracts` | scan + copy |
| DynamoDB change-requests | `acdl-change-requests` | `nova-change-requests` | scan + copy |
| Secrets Manager secret | `acdl/github-token` | `nova/github-token` | recreate + re-store |
| ECR repo | `acdl-microservice` | `nova-microservice` | re-push |
| ECS cluster/service/task/role | `acdl-microservice` | `nova-microservice` | recreate |
| IAM user + policy | `acdl-spike-runner` (+ `-policy`) | `nova-spike-runner` (+ `-policy`) | re-bootstrap |
| IAM act-runner role | `acdl-act-runner-role` | `nova-act-runner-role` | re-bootstrap |
| IAM deploy role | `acdl-deploy-<repo>` | `nova-deploy-<repo>` | re-bootstrap |
| S3 state bucket | `acdl-tfstate-581513795199-us-east-1` | `nova-tfstate-581513795199-us-east-1` | `-migrate-state` |
| DynamoDB outbox | `acdl-outbox` | `nova-outbox` | scan + copy |
| Platform VPC/subnet/IGW/RT | `acdl-shared*` | `nova-shared*` | recreate (brief downtime) |
| CI VPC/subnet/SG/cluster | `acdl-ci-*` | `nova-ci-*` | recreate (CI-only) |
| ALB name prefix | `acdl-alb` | `nova-alb` | recreate (brief downtime, LAST) |
## Migration ordering (binding)
Order: **KMS alias → SNS/SG → Lambda → DynamoDB → ECR → IAM → state bucket → ALB**.
Each step is independently rollback-able. The ALB is last because it
requires the briefest downtime window.
---
## Pre-flight
1. **Announce the maintenance window** (consumers are notified via the
P1 migration guide `docs/NOVA_MIGRATION.md`).
2. **Back up state** for every stack (see §State bucket — back up the
state JSON *before* `-migrate-state`).
3. Confirm `NOVA_LIFECYCLE_MODE=plan` (default) so CI does not mutate
AWS during the window.
4. Confirm the new `nova-*` destination tables/repos will be created by
the same Terraform apply (no manual pre-creation needed).
## Step 1 — KMS alias (`alias/acdl-platform``alias/nova-platform`)
- **Command (in `terraform/platform/`):**
```bash
terraform init -upgrade
terraform apply -replace=aws_kms_alias.nova_platform
```
(Terraform destroys the old alias + creates the new one — aliases are
cheap; the underlying key ID is unchanged.)
- **Verify:** `aws kms list-aliases --query 'Aliases[?AliasName==`alias/nova-platform`]'` returns the new alias; `alias/acdl-platform` is gone.
- **Rollback:** `terraform apply -replace=aws_kms_alias.nova_platform` against the prior revision (re-creates `alias/acdl-platform`). Resources encrypted by the key are unaffected (key ID unchanged).
## Step 2 — SNS topic + Security group (recreate)
- **Command:** `terraform apply` in `terraform/platform/`.
- SNS `acdl-sod-halt``nova-sod-halt` (the topic ARN changes; update `NOVA_SOD_HALT_TOPIC_ARN` wherever it is set).
- SG `acdl-ecs-sg``nova-ecs-sg` (the security group is re-attached to running ECS tasks; brief task restart).
- **Verify:** `aws sns list-topics` shows `nova-sod-halt`; `aws ec2 describe-security-groups` shows `nova-ecs-sg`.
- **Rollback:** `terraform apply` the prior revision re-creates the `acdl-*` names. The SNS topic has no message backlog (halt artifacts are fire-and-forget); the SG drift resolves on next task deploy.
## Step 3 — Lambda (recreate)
- **Command:** `terraform apply` in `terraform/platform/`.
- Lambda function `acdl-contract-ingestor``nova-contract-ingestor`.
- Execution role `acdl-contract-ingestor-role``nova-contract-ingestor-role`.
- Inline policy `acdl-contract-ingestor-policy``nova-contract-ingestor-policy`.
- The Lambda env vars (`CONTRACTS_TABLE`, `GITHUB_TOKEN_SECRET_ID`) now resolve to `nova-*` defaults.
- **Verify:** `aws lambda list-functions` shows `nova-contract-ingestor`; the Function URL returns 200 on a SigV4-signed invoke. The `consumer_invoke_policy.json` rendered output (Terraform `consumer_invoke_policy_rendered`) now references `function:nova-contract-ingestor` — re-distribute to consumer deploy roles.
- **Rollback:** `terraform apply` the prior revision re-creates `acdl-contract-ingestor`. Consumer deploy roles must point back at the old Function ARN (re-distribute the prior `consumer_invoke_policy.json`).
## Step 4 — DynamoDB (scan + copy)
DynamoDB table names are immutable post-creation, so the migration is a
**scan + copy** (not a rename). The new `nova-*` tables are created by
the same Terraform apply (Step 3). The data-migration script copies
every item and verifies row counts.
- **Command (from repo root):**
```bash
# Dry-run first (no writes):
python3 scripts/migrate_dynamodb_data.py
# Execute the copy:
python3 scripts/migrate_dynamodb_data.py --apply
# A single table:
python3 scripts/migrate_dynamodb_data.py --table contracts --apply
```
The script scans `acdl-contracts` → copies to `nova-contracts`, and
`acdl-change-requests``nova-change-requests`, then verifies the
destination row count == source row count (re-scan, not
`DescribeTable.ItemCount` which lags ~6h).
- **Verify:**
```bash
# Row counts must match (printed by the script). Manual cross-check:
aws dynamodb scan --table-name nova-contracts --select COUNT
aws dynamodb scan --table-name acdl-contracts --select COUNT
```
Then **point consumers at the new tables** (the Lambda already reads
`nova-*` defaults; any direct DynamoDB consumers update their env).
- **Keep the old tables** (`acdl-contracts`, `acdl-change-requests`)
until consumers are verified reading from `nova-*`. **Deletion is a
manual post-verification step:**
```bash
aws dynamodb delete-table --table-name acdl-contracts
aws dynamodb delete-table --table-name acdl-change-requests
```
Only delete after a full soak period confirms `nova-*` reads succeed.
- **Rollback:** Re-point consumers at `acdl-*` (the old tables are
retained). The copy is additive (no data loss). To roll back a partial
copy, re-run `--apply` (idempotent — `PutItem` overwrites).
### Outbox table (`acdl-outbox``nova-outbox`)
The evidence outbox table follows the same scan+copy pattern (it is
created by `terraform/bootstrap/create_state_backend.py`).
- **Command:** `python3 scripts/migrate_dynamodb_data.py --source acdl-outbox --dest nova-outbox --apply`
- The `core/outbox_writer.py` default + `core/regression_verify.py`
CAP-015 probe now reference `nova-outbox` (P4 updated both). The
regression gate's live-AWS CAP-015 will return `Verified` once the
`nova-outbox` table exists live; until then it is `Decayed` (the gate
is re-run at milestone complete after the live migration).
## Step 5 — ECR (re-push)
- **Command:** `terraform apply` in `terraform/microservice/` creates
the new `nova-microservice` ECR repo. Re-push the image:
```bash
python3 scripts/push_consumer_image.py # creates nova-microservice + prints docker tag/push
```
(The script's `ECR_REPO_NAME` is now `nova-microservice`.)
- **Verify:** `aws ecr describe-repositories` shows `nova-microservice`; `docker pull <acct>.dkr.ecr.us-east-1.amazonaws.com/nova-microservice:latest` succeeds.
- **Rollback:** The old `acdl-microservice` repo is retained until the
soak passes. Re-push to it if a rollback is needed. Delete it manually:
`aws ecr delete-repository --repository-name acdl-microservice --force`.
## Step 6 — IAM (re-bootstrap)
- **Command:**
```bash
export NOVA_BOOTSTRAP_AWS_ACCESS_KEY_ID="<root key>"
export NOVA_BOOTSTRAP_AWS_SECRET_ACCESS_KEY="<root secret>"
python3 terraform/bootstrap/create_state_backend.py # creates nova-outbox (idempotent)
python3 terraform/bootstrap/create_iam_user.py # creates nova-spike-runner
python3 terraform/bootstrap/apply_iam_baseline.py # creates nova-spike-runner-policy + nova-act-runner-role
bash scripts/rotate_spike_key.sh # rotates the nova-spike-runner key
```
The deploy role `acdl-deploy-<repo>``nova-deploy-<repo>` is
created by the bootstrap (the deploy workflow
`.gitea/.github/workflows/deploy.yml` now references
`role/nova-deploy-{1}`).
- **Verify:** `aws iam get-user --user-name nova-spike-runner`;
`aws iam list-attached-user-policies --user-name nova-spike-runner`
shows `nova-spike-runner-policy`;
`aws iam get-role --role-name nova-act-runner-role`.
- **Rollback:** Re-run the prior bootstrap scripts (they create
`acdl-spike-runner` + `acdl-act-runner-role`). The deploy workflow's
`role-to-assume` must be reverted to `acdl-deploy-` (prior revision).
## Step 7 — State bucket (`acdl-tfstate-*``nova-tfstate-*`, `-migrate-state`)
The S3 state backend is renamed. Terraform's `-migrate-state` copies the
state objects to the new bucket. **Back up the state JSON first.**
- **Back up state (per stack):**
```bash
for stack in platform microservice ci-vpc; do
aws s3 cp s3://acdl-tfstate-581513795199-us-east-1/$stack/terraform.tfstate \
./backup-$stack.tfstate
done
```
- **Command (per stack):** the backend config in each
`terraform/*/terraform.tf` now points at `nova-tfstate-...`.
```bash
cd terraform/platform
terraform init -migrate-state # copies state acdl-tfstate → nova-tfstate
cd ../microservice
terraform init -migrate-state
cd ../ci-vpc
terraform init -migrate-state
```
- **Verify:** `aws s3 ls s3://nova-tfstate-581513795199-us-east-1/`
shows the state keys; `terraform state list` in each dir lists the
expected resources.
- **Rollback:** Point the backend back at `acdl-tfstate-*` and re-run
`terraform init -migrate-state` (restores from the backup bucket). The
old `acdl-tfstate-*` bucket is retained until the soak passes. Delete
it manually:
`aws s3 rb s3://acdl-tfstate-581513795199-us-east-1 --force`.
## Step 8 — ALB (recreate, brief downtime, LAST)
The ALB is last because its recreation requires the briefest downtime
window (the ECS service is re-attached to the new target group).
- **Command:** `terraform apply` in `terraform/microservice/`. The ALB
`acdl-microservice` / `acdl-alb``nova-microservice` / `nova-alb`.
- **Verify:** `aws elbv2 describe-load-balancers` shows the new ALB;
`curl http://<new-alb-dns>/` returns 200.
- **Rollback:** `terraform apply` the prior revision re-creates the
`acdl-*` ALB (brief downtime again). The old ALB DNS is retained until
consumers are re-pointed.
---
## Post-migration
1. **Soak:** run consumers against `nova-*` for a full verification
window (deploy a test contract end-to-end).
2. **Delete old resources** (manual, only after soak):
- DynamoDB: `acdl-contracts`, `acdl-change-requests`, `acdl-outbox`
- ECR: `acdl-microservice`
- IAM: `acdl-spike-runner` (+ policy), `acdl-act-runner-role`,
`acdl-deploy-<repo>`
- S3: `acdl-tfstate-581513795199-us-east-1`
- SNS: `acdl-sod-halt`
- SG: `acdl-ecs-sg`
- Secrets Manager: `acdl/github-token`
- KMS alias: `alias/acdl-platform`
- ALB: `acdl-alb` / `acdl-microservice`
3. **Regression gate:** re-run `bash scripts/run_regression.sh`. The
live-AWS CAP-013..016 probes should return `Verified` (the `nova-*`
tables + state bucket exist). CAP-015 (outbox) flips from `Decayed`
`Verified` once `nova-outbox` is live.
## What P5 owns (not P4)
- **Remove dual-read fallback:** `core/env.py` `get_env()` drops the
`ACDL_*` fallback; shell scripts drop `:-$ACDL_X`. P4 keeps the
dual-read (deployments don't break mid-window).
- **`nova_tagging.py` hard-fail on `acdl:*`:** P3 set hard mode (no
`acdl:*`-only tags); P5 tightens to fail on any `acdl:*` presence. P4
leaves P3's behavior.
- **Delete `ACDL_*` Gitea secrets:** the `NOVA_*` aliases created in P2
are now the only source.
- **Finalize `docs/NOVA_MIGRATION.md`:** mark the migration complete
(cutoff passed).
- **Milestone ship:** tag `v1.15.4`, merge to `main`, Gitea release.
## Files touched in P4
- `terraform/platform/main.tf`, `terraform/microservice/main.tf`,
`terraform/ci-vpc/main.tf` — resource renames + backend bucket.
- `terraform/{platform,microservice,ci-vpc}/terraform.tf` — state bucket.
- `terraform/platform/consumer_invoke_policy.json` — Lambda ARN.
- `terraform/bootstrap/{create_state_backend,create_iam_user,apply_iam_baseline}.py`,
`spike_runner_policy.json`, `.bootstrap_state.json`, `README.md`
IAM/outbox/state-bucket renames.
- `modules/l1/*/terraform/**` + `modules/l1/alb/instance.json` — L1
resource-name defaults.
- `modules/l2/microservice/composition.json``nova-app-role` default.
- `core/lambda/contract_ingestor.py` — default table names (D-111).
- `core/outbox_writer.py`, `core/regression_verify.py`,
`core/local_emulators.py` — outbox table consistency (cross-territory,
minimal).
- `.gitea/workflows/deploy.yml` + `.github/workflows/deploy.yml`
`nova-deploy-` role ARN + artifact names.
- `scripts/migrate_dynamodb_data.py` (NEW), `scripts/rotate_spike_key.sh`,
`scripts/push_consumer_image.py`.
- `tests/**` — fixtures updated to assert `nova-*`.
+177
View File
@@ -0,0 +1,177 @@
# Nova Migration Guide — What Consumers Must Know
> **STATUS: COMPLETE (milestone v1.15.4, 2026-07-30).** The Nova rebrand
> is fully rolled out. The dual-read / parallel-write grace period has
> ended (P5 cutoff passed). All `ACDL_*` env var fallbacks, `.acdl/`
> consumer-path fallbacks, `/acdl/` SSM-path fallbacks, `acdl:*` tag-key
> fallbacks, and `acdl-*` AWS resource names are removed. Consumers must
> use the `NOVA_*` / `.nova/` / `/nova/` / `nova:*` / `nova-*` names
> exclusively. If you have not yet migrated, follow the steps below.
> **Nova** is the new product brand for the platform formerly known as
> **ACDL** (Agentic Cloud Delivery Platform). This guide documents the
> breaking changes from the rebrand rollout (Phases P2P4, cutoff P5)
> and tells you exactly what to do.
## What is NOT changing
- **The Gitea repository name** (`continuous-intelligence/acdl`) is **not**
changing. Only the product brand is changing. The `uses:` reference
(`acdl/.github/workflows/deploy.yml@vX.Y`) and the GitHub `acdl/acdl` repo
path are unchanged for the duration of the rebrand; the workflow
`uses:` reference will be migrated in a later, separately-announced step.
- **The platform behavior** is unchanged. Same pipeline stages, same
contract schema, same confidence model, same evidence stream, same
modules. Only the brand, the on-disk path, the env var names, the SSM
path, the AWS tag keys, and the AWS resource names are changing.
## The 5 breaking changes
Five things that consumers may reference are being renamed. Each is
scheduled into a phase, ships with a grace period, and has a cutoff.
### 1. Consumer contract path — Phase P2
- **Old:** `.acdl/contract.yml`
- **New:** `.nova/contract.yml`
- **Phase:** P2 (env vars + consumer path)
- **Grace period:** during P2P4 the deploy workflow reads **both** paths
(`.nova/contract.yml` first, falling back to `.acdl/contract.yml` if the
new path is absent). Your existing contracts keep working until P5.
- **Cutoff:** P5 removes the `.acdl/` fallback. Move your contract file
before P5.
- **What you must do:** rename the directory in your consumer repo from
`.acdl/` to `.nova/` and update any `contract:` workflow input that
points at the old path. Nothing else changes in the contract content.
### 2. Environment variables — Phase P2
- **Old:** `ACDL_*` (e.g. `ACDL_LIFECYCLE_MODE`, `ACDL_AWS_ACCOUNT_ID`,
`ACDL_BOOTSTRAP_AWS_ACCESS_KEY_ID`, …)
- **New:** `NOVA_*` (e.g. `NOVA_LIFECYCLE_MODE`, `NOVA_AWS_ACCOUNT_ID`,
`NOVA_BOOTSTRAP_AWS_ACCESS_KEY_ID`, …)
- **Phase:** P2 (env vars + consumer path)
- **Grace period — dual-read fallback:** during P2P4 the platform reads
**`NOVA_*` first, then falls back to `ACDL_*`** if the Nova variable is
unset. This means your CI secrets, workflow env blocks, and local
`.env.secrets` keep working unchanged through P4. You do not need to
rename everything in one shot — rename a variable and the dual-read picks
it up; leave one old and it still resolves.
- **Cutoff:** P5 removes the `ACDL_*` fallback. After P5, only `NOVA_*`
is read.
- **What you must do:** rename your `ACDL_*` CI secrets, workflow `env:`
blocks, and any local `.env.secrets` entries to `NOVA_*`. Because of the
dual-read, you can do this incrementally across P2P4 — but it must be
complete before P5.
### 3. SSM parameter path — Phase P3 (DONE)
- **Old:** `/acdl/{env}/{contractId}/{output}`
- **New:** `/nova/{env}/{contractId}/{output}`
- **Phase:** P3 (SSM paths + tag keys) — **shipped in P3**
- **Grace period — parallel-write:** during P3P4 the platform **writes
every output to both** the `/acdl/…` and `/nova/…` SSM paths, and reads
from `/nova/…` first (falling back to `/acdl/…`). Any hardcoded SSM path
reads in your application code keep resolving through P4. The P3
migration script (`scripts/migrate_ssm_paths.py`) copies existing
`/acdl/…` parameters to `/nova/…`, verifies the copy, and deletes the
old ones.
- **Cutoff:** P5 stops writing to `/acdl/…` and removes the read fallback.
After P5 only `/nova/…` exists.
- **What you must do:** if your application code or runbooks read deploy
outputs from SSM by hardcoded path, update the path prefix from `/acdl/`
to `/nova/`. If you consume outputs only via the PR-comment / GitHub
issue surface, you do nothing — the platform republishes under the new
path automatically.
### 4. AWS tag keys — Phase P3 (DONE)
- **Old:** `acdl:owner`, `acdl:environment`, `acdl:contract`,
`acdl:cost-center`, `acdl:ref`
- **New:** `nova:owner`, `nova:environment`, `nova:contract`,
`nova:cost-center`, `nova:ref`
- **Phase:** P3 (SSM paths + tag keys) — **shipped in P3**
- **Grace period — parallel-tag period:** during P3P4 the platform
**tags every resource with both** the `acdl:*` and `nova:*` keys (same
values). The ABAC session policy matches on **either** key set, so your
existing scoped permissions keep working. The default cost-center value
moves from `acdl-default` to `nova-default` (both written during the
parallel-tag period). Terraform now emits `nova:*` keys; old `acdl:*`
tags on pre-P3 live resources are removed by the P4 runbook's
`scripts/untag_acdl_keys.py` step after the `nova:*` tags are applied
live.
- **Cutoff:** P5 stops writing the `acdl:*` keys and the ABAC policy matches
only on `nova:*`. After P5, resources created before P5 still carry the
old `acdl:*` tags (tags are not retroactively rewritten) but **new**
resources are tagged `nova:*` only, and the policy no longer grants
access via `acdl:*`.
- **What you must do:** if you have IAM policies, Cost Explorer filters,
or billing groupings that key off `acdl:*` tag keys, add a parallel
`nova:*` condition (or migrate to `nova:*`) before P5. The platform
handles the dual-tagging; you only need to update your own tag-key
references.
### 5. AWS resource names — Phase P4
- **Old:** `acdl-*` (DynamoDB tables `acdl-contracts`,
`acdl-change-requests`; Lambda `acdl-contract-ingestor`; SNS
`acdl-sod-halt`; security group `acdl-ecs-sg`; KMS alias
`alias/acdl-platform`; ECS services, ECR repos, IAM user
`acdl-spike-runner`, state bucket `acdl-tfstate-*`, ALB `acdl-alb`,
`acdl-deploy-*`)
- **New:** `nova-*` (the same resources, prefixed `nova-`)
- **Phase:** P4 (resource names) — **maintenance window**
- **Grace period:** P4 is a **planned maintenance window**. AWS resources
cannot be renamed in place, so P4 provisions the `nova-*` resources,
migrates data (DynamoDB tables, S3 state), repoints the platform, and
tears down the `acdl-*` resources. The platform team schedules and
announces the window; consumers do not provision or rename anything
themselves.
- **Cutoff:** the `acdl-*` resources are decommissioned at the end of the
P4 maintenance window. After P4, only `nova-*` resources exist.
- **What you must do:** nothing for the resource names themselves — the
platform owns the rename. If your application code or runbooks reference
a specific `acdl-*` resource by name (e.g. a hardcoded DynamoDB table
name or ECR URI), update it to the `nova-*` name during P4. The platform
publishes the exact old → new name mapping with the P4 announcement.
## Timeline at a glance
| Phase | What ships | Grace period | Cutoff |
|-------|------------|--------------|--------|
| **P1** (this phase) | Brand prose, docs, decks, schema `$id`, release titles | n/a (prose only) | n/a |
| **P2** | `.nova/` contract path + `NOVA_*` env vars | dual-read: `.nova/``.acdl/`, `NOVA_*``ACDL_*` | **P5** removes fallback |
| **P3** | `/nova/` SSM path + `nova:*` tag keys | parallel-write (SSM) + parallel-tag (ABAC matches either) | **P5** removes old path/tags |
| **P4** | `nova-*` AWS resource names | maintenance window (platform-owned migration) | end of P4 window |
| **P5** | Fallback removal | — | `ACDL_*` env vars, `.acdl/` path, `/acdl/` SSM, `acdl:*` tags stop working |
## What consumers must do (checklist)
1. **Before P5 — contract path:** move `.acdl/contract.yml`
`.nova/contract.yml` in your consumer repo; update the `contract:`
workflow input. *(Can be done any time in P2P4.)*
2. **Before P5 — env vars:** rename `ACDL_*` CI secrets / workflow `env:`
blocks / local `.env.secrets` to `NOVA_*`. *(Incremental during P2P4;
dual-read keeps you green.)*
3. **Before P5 — SSM reads:** if you read deploy outputs from SSM by
hardcoded `/acdl/…` path, update to `/nova/…`. *(Skip if you consume
outputs via PR comments only.)*
4. **Before P5 — tag-key references:** if you have IAM policies, Cost
Explorer filters, or billing groupings keyed off `acdl:*`, add or
migrate to `nova:*`. *(Platform handles dual-tagging.)*
5. **During P4 — resource-name references:** if your code or runbooks
reference a specific `acdl-*` AWS resource by name, update to the
`nova-*` name per the P4 mapping announcement. *(Platform owns the
rename itself.)*
## Questions
If anything in this guide is unclear, or you are unsure whether your
consumer repo references a renamed value, open an issue on the platform
repo. The platform team will confirm what you need to change and when.
> **Note:** the real Gitea repository name (`continuous-intelligence/acdl`)
> is **not** changing — only the product brand. The `uses:` workflow
> reference and repo path are migrated in a separately-announced later step;
> until then, keep your `uses: acdl/.github/workflows/deploy.yml@vX.Y`
> reference as-is.
-87
View File
@@ -1,87 +0,0 @@
# Nova Onboarding — Autonomous Request Path (v1.16, REQ-182..184)
The v1.16 milestone implements the **request path** of the autonomous
onboarding flow (D-113). A consumer can submit an onboarding request
without contacting the platform team; the platform generates an
environment binding + (in a future milestone) provisions the AWS resources.
## The 3-step request path
### Step 1 — Submit an onboarding request (P18, REQ-182)
A consumer submits an onboarding request to the Nova platform Lambda:
```bash
# Via the Lambda Function URL (IAM auth):
curl -X POST "$NOVA_LAMBDA_URL" \
-H "Content-Type: application/json" \
-d '{
"action": "onboard_consumer",
"consumerRepo": "acdl/my-app",
"requestedEnvironment": "dev",
"ownerId": "team-x",
"billingTag": "cost-center-x"
}'
```
The Lambda validates the payload against
[`schemas/onboarding.schema.json`](../schemas/onboarding.schema.json),
then writes a `pending` row to the `nova-contracts` DynamoDB table
(D-119). No AWS resources are created by this action (D-113).
### Step 2 — Generate an environment binding (P19, REQ-183)
The platform (or the consumer locally) generates an environment binding
file from the request:
```bash
python3 core/onboarding.py --request '{
"consumerRepo": "acdl/my-app",
"requestedEnvironment": "qa",
"ownerId": "team-x",
"billingTag": "cost-center-x"
}' --out core/environments/qa.json
```
This produces a `<env>.json` from the `dev.json` template, filling in
the `ownerId` + `billingTag` + a description. The `account_id` is a
placeholder (`000000000000`) for the platform team to fill with the real
account. The generated file validates against
[`schemas/environment.schema.json`](../schemas/environment.schema.json).
### Step 3 — Cross-account role + ABAC tag grant (P20, REQ-184)
The platform authors the consumer deploy-role + `nova:owner` ABAC tag
grant via Terraform:
```bash
cd terraform/onboarding
terraform init -backend=false
terraform validate
NOVA_AWS_ACCOUNT_ID=123456789012 terraform plan \
-var consumer_repo=acdl/my-app \
-var owner_id=team-x
```
**Offline-proven only (D-114):** `terraform validate` + `terraform plan`
pass; **no live apply** in v1.16. The live apply (creating the real
cross-account role + OIDC trust) is deferred to a future feature
milestone (D-113).
## What is NOT automated (deferred)
- **Real AWS account/network/state provisioning** — the request path
generates a binding file with a placeholder `account_id`; the actual
AWS account creation + VPC + state backend is a future feature (D-113).
- **Live cross-account role apply** — the Terraform is offline-proven
only (D-114); live apply is deferred.
- **OIDC trust policy** — the onboarding Terraform uses a placeholder
OIDC provider; real OIDC federation is blocked on
upstream forge OIDC support (carries forward from v1.1).
## See also
- [`schemas/onboarding.schema.json`](../schemas/onboarding.schema.json) — the request schema
- [`core/onboarding.py`](../core/onboarding.py) — the env-file generator
- [`terraform/onboarding/`](../terraform/onboarding/) — the role-grant Terraform
- [`core/environments/README.md`](../core/environments/README.md) — environment binding docs
+1 -1
View File
@@ -230,7 +230,7 @@ change to the modules/stack/confidence/audit.
- A MAJOR bump requires a new registry entry (immutable publication); the - A MAJOR bump requires a new registry entry (immutable publication); the
old entry enters a 12-month deprecation window. old entry enters a 12-month deprecation window.
- The central deploy pipeline is referenced by a floating MAJOR + MINOR tag - The central deploy pipeline is referenced by a floating MAJOR + MINOR tag
(e.g. `@v1.19`); patch fixes flow within the tag, breaking changes land (e.g. `@v1.13`); patch fixes flow within the tag, breaking changes land
under the next MINOR tag. under the next MINOR tag.
See [Versioning](pipeline/versioning) for the consumer-facing details. See [Versioning](pipeline/versioning) for the consumer-facing details.
+21 -57
View File
@@ -19,7 +19,7 @@ definitions.
```mermaid ```mermaid
flowchart LR flowchart LR
A["your repo<br/>(app code + contracts + CI definitions)"] -->|uses: nova/.github/workflows/deploy.yml@v1.19| B A["your repo<br/>(app code + contracts + CI definitions)"] -->|uses: acdl/.github/workflows/deploy.yml@v1.13| B
B["platform runners<br/>(modules + pipelines + adapters + schemas)"] -->|contract -&gt; resolver -&gt; stack -&gt; adapter<br/>-&gt; security checks -&gt; infrastructure plan -&gt; policy checks<br/>-&gt; confidence -&gt; apply -&gt; evidence event| C B["platform runners<br/>(modules + pipelines + adapters + schemas)"] -->|contract -&gt; resolver -&gt; stack -&gt; adapter<br/>-&gt; security checks -&gt; infrastructure plan -&gt; policy checks<br/>-&gt; confidence -&gt; apply -&gt; evidence event| C
C["your resources in AWS"] C["your resources in AWS"]
``` ```
@@ -27,13 +27,13 @@ flowchart LR
## Versioning the `uses:` reference ## Versioning the `uses:` reference
The central deployment pipeline is **always versioned with floating MAJOR The central deployment pipeline is **always versioned with floating MAJOR
and MINOR tags** (e.g. `nova/pipelines/contract.yml@v1.19`). Version and MINOR tags** (e.g. `acdl/pipelines/contract.yml@v1.13`). Version
constraints cannot be expressed inside the contract, so the tag in constraints cannot be expressed inside the contract, so the tag in
`uses:` is the only immutability lever a consumer has. See `uses:` is the only immutability lever a consumer has. See
[Versioning](pipeline/versioning) for the full rationale. [Versioning](pipeline/versioning) for the full rationale.
**Unversioned references are discouraged.** Do not use `@main` or a bare **Unversioned references are discouraged.** Do not use `@main` or a bare
`nova/pipelines/contract.yml`. `acdl/pipelines/contract.yml`.
## Prerequisites ## Prerequisites
@@ -47,7 +47,7 @@ platform-managed. See [Environments](environments/).
environment is bound, your first pipeline run emits a friendly onboarding environment is bound, your first pipeline run emits a friendly onboarding
prompt. See [Environments](environments/). prompt. See [Environments](environments/).
- **Authorization to reference the central pipeline.** Onboarding grants - **Authorization to reference the central pipeline.** Onboarding grants
your repo the right to `uses: nova/.github/workflows/deploy.yml@v1.19`. your repo the right to `uses: acdl/.github/workflows/deploy.yml@v1.13`.
Contact the platform team if you have not been onboarded. Contact the platform team if you have not been onboarded.
## Step 1 — Create a consumer repo ## Step 1 — Create a consumer repo
@@ -94,7 +94,7 @@ Nova deployment workflow with a **versioned tag** (floating MAJOR + MINOR):
```yaml ```yaml
jobs: jobs:
deploy: deploy:
uses: nova/.github/workflows/deploy.yml@v1.19 uses: acdl/.github/workflows/deploy.yml@v1.13
with: with:
contract: .nova/contract.yml contract: .nova/contract.yml
environment: dev environment: dev
@@ -140,10 +140,10 @@ name: microservice
| Field | Type | Required | Description | | Field | Type | Required | Description |
|-------|------|----------|-------------| |-------|------|----------|-------------|
| `id` | string | yes | Short operational acronym (3-6 chars, lowercase + digits + hyphens). Becomes `stack.name`: the Terraform state key (`spike/<id>/<env>/terraform.tfstate`), the outbox event identity, and the resource naming prefix. Stable across deploys and environment promotions. | | `uses` | string | yes | Reference to the central deployment pipeline, **versioned** with a floating MAJOR+MINOR tag (e.g. `acdl/pipelines/contract.yml@v1.13`). Bare or `@main` references are discouraged. See [Versioning](pipeline/versioning). |
| `name` | string | yes | Full human-readable stack name. Becomes `stack.title`: the display name in PR comments, evidence records, and dashboards. | | `module` | string | yes | Module name from the registry — any primitive or module (e.g. `static-assets`, `microservice`, `s3`). See the [module catalog](modules/). |
| `environment` | string | yes | The platform-managed environment to deploy to (`dev`, `qa`, `prod`, or `dr`). See [Environments](environments/). | | `environment` | string | yes | The platform-managed environment to deploy to (e.g. `dev`). See [Environments](environments/). |
| `infrastructure` | object | yes | Map of modules to deploy, keyed by module name (matching a registry key in `modules/registry.json`). Each entry carries an optional `version` (defaults to latest published) and per-module `inputs`. One entry = single-module deploy; N entries = multi-module manifest. | | `inputs` | object | yes | Module-specific inputs (see the module's README). |
### Module inputs ### Module inputs
@@ -177,15 +177,14 @@ on:
branches: [main] branches: [main]
jobs: jobs:
deploy: deploy:
uses: nova/.github/workflows/deploy.yml@v1.19 uses: acdl/.github/workflows/deploy.yml@v1.13
with: with:
contract: .nova/contract.yml contract: .nova/contract.yml
environment: dev
``` ```
That is the entire consumer-side workflow. When you push to `main`: That is the entire consumer-side workflow. When you push to `main`:
1. The platform runner resolves `uses: nova/.github/workflows/deploy.yml@v1.19` 1. The platform runner resolves `uses: acdl/.github/workflows/deploy.yml@v1.13`
to the reusable workflow **at the pinned tag**. to the reusable workflow **at the pinned tag**.
2. A **platform-provided runner** checks out **your** repo. 2. A **platform-provided runner** checks out **your** repo.
3. The runner checks out the **Nova platform repo** into the workspace — 3. The runner checks out the **Nova platform repo** into the workspace —
@@ -230,7 +229,7 @@ flowchart TD
S5["policy checks<br/>(adapter -&gt; PolicyCheckResult)"] --> S6 S5["policy checks<br/>(adapter -&gt; PolicyCheckResult)"] --> S6
S6["confidence<br/>score + band (dev &gt;= 0.50)"] --> S7 S6["confidence<br/>score + band (dev &gt;= 0.50)"] --> S7
S7["evidence event<br/>to the audit outbox"] --> S8 S7["evidence event<br/>to the audit outbox"] --> S8
S8["infrastructure apply<br/>(autonomous in dev;<br/>higher envs apply after HITL)"] S8["infrastructure apply<br/>(dev only)"]
``` ```
1. **validate-contract** — validates your contract YAML against the contract 1. **validate-contract** — validates your contract YAML against the contract
@@ -251,10 +250,9 @@ flowchart TD
threshold is ≥ 0.50. If the band is `pass`, the pipeline proceeds. threshold is ≥ 0.50. If the band is `pass`, the pipeline proceeds.
7. **evidence event** — a hash-chained evidence event is written to the 7. **evidence event** — a hash-chained evidence event is written to the
audit outbox. audit outbox.
8. **infrastructure apply** (autonomous in dev; higher environments apply 8. **infrastructure apply** (dev only) — the infrastructure plan is applied,
after HITL attestation) — the infrastructure plan is applied, creating creating the resources in your AWS account. An evidence event for the
the resources in your AWS account. An evidence event for the apply is apply is recorded.
recorded.
## Step 6 — What gets created ## Step 6 — What gets created
@@ -291,14 +289,7 @@ push your container image to the ECR repo the platform created.
## Step 8 — Promote to qa / prod ## Step 8 — Promote to qa / prod
There are **two supported promotion shapes**. Both are valid; pick the one Change `environment` in your contract (the infrastructure stays the same):
that fits your repo's workflow.
### Shape A — edit the environment field (destroy-then-rebuild)
Change `environment` in your contract (the infrastructure stays the same).
The contract `id` stays stable, so the platform knows this is the same
stack moving to a new environment:
```yaml ```yaml
id: assets id: assets
@@ -310,32 +301,10 @@ infrastructure:
inputs: { ... } inputs: { ... }
``` ```
**What happens when you change `environment: dev``environment: qa`:**
the platform detects that the environment changed on a known contract `id`.
Before building the new environment, it **destroys the prior environment's
resources** (Terraform state key `spike/{id}/dev/`) and records an evidence
event for the destroy. Only then does it apply the new environment (state
key `spike/{id}/qa/`). **There is no orphan path** — if the destroy fails,
the pipeline fails closed (no apply runs, no resources are left behind).
This is full lifecycle management: the platform never creates a state
where prior-environment resources are abandoned.
Higher environments require human attestation (a platform-runner deployment Higher environments require human attestation (a platform-runner deployment
approval) and higher confidence thresholds. See [Environments](environments/) approval) and higher confidence thresholds. See [Environments](environments/)
for the full table. for the full table.
> **Note:** the destroy-then-rebuild runs within the same AWS account (the
> current platform scaffold uses one account). Cross-account promotion
> (separate accounts per env) is a future milestone.
### Shape B — per-environment caller workflows (no editing)
Alternatively, keep one contract per environment (or one contract + the
`environment` workflow input) and run the matching CI job to promote. This
avoids the destroy step because each environment has its own state from the
first deploy. See [Per-environment deployment](#per-environment-deployment)
below for the full pattern.
## Step 9 — Compliance extensions ## Step 9 — Compliance extensions
Each module lists compliance extension points for the future compliance Each module lists compliance extension points for the future compliance
@@ -357,8 +326,8 @@ per-module extension points. Common examples:
| Contract schema | `schemas/contract.schema.json` | JSON Schema for consumer contracts. | | Contract schema | `schemas/contract.schema.json` | JSON Schema for consumer contracts. |
| Stack schema | `schemas/stack.schema.json` | JSON Schema for the resolved stack instance. | | Stack schema | `schemas/stack.schema.json` | JSON Schema for the resolved stack instance. |
| Module catalog | [modules/](modules/) | All primitives and modules. | | Module catalog | [modules/](modules/) | All primitives and modules. |
| Sample contract | `contracts/static-assets.yml` | The reference example contract (used with caller workflow `@v1.19`). | | Sample contract | `contracts/static-assets.yaml` | The reference example contract (uses `@v1.13`). |
| Sample contract | `contracts/microservice.yml` | The microservice example contract (used with caller workflow `@v1.19`). | | Sample contract | `contracts/microservice.yaml` | The microservice example contract (uses `@v1.13`). |
| Module examples | `modules/<name>/examples/` | Validated per-module example contracts (`simple.yaml` + `complex.yaml`). | | Module examples | `modules/<name>/examples/` | Validated per-module example contracts (`simple.yaml` + `complex.yaml`). |
| Contract resolver | `core/contract_resolver.py` | Resolves contracts to stack instances. | | Contract resolver | `core/contract_resolver.py` | Resolves contracts to stack instances. |
| Angine adapter | `adapters/terraform/adapter.py` | Compiles stack instances to infrastructure. | | Angine adapter | `adapters/terraform/adapter.py` | Compiles stack instances to infrastructure. |
@@ -384,7 +353,7 @@ destruction:
use `mode: decommission` with the `changeRequestId` input: use `mode: decommission` with the `changeRequestId` input:
```yaml ```yaml
uses: nova/.github/workflows/deploy.yml@v1.19 uses: acdl/.github/workflows/deploy.yml@v1.13
with: with:
contract: .nova/contract.yml contract: .nova/contract.yml
mode: decommission mode: decommission
@@ -426,11 +395,6 @@ separately (or left running to monitor the decommissioned stack's
endpoints going dark). endpoints going dark).
## Per-environment deployment ## Per-environment deployment
> **This is Shape B** (the alternative to [Shape A's edit-and-destroy
> path](#step-8--promote-to-qa--prod) in Step 8). Shape B avoids the
> destroy step because each environment has its own state from the first
> deploy — no prior environment to tear down.
Nova supports a **promotion-without-editing** model: you do not edit the Nova supports a **promotion-without-editing** model: you do not edit the
`environment:` field in a contract to promote dev → qa → prod → dr. `environment:` field in a contract to promote dev → qa → prod → dr.
Instead, there is **one CI job per environment**, each pointing at its Instead, there is **one CI job per environment**, each pointing at its
@@ -457,7 +421,7 @@ name: static-assets
``` ```
**Shape 2 — single contract + `environment` workflow input:** the **Shape 2 — single contract + `environment` workflow input:** the
reusable deploy workflow (`nova/.github/workflows/deploy.yml@v1.19`) reusable deploy workflow (`acdl/.github/workflows/deploy.yml@v1.13`)
declares an `environment` input. When non-empty, it overrides the declares an `environment` input. When non-empty, it overrides the
contract's `environment` field at load time (before interpolation), so contract's `environment` field at load time (before interpolation), so
the same contract can be promoted by passing a different environment: the same contract can be promoted by passing a different environment:
@@ -472,7 +436,7 @@ on: workflow_dispatch:
required: true required: true
jobs: jobs:
deploy-qa: deploy-qa:
uses: nova/.github/workflows/deploy.yml@v1.19 uses: acdl/.github/workflows/deploy.yml@v1.13
with: with:
environment: qa environment: qa
contract: .nova/contract.yml contract: .nova/contract.yml
+7
View File
@@ -78,3 +78,10 @@ Planned future features (no dates; tracked in the internal roadmap):
- [Consumer Guide](consumer-guide) — start here if you are a consumer. - [Consumer Guide](consumer-guide) — start here if you are a consumer.
- [Architecture](architecture) — start here if you are a platform engineer. - [Architecture](architecture) — start here if you are a platform engineer.
- The [README](https://github.com/nova/nova) describes the platform repo. - The [README](https://github.com/nova/nova) describes the platform repo.
> **Note:** The product brand is **Nova** (formerly ACDL — Agentic Cloud
> Delivery Platform). The Gitea repository name (`continuous-intelligence/acdl`)
> and the GitHub `uses:` reference (`acdl/.github/workflows/deploy.yml@…`)
> are unchanged during the rebrand transition; only the product name is
> changing. See the [Nova migration guide](NOVA_MIGRATION) for the
> scheduled breaking changes.
-21
View File
@@ -1,21 +0,0 @@
# AI Decision Accuracy — Definition of Success
> KPI: AI Decision Accuracy
> Target: ≥ 99.5% (no rollback, no follow-up incident within 5 min of action)
**What this number means:** the percentage of AI decisions (confidence-
gated policy engine outcomes) that were NOT followed by an apply failure
or incident within 5 minutes. A high-confidence decision that later
caused an incident does NOT count as accurate.
**How it's computed:** `count(decisions WHERE outcome = 'succeeded' AND
no incident within 5min)` ÷ `total decisions`. Correlation via
`decision_id``run_id` → subsequent `apply.failed` or `incident.detected`
events.
**What "good" looks like:** ≥ 99.5% means fewer than 1 in 200 decisions
cause a secondary failure. The 0.5% allowance is for novel edge cases.
**D-122 honesty:** Nova's "AI" is the confidence-gated policy engine
(confidence_signal + HITL gate), not an LLM planner. The Decision Ledger
captures this real decision path — not a fabricated "AI agent."
-18
View File
@@ -1,18 +0,0 @@
# Attestation Coverage — Definition of Success
> KPI: Attestation Coverage
> Target: 100% of prod/dr promotions attested by a human
**What this number means:** every production and disaster-recovery
promotion has a recorded human attestation (approver identity, 8-concern
matrix result, separation-of-duties check on prod). This is the
"autonomy in operations, human in accountability" proof.
**How it's computed:** `count(prod/dr promotions with attestation.recorded
event) ÷ count(total prod/dr promotions)`. Sourced from the Decision
Ledger (`attestation.recorded` events) + `hitl_gates.py` + outbox
`approver_*` attributes.
**What "good" looks like:** 100% means no prod/dr promotion ever lands
without a human sign-off on record. The absence of an operator is never
the absence of a record (NORTH_STAR Anti-Goal #3).
-16
View File
@@ -1,16 +0,0 @@
# Confidence-Gate Halt Rate — Definition of Success
> KPI: Confidence-Gate Halt Rate
> Target: not a committed target (operational signal)
**What this number means:** how often the confidence gate itself halted
a run (band = block), independent of HITL blocks. The gate is the AI's
self-halt; HITL is the human gate. This distinguishes the AI's
self-regulation from human escalation.
**How it's computed:** `count(runs WHERE confidence_band = 'block')` ÷
`total runs`.
**What "good" looks like:** a low but non-zero rate means the gate is
working (catching genuinely uncertain runs) without being overly
conservative (blocking everything).
-18
View File
@@ -1,18 +0,0 @@
# Cost Savings via Infracost Estimates — Definition of Success
> KPI: Cost Savings via Infracost Estimates
> Target: ≥ 25% on pilot estates (partial)
**What this number means:** the pre-apply cost estimate from Infracost
shows the delta between the planned infrastructure and the current
state. Negative deltas = savings.
**How it's computed:** `sum(fact_cost_estimate.delta_usd WHERE delta < 0)`
per period.
**What's grounded:** the pre-apply estimate (Infracost reads plan JSON,
offline).
**What's deferred:** actual-spend reconciliation from AWS CUR (D-096 —
needs live AWS billing). The placeholder view
`placeholder_live_cur_reconciliation.csv` has the schema ready.
-16
View File
@@ -1,16 +0,0 @@
# Decision Ledger Coverage — Definition of Success
> KPI: Decision Ledger Coverage
> Target: 100% of AI actions with backfilled outcome
**What this number means:** every AI decision (confidence-gated policy
engine outcome) is captured in the Decision Ledger with its outcome
backfilled from the subsequent apply.completed/failed event.
**How it's computed:** `count(decision_ledger rows WHERE outcome ≠
'pending') ÷ count(decision_ledger rows)`. Sourced from
`metrics/decision_ledger.db`.
**What "good" looks like:** 100% means no AI decision is ever lost or
left without an outcome. The ledger is the trust substrate (NORTH_STAR
Objective #2).
-13
View File
@@ -1,13 +0,0 @@
# Deployment Frequency — Definition of Success
> KPI: Deployment Frequency
> Target: not a committed target (operational signal)
**What this number means:** the rate of infrastructure state updates
deployed safely per day. A DORA-adjacent metric for infrastructure.
**How it's computed:** `count(run.completed WHERE exit_code = 0)` per
day.
**What "good" looks like:** multiple deploys per day (vs. weekly/monthly
for human ops teams).
-17
View File
@@ -1,17 +0,0 @@
# FTE Hours Saved (Toil Reallocation Value) — Definition of Success
> KPI: FTE Hours Saved
> Target: ≥ 70% of pre-Nova FTE allocation (derived)
**What this number means:** the engineering hours saved by automated
operations, valued at the blended engineering rate. This is what those
hours were spent on instead (the "toil reallocation" — capital freed
up from ops to feature development).
**How it's computed:** `run count × manual baseline minutes per run ÷ 60
× blended hourly rate`. The manual baseline is the estimated time a
human team would take for the same operation (e.g., 30 min/ticket).
**Honesty caveat:** computed on N internal runs today; the production-
denominator activates post-pilot. The formula is grounded; the
production numbers are not yet.
@@ -1,18 +0,0 @@
# Human Escalation Frequency — Definition of Success
> KPI: Human Escalation Frequency
> Target: < 0.1% of platform actions (Post-Pilot)
**What this number means:** how often the AI platform was forced to fall
back or escalate to a human operator due to low confidence. This is the
inverse of Touchless Resolution Rate, scoped to operational escalations
only.
**How it's computed:** `count(runs WHERE hitl_block = 1 AND reason =
'confidence')` ÷ `total runs`. Attestation sign-offs are excluded.
**What "good" looks like:** < 0.1% means fewer than 1 in 1000 runs
require human intervention. Near-zero is the goal.
**What would be "gamer metrics":** counting attestation sign-offs as
escalations (they're not — they're designed controls).
-19
View File
@@ -1,19 +0,0 @@
# MTTR (Platform-Run) — Definition of Success
> KPI: MTTR (p95)
> Target: < 60 seconds
**What this number means:** the time from a platform-run failure
(apply.failed) to a successful retry. This is platform-run MTTR, not
infra-incident MTTR (which requires an incident detection system that
Nova doesn't have yet — deferred).
**How it's computed:** p95 of `successful_retry.time failed_run.time`
across all runs that failed then succeeded.
**What "good" looks like:** < 60 seconds means the platform recovers
from a failed run in under a minute, 95% of the time.
**What's deferred:** infra-incident MTTR (anomaly detected → healed)
requires an incident detection/remediation system (self-healing
velocity). That's a future emitter.
-15
View File
@@ -1,15 +0,0 @@
# Platform ROI — Definition of Success
> KPI: Platform ROI
> Target: ≥ 250% measured annually (derived)
**What this number means:** the total financial value delivered (labor
savings + cloud cost optimization + avoided downtime losses) vs. the
platform's operational/licensing cost.
**Formula:** `(FTE hours saved × blended rate + cloud savings + avoided
downtime) ÷ platform op cost`.
**Honesty caveat:** computed on N internal runs today; the production-
denominator activates post-pilot. The formula is grounded; the
production numbers are not yet.
-14
View File
@@ -1,14 +0,0 @@
# Zero-Trust Policy Compliance Rate — Definition of Success
> KPI: Zero-Trust Policy Compliance Rate
> Target: not a committed target (operational signal)
**What this number means:** the percentage of infrastructure assets
continuously verified as compliant with security baselines and policies.
**How it's computed:** `1 count(assets WHERE last_scan.status ≠ pass)
÷ count(assets)`. Sourced from `fact_policy_check` (Checkov results).
**What "good" looks like:** 100% means every resource passed every
policy check. The Nova tagging standard (nova_tagging.py, hard mode) is
the primary check.
-13
View File
@@ -1,13 +0,0 @@
# Provisioning Lead Time — Definition of Success
> KPI: Provisioning Lead Time
> Target: not a committed target (operational signal)
**What this number means:** the time from intent received (run.started)
to apply completed (run.completed). Measures how fast Nova provisions
compliant environments.
**How it's computed:** `run.completed_at run.started_at` per run.
**What "good" looks like:** minutes, not days. The reduction from days
(human ops) to minutes (autonomous) is the velocity proof.
-23
View File
@@ -1,23 +0,0 @@
# Touchless Resolution Rate — Definition of Success
> KPI: Touchless Resolution Rate
> Target: ≥ 99% across production estates (Post-Pilot)
**What this number means:** the percentage of platform runs that complete
end-to-end without an operational HITL block. An operational HITL block
is a confidence-driven escalation (the AI's confidence was too low to
proceed). Attestation gates (qa/prod/dr sign-offs) are NOT counted as
escalations — they are designed controls, not autonomy failures.
**How it's computed:** `runs WHERE hitl_block = 0 AND environment = 'dev'`
÷ `total runs` (dev environment only, where attestation gates don't apply).
For production estates: `runs WHERE hitl_block = 0` ÷ `total runs`
excluding attestation-gate sign-offs.
**What "good" looks like:** ≥ 99% means fewer than 1 in 100 runs require
human intervention due to low confidence. The 1% allowance is for
genuine edge cases (novel failure modes, blast-radius exceedances).
**What would be "gamer metrics":** counting attestation gates as
"touchless" (they're not — they're human by design) or counting only
dev runs (cherry-picking the easiest environment).
+1 -1
View File
@@ -39,7 +39,7 @@ It is exposed to consumer repos as a **reusable workflow**:
- `.github/workflows/deploy.yml` — GitHub Actions (production) - `.github/workflows/deploy.yml` — GitHub Actions (production)
A consumer repo invokes the reusable workflow via a **versioned tag** A consumer repo invokes the reusable workflow via a **versioned tag**
(floating MAJOR + MINOR, e.g. `nova/.github/workflows/deploy.yml@v1.19`). (floating MAJOR + MINOR, e.g. `acdl/.github/workflows/deploy.yml@v1.13`).
The workflow checks out the consumer repo, then checks out the Nova platform The workflow checks out the consumer repo, then checks out the Nova platform
repo into the runner workspace, and runs `scripts/run_platform.sh` against repo into the runner workspace, and runs `scripts/run_platform.sh` against
the consumer's contract. The consumer never clones the platform repo or the consumer's contract. The consumer never clones the platform repo or
+2 -2
View File
@@ -26,7 +26,7 @@ tag** in a consumer's CI workflow definition:
```yaml ```yaml
jobs: jobs:
deploy: deploy:
uses: nova/.github/workflows/deploy.yml@v1.19 uses: acdl/.github/workflows/deploy.yml@v1.13
with: with:
contract: .nova/contract.yml contract: .nova/contract.yml
``` ```
@@ -36,7 +36,7 @@ itself — the contract no longer carries a `uses:` field). The CI workflow
`uses:` tag is the only immutability lever a consumer has. `uses:` tag is the only immutability lever a consumer has.
**Unversioned references are discouraged.** Do not use `@main` or a bare **Unversioned references are discouraged.** Do not use `@main` or a bare
`nova/.github/workflows/deploy.yml``main` is constantly updated and can `acdl/.github/workflows/deploy.yml``main` is constantly updated and can
cause unexpected failures. Pinning to a MAJOR+MINOR tag means: cause unexpected failures. Pinning to a MAJOR+MINOR tag means:
- **Immutability** — the pipeline behavior you tested is the behavior you - **Immutability** — the pipeline behavior you tested is the behavior you
+256 -190
View File
@@ -2,184 +2,227 @@
Leadership-facing presentation decks for the Nova platform. Leadership-facing presentation decks for the Nova platform.
## The 3-step slide creation process ## The 4-step slide creation process
Every presentation in this folder is produced by the same three-step Every presentation in this folder is produced by the same four-step process.
process. **Never edit the rendered HTML, either PPTX, or the talking **Never edit the Marp deck, the PPTX, or the talking points directly** —
points directly** — always start from the Marp deck source of truth always start from the full markdown source of truth (Step 1), synthesize the
(Step 1), render it (Step 2), then distill the talking points (Step 3). Marp deck (Step 2), export to HTML + PPTX (Step 3), then distill the talking
This keeps a reviewable, plain-text source of truth for every deck and a points (Step 4). This keeps a reviewable, plain-text source of truth for
presenter-ready cue sheet for delivery. every deck and a presenter-ready cue sheet for delivery.
``` ```
Step 1: Author the deck Step 2: Render Step 3: Talking points Step 1: full markdown Step 2: Marp deck Step 3: HTML + PPTX Step 4: Talking points
(source of truth) ──► (HTML + dual PPTX) ──► (presenter cues) (source of truth) ──► (lean, 10 slides) ──► (rendered) ──► (presenter cues)
*-marp.md *.html *-talking-points.md *.md *-marp.md *.html / *.pptx *-talking-points.md
+ ## Slide N — Title + mermaid PNGs + 3-6 bullets per slide + speaker notes + embedded PNG diagrams + 3-6 bullets per slide
+ <!-- Speaker notes: --> + MARP PPTX (image-of-slide) + key takeaway per slide + mermaid code blocks + Marp frontmatter + key takeaway per slide
+ <!-- Talking points: --> + python PPTX (structured) + indexed by slide # + maturity badges + indexed by Marp slide #
+ <div class="benefit"> + base64-inlined HTML + content distilled from + no speaker notes + content distilled from Step 1
+ embedded PNG diagrams (self-contained) the Marp deck
``` ```
### Step 1 — Author the deck (source of truth) ### Step 1 — Full markdown (source of truth)
**File convention:** `<deck-name>-marp.md` (e.g. **File convention:** `<deck-name>.md` (e.g. `how-the-platform-works.md`).
`nova-autonomous-cloud-delivery-marp.md`).
This is the **sole source of truth** — the Marp deck that is both authored Write the complete deck as a standard markdown file. This is the **source of
and rendered. It contains: truth** — it contains:
- Every slide as an `## Slide N — Title` H2 section.
- Tight bullets with leadership-relevant content.
- A `> **Speaker notes:**` block at the end of each slide with the nuance,
the "who cares and why," and the honesty caveats.
- Mermaid diagrams as ```` ```mermaid ```` fenced code blocks (these render
on GitHub/Pages but not in Marp — Step 2 converts them to images).
- An honest "shipped vs. planned" framing: every "available today" claim is
grounded in shipped/verified work; every "planned" item is explicitly
marked.
**Why this file is the source of truth:** it is reviewable in any markdown
viewer, diffs cleanly in git, and carries the full reasoning (speaker notes)
that a presenter needs. The Marp deck and PPTX are *derived artifacts* — if a
fact is wrong, fix it here and re-run Steps 2 and 3.
### Step 2 — Marp deck synthesis
**File convention:** `<deck-name>-marp.md` (e.g. `how-the-platform-works-marp.md`).
Synthesize the full markdown into a lean Marp deck:
- **Marp frontmatter** at the top: `marp: true`, `theme: default`, - **Marp frontmatter** at the top: `marp: true`, `theme: default`,
`paginate: true`, `size: 16x9`, a header/footer, and an inline `style:` `paginate: true`, `size: 16x9`, a header/footer, and an inline `style:`
block carrying the S&P palette (`#D6002A` red, `#1B1B1B` black, the block for fonts, colors, tables, badges.
`section.title` rule). The styling is **inline** — no standalone theme - **No speaker notes.** The Marp deck is what the audience sees; the
CSS is loaded at render time. speaker notes live only in the Step 1 source of truth.
- Every slide as an `## Slide N — Title` (or `## Appendix A1 — Title`) H2 - **Mermaid diagrams → PNG images.** Marp does not render mermaid fenced
section. The H1 title slide precedes slide 1. blocks natively. Extract each mermaid block from Step 1 into a `.mmd`
- Tight bullets with leadership-relevant content. source file under `assets/mmd/`, render it to PNG under `assets/png/`,
- **Speaker notes** as `<!-- Speaker notes: ... -->` HTML comments at the and embed it with `![w:1000](assets/png/<name>.png)`.
end of each slide. Marp excludes HTML comments from the rendered slide; - **`<!-- _class: title -->` + `<!-- _paginate: false -->`** on title and
they are for authors/presenters only. closing slides for the dark-background title style.
- **Talking points** as `<!-- Talking points: ... -->` HTML comments (also - **Maturity badges** using inline spans:
excluded from rendering — Step 3 mirrors them into a standalone cue `<span class="badge planned">Planned</span>`
sheet). - **Tighter prose** than Step 1 — strip the speaker-note nuance; keep the
- **Benefit callouts** as `<div class="benefit">...</div>` (styled by the leadership-relevant selling points.
inline `style:` block — italic, S&P-red top border). No `**Benefit:**`
text prefixes.
- Mermaid diagrams **pre-rendered to PNG** under `assets/png/` and embedded
with `![w:1000](assets/png/<name>.png)` (or `h:480 class:tall` for tall
images). The `.mmd` sources live under `assets/mmd/`.
- **No maturity badges**, **no version in the footer**, **no internal
decision/requirement IDs or `.py` file paths** in the slide bodies
(those live in the `.ciagent/` files only; speaker-note HTML comments are
exempt).
- An honest "shipped vs. deferred" framing: every "available today" claim
is grounded in shipped/verified work; every "deferred" item is explicitly
marked with the blocking work in plain language.
**Why the Marp deck is the source of truth:** it is reviewable in any ### Step 3 — Render to HTML and PPTX
markdown viewer, diffs cleanly in git, and carries the full reasoning
(speaker notes) that a presenter needs. The HTML and PPTX are *derived
artifacts* — if a fact is wrong, fix it here and re-run Step 2.
> **`nova-sp-theme.css` is RETIRED from render.** The standalone theme Both formats are derived from the Marp deck. **HTML is committed to the repo**
> stylesheet under `assets/nova-sp-theme.css` is kept as a **reference (viewable in any browser, self-contained with base64-embedded images). **PPTX
> only** and is **not loaded at render time**. The live styling is the is uploaded to the Gitea release** as a downloadable attachment (binary, not
> inline `style:` block in the `-marp.md` frontmatter. Do NOT pass the CSS committed to git).
> via `--theme`; it is not in the render path.
### Step 2 — Render (HTML + dual PPTX) #### HTML export (committed to repo)
`bash scripts/render_slides.sh [deck-name]` renders the Marp deck
end-to-end:
1. **Mermaid PNGs** — each `assets/mmd/*.mmd``assets/png/*.png`
(S&P-themed via `sp-theme.json`, 2x scale, transparent background).
2. **MARP HTML**`*-marp.md``*.html` (S&P inline style, Marp default
theme). Pinned `@marp-team/marp-cli@4.5.0`.
3. **MARP PPTX**`*-marp.md``*.pptx` (image-of-slide PPTX; the primary
release attachment).
4. **Inline images**`scripts/inline_images.py` rewrites the HTML to
base64-embed every `assets/` image so the HTML is self-contained (no
external asset folder needed for redistribution).
5. **python PPTX**`scripts/render_pptx.py` produces a second,
structured, editable PPTX (`*-python.pptx`) with native text boxes,
native tables, embedded pictures, and italic benefit callouts.
6. **Stage** — all rendered artifacts (PNGs + HTML + both PPTX) are
`git add`-ed for commit.
```bash ```bash
bash scripts/render_slides.sh nova-autonomous-cloud-delivery CHROME_PATH=/root/.cache/ms-playwright/chromium-1217/chrome-linux64/chrome \
npx --yes @marp-team/marp-cli@latest --allow-local-files \
docs/presentations/<deck-name>-marp.md \
-o docs/presentations/<deck-name>.html
``` ```
Both the HTML and both PPTX files are committed to the repo; the MARP HTML export inlines images as base64 data URIs — no `--allow-local-files`
PPTX is also attached to the phase's release via needed for self-contained output, but it's required when the Marp deck
`scripts/attach_release_asset.py`. references local PNG assets. The resulting HTML is a single self-contained
file that renders the full deck with the S&P Global Energy theme.
#### Dual-PPTX output **Re-render the HTML whenever the Marp source changes.** The HTML files are
committed artifacts, not generated on-the-fly — they must be re-rendered and
re-committed when the Marp deck is updated.
| PPTX | File | Render | Purpose | #### PPTX export (uploaded to Gitea release)
|---|---|---|---|
| **MARP PPTX** | `*.pptx` | `@marp-team/marp-cli` (Chrome screenshot of each slide) | Image-of-slide; the primary release attachment (pixel-perfect, not editable) |
| **python PPTX** | `*-python.pptx` | `scripts/render_pptx.py` (python-pptx) | Structured, editable PPTX (native text boxes, tables, pictures) for comparison/editing |
### Step 3 — Talking points (presenter cues) ```bash
CHROME_PATH=/root/.cache/ms-playwright/chromium-1217/chrome-linux64/chrome \
npx --yes @marp-team/marp-cli@latest --allow-local-files \
docs/presentations/<deck-name>-marp.md \
-o <output-path>.pptx
```
The `--allow-local-files` flag is **required** for PPTX export so the local
PNG diagrams are embedded in the file. PPTX files are not committed to the
repo (binary, no meaningful diffs) — they are uploaded to the Gitea release
as downloadable attachments.
### Step 4 — Talking points (presenter cues)
**File convention:** `<deck-name>-talking-points.md` (e.g. **File convention:** `<deck-name>-talking-points.md` (e.g.
`nova-autonomous-cloud-delivery-talking-points.md`). `how-the-platform-works-talking-points.md`).
Distill the deck's `<!-- Talking points: -->` HTML comments into Distill the source of truth (Step 1) into presenter-ready cues, indexed by
presenter-ready cues, indexed by the Marp deck (Step 1) slide structure: the Marp deck (Step 2) slide structure:
- **One section per Marp slide**`## Slide N — Title`, matching the Marp - **One section per Marp slide**`## Slide N — Title`, matching the Marp
deck's 20 main + 1 appendix slide structure exactly. deck's 11 main + Appendix TOC + appendix slide structure exactly. The Marp deck
- **3-6 talking point bullets per slide** — punchy, actionable cues provides the indexing and context (what the audience sees); the source
distilled from the Marp deck's `<!-- Talking points: -->` comments. markdown provides the content (the speaker notes, the detail, the nuance).
- **3-6 talking point bullets per slide** — punchy, actionable cues distilled
from the source markdown's speaker notes. NOT the speaker notes verbatim
(those are too long and too contextual). These are prompts: "Land this
point," "Contrast with X," "Be honest about Y."
- **Key takeaway per slide** — the one memorable thing the audience should - **Key takeaway per slide** — the one memorable thing the audience should
walk away with from that slide. walk away with from that slide.
- **No content duplication** — the talking points reference the Marp - **No content duplication** — the talking points reference the Marp slides
slides for visual context. for visual context and the source markdown for full detail. They don't
repeat either; they bridge them.
**Why this file exists:** a presenter needs a cue sheet they can glance at
during delivery — not the full speaker notes (too long), not the Marp slides
(no detail). The talking points file is the middle layer: what to say, in
what order, with what emphasis, per slide.
**When to update:** re-distill the talking points whenever the Marp deck
structure changes (slides added, removed, merged, or re-ordered) or whenever
the source markdown's speaker notes are updated. The talking points are a
*derived artifact* — if a fact is wrong, fix it in the source markdown (Step 1)
and re-distill.
## Directory layout ## Directory layout
``` ```
docs/presentations/ docs/presentations/
├── README.md ← this file ├── README.md ← this file
├── nova-autonomous-cloud-delivery-marp.md ← Step 1: sole source of truth (title + 20 main + 1 appendix = 22 slides + speaker notes + talking points) ├── how-the-platform-works.md ← Step 1: full source of truth
├── nova-autonomous-cloud-delivery.html ← Step 2: rendered HTML (committed, S&P inline style, base64-inlined images) ├── how-the-platform-works-marp.md ← Step 2: Marp deck (11 main + TOC + 8 appendix = 20)
├── nova-autonomous-cloud-delivery.pptx ← Step 2: MARP PPTX (image-of-slide, primary release attachment) ├── how-the-platform-works.html ← Step 3: rendered HTML (committed)
├── nova-autonomous-cloud-delivery-python.pptx ← Step 2: python-pptx (structured, editable) ├── how-the-platform-works-talking-points.md ← Step 4: presenter cues (20 sections)
├── nova-autonomous-cloud-delivery-talking-points.md ← Step 3: presenter cues (21 sections) ├── the-developer-experience.md ← Step 1: full source of truth
├── the-developer-experience-marp.md ← Step 2: Marp deck (11 main + TOC + 7 appendix = 19)
├── the-developer-experience.html ← Step 3: rendered HTML (committed)
├── the-developer-experience-talking-points.md ← Step 4: presenter cues (19 sections)
└── assets/ └── assets/
├── nova-sp-theme.css ← RETIRED from render — reference only (not loaded; live styling is the inline `style:` block)
├── puppeteer-config.json ← no-sandbox config for mmdc ├── puppeteer-config.json ← no-sandbox config for mmdc
├── mmd/ ← mermaid source files (Step 2 input) ├── mmd/ ← mermaid source files (Step 2 input)
│ ├── sp-theme.json ← S&P Red/Black/White theme (mermaid-cli --configFile) │ ├── sp-theme.json ← S&P Red/Black/White theme (mermaid-cli --configFile)
── ... (per-slide .mmd files) ── platform-works-01-contract-driven.mmd
└── png/ ← rendered mermaid PNGs (committed, S&P-themed, 2x, transparent) │ ├── platform-works-02-frictions.mmd
│ ├── platform-works-02-end-to-end-flow.mmd
│ ├── platform-works-03-north-star.mmd
│ ├── platform-works-03-scope-boundary.mmd
│ ├── platform-works-04-confidence-signal.mmd
│ ├── platform-works-05-attestation-flow.mmd
│ ├── platform-works-07-zero-trust.mmd
│ ├── developer-experience-01b-scope-boundary.mmd
│ ├── developer-experience-02-what-dev-does.mmd
│ ├── developer-experience-03-no-cloning.mmd
│ ├── developer-experience-04-promotion-journey.mmd
│ ├── developer-experience-05-catalog.mmd
│ ├── developer-experience-07-decommission.mmd
│ ├── developer-experience-08-semver.mmd
│ ├── platform-architecture.mmd ← shared high-level logical architecture (both decks)
│ └── road-to-north-star.mmd
└── png/ ← rendered PNGs (embedded in Marp)
├── platform-works-01-contract-driven.png
├── platform-works-02-frictions.png
├── platform-works-02-end-to-end-flow.png
├── platform-works-03-north-star.png
├── platform-works-03-scope-boundary.png
├── platform-works-04-confidence-signal.png
├── platform-works-05-attestation-flow.png
├── platform-works-07-zero-trust.png
├── developer-experience-01b-scope-boundary.png
├── developer-experience-02-what-dev-does.png
├── developer-experience-03-no-cloning.png
├── developer-experience-04-promotion-journey.png
├── developer-experience-05-catalog.png
├── developer-experience-07-decommission.png
├── developer-experience-08-semver.png
├── platform-architecture.png ← shared high-level logical architecture (both decks)
└── road-to-north-star.png
``` ```
## Tooling & scripts
| Script | Purpose |
|---|---|
| `scripts/render_slides.sh` | End-to-end render: mermaid PNGs → MARP HTML + PPTX → base64-inlined HTML → python-pptx PPTX → stage all artifacts. Pinned `@marp-team/marp-cli@4.5.0` + `@mermaid-js/mermaid-cli@11.16.0`. |
| `scripts/inline_images.py` | Rewrites the rendered HTML to base64-embed every `assets/` image (self-contained HTML for redistribution). |
| `scripts/render_pptx.py` | Produces the structured, editable `*-python.pptx` (native text boxes, tables, pictures, italic benefit callouts) via `python-pptx`. |
| `scripts/attach_release_asset.py` | Attaches the MARP PPTX to the phase's release. |
| Dependency | Where declared | Purpose |
|---|---|---|
| `@marp-team/marp-cli@4.5.0` | `scripts/render_slides.sh` (pinned) | Marp → HTML + PPTX |
| `@mermaid-js/mermaid-cli@11.16.0` | `scripts/render_slides.sh` (pinned) | Mermaid → PNG |
| `python-pptx>=0.6.23` | `pyproject.toml` `[project.optional-dependencies] slides` | Structured PPTX (`pip install -e ".[slides]"`) |
## Conventions ## Conventions
### Slide structure ### Appendix structure
Each Marp deck has **1 title slide + 20 main slides + 1 appendix slide = 22 Each Marp deck has **11 main slides + an Appendix TOC + appendix slides**. The
rendered slides** (21 `## ` sections + the H1 title slide). The main 20 main 11 are the presentation; the appendix is for deep dives and Q&A backup.
are the presentation; the appendix is for Q&A backup. (v1.22 split slides The platform-works deck has 8 appendix slides (A1A8); the developer-experience
3 and 8 to relieve overflow, increasing the main count from 18 to 20.) deck has 7 appendix slides (A1A7). Both include an Appendix TOC slide.
- **Title slide** (H1): `<!-- _class: title -->` + `<!-- _paginate: false -->` - **Main slides** (1-11): the story arc, high-impact, minimal text,
for the dark-background title style (S&P-red top border on black). visual-heavy. These are what the audience sees during the talk.
- **Main slides** (1-20): the story arc — Problem → Solution → Proof → - **Appendix slides** (TOC + A1..An): detail-heavy slides moved out of the
Roadmap + Ask. These are what the audience sees during the talk. main 10 to preserve the narrative flow. The appendix starts with a TOC
- **Appendix slide** (A1): the Metrics Glossary — detail-heavy reference slide listing the contents, followed by detail slides and a glossary.
for Q&A. - **The Road to the North Star** is a required appendix slide in both decks
— a phased timeline from v1.0 demo to the North Star, annotated as
"proposed phasing, not formally planned."
- **The Glossary** is a required appendix slide in both decks — defines
acronyms (OIDC, ABAC, CMK, CMDB, RPO, HITL, VCS, NFR) for the audience.
### Honesty framing ### Maturity framing
Every capability claim in the deck is grounded, derived, or honestly Every capability claim in a deck is tagged with a `Planned` badge when the item is on the roadmap but not yet implemented:
deferred with its blocking work named in plain language. Internal
provenance (decision IDs, requirement IDs, internal file paths) is kept | Badge | Meaning |
out of the audience-facing slide bodies — those live in the `.ciagent/` |---|---|
files only (and may appear inside `<!-- ... -->` speaker-note comments, | `Planned` | On the roadmap, not yet implemented |
which Marp excludes from the rendered slide). When in doubt, check
`.ciagent/ROADMAP.md` and the milestone status in `.ciagent/PROJECT.md`. This is non-negotiable for a leadership audience: never present a roadmap
item as a current capability, and never bury a tested capability's
availability. When in doubt, check `.ciagent/ROADMAP.md` and the milestone
status in `.ciagent/PROJECT.md`.
### Audience ### Audience
@@ -191,72 +234,105 @@ Head of Infrastructure, Head of DevOps. The framing rules:
"composition." "composition."
- **Selling points forward.** Each slide leads with the leadership-relevant - **Selling points forward.** Each slide leads with the leadership-relevant
outcome; the mechanism follows. outcome; the mechanism follows.
- **Security, remediation velocity, reliability, lead time, observability, - **Zero-trust, security, observability, auditability, DX, citizen
citizen developer** are the themes — not implementation details. developer** are the themes — not implementation details.
- **"Infrastructure operations become visible"** is the recurring theme
across the deck.
### Diagrams ### Diagrams
Mermaid diagrams are authored as `assets/mmd/*.mmd` source files and Mermaid diagrams in the Step 1 source use the repo's existing `flowchart`
rendered to PNG under `assets/png/`: style (renders on GitHub/Pages). For the Marp deck (Step 2):
1. Author the mermaid block as `assets/mmd/<deck>-<slide>-<name>.mmd`. 1. Extract the mermaid block into `assets/mmd/<deck>-<slide>-<name>.mmd`.
2. Use **horizontal layouts** (`flowchart LR`) or **subgraph row-wrapping** 2. Use **horizontal layouts** (`flowchart LR`) or **subgraph row-wrapping**
for wide diagrams so the PNG fits a 16:9 slide without shrinking to for wide diagrams so the PNG fits a 16:9 slide without shrinking to
illegibility. illegibility. A 9-node sequential `flowchart TD` renders as a tall thin
3. Render with a 2x scale factor and transparent background for crisp strip — restructure it as 2-row subgraphs or `flowchart LR`.
slides (`scripts/render_slides.sh` does this with the S&P theme JSON). 3. Render with a 2x scale factor and transparent background for crisp slides.
4. Embed with `![w:1000](assets/png/<name>.png)` (or `h:480 class:tall` 4. Embed with `![w:1000](assets/png/<name>.png)` (or `h:320` for tall images).
for tall images).
5. The render pipeline base64-inlines the PNGs into the committed HTML so
the HTML is self-contained.
## Build commands ## Build commands
### Prerequisites ### Prerequisites
- **Node.js + npx** (for `@marp-team/marp-cli` and `@mermaid-js/mermaid-cli`) - Node.js + npx (for `@marp-team/marp-cli` and `@mermaid-js/mermaid-cli`)
- **A Chrome/Chromium binary** (Marp PPTX export requires it) - A Chrome/Chromium binary (Marp PPTX export requires it)
- **Python 3.10+** with the `slides` extra: `pip install -e ".[slides]"`
(installs `python-pptx>=0.6.23`)
This environment has a working Chromium at: This environment has a working Chromium at:
`/root/.cache/ms-playwright/chromium-1217/chrome-linux64/chrome` `/root/.cache/ms-playwright/chromium-1217/chrome-linux64/chrome`
### Render the deck (HTML + dual PPTX + inlined images) ### Render all mermaid diagrams to PNG
```bash ```bash
bash scripts/render_slides.sh nova-autonomous-cloud-delivery cd docs/presentations/assets
for f in mmd/*.mmd; do
name=$(basename "$f" .mmd)
PUPPETEER_EXECUTABLE_PATH=/root/.cache/ms-playwright/chromium-1217/chrome-linux64/chrome \
npx --yes @mermaid-js/mermaid-cli@latest \
-i "$f" -o "png/$name.png" \
-p puppeteer-config.json -s 2 -b transparent \
--configFile mmd/sp-theme.json
done
``` ```
This renders all mermaid PNGs, the HTML (with base64-inlined images), the The `puppeteer-config.json` passes `--no-sandbox` to the headless browser
MARP PPTX, and the python-pptx PPTX, and stages them for commit. Both (required when running as root in this environment). The `--configFile
HTML and both PPTX files are committed to the repo; the MARP PPTX is also mmd/sp-theme.json` applies the S&P Global Red/Black/White theme (dark
attached to the phase's release. `#1B1B1B` accent nodes with `#D6002A` red borders, white supporting nodes,
`#F0F0F0` subgraph backgrounds). Each `.mmd` file also carries the same
theme inline via a `%%{init:...}%%` block so it renders correctly even
without the `--configFile` flag.
### Export a Marp deck to HTML (committed to repo)
```bash
CHROME_PATH=/root/.cache/ms-playwright/chromium-1217/chrome-linux64/chrome \
npx --yes @marp-team/marp-cli@latest --allow-local-files \
docs/presentations/<deck-name>-marp.md \
-o docs/presentations/<deck-name>.html
```
HTML export inlines images as base64 data URIs. The `--allow-local-files`
flag is needed when the Marp deck references local PNG assets (like the
diagram images in `assets/png/`). The resulting HTML is self-contained.
**The HTML files are committed artifacts** — re-render and re-commit whenever
the Marp source changes.
### Export a Marp deck to PPTX (uploaded to Gitea release)
```bash
CHROME_PATH=/root/.cache/ms-playwright/chromium-1217/chrome-linux64/chrome \
npx --yes @marp-team/marp-cli@latest --allow-local-files \
docs/presentations/<deck-name>-marp.md \
-o <output-path>.pptx
```
`--allow-local-files` is **required** for PPTX so local PNG diagrams are
embedded in the file. PPTX files are not committed to git — upload them as
attachments to the Gitea release.
## Adding a new presentation ## Adding a new presentation
1. **Author the Marp deck** as `<deck-name>-marp.md` — frontmatter 1. **Write the full markdown** as `<deck-name>.md` following the
(`marp: true`, `theme: default`, `paginate: true`, `size: 16x9`, an `## Slide N — Title` + `> **Speaker notes:**` structure. This is the
inline `style:` block with the S&P palette), `## Slide N — Title` source of truth.
sections, `<!-- Speaker notes: -->` + `<!-- Talking points: -->` HTML 2. **Extract any mermaid diagrams** into `assets/mmd/<deck-name>-<slide>-<name>.mmd`
comments, and `<div class="benefit">` callouts. This is the sole source and render them to `assets/png/` (command above).
of truth. 3. **Synthesize the Marp deck** as `<deck-name>-marp.md` with frontmatter,
2. **Author any mermaid diagrams** as `assets/mmd/<deck-name>-<slide>-<name>.mmd` no speaker notes, embedded PNGs, and maturity badges.
(Step 2 renders them to `assets/png/`). 4. **Render to HTML** with `--allow-local-files` and commit the HTML to
3. **Render** via `bash scripts/render_slides.sh <deck-name>` — this `docs/presentations/<deck-name>.html`.
produces the HTML (base64-inlined), the MARP PPTX, and the python-pptx 5. **Render to PPTX** with `--allow-local-files` and upload to the Gitea
PPTX, and stages all of them (plus the PNGs) for commit. release (do not commit PPTX to git).
4. **Distill the talking points** as `<deck-name>-talking-points.md` — one 6. **Distill the talking points** as `<deck-name>-talking-points.md` — one
section per Marp slide, 3-6 talking point bullets + key takeaway, section per Marp slide, 3-6 talking point bullets + key takeaway, content
content distilled from the Marp deck's `<!-- Talking points: -->` distilled from the source markdown (Step 1), indexed by the Marp deck
comments, indexed by the Marp deck slide structure. (Step 2) slide structure.
5. **Verify** the PPTX slide count and that media files are embedded: 7. **Verify** the PPTX slide count and that media files are embedded:
```bash ```bash
python3 -c " python3 -c "
import zipfile, re import zipfile, re
with zipfile.ZipFile('docs/presentations/<deck-name>.pptx') as z: with zipfile.ZipFile('<output>.pptx') as z:
slides = [n for n in z.namelist() if re.match(r'ppt/slides/slide\d+\.xml$', n)] slides = [n for n in z.namelist() if re.match(r'ppt/slides/slide\d+\.xml$', n)]
media = [n for n in z.namelist() if n.startswith('ppt/media/')] media = [n for n in z.namelist() if n.startswith('ppt/media/')]
print(f'{len(slides)} slides, {len(media)} media files') print(f'{len(slides)} slides, {len(media)} media files')
@@ -265,17 +341,7 @@ attached to the phase's release.
## Current decks ## Current decks
| Deck | Source of truth (Step 1) | Rendered HTML + dual PPTX (Step 2) | Talking points (Step 3) | Slides | Audience | | Deck | Source of truth (Step 1) | Marp deck (Step 2) | Rendered HTML (Step 3) | Talking points (Step 4) | Slides | Audience |
|---|---|---|---|---|---| |---|---|---|---|---|---|---|
| Nova — The Autonomous Cloud Delivery Platform | `nova-autonomous-cloud-delivery-marp.md` | `nova-autonomous-cloud-delivery.html` (inlined) + `nova-autonomous-cloud-delivery.pptx` (MARP, release-attached) + `nova-autonomous-cloud-delivery-python.pptx` (structured) | `nova-autonomous-cloud-delivery-talking-points.md` | title + 20 main + 1 appendix (22) | CTO, Head of Cloud, Head of Infra, Head of DevOps | | How the Platform Works | `how-the-platform-works.md` | `how-the-platform-works-marp.md` | `how-the-platform-works.html` | `how-the-platform-works-talking-points.md` | 11 main + TOC + 8 appendix (20) | CTO, Head of Cloud, Head of Infra, Head of DevOps |
| The Developer Experience | `the-developer-experience.md` | `the-developer-experience-marp.md` | `the-developer-experience.html` | `the-developer-experience-talking-points.md` | 11 main + TOC + 7 appendix (19) | CTO, Head of Cloud, Head of Infra, Head of DevOps |
> **v1.23:** the slide creation process collapsed from 4 steps to 3 — the
> plain `<deck-name>.md` was deleted; `<deck-name>-marp.md` is now the
> sole source of truth. The standalone `nova-sp-theme.css` was retired
> from render (the live styling is the inline `style:` block in the
> `-marp.md` frontmatter; the CSS file is retained as a reference only).
> Speaker notes moved from blockquotes into `<!-- Speaker notes: -->`
> HTML comments. Benefit callouts moved from `**Benefit:**` prefixes to
> `<div class="benefit">`. The render pipeline now produces a dual-PPTX
> output (MARP image-of-slide + python-pptx structured) and base64-inlines
> all images into the committed HTML.
@@ -1,11 +0,0 @@
%%{init: {"theme": "base", "themeVariables": {"primaryColor": "#1B1B1B", "primaryBorderColor": "#D6002A", "primaryTextColor": "#fff", "secondaryColor": "#fff", "secondaryBorderColor": "#D6002A", "secondaryTextColor": "#1B1B1B", "tertiaryColor": "#F0F0F0", "clusterBkg": "#F0F0F0", "lineColor": "#1B1B1B", "fontFamily": "\"Akkurat Pro\", \"Helvetica Neue\", \"Arial\", sans-serif"}}}%%
flowchart TB
A["Contract → Resolver → Adapter"] --> D["Checkov (static code)"]
D --> E["Terraform plan"]
E --> F["Wiz (on plan) → Confidence signal → Stage gate"]
F --> I["Apply → Evidence + Ledger"]
classDef accent fill:#1B1B1B,color:#fff,stroke:#D6002A,stroke-width:2px
classDef supporting fill:#fff,color:#1B1B1B,stroke:#D6002A,stroke-width:1px
class D,E,F accent
class A,I supporting
@@ -1,17 +0,0 @@
%%{init: {"theme": "base", "themeVariables": {"primaryColor": "#1B1B1B", "primaryBorderColor": "#D6002A", "primaryTextColor": "#fff", "secondaryColor": "#fff", "secondaryBorderColor": "#D6002A", "secondaryTextColor": "#1B1B1B", "tertiaryColor": "#F0F0F0", "clusterBkg": "#F0F0F0", "lineColor": "#1B1B1B", "fontFamily": "\"Akkurat Pro\", \"Helvetica Neue\", \"Arial\", sans-serif"}}}%%
flowchart TB
A["Platform<br/>components"] --> B["CloudEvents<br/>envelope"]
B --> C["Event log"]
B --> D["Decision<br/>ledger"]
B --> E["Run records"]
C --> F["Collector"]
D --> F
E --> F
F --> G["Cold store"]
G --> H["PowerBI<br/>views"]
H --> I["Live ops<br/>dashboard"]
classDef accent fill:#1B1B1B,color:#fff,stroke:#D6002A,stroke-width:2px
classDef supporting fill:#fff,color:#1B1B1B,stroke:#D6002A,stroke-width:1px
class B,F,G,H,I accent
class A,C,D,E supporting
-136
View File
@@ -1,136 +0,0 @@
/* RETAINED AS REFERENCE ONLY not loaded at render time.
* The live deck uses Marp `default` theme + an inline `style:` block in
* the -marp.md frontmatter. This file is kept for future styling work
* reference. Do NOT pass via `--theme`; it is not in the render path.
*/
/* @theme nova-sp */
/* Nova S&P Global Energy theme for Marp decks.
*
* Palette: S&P Red (#D6002A), Black (#1B1B1B), White (#FFFFFF), Grey (#F0F0F0).
* Font: Akkurat Pro (fallback Helvetica Neue / Arial).
*
* This theme is a STANDALONE stylesheet (applied via `marp --theme
* nova-sp-theme.css`). It does NOT `@import "default"` because Marp's
* default theme applies `padding: 56px 64px` (which does not reserve
* header/footer space) and other base styles (font, color, list spacing)
* that would conflict with the S&P palette. Instead, this theme sets
* the padding explicitly: 48px top (reserves header space), 40px bottom
* (reserves footer space), 56px sides. This gives precise control over
* the padding budget. (GRILL revision 2 @import rejection documented.)
*
* v1.22 (REQ-254,255,256): added section padding + overflow handling,
* aspect-ratio-aware image rules, title-slide chrome suppression,
* paragraph/list/table spacing tightening.
*/
:root {
--sp-red: #D6002A;
--sp-black: #1B1B1B;
--sp-white: #FFFFFF;
--sp-grey: #F0F0F0;
--sp-dark-grey: #2E2E2E;
}
/* Base section padding reserves header (top) + footer (bottom) space.
* REQ-254: zero padding was the root cause of "out of whack" layout.
* 48px top reserves header chrome; 40px bottom reserves footer chrome;
* 56px sides give breathing room. */
section {
font-family: "Akkurat Pro", "Helvetica Neue", "Arial", sans-serif;
font-size: 22px;
color: var(--sp-black);
background: var(--sp-white);
padding: 48px 56px 40px;
overflow: auto;
}
/* Headings — S&P Red */
h1 { color: var(--sp-red); font-size: 34px; margin-bottom: 0.3em; }
h2 { color: var(--sp-red); font-size: 26px; margin-bottom: 0.2em; }
h3 { color: var(--sp-red); font-size: 22px; margin-bottom: 0.2em; }
h4 { color: var(--sp-dark-grey); font-size: 20px; margin-bottom: 0.15em; }
/* REQ-256: tighten h2 + lead-paragraph spacing (the deck's recurring
* `## Slide N Title` + `**bold lead**` pattern). Default <p> margins
* waste ~44px per slide; this reclaims ~22px. */
section h2 + p { margin-top: 0.2em; }
section p { margin: 0.4em 0; }
/* Title slides — black background, red top border */
section.title {
background: var(--sp-black);
color: var(--sp-white);
border-top: 8px solid var(--sp-red);
}
section.title h1 { color: var(--sp-white); }
section.title h2 { color: var(--sp-white); }
/* REQ-256: suppress header/footer chrome on title slides. The
* `<!-- _class: title -->` + `<!-- _paginate: false -->` directives
* only suppress the page number, not the chrome. This prevents the
* header/footer from colliding with title/appendix content. */
section.title header, section.title footer { display: none; }
/* Tables — grey header with red underline, explicit white body for readability on any background */
table { font-size: 18px; width: 100%; border-collapse: collapse; background: var(--sp-white); }
th { background: var(--sp-grey); border-bottom: 2px solid var(--sp-red); padding: 4px 8px; text-align: left; }
td { background: var(--sp-white); color: var(--sp-black); border-bottom: 1px solid var(--sp-grey); padding: 4px 8px; }
/* Ensure tables on dark/title slides remain readable: white card with a subtle border */
section.title table, section table { background: var(--sp-white); }
section.title td, section td { background: var(--sp-white); color: var(--sp-black); }
section.title th, section th { background: var(--sp-grey); color: var(--sp-black); }
/* REQ-256: dense tables (8 rows) use tighter cell padding so 10-13 row
* tables (slides 8, 12, A1) fit. Apply via `table.dense` class in the
* marp deck. */
table.dense td, table.dense th { padding: 4px 8px; }
table.dense { font-size: 16px; }
/* Blockquotes — red left border */
blockquote { border-left: 4px solid var(--sp-red); color: var(--sp-dark-grey); font-size: 20px; padding-left: 12px; }
/* Code — dark background */
pre { background: var(--sp-black); color: var(--sp-white); border-radius: 4px; padding: 12px; font-size: 16px; }
code { background: var(--sp-grey); color: var(--sp-black); border-radius: 2px; padding: 1px 4px; font-size: 18px; }
pre code { background: transparent; color: inherit; }
/* REQ-255: aspect-ratio-aware image rules. The blunt `max-height: 320px`
* broke `w:` directives on tall images (slide 9) and did nothing for
* ultra-wide images (slide 6). The new rule uses `object-fit: contain`
* and `max-width: 100%` so images scale within the content area without
* ignoring explicit `w:`/`h:` directives. */
img { display: block; margin: 0 auto; max-width: 100%; max-height: 380px; object-fit: contain; }
/* Wide diagrams (ultra-wide aspect): tighter max-height so they don't
* render as a thin strip. Apply via `![w:1000 class:wide]` or rely on
* the default max-height which is already tighter. */
img.wide { max-height: 280px; }
/* Tall diagrams: more vertical room. Apply via `![h:480 class:tall]`. */
img.tall { max-height: 480px; }
/* Header/footer — subtle grey */
header { color: var(--sp-dark-grey); border-bottom: 1px solid var(--sp-grey); }
footer { color: var(--sp-dark-grey); border-top: 1px solid var(--sp-grey); }
/* Maturity badges */
.badge { display: inline-block; padding: 2px 8px; border-radius: 4px; font-size: 14px; font-weight: 600; }
.badge.today { background: #c6f6d5; color: #22543d; }
.badge.planned { background: #fef3c7; color: #78350f; }
/* Pagination — S&P Red progress bar */
.bespoke-progress-parent { background: var(--sp-grey); }
.bespoke-progress-bar { background: var(--sp-red) !important; }
/* Lists — tighter. REQ-256: add ol styling (match ul). */
ul { margin-top: 0.3em; }
ol { margin-top: 0.3em; }
li { margin-bottom: 0.2em; }
/* Strong — S&P Red for emphasis in lead lines */
strong { color: var(--sp-red); }
/* REQ-256: PPTX export fidelity no scrollbars in exported slides.
* The `overflow: auto` above is an authoring-time signal; in print/PPTX
* we clamp to `hidden` so the exported slide is clean. */
@media print {
section { overflow: hidden; }
}
Binary file not shown.

Before

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 63 KiB

@@ -0,0 +1,334 @@
---
marp: true
theme: default
paginate: true
size: 16x9
header: "How The Platform Works"
footer: "Internal"
style: |
section {
font-family: "Akkurat Pro", "Helvetica Neue", "Arial", sans-serif;
font-size: 26px;
color: #1B1B1B;
}
h1 { color: #D6002A; font-size: 40px; margin-bottom: 0.3em; }
h2 { color: #D6002A; font-size: 32px; margin-bottom: 0.2em; }
section.title { background: #1B1B1B; color: #fff; border-top: 8px solid #D6002A; }
section.title h1 { color: #fff; }
table { font-size: 22px; width: 100%; }
th { background: #F0F0F0; }
blockquote { border-left: 4px solid #D6002A; color: #2E2E2E; font-size: 24px; }
img { display: block; margin: 0 auto; max-height: 300px; }
.badge {
display: inline-block; padding: 2px 8px; border-radius: 4px;
font-size: 16px; font-weight: 600;
}
.planned { background: #fef3c7; color: #78350f; }
---
<!-- _class: title -->
<!-- _paginate: false -->
# How The Platform Works
### Nova — The New Dawn of DevSecOps
<style>
section.title h1 { font-size: 44px; margin-bottom: 0.1em; }
section.title h3 { color: #F0F0F0; font-weight: 400; font-size: 22px; margin-top: 0; }
</style>
---
# Four frictions slow every team
![w:1100](assets/png/platform-works-02-frictions.png)
- **Cognitive load** — services inconsistent in security and observability
- **Operational work** — manual promotion scaling with the system
- **Red tape** — tickets and handoffs scaling with the organization
- **Scalability** — throughput without scaling platform engineers
---
# The platform at a glance
![w:1100](assets/png/platform-architecture.png)
- **Consumer surfaces** — technical dev or citizen dev; both produce a contract
- **Central pipeline** — fixed stages, identical for every deployment: validate → resolve → security → plan → policy → confidence → evidence → apply
- **Module catalog + engine adapter** — security-reviewed blocks; the adapter is the only engine-specific code (Terraform today)
- **HITL gates + evidence stream** — human attestation for qa/prod/dr; every deployment writes a hash-chained event (RPO = 0)
---
# Declare intent; the platform delivers safe production
![w:1100](assets/png/platform-works-03-north-star.png)
- A merged change progresses **without a ticket or thread**
- A **non-technical consumer** ships by declaring intent
- Every production change is **traceable to a human attestation**
---
# Nova owns infrastructure, not your app
![w:1100](assets/png/platform-works-03-scope-boundary.png)
- **Upstream is anything** — IDE, agentic SDLC, or vibe coding
- **Nova is infrastructure only** — provisions and governs AWS resources
- **Not a general-purpose AI** — autonomy is narrow, policy-bounded
- **Not a permissive highway** — no escape hatches
---
# One YAML file. The platform owns everything else.
![w:850](assets/png/platform-works-01-contract-driven.png)
- **Module** — pre-built, security-reviewed building blocks
- **Environment**`dev`, `qa`, `prod`, `dr`; bar rises with sensitivity
- **Inputs** — cpu, memory, port, desired_count
- Consumer provides **no AWS account, no VPC, no state backend**
---
# Same stages, same checks, every deployment
![w:1100](assets/png/platform-works-02-end-to-end-flow.png)
- **Security and policy checks run *before* any infra is created**
- **Every stage produces a record** — no "unchecked" path
---
# No long-lived credentials. Blast radius contained.
![w:1100](assets/png/platform-works-07-zero-trust.png)
- **OIDC federation** — short-lived token per job, no stored credential <span class="badge planned">Planned: all runners</span>
- **ABAC, not role-based** — repo identity + resource tags scope every action
- **A consumer can only touch its own tagged resources.** One consumer can never affect another.
---
# Safety is a measurable signal, not a black box
![w:900](assets/png/platform-works-04-confidence-signal.png)
- **Six weighted inputs** — manually tuned, auditable per-input breakdown
| Environment | Threshold | Attester |
|---|---|---|
| dev | ≥ 0.50 | No one — autonomous |
| qa | ≥ 0.75 | QA <span class="badge planned">Planned</span> |
| prod | ≥ 0.90 | SRE <span class="badge planned">Planned</span> |
- **A single critical finding hard-blocks** — not averaged away
---
# Every change traceable to a human attestation
![w:1100](assets/png/platform-works-05-attestation-flow.png)
- **Dev is fully autonomous** — confidence signal is the only gate
- **qa, prod, dr require human attestation** — contract + plan + evidence <span class="badge planned">Planned</span>
- **Separation of duties** — QA approver ≠ prod approver; platform **blocks on a match** <span class="badge planned">Planned</span>
- **Hash-chained evidence event** — tampering breaks the chain. **RPO = 0**
---
<!-- _class: title -->
<!-- _paginate: false -->
# The vision realized
- **Velocity without sacrificing safety** — speed in ergonomics, safety in unbypassable gates
- **Security, observability, compliance as platform defaults** — not per-team effort
- **Auditability as a byproduct, not a project** — every change traceable to a human attestation
- **Blast radius contained by design** — OIDC + ABAC, only your own tagged resources
- **Infrastructure as a utility, not a craft** — consume, don't maintain
- **A path to the citizen developer** — same envelope, senior engineer or non-technical
---
<!-- _class: title -->
<!-- _paginate: false -->
# Appendix
**Contents:**
1. Platform-Managed Environments (detail)
2. Observability Built In (detail)
3. Security by Construction (the full defaults inventory)
4. The Road to the North Star (phased roadmap)
5. Testing vs. Planned (full inventory)
6. Glossary
7. Operating Model & Cost (real AWS spend + pre-mortem)
8. Verified by Construction (the v1.11 architecture)
---
# A1 — Platform-Managed Environments
A consumer provides **no AWS account, no VPC, no subnet, no state backend, no runner key.** The platform owns the blast radius.
A named environment is a platform-owned bundle of:
- An AWS account (or a scoped partition of one)
- A network (VPC + subnets)
- A state backend (S3 + DynamoDB for state + locking)
- An IAM role surfaced via ABAC, scoped to the consumer's identity and resource tags
The consumer selects an environment **by name** in their contract. The platform resolves it at run time. **The consumer never sees raw credentials.**
**Friendly onboarding:** the first run detects no environment and emits a guided prompt (not an opaque failure). <span class="badge planned">Self-service: planned</span>
---
# A2 — Observability Built In
Monitoring is **a platform default, not a per-team project.**
- **Uptime monitoring deployed automatically with every stack** — separate state, feature flag to disable
- **Monitored endpoints passed from the deployment's own outputs** — no manual endpoint registration
- **Alert channels:** Microsoft Teams webhook, email, SMS, and GitHub issues
- **The uptime URL is published to the developer** via a PR comment
- **Roadmap:** deeper observability bootstrap (dashboards, runbooks, on-call bindings) <span class="badge planned">Planned</span>
---
# A3 — Security by Construction
Security defaults that **do not require a team to opt in.** Checks run on **every** deployment, normalized to a single schema.
- **Policy checks** (Checkov, Wiz, Kyverno) — secrets, public ingress, IAM wildcards, **required tagging** — all run *before* infra is created
- **Encryption on every resource** — at-rest on by default; per-stack CMKs with 90-day rotation, **no shared keys across stacks**
- **Deletion protection on by default**`prevent_destroy` on unless explicitly disabled via a documented flag
- **Safe decommission** — a 2-step pipeline with **two SRE attestation gates** and a **change-request validated against the CMDB**
---
<!-- _class: title -->
<!-- _paginate: false -->
# A4 — The Road to the North Star
*Proposed phasing — not formally planned.*
![w:1100](assets/png/road-to-north-star.png)
---
<!-- _class: title -->
<!-- _paginate: false -->
# A5 — Testing vs. Planned (Full Inventory)
<style>
section { font-size: 18px; }
td { font-size: 16px; vertical-align: top; }
ul { margin: 0; padding-left: 1.2em; }
li { margin-bottom: 2px; }
</style>
**22/22 Verified** — the v1.11 lifecycle pipeline ran apply→modify→destroy against live AWS for every L1 + L2 module, then tore down to zero-cost (D-096). The v1.10 "6 deploy-unverified (IAM drift)" status is closed (CAP-013 fixed in P67).
<table style="width: 100%; border: none;">
<tr>
<td style="width: 52%; border: none; padding-right: 12px;">
**Testing** (22/22 Verified — works internally, dev pilot-ready)
- Contract-driven deploys with a versioned reusable workflow
- Module catalog (primitives + modules) with validated examples
- Zero-trust OIDC + ABAC on GitHub Actions runners
- Security + policy checks before infra creation (Checkov; Wiz + Kyverno ready)
- Confidence signal (6 inputs, per-env thresholds) gating promotion
- Hash-chained, tamper-evident evidence outbox (RPO = 0)
- Encryption by default + per-stack customer-managed keys
- Deletion protection by default + safe decommission with SRE gates
- Uptime monitoring deployed automatically with every stack
- Platform-managed environments + friendly onboarding
- Engine-agnostic core (1 adapter: Terraform) + VCS-agnostic ingestion
</td>
<td style="width: 48%; border: none; padding-left: 12px;">
**Planned** (on the roadmap)
- Real OIDC federation on all platform runners
- HITL wiring for qa / prod / dr environments
- Full regulatory ledger: S3 Object Lock + JWS signatures + daily checkpoints
- Compliance milestone: GDPR, SOX, SOC2, DORA extension points
- Environment self-service provisioning
- Dynamic module creation from a contract (agentic citizen-developer flow)
- Pattern recognition compounds value over time
- Additional engine adapters (OpenTofu, Pulumi, Kubernetes CRDs)
- Deeper observability bootstrap (dashboards, runbooks, on-call)
</td>
</tr>
</table>
---
# A6 — Glossary
| Term | Meaning |
|---|---|
| **OIDC** | OpenID Connect — federation protocol for short-lived tokens, no long-lived credentials |
| **ABAC** | Attribute-Based Access Control — access scoped by resource tags + repo identity, not roles |
| **CMK** | Customer-Managed Key — per-stack encryption key, 90-day rotation, no shared keys |
| **CMDB** | Configuration Management Database — validates change requests for decommission |
| **RPO** | Recovery Point Objective — RPO = 0 means evidence is written synchronously, no data loss |
| **HITL** | Human-in-the-Loop — deliberate human attestation required for qa/prod/dr environments |
| **VCS** | Version Control System — the git hosting platform (GitHub, Gitea, GitLab) |
| **NFR** | Non-Functional Requirement — encryption, tagging, observability standards |
| **IR** | Intermediate Representation — the engine-agnostic stack definition between contract and Terraform |
---
# A7 — Operating Model & Cost
<style>
section { font-size: 20px; }
table { font-size: 18px; }
</style>
Nova runs at **zero cloud cost** for day-to-day development. AWS spend was measured via Cost Explorer (`COST.md`, 2026-07-28):
| Metric | Value |
|--------|-------|
| Total spend (8 days) | **$0.001883** |
| Daily average | $0.000235 |
| Projected monthly | ~$0.007 |
| Peak day | 2026-07-27 ($0.000867) |
- **S3 dominates** (98.8%, terraform state bucket) — no compute ran because v1.0→v1.10 was plan-only for IAM-gated capabilities
- **Local emulators are the primary tier** — the full pipeline runs in-process, no AWS credentials
- **Live-AWS verification is milestone-scoped, then torn down.** The pipeline now **defaults to plan-only** on every PR; `NOVA_LIFECYCLE_MODE=full` overrides to apply→destroy for milestone verification (REQ-134, v1.12).
- **Cost drivers** are spike-scoped: Terraform plan reads (free), S3 state storage (cents), DynamoDB outbox (cents). Any spike > $1/day is an anomaly.
**Pre-mortem (`PRE_MORTEM.md`):** the v1.10 decay incident (diff-scoped VERIFY missed 7 adapter defects) is the root pattern: *a claim outruns the verification that backs it.* Four forward failure modes + structural mitigations (regression-tested IAM baseline, mandatory teardown, verified-only deck claims, honest scope).
---
<!-- _class: title -->
<!-- _paginate: false -->
# A8 — Verified by Construction
<style>
section { font-size: 20px; }
</style>
Two architectural pillars make "Verified" a structural property, not a claim:
- **The stateless adapter (918 → ~80 lines).** The Terraform adapter was a 918-line monolith with 3 constant tables and 39 type-specific branches. It is now a ~80-line **stateless assembler**: it owns no module content — no resource shape, no nested HCL blocks, no defaults. Each L1 module ships a real `terraform/` module dir owning its shape, nested blocks, and defaults. The adapter reads the registry and emits `module "x" { source = ... }` blocks. A new module is a new terraform dir, not a code change. *(The v1.12 P67 fix closed a dedup defect for multi-resource L1s — ecs-service, alb; CAP-013 now Verified.)*
- **Pipeline-driven lifecycle testing.** A `modules-lifecycle` pipeline matrix-runs each L1 and L2 module's `examples/{simple,complex}.yml` contracts through apply→modify→destroy against live AWS. **The "test" = the pipeline cell going green.** Defaults to **plan-only** on every PR (fast, no AWS mutation, no cost); `NOVA_LIFECYCLE_MODE=full` overrides to the real apply→destroy for milestone verification (REQ-134, v1.12). The regression gate (D-091) re-runs all 22 capabilities at milestone completion — **22/22 Verified** as of v1.12.
The v1.10 lesson is the negative space: a 918-line adapter with type-specific branches decayed silently. The ~80-line stateless adapter + the milestone regression gate are the structural fix.
@@ -0,0 +1,248 @@
# How The Platform Works — Talking Points
> **Companion to:** `how-the-platform-works-marp.md` (11 main + Appendix TOC + 8 appendix = 20 slides)
> **Content source:** `how-the-platform-works.md` (full source of truth with speaker notes)
> **Purpose:** Presenter-ready cues — 3-6 talking points per slide + the one key takeaway the audience should remember.
> **Audience:** Senior Leadership — CTO, Head of Cloud, Head of Infrastructure, Head of DevOps
---
## Slide 1 — Title
**Talking points:**
- Brief introduction — this deck explains *how* the platform works internally, not the developer experience (that's the companion deck)
- Set the frame: the platform is not a CI/CD tool — it's the organizational lever for shipping safely at the pace the business demands
- Every "Testing" claim is Verified — 22/22 capabilities via the v1.11 lifecycle pipeline (see A8)
**Key takeaway:** The platform is the organizational lever for safe, fast shipping.
---
## Slide 2 — Four frictions slow every team
**Talking points:**
- Open with the cost of the status quo — every team running its own pipeline, Terraform, and review checklist pays a tax that doesn't differentiate the business
- The four frictions are categorically parallel: cognitive load, operational work, red tape, scalability
- The platform absorbs all four — that is the value proposition in one sentence
- Don't dwell here; this is the setup for the before/after contrast on the next slide
**Key takeaway:** Four frictions slow every team. The platform absorbs all four.
---
## Slide 3 — The platform at a glance
**Talking points:**
- One-slide map of the whole platform — use it to orient the audience before diving into any single component
- The leadership-relevant beats: (1) two surfaces, one pipeline, one evidence stream — the convergence is the design; (2) the pipeline stages are fixed and identical for every consumer; (3) the engine adapter is the only engine-specific code, which makes the catalog and confidence model portable
- Don't walk every node — point to the boundaries and say "the rest of this deck zooms into each of these"
- The contract schema is the boundary between upstream and Nova; everything left of it is the consumer's, everything right of it is the platform's
**Key takeaway:** Two surfaces, one pipeline, one evidence stream. The rest of the deck zooms in.
---
## Slide 4 — Declare intent; the platform delivers safe production
**Talking points:**
- Land the before/after contrast: today's queue vs. Nova's autonomous flow
- The litmus test: if a platform engineer still has to touch a ticket for a dev→qa promotion, we haven't delivered the vision
- The North Star is one sentence: "declare intent → safe production deployment"
- A non-technical consumer ships by declaring intent — no workflow, no config file, no module
**Key takeaway:** Declare intent; the platform delivers safe production — autonomously, with a complete audit trail.
---
## Slide 5 — Nova owns infrastructure, not your app
**Talking points:**
- The platform is deliberately scoped — it is not trying to be everything
- The sovereign boundary: the platform team owns delivery and infrastructure, not the upstream development process
- The anti-goals are as important as the goals — they tell leadership what not to expect
- Upstream is anything: IDE, agentic SDLC, or vibe coding — Nova doesn't care how the contract was produced
**Key takeaway:** Nova is infrastructure only. App build/test/deploy is upstream.
---
## Slide 6 — One YAML file. The platform owns everything else.
**Talking points:**
- Hold this slide — emphasize the asymmetry. The consumer's surface is intentionally tiny; the platform's surface is large and opinionated
- The contract names three things: module, environment, inputs — that's the entire consumer-facing interface to production
- The contract shows infrastructure inputs (cpu, memory, desired_count, port) — not a container image. The image is upstream; the platform governs infrastructure
- The consumer provides no AWS account, no VPC, no state backend — the platform owns the blast radius
**Key takeaway:** One YAML file. The platform owns everything else.
---
## Slide 7 — Same stages, same checks, every deployment
**Talking points:**
- Walk left to right once — don't dwell on internals; the point is the flow is fixed, opinionated, and identical for every consumer
- The two leadership-relevant beats: (1) checks before creation, (2) every stage is evidenced
- No team-specific pipelines, no tribal runbooks — the flow is the contract
- The confidence signal (Slide 9) is where the "safety is computed" story lands
**Key takeaway:** Same stages, same checks, every deployment. No "unchecked" path.
---
## Slide 8 — No long-lived credentials. Blast radius contained.
**Talking points:**
- This is the slide for the Head of Cloud/Security — the key phrase is "blast radius contained to the consumer's own stack"
- Contrast with the common failure mode of shared CI roles that can touch any account resource
- OIDC federation: short-lived token per job, no credential stored in the consumer repo or runner secret
- ABAC, not role-based: repo identity + resource tags scope every action — a consumer can only touch its own tagged resources
- The static-key override exists for edge cases but is rotated daily on platform runners; it is never the default
**Key takeaway:** No long-lived credentials. A consumer can only touch its own tagged resources.
---
## Slide 9 — Safety is a measurable signal, not a black box
**Talking points:**
- This is the bet that separates this platform from "yet another CI/CD tool" — reliance on operator instinct or tenure is not a substitute
- The signal is auditable; the thresholds are tunable by Infra & Ops + SRE jointly, and any override is itself a confidence-event in the audit stream
- Six weighted inputs: policy, validation, freshness, provenance, history, NFRs — manually tuned, auditable per-input breakdown
- If a consumer asks "why 0.62?", the platform answers with a per-input breakdown — not a black box
- A single critical finding hard-blocks — critical findings are not averaged away
**Key takeaway:** Safety is a measurable, explainable signal — not a black box.
---
## Slide 10 — Every change traceable to a human attestation
**Talking points:**
- The "lower environments autonomous, higher environments attested" tenet resolves the classic "move fast vs. be safe" false dichotomy
- Be honest: the separation-of-duties *mechanism* is designed and the dev path is wired; qa/prod/dr wiring is on the roadmap
- The audit trail is a byproduct of deployment, not a project — every production change is traceable to a human attestation
- The full regulatory ledger (S3 Object Lock, JWS signatures, daily checkpoints) is planned; what ships today is the outbox + hash chain that makes every event tamper-evident and queryable
- RPO = 0 — the evidence write is synchronous; a deployment is not acknowledged until the evidence event is durably recorded
**Key takeaway:** Every change is traceable to a human attestation and a tamper-evident evidence event.
---
## Slide 11 — The vision realized
**Talking points:**
- Close on the strategic frame — the platform is not "a CI/CD tool," it's the organizational lever for shipping safely at the pace the business demands
- Velocity without sacrificing safety: speed is in the ergonomics, safety is in the unbypassable gates
- Security, observability, compliance as platform defaults — not per-team effort, not post-hoc remediation
- A path to the citizen developer: the same safety envelope serves a senior engineer and a non-technical consumer
- Invite questions; the companion deck ("The Developer Experience") covers who uses the platform and how fast/safe they ship
**Key takeaway:** Ship safely at the pace the business demands, with the security and audit posture the regulators require.
---
## Appendix TOC — Appendix
**Talking points:**
- These are deep-dive slides for follow-up questions — don't walk them in the main 15-minute talk
- Pull them up when an audience member wants detail on a specific topic
- The appendix is indexed to match the Marp deck's A1-A8 structure
**Key takeaway:** Deep dives available — pull the relevant appendix slide when asked.
---
## A1 — Platform-Managed Environments
**Talking points:**
- For the Head of Cloud: this is the governance story — the platform team owns the accounts, the network design, the state hygiene
- Consumers can't drift into misconfigured state backends or over-permissioned roles because they never touch them
- The onboarding prompt matters — first impressions of a platform are made when it fails for the first time
- Self-service environment provisioning is planned
**Key takeaway:** The consumer never sees raw credentials. The platform owns the blast radius.
---
## A2 — Observability Built In
**Talking points:**
- The Head of DevOps cares about this — "you don't deploy a service and *then* remember to set up monitoring; the platform does it as part of the deploy"
- Uptime monitoring deployed automatically with every stack — separate state, feature flag to disable
- The feature flag means teams with existing monitoring (e.g. Datadog) can opt out cleanly
- Deeper observability bootstrap (dashboards, runbooks, on-call bindings) is on the roadmap
**Key takeaway:** Monitoring is a platform default, not a per-team project.
---
## A3 — Security by Construction
**Talking points:**
- The phrase to land is "secure by default, not secure by effort"
- The selling point is *normalization* — we can add a new security tool without changing the confidence model or the evidence stream
- For the Head of Security: tagging standards are enforced, not advisory — a missing `nova:owner` tag fails the check, not a warning
- The decommission flow is the counter-argument to "deletion protection makes cleanup impossible" — it's a deliberate, gated, two-approval path
**Key takeaway:** Secure by default, not secure by effort. Checks run before infra is created.
---
## A4 — The Road to the North Star
**Talking points:**
- Be clear with leadership: this is a proposed phasing, not a formally committed plan
- The phases are sequenced by dependency, not by calendar — each phase's items are gated on the prior phase's maturity
- Phase 1 is now fully Verified (22/22) and torn down to zero-cost — it is no longer aspirational
- Invite questions on any phase boundary
**Key takeaway:** Proposed phasing, not formally planned. Phase 1 is Verified; Phase 4 is the North Star.
---
## A5 — Testing vs. Planned (Full Inventory)
**Talking points:**
- Close on honesty — the platform delivers real, verifiable value today: 22/22 auto-verifiable capabilities Verified via the v1.11 lifecycle pipeline
- The roadmap is concrete, not aspirational hand-waving — 9 planned items, each with a defined milestone and a clear reason it isn't shipped yet (usually an upstream dependency, not an engineering gap)
- Emphasize: 0 consumer adoption today — "Testing" means it works internally and is dev pilot-ready, not that it's released
- The lifecycle pipeline defaults to plan-only on every PR; `NOVA_LIFECYCLE_MODE=full` overrides for milestone verification
**Key takeaway:** 22/22 Verified today. 9 planned, each with a clear milestone and reason.
---
## A6 — Glossary
**Talking points:**
- Use this slide as a reference when the audience asks for term definitions
- Don't read it aloud — point to it as a takeaway reference
- All acronyms used in the deck are defined here
**Key takeaway:** Reference slide — don't read aloud.
---
## A7 — Operating Model & Cost
**Talking points:**
- The headline for the Head of Cloud / Finance: less than one cent over 8 days of active development; zero BAU cloud spend
- The lifecycle pipeline defaults to plan-only so the PR-time cost is zero
- The pre-mortem is the credibility slide — we already asked "how does this fail?" and the mitigations are structural
- The v1.10 decay incident is disclosed honestly, not hidden — that disclosure IS the mitigation
**Key takeaway:** Zero BAU cloud cost. Pre-mortemed failure modes with structural mitigations.
---
## A8 — Verified by Construction
**Talking points:**
- This is the deep-dive slide for the Head of Engineering / Architecture — the two pillars answer "how do you keep the decks honest?"
- The adapter is simple enough to reason about (a stateless assembler); the lifecycle pipeline is the automated verification that backs every "Testing" claim
- The v1.10 lesson is the negative space: a 918-line adapter with type-specific branches decayed silently because the VERIFY gate was diff-scoped
- The ~80-line stateless adapter + the milestone regression gate are the structural fix
- The plan-only default (v1.12) means verification runs on every PR at zero cost, with the full apply→destroy gated behind a CI variable override
**Key takeaway:** "Verified" is a structural property, not a claim — the stateless adapter + lifecycle pipeline make it so.
File diff suppressed because one or more lines are too long
@@ -0,0 +1,488 @@
# How The Platform Works
> **Subtitle:** Nova — The New Dawn of DevSecOps
> **Audience:** Senior Leadership, CTO, Head of Cloud, Head of Infrastructure, Head of DevOps
> **Length:** ~16 minutes · 11 main + Appendix TOC + 8 appendix = 20 slides
> **Purpose:** Sell the platform's value to tech leadership — zero-trust, security, observability, auditability, and the shift from "operators guess" to "the platform computes safety."
> **Maturity framing:** "Testing" = works internally, dev pilot-ready. "Planned" = on the roadmap, not yet implemented. "Agentic" = involves AI agents or autonomous decision-making.
> **Re-verification (2026-07-29):** Every "Testing" claim in this deck was re-verified in v1.10 Phase 54 (D-093) and again in v1.11 via the pipeline-driven lifecycle tests (P59P62). The headline E2E (contract → resolver → adapter → terraform init/validate/plan) passes against the live AWS account; the local emulating tier (Phase 53) runs the full E2E with no cloud credentials. **22/22 auto-verifiable capabilities Verified** (CAP-013 fixed in v1.12 P67 — the adapter's multi-resource L1 dedup defect is closed; CAP-017/018 probe bugs fixed). The v1.11 lifecycle pipeline ran apply→modify→destroy against live AWS and was then torn down to zero-cost (D-096). See `.ciagent/CAPABILITY_INVENTORY.md` and `.ciagent/PRE_MORTEM.md`.
---
## Slide 1 — Title
# How The Platform Works
### Nova — The New Dawn of DevSecOps
**Security as a seamless enabler of fast deployments — not a bottleneck, not a "no" department.**
> **Speaker notes:** Brief introduction — this deck explains *how* the platform works internally, not what the developer experience is (that's the companion deck). Set the frame: the platform is not a CI/CD tool — it's the organizational lever for shipping safely at the pace the business demands.
---
## Slide 2 — Four frictions slow every team
Most teams can write code; far fewer get the infrastructure right. Delivery scales with the **coordination surface around it**, not the engineering inside it.
```mermaid
flowchart LR
subgraph ROW1 [" "]
direction LR
A["Cognitive load\nauthoring infra correctly"]
B["Operational work\nmerged → running"]
end
subgraph ROW2 [" "]
direction LR
C["Red tape\ntickets, approvals, handoffs"]
D["Scalability\nthroughput without headcount"]
end
A ~~~ B
C ~~~ D
A ~~~ C
B ~~~ D
```
- **Cognitive load** — the long tail of services, inconsistent in security and observability.
- **Operational work** — manual promotion that scales with the system, not the change.
- **Red tape** — tickets and handoffs that scale with the organization.
- **Scalability** — throughput without linearly scaling platform engineers.
> **Speaker notes:** Open with the cost of the status quo. Every team that stands up its own pipeline, its own Terraform, its own review checklist is paying a tax that doesn't differentiate the business. The platform absorbs all four frictions — that is the value proposition in one sentence.
---
## Slide 3 — The platform at a glance
One picture of the whole platform — the components, how they connect, and where the boundaries are. The rest of this deck zooms into each piece.
```mermaid
flowchart TD
subgraph UP ["Consumer surfaces — upstream"]
direction LR
U1["Technical dev\napp code + contract"]
U2["Citizen dev\nintent → AI agent → contract"]
end
subgraph ACDL ["Nova — infrastructure only"]
direction TB
CS["Contract schema\n(validate + fail-fast)"]
subgraph PIPE ["Central pipeline — fixed stages, every deployment"]
direction LR
P1["Validate"] --> P2["Resolve\ntarget stack"] --> P3["Security\nchecks"] --> P4["Infra plan"] --> P5["Policy\nchecks"] --> P6["Confidence\nsignal"] --> P7["Evidence\nevent"] --> P8["Infra apply"]
end
CAT["Module catalog\nprimitives + modules\n(security-reviewed)"]
ADAPT["Engine adapter\n(stateless → Terraform)"]
ENV["Platform-managed\nenvironments\naccount · VPC · state · IAM"]
HITL["HITL gates\nqa · prod · dr"]
EVID["Evidence stream\nhash-chained outbox\n(RPO = 0)"]
CS --> PIPE
CAT --> P2
ADAPT --> P4
ADAPT --> P8
ENV --> P8
P6 --> HITL
HITL --> P8
P7 --> EVID
end
subgraph DOWN ["Downstream"]
direction LR
D1["AWS resources\nrunning\n(tagged, encrypted)"]
D2["Consumer pipeline\ndeploys image"]
end
U1 --> CS
U2 --> CS
P8 --> D1
D1 --> D2
```
- **Consumer surfaces** — technical dev or citizen dev; both produce a contract. Upstream is anything.
- **Contract schema** — the boundary between upstream and Nova; validated fail-fast.
- **Central pipeline** — fixed stages, identical for every deployment: validate → resolve → security → plan → policy → confidence → evidence → apply.
- **Module catalog** — security-reviewed primitives + modules the resolver expands against.
- **Engine adapter** — stateless; the only engine-specific code (Terraform today).
- **Platform-managed environments** — account, VPC, state, IAM role; the platform owns the blast radius.
- **HITL gates** — human attestation for qa/prod/dr; dev is autonomous.
- **Evidence stream** — hash-chained outbox, RPO = 0, written by every deployment.
> **Speaker notes:** This is the one-slide map of the platform. Use it to orient the audience before diving into any single component. The leadership-relevant beats: (1) two surfaces, one pipeline, one evidence stream — the convergence is the design; (2) the pipeline stages are fixed and identical for every consumer — no team-specific pipelines; (3) the engine adapter is the only engine-specific code, which is what makes the catalog and confidence model portable. Don't walk every node; point to the boundaries and say "the rest of this deck zooms into each of these."
---
## Slide 4 — Declare intent; the platform delivers safe production
Consumers **declare intent**; the platform delivers **safe production deployment** — automatically, safely, with a complete audit trail.
```mermaid
flowchart LR
subgraph TODAY ["Today"]
direction TB
A["Merged change"]
B["Waits in queue"]
C["Ticket + approvals"]
D["Manual promotion"]
A --> B --> C --> D
end
subgraph ACDL ["With Nova"]
direction TB
E["Declare intent\n(one YAML contract)"]
F["Platform delivers\nsafely, autonomously"]
G["Traceable to\nhuman attestation"]
E --> F --> G
end
TODAY -.before.-> ACDL
```
- A merged change progresses **without a platform engineer joining a thread.**
- A **non-technical consumer** ships by declaring intent — no workflow, no config file, no module.
- Every production change is **traceable to a human attestation** and an immutable evidence stream.
> **Speaker notes:** Land the before/after contrast: today's queue vs. Nova's autonomous flow. The litmus test: if a platform engineer still has to touch a ticket for a dev→qa promotion, we haven't delivered the vision. The North Star is "declare intent → safe production deployment."
---
## Slide 5 — Nova owns infrastructure, not your app
The platform is deliberately scoped — it is not trying to be everything.
```mermaid
flowchart LR
subgraph UP ["Upstream — anything"]
direction TB
A["IDE / IDE + AI\n(dev writes contract)"]
B["Agentic SDLC\n(agent writes contract)"]
C["Citizen dev\n(vibe codes → AI agent\n→ contract)"]
end
subgraph ACDL ["Nova — infrastructure only"]
D["Contract\nvalidated"]
E["Resolve → Plan\nSecurity + Policy checks\nConfidence signal"]
F["Provision\nAWS resources"]
G["Evidence\nhash-chained"]
end
subgraph DOWN ["Downstream"]
H["AWS resources\nrunning"]
I["Consumer pipeline\ndeploys image"]
end
A --> D
B --> D
C --> D
D --> E
E --> F
E --> G
F --> H
H --> I
```
- **Upstream is anything** — IDE, agentic SDLC, or vibe coding. Nova doesn't care how the contract was produced.
- **Nova is infrastructure only** — it provisions and governs AWS resources. App build/test/deploy is upstream.
- **Not a general-purpose AI** — autonomy is narrow, scoped to delivery, bounded by strict policy.
- **Not a permissive highway** — no escape hatches to bypass the confidence framework.
> **Speaker notes:** The sovereign boundary means the platform team owns delivery and infrastructure, not the upstream development process. The anti-goals are as important as the goals: they tell leadership what not to expect.
---
## Slide 6 — One YAML file. The platform owns everything else.
The contract is the boundary between upstream and Nova. It's all a consumer writes.
```mermaid
flowchart LR
A["Consumer<br/>writes a contract"] --> B["Platform resolves,<br/>compiles, checks,<br/>deploys, records"]
B --> C["Resources running in AWS<br/>+ tamper-evident evidence"]
```
- **Which module** — a catalog of pre-built, security-reviewed building blocks.
- **Which environment**`dev`, `qa`, `prod`, or `dr`. The bar rises automatically with sensitivity.
- **Which inputs** — infrastructure values that vary per deployment (cpu, memory, port, desired_count).
- The consumer provides **no AWS account, no VPC, no state backend** — the platform owns the blast radius.
> **Speaker notes:** Emphasize the asymmetry. The consumer's surface is intentionally tiny — a contract that fits on one screen. The platform's surface is large and opinionated. The contract examples show infrastructure inputs (cpu, memory, desired_count, port) — not a container image. The image is upstream; the platform governs infrastructure.
---
## Slide 7 — Same stages, same checks, every deployment
Every deployment runs the same stages, in the same order, with the same checks — no team-specific pipelines, no tribal runbooks.
```mermaid
flowchart TD
A["Consumer contract<br/>(module + environment + inputs)"] --> B["Validate contract<br/>against the schema"]
B --> C["Resolve to a target stack<br/>(expand the module's pattern)"]
C --> D["Security checks<br/>(before any infra is created)"]
D --> E["Infrastructure plan<br/>(platform compiles the stack)"]
E --> F["Policy checks<br/>(normalized results)"]
F --> G["Confidence signal<br/>(6 inputs → score + band)"]
G --> H["Evidence event<br/>(hash-chained, tamper-evident)"]
H --> I["Infrastructure apply<br/>(dev only — higher envs hold for attestation)"]
```
- **Security and policy checks run *before* any infrastructure is created** — not as a post-deployment audit.
- **Every stage produces a record** that feeds the confidence signal and the evidence stream. No "unchecked" path.
> **Speaker notes:** Walk left to right once. Don't dwell on internals — the point is that the flow is fixed, opinionated, and identical for every consumer. The two leadership-relevant beats: (1) checks before creation, (2) every stage is evidenced. The confidence signal (Slide 9) is where the "safety is computed" story lands.
---
## Slide 8 — No long-lived credentials. Blast radius contained.
Consumer repositories hold **no long-lived cloud credentials.** Ever.
```mermaid
flowchart LR
A["Consumer repo\n(no credentials)"]
B["OIDC federation\nshort-lived token"]
C["ABAC session policy\nrepo identity + tags"]
D["Tagged resources\nonly"]
A --> B --> C --> D
```
- **Authentication — OIDC federation.** Each job mints a short-lived token; no credential stored in the consumer repo or runner secret. <span class="badge planned">Planned: all runners</span>
- **Authorization — attribute-based (ABAC), not role-based.** Two attribute classes scope every action:
- **Repository identity** — trust policy binds to the exact consumer repo + branch.
- **Resource tags** — every resource tagged `nova:owner` + `nova:contract`; session policy grants access **only to matching tags.**
- **The effect:** a consumer can only touch the resources it created. One consumer can never affect another.
> **Speaker notes:** This is the slide for the Head of Cloud/Security. The key phrase is "blast radius contained to the consumer's own stack." Contrast with the common failure mode of shared CI roles that can touch any account resource. The static-key override exists for edge cases but is rotated daily on platform runners; it is never the default.
---
## Slide 9 — Safety is a measurable signal, not a black box
Every delivery action produces a **measurable, explainable confidence signal** — a weighted sum of observable facts, not a black box.
```mermaid
flowchart LR
P["Policy"] --> S["Score"]
V["Validation"] --> S
F["Freshness"] --> S
Pr["Provenance"] --> S
H["History"] --> S
N["NFRs"] --> S
S --> B["Band + threshold"]
```
- **Six weighted inputs** — policy, validation, freshness, provenance, history, NFRs. Manually tuned, auditable. If a consumer asks "why 0.62?", the platform answers with a per-input breakdown.
- **Per-environment thresholds** that rise with sensitivity:
| Environment | Threshold | Attester |
|---|---|---|
| dev | ≥ 0.50 | No one — autonomous |
| qa | ≥ 0.75 | QA <span class="badge planned">Planned</span> |
| prod | ≥ 0.90 | SRE <span class="badge planned">Planned</span> |
- **A single critical finding hard-blocks** — critical findings are not averaged away.
> **Speaker notes:** This is the bet that separates this platform from "yet another CI/CD tool." Reliance on operator instinct or tenure is not a substitute. The signal is auditable; the thresholds are tunable by Infra & Ops + SRE jointly, and any override is itself a confidence-event in the audit stream. Leadership cares because it makes promotion decisions *reviewable*.
---
## Slide 10 — Every change traceable to a human attestation
Computed safety handles the gate. Humans still matter — here's how accountability works.
```mermaid
flowchart LR
subgraph DEV ["dev — autonomous"]
D1["Confidence ≥ 0.50\n→ apply"]
end
subgraph GATED ["qa / prod / dr — gated"]
G1["Confidence ≥ threshold"]
G2["Human attestation\nreviews contract\n+ plan + evidence"]
G3["Separation of duties\nQA ≠ prod approver"]
G1 --> G2 --> G3
end
DEV --> OUT["Hash-chained\nevidence event\n(RPO = 0)"]
GATED --> OUT
```
- **Dev is fully autonomous.** The confidence signal (≥ 0.50) is the only gate.
- **qa, prod, dr require human attestation** — the approver reviews contract, planned Terraform, and accumulated evidence. <span class="badge planned">Planned</span>
- **Separation of duties is enforced** — the QA approver **cannot** be the prod approver. The platform **blocks on a match.** <span class="badge planned">Planned</span>
- **Every deployment writes a hash-chained evidence event** — tampering breaks the chain. **RPO = 0.**
> **Speaker notes:** The "lower environments autonomous, higher environments attested" tenet is the resolution to the classic "move fast vs. be safe" false dichotomy. Be honest: the separation-of-duties *mechanism* is designed and the dev path is wired; qa/prod/dr wiring is on the roadmap. The audit trail is a byproduct of deployment, not a project. The full regulatory ledger (S3 Object Lock, JWS signatures, daily checkpoints) is planned; what ships today is the outbox + hash chain that makes every event tamper-evident and queryable.
---
## Slide 11 — The vision realized
- **Velocity without sacrificing safety.** Speed is in the ergonomics; safety is in the gates the consumer cannot bypass.
- **Security, observability, and compliance as platform defaults** — not per-team effort, not post-hoc remediation.
- **Auditability as a byproduct, not a project.** Every production change is traceable to a human attestation and a tamper-evident evidence event.
- **Blast radius contained by design.** Zero-trust OIDC + ABAC means a consumer can only touch its own tagged resources.
- **Infrastructure as a utility, not a craft.** Teams consume infrastructure, they don't maintain it.
- **A path to the citizen developer.** The same safety envelope serves a senior engineer and a non-technical consumer.
> **Speaker notes:** Close on the strategic frame. The platform is not "a CI/CD tool" — it's the organizational lever for shipping safely at the pace the business demands, with the security and audit posture the regulators require. The investment is in the abstraction, not the tool.
---
## Appendix — Table of Contents
For deep dives — these slides cover details omitted from the main 10.
**Contents:**
1. Platform-Managed Environments (detail)
2. Observability Built In (detail)
3. Security by Construction (the full defaults inventory)
4. The Road to the North Star (phased roadmap)
5. Testing vs. Planned (full inventory)
6. Glossary
7. Operating Model & Cost (real AWS spend + pre-mortem)
8. Verified by Construction (the v1.11 architecture)
> **Speaker notes:** These are deep-dive slides for follow-up questions. Don't walk them in the main 15-minute talk — pull them up when an audience member wants detail on a specific topic.
---
## A1 — Platform-Managed Environments
A consumer provides **no AWS account, no VPC, no subnet, no state backend, no runner key.** The platform owns the blast radius.
A named environment is a platform-owned bundle of:
- An AWS account (or a scoped partition of one).
- A network (VPC + subnets).
- A state backend (S3 + DynamoDB for infrastructure state + locking).
- An IAM role surfaced to the consumer via ABAC, scoped to the consumer's repository identity and resource tags.
The consumer selects an environment **by name** in their contract (`environment: dev`). The platform resolves the name to the underlying account/network/state/role at run time. **The consumer never sees the raw credentials.**
**Friendly onboarding:** the first run detects no environment and emits a guided prompt (not an opaque failure) telling the consumer what the platform will provision and how to request it. *(Testing.)* **Self-service environment provisioning is planned.**
> **Speaker notes:** For the Head of Cloud: this is the governance story. The platform team owns the accounts, the network design, the state hygiene. Consumers can't drift into misconfigured state backends or over-permissioned roles because they never touch them. The onboarding prompt matters — first impressions of a platform are made when it fails for the first time.
---
## A2 — Observability Built In
Monitoring is **a platform default, not a per-team project.** *(Testing.)*
- **Uptime monitoring deployed automatically with every stack** — a dedicated monitoring instance (Uptime-kuma on ECS Fargate) is provisioned after any module deploy, in a separate state, with a feature flag to disable.
- **Monitored endpoints passed from the deployment's own outputs** — the platform constructs a synthetic monitoring contract from what was just deployed. No manual endpoint registration.
- **Alert channels:** Microsoft Teams webhook, email, SMS, and GitHub issues. *(Testing.)*
- **The uptime URL is published to the developer** via a PR comment — they don't hunt for it.
- **Roadmap:** deeper observability bootstrap (dashboards, runbooks, on-call bindings) as first-class contract fields for prod/dr. *(Planned.)*
> **Speaker notes:** The Head of DevOps cares about this. The framing: "you don't deploy a service and *then* remember to set up monitoring — the platform does it as part of the deploy." The feature flag means teams with existing monitoring (e.g. Datadog) can opt out cleanly.
---
## A3 — Security by Construction
Security defaults that **do not require a team to opt in.** Checks run on **every** deployment, normalized to a single schema regardless of which engine produced them. *(Testing.)*
- **Infrastructure-as-code policy** (Checkov) — secrets in plaintext, public ingress, IAM wildcards, KMS key references, **required tagging standards** (`nova:owner`, `nova:contract`, `nova:environment`, `nova:cost-center`). All run *before* infra is created.
- **Cloud security posture** (Wiz adapter) — translates cloud security findings into the same normalized record. *(Adapter testing; activates when a Wiz tenant is configured.)*
- **Kubernetes-native policy** (Kyverno adapter) — ready for the GitOps reconciler roadmap item. *(Adapter testing; inactive for Terraform-only stacks.)*
- **Encryption on every resource** — at-rest encryption is on by default for every primitive (S3, RDS, ECR, ECS, and more). *(Testing.)*
- **Per-stack customer-managed keys (CMKs)** — one key per deployment, 90-day rotation at creation, **no shared keys across stacks.** *(Testing.)*
- **Managed-key fallback with a loud warning** — standalone primitives fall back to cloud-managed keys only when no CMK is provided, and the platform warns explicitly. *(Testing.)*
- **Deletion protection on by default** — every resource has `prevent_destroy` on unless a consumer explicitly disables it via a documented feature flag. *(Testing.)*
- **Safe decommission** — a 2-step pipeline (disable protection → zero counts → destroy) with **two SRE human-attestation gates** and a **change-request validated against the platform CMDB** before any destructive action. *(Testing.)* Encryption keys enter a grace window (default 30 days) so encrypted data remains recoverable during decommission.
> **Speaker notes:** The phrase to land is "secure by default, not secure by effort." The selling point is *normalization* — we can add a new security tool without changing the confidence model or the evidence stream. For the Head of Security: tagging standards are enforced, not advisory — a missing `nova:owner` tag fails the check, not a warning. The decommission flow is the counter-argument to "deletion protection makes cleanup impossible" — it's a deliberate, gated, two-approval path, not a lock with no key.
---
## A4 — The Road to the North Star
*Proposed phasing — not formally planned.*
A phased roadmap from the current Testing baseline to the full North Star:
- **Phase 1 — Testing baseline (current, v1.12):** contract-driven deploys, zero-trust OIDC + ABAC on GitHub Actions, confidence signal gating, hash-chained evidence, encryption by default, deletion protection + safe decommission, uptime monitoring, platform-managed environments. **22/22 capabilities Verified** via the v1.11 lifecycle pipeline (apply→modify→destroy against live AWS, then torn down to zero-cost). The stateless adapter + lifecycle pipeline are the structural verification (see A8).
- **Phase 2 — Production readiness:** HITL wiring for qa/prod/dr, all-runner OIDC, full regulatory ledger (S3 Object Lock + JWS signatures + daily checkpoints), environment self-service.
- **Phase 3 — Compliance & expansion:** compliance milestone (GDPR, SOX, SOC2, DORA extension points), additional engine adapters (OpenTofu, Pulumi, Kubernetes CRDs), deeper observability bootstrap.
- **Phase 4 — Agentic frontier:** dynamic module creation from a contract (the agentic citizen-developer composition mechanism), pattern recognition that compounds value over time.
> **Speaker notes:** Be clear with leadership: this is a proposed phasing, not a formally committed plan. The phases are sequenced by dependency, not by calendar — each phase's items are gated on the prior phase's maturity. Phase 1 is now fully Verified (22/22) and torn down to zero-cost — it is no longer aspirational. Invite questions on any phase boundary.
---
## A5 — Testing vs. Planned (Full Inventory)
> **Verification status (v1.12, 2026-07-29):** 22/22 auto-verifiable capabilities **Verified** — the v1.11 lifecycle pipeline ran apply→modify→destroy against live AWS for every L1 + L2 module, then tore down to zero-cost (D-096). The v1.10 "6 deploy-unverified (IAM drift)" status is closed (CAP-013 fixed in P67). See `CAPABILITY_INVENTORY.md`.
**Testing** (works internally, dev pilot-ready — 22/22 Verified via lifecycle pipeline + regression gate):
- Contract-driven deploys with a versioned reusable workflow.
- Module catalog (primitives + modules) with validated examples.
- Zero-trust OIDC + ABAC on GitHub Actions runners.
- Security + policy checks before infra creation (Checkov; Wiz + Kyverno adapters ready).
- Confidence signal (6 inputs, per-env thresholds) gating promotion. *(Agentic.)*
- Hash-chained, tamper-evident evidence outbox (RPO = 0).
- Encryption by default + per-stack customer-managed keys.
- Deletion protection by default + safe decommission with SRE gates + CMDB validation.
- Uptime monitoring deployed automatically with every stack.
- Platform-managed environments + friendly onboarding.
- Engine-agnostic core (1 adapter: Terraform) + VCS-agnostic ingestion (GitHub + Gitea).
**Planned** (on the roadmap, not yet implemented) — 9 capabilities:
- Real OIDC federation on all platform runners (Gitea Actions OIDC pending an upstream merge).
- HITL wiring for qa / prod / dr environments (design shipped; wiring is next).
- Full regulatory ledger: S3 Object Lock (7-yr compliance mode) + JWS detached signatures + daily checkpoints.
- Compliance milestone: per-module extension points for GDPR, SOX, SOC2, DORA.
- Environment self-service (a consumer-facing flow to request and provision a new environment).
- Dynamic module creation from a contract (the agentic "citizen developer" composition mechanism). *(Agentic.)*
- Pattern recognition compounds value over time. *(Agentic.)*
- Additional engine adapters (OpenTofu, Pulumi, Kubernetes CRDs).
- Deeper observability bootstrap (dashboards, runbooks, on-call bindings).
> **Speaker notes:** Close on honesty. The platform delivers real, verifiable value today — 22/22 auto-verifiable capabilities are Verified via the v1.11 lifecycle pipeline (apply→modify→destroy against live AWS) + the D-091 regression gate. The roadmap is concrete, not aspirational hand-waving — 9 planned items, each with a defined milestone and a clear reason it isn't shipped yet (usually an upstream dependency, not an engineering gap). Emphasize: 0 consumer adoption today — "Testing" means it works internally and is dev pilot-ready, not that it's released. The lifecycle pipeline defaults to **plan-only** on every PR (fast, no AWS mutation, no cost); a CI variable (`NOVA_LIFECYCLE_MODE=full`) overrides to the real apply→destroy for milestone verification (REQ-134, v1.12).
---
## A6 — Glossary
| Term | Meaning |
|---|---|
| **OIDC** | OpenID Connect — federation protocol for short-lived tokens, no long-lived credentials |
| **ABAC** | Attribute-Based Access Control — access scoped by resource tags + repo identity, not roles |
| **CMK** | Customer-Managed Key — per-stack encryption key, 90-day rotation, no shared keys |
| **CMDB** | Configuration Management Database — validates change requests for decommission |
| **RPO** | Recovery Point Objective — RPO = 0 means evidence is written synchronously, no data loss |
| **HITL** | Human-in-the-Loop — deliberate human attestation required for qa/prod/dr environments |
| **VCS** | Version Control System — the git hosting platform (GitHub, Gitea, GitLab) |
| **NFR** | Non-Functional Requirement — encryption, tagging, observability standards |
| **IR** | Intermediate Representation — the engine-agnostic stack definition between contract and Terraform |
> **Speaker notes:** Use this slide as a reference when the audience asks for term definitions. Don't read it aloud — point to it as a takeaway reference.
---
## A7 — Operating Model & Cost (real AWS spend + pre-mortem)
Nova runs at **zero cloud cost** for day-to-day development. The v1.0→v1.10 AWS spend was measured directly via Cost Explorer (`COST.md`, 2026-07-28):
| Metric | Value |
|--------|-------|
| Total spend (8 days) | **$0.001883** |
| Daily average | $0.000235 |
| Projected monthly | ~$0.007 |
| Peak day | 2026-07-27 ($0.000867 — v1.10 regression + verify run) |
- **S3 dominates** (98.8%, terraform state bucket) — no compute (ECS/Lambda) ran because v1.0→v1.10 was plan-only for IAM-gated capabilities.
- **Local emulators are the primary tier** — the full pipeline runs in-process, no AWS credentials, no Checkov, no DynamoDB. *(Testing.)*
- **Live-AWS verification is milestone-scoped, then torn down.** The v1.11 lifecycle pipeline ran apply→modify→destroy for every module, then tore down to zero-cost steady state (D-096 — teardown mandatory before milestone COMPLETE; no merge to main until `terraform show` confirms no resources). The lifecycle pipeline now **defaults to plan-only** on every PR (fast, no AWS mutation, no cost); a CI variable (`NOVA_LIFECYCLE_MODE=full`) overrides to the real apply→destroy for milestone verification (REQ-134, v1.12).
- **Cost drivers** are spike-scoped: Terraform plan reads (free), S3 state storage (cents), DynamoDB outbox (cents). Any cost spike > $1/day is an anomaly.
**Pre-mortem (`PRE_MORTEM.md`):** the project's failure modes were pre-mortemed before the leadership pitch. The v1.10 decay incident (diff-scoped VERIFY missed 7 adapter defects across 8 NFR-patch phases — decks advertised capability that wasn't reproducible) is the root pattern: *a claim outruns the verification that backs it.* Four forward failure modes + structural mitigations: (FM-1) IAM-drift recurrence → IAM policy baseline is regression-tested; (FM-2) cost spike from un-torn-down stacks → D-096 mandatory teardown; (FM-3) deck overstates capability → verified-only claims + decks unfrozen only after re-verification; (FM-4) pilot contract gap → honest scope (microservice + static-assets today; the L2 pattern is extensible). All mitigations are structural, not procedural.
> **Speaker notes:** This is the slide for the Head of Cloud / Finance. The headline: less than one cent over 8 days of active development; zero BAU cloud spend; the lifecycle pipeline defaults to plan-only so the PR-time cost is zero. The pre-mortem is the credibility slide — we have already asked "how does this fail?" and the mitigations are structural (regression-tested baselines, mandatory teardown, verified-only deck claims). The v1.10 decay incident is disclosed honestly, not hidden — that disclosure IS the mitigation.
---
## A8 — Verified by Construction (the v1.11 architecture)
v1.11 rebuilt the platform on two architectural pillars that make "Verified" a structural property, not a claim:
- **The stateless adapter (REQ-123, 918 → ~80 lines).** The Terraform adapter was a 918-line monolith with 3 constant tables and 39 type-specific branches. It is now a ~80-line **stateless assembler**: it owns no module content — no resource shape, no nested HCL blocks, no defaults, no type-specific logic. Each L1 module ships a real `terraform/` module dir owning its resource shape, nested blocks, and defaults (centralized in `locals.tf`). The adapter reads the registry and emits `module "x" { source = ... }` blocks. No type-specific logic in the adapter means a new module is a new terraform dir, not a code change. *(The v1.12 P67 fix closed a dedup defect where multi-resource L1s — ecs-service, alb — produced invalid Terraform; CAP-013 now Verified.)*
- **Pipeline-driven lifecycle testing (REQ-127/128).** A `modules-lifecycle` pipeline matrix-runs each L1 and L2 module's `examples/{simple,complex}.yml` contracts through apply→modify→destroy against live AWS. No per-module Python. **The "test" = the pipeline cell going green.** Defaults to **plan-only** on every PR (fast, no AWS mutation, no cost); `NOVA_LIFECYCLE_MODE=full` runs the real apply→destroy for milestone verification (REQ-134, v1.12). The regression gate (D-091) re-runs all 22 capabilities at milestone completion — 22/22 Verified as of v1.12.
> **Speaker notes:** This is the deep-dive slide for the Head of Engineering / Architecture. The two pillars are the answer to "how do you keep the decks honest?" The adapter is simple enough to reason about (a stateless assembler), and the lifecycle pipeline is the automated verification that backs every "Testing" claim. The v1.10 lesson is the negative space: a 918-line adapter with type-specific branches decayed silently because the VERIFY gate was diff-scoped. The ~80-line stateless adapter + the milestone regression gate are the structural fix. The plan-only default (v1.12) means this verification runs on every PR at zero cost, with the full apply→destroy gated behind a CI variable override.
@@ -1,435 +0,0 @@
---
marp: true
theme: default
paginate: true
size: 16x9
footer: 'Nova — The Autonomous Cloud Delivery Platform'
style: |
section { font-family: "Akkurat Pro", "Helvetica Neue", "Arial", sans-serif; font-size: 22px; color: #1B1B1B; padding: 48px 56px 40px; overflow: auto; }
h1 { color: #D6002A; font-size: 34px; margin-bottom: 0.3em; }
h2 { color: #D6002A; font-size: 26px; margin-bottom: 0.2em; }
h3 { color: #D6002A; font-size: 22px; margin-bottom: 0.2em; }
section.title { background: #1B1B1B; color: #fff; border-top: 8px solid #D6002A; }
section.title h1, section.title h2 { color: #fff; }
section.title header, section.title footer { display: none; }
table { font-size: 18px; width: 100%; border-collapse: collapse; }
th { background: #F0F0F0; border-bottom: 2px solid #D6002A; padding: 4px 8px; text-align: left; }
td { border-bottom: 1px solid #F0F0F0; padding: 4px 8px; }
blockquote { border-left: 4px solid #D6002A; color: #2E2E2E; font-size: 20px; padding-left: 12px; }
pre { background: #1B1B1B; color: #fff; border-radius: 4px; padding: 12px; font-size: 16px; }
code { background: #F0F0F0; color: #1B1B1B; border-radius: 2px; padding: 1px 4px; font-size: 18px; }
pre code { background: transparent; color: inherit; }
img { display: block; margin: 0 auto; max-width: 100%; max-height: 380px; object-fit: contain; }
strong { color: #D6002A; }
.benefit { margin-top: 0.6em; padding-top: 0.4em; border-top: 1px solid #D6002A; color: #1B1B1B; font-size: 20px; font-style: italic; }
section.title .benefit { color: #fff; }
@media print { section { overflow: hidden; } }
---
<!-- _class: title -->
<!-- _paginate: false -->
# Nova — The Autonomous Cloud Delivery Platform
**Shifting from Operational Overhead to Strategic Value**
Product Development & Citizen Developer Overview
---
## Slide 1 — The Problem
**Product teams now own their cloud infrastructure — but ownership without discipline is destroying value.**
- **No lifecycle planning.** Resources are authored for creation, not for patching or rollback — so changes are destructive.
- **No proactive scanning in authoring.** AI-frontier models exploit zero-days faster than teams can react; modules must be scanned as code and at runtime, remediated at threat pace.
- **Bandwidth gaps.** Remediation plus the push for innovation leaves operations under-resourced; detections are missed, incidents grow.
- **Tribal knowledge.** Operations depend on a few administrators; when they leave, the knowledge leaves with them. The platform should encode the discipline, not the person.
<div class="benefit">an autonomous cloud delivery platform that encodes discipline as policy, scans proactively, remediates rapidly, and makes operations visible to leadership.</div>
<!-- Speaker notes: Do not frame this as "humans are the problem." The problem is that ownership was granted without the discipline, tooling, and lifecycle planning that infrastructure requires. The operator is not the bottleneck because operators exist — the bottleneck is that operations depend on a few individuals instead of an encoded system. -->
<!-- Transition: Here is the destination Nova is building toward. -->
<!-- Talking points: Open with the shift: "you build it, you run it" put Terraform into product teams — ownership without discipline is destroying value; Land the lifecycle-planning gap: resources authored for creation, not for patching/rollback → destructive changes; Land the urgency: AI-era 0-day pace demands proactive scanning as code + at runtime, remediated at threat pace; Call out tribal knowledge / the rockstar-operator problem — the platform should encode the discipline, not the person; Do NOT frame this as "humans are the problem" — the problem is ownership without the discipline and tooling; Key takeaway: the problem is infrastructure ownership without discipline; the answer is an autonomous platform that encodes the discipline -->
---
## Slide 2 — Nova's 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; the operator is never in the loop of normal operations.**
- **Visibility is the recurring theme** — security posture, remediation velocity, reliability, and lead time as queryable signals
- **Provable, not promised** — trust established by deterministic scripts that calculate a score; the platform functions without AI
- **Autonomy in operations, human at stage gates** — QA signs off for production; SRE greenlights operational readiness
<div class="benefit">the destination is autonomous operations with provable trust — security, remediation velocity, reliability, and lead time made visible to leadership, not promised to them.</div>
<!-- Speaker notes: "Visible" is the operative word. The vision is not just that operations run without an operator — it is that operations become observable, queryable, and accountable. That is what makes the trust defensible. -->
<!-- Transition: The vision is ambitious — here are the strategic objectives that make it concrete, and the anti-goals that keep it focused. -->
<!-- Talking points: Read the vision verbatim — "infrastructure operations become visible" is the operative phrase; Emphasize "provable, not promised" — trust established by deterministic scripts; the platform functions without AI; State the attestation model up front: QA for production, SRE for operational readiness; Key takeaway: autonomous operations with provable trust — security, remediation velocity, reliability, lead time made visible, not promised -->
---
## Slide 3 — Strategic Objectives
**4 Strategic Objectives:**
1. **Zero-touch operations** — autonomy as the default, not the demo; stage-gate attestation (QA, SRE) remains human by design
2. **Provable trust in automated decisions** — deterministic scripts calculate a score; the platform functions without AI; Decision Ledger, confidence scoring, circuit breakers, blast-radius controls
3. **Compounding, quantifiable ROI** — four CTO-grade metrics, all flowing into PowerBI:
- **Lead Time** (PR → Production) · **Infrastructure Vulnerability Count** (trend) · **MTTR** · **Cloud Spend Reduction**
4. **Integrate with externally owned development platforms — regardless of source** — PDLC, SDLC, Agentic, or Citizen Developer; Nova provides skills + MCP endpoints; all prod intents go through the same controls and quality gates
<div class="benefit">the scope is explicit — Nova governs infrastructure and delivery, integrates with any upstream source through one validated contract, and measures success on four metrics a CTO can repeat back.</div>
<!-- Speaker notes: Objective #2 is the one to land carefully: trust is established by deterministic scoring, not by an LLM. The platform functions without AI. -->
<!-- Transition: The objectives are concrete — here is what Nova is NOT, to keep it focused. -->
<!-- Talking points: Objective #1: zero-touch operations — autonomy as the default, not the demo; stage-gate attestation (QA, SRE) remains human by design; Objective #2 is the one to land carefully: trust = deterministic scoring, not an LLM; the platform functions without AI; Objective #3: four CTO-grade metrics (Lead Time, Vuln Count, MTTR, Spend) — all flow into PowerBI; Objective #4 is the integration thesis: Nova integrates with any upstream source; provides skills + MCP; all prod intents go through the same controls; Key takeaway: the scope is explicit — Nova governs infra + delivery, integrates with any source through one contract, measures success on four CTO metrics -->
---
## Slide 4 — Anti-Goals (What Nova Is NOT)
1. Not a general-purpose AI agent platform
2. Not a system that removes humans from accountability — only from normal operations
3. Not an upstream development platform (no product backlogs, IDE, code authorship)
4. Not a replacement for the Product Development Lifecycle (PDLC)
<div class="benefit">the boundaries are explicit — Nova is purpose-built for infrastructure operations and delivery, not a general-purpose AI agent or an upstream development platform.</div>
<!-- Speaker notes: Anti-goals #3 and #4 protect the scope boundary — Nova will not become an IDE or a product-planning tool. -->
<!-- Transition: The scope boundary is explicit — here is exactly where Nova sits relative to the product development lifecycle. -->
<!-- Talking points: Not a general-purpose AI agent platform; Not a system that removes humans from accountability — only from normal operations; Not an upstream development platform (no product backlogs, IDE, code authorship); Not a replacement for the Product Development Lifecycle (PDLC); Anti-goals #3 and #4 protect the scope boundary — Nova will not become an IDE or a product-planning tool; Key takeaway: the boundaries are explicit — Nova is purpose-built for infra ops + delivery, not a general-purpose AI agent or an upstream dev platform -->
---
## Slide 5 — Scope: Downstream of PDLC
**Nova governs infrastructure and delivery. The PDLC is upstream — Nova stays downstream of it. Integration is through one validated contract.**
- **The PDLC is upstream** — product backlog, code authorship (AI agent, IDE, agentic SDLC), sprint planning, application business logic. Nova stays downstream of it.
- **Nova is downstream:** contract ingestion → submission-readiness gate → policy enforcement → cloud resource lifecycle → environment progression (dev → qa → prod → dr) → immutable audit + attestation
- **One validated contract** — any upstream source (AI agent, agentic SDLC, dev platform) produces submissions subject to the same compliance standards; Nova validates the submission, not the author
<div class="benefit">a clean scope boundary — Nova is purpose-built for infrastructure operations and integrates with any upstream source through one contract, so the platform team's surface area stays bounded.</div>
<!-- Speaker notes: This slide protects the scope. The moment Nova starts owning the PDLC, it loses focus. The contract boundary is what keeps Nova deep on infrastructure and delivery rather than shallow on everything. -->
<!-- Transition: With the scope clear, here is who owns what across the delivery lifecycle. -->
<!-- Talking points: Nova governs infra + delivery only; the PDLC (backlog, code authorship, IDE) is upstream — Nova stays downstream of it; Integration is only through the validated contract boundary; Any upstream source (AI agent, agentic SDLC, dev platform) produces submissions subject to the same compliance standards; Nova validates the submission, not the author; Key takeaway: Nova is purpose-built for infrastructure operations; the scope boundary is clean and bounded -->
---
## Slide 6 — RACI: Who Owns What
**Four roles, one matrix — citizen developer owns FRs + UAT, platform owns NFRs + infra, quality engineering owns the gate evidence, SRE owns operational readiness.**
| Work Category | Citizen Dev | Platform | Quality Eng | SRE |
|---|---|---|---|---|
| Functional Requirements | **R/A** | C | I | I |
| User Acceptance Testing | **R/A** | C | I | I |
| Non-Functional Requirements | I | **R/A** | C | C |
| Infrastructure (cloud, state, IAM) | I | **R/A** | I | C |
| QA (policy, confidence, schema) | C | R | **R/A** | I |
| Production deployment to cloud | I | **R/A** | C | C |
| Quality attestation (QA sign-off) | **A** | R | **R** | I |
| Production readiness (SRE sign-off) | **A** | R | C | **R** |
**R**=Responsible · **A**=Accountable (sign-off) · **C**=Consulted · **I**=Informed. Production readiness is co-owned: the platform runs attestations agentically; the citizen developer authorizes the promotion at the stage gate.
<div class="benefit">every party knows what they bring, what the platform provides, what quality engineering guards, and where SRE signs off — accountability is explicit, never diffuse.</div>
<!-- Speaker notes: Quality attestation is now owned by Quality Engineering (not the Platform), and Production readiness is owned by SRE. The Platform runs the checks agentically but is never the Accountable party for the gate — that separation keeps the platform honest. -->
<!-- Transition: With ownership clear, here is how the pipeline enforces it. -->
<!-- Talking points: Four roles now: Citizen Developer, Platform, Quality Engineering, SRE; Quality attestation is owned by Quality Engineering (not the Platform); Production readiness is owned by SRE; The Platform runs the checks agentically but is never the Accountable party for the gate — that separation keeps the platform honest; Production readiness is co-owned: the platform runs attestations; the citizen developer authorizes the promotion at the stage gate; Key takeaway: you bring FRs + UAT; Nova provides NFRs + infra; QE guards the gate evidence; SRE signs off on production readiness -->
---
## Slide 7 — The Platform Pipeline
**How intent becomes verified infrastructure — fail-fast policy scanning before the plan, runtime scanning after it.**
![h:480 class:tall](assets/png/platform-pipeline.png)
- **The pipeline** — see the diagram; two scan stages (static code, then resolved plan) feed a confidence signal to the stage gate before apply + evidence + ledger
- **Fail-fast, quick feedback** — Checkov runs on the authored Terraform code before `terraform plan` so developers get immediate policy feedback
- **Wiz on the plan when configured; Checkov as a drop-in otherwise** — Wiz scans the plan output; when Wiz credentials are absent, Checkov runs against the plan instead. **Wiz and Checkov are never both run on the plan.**
<div class="benefit">two layers of scanning, zero operator involvement in normal operations — fast deterministic feedback at authoring time and a runtime scan on the resolved plan.</div>
<!-- Speaker notes: The two-stage scan is the key design: static code scanning catches policy violations before the cost of a plan; runtime plan scanning catches what the static code cannot (resolved values, cross-resource issues). The platform picks the runtime scanner based on configuration — never both, to avoid duplicate noise. -->
<!-- Transition: The pipeline produces decisions — here is how every decision is captured and made accountable. -->
<!-- Talking points: Walk the pipeline left-to-right: contract → resolver → adapter → Checkov (static) → plan → Wiz (on plan) → confidence → gate → apply; Two-stage scan: Checkov on static code BEFORE the plan (fail-fast dev feedback); Wiz on the plan (or Checkov as drop-in if no Wiz creds); Never both Wiz + Checkov on the plan — avoid duplicate noise; Dev is autonomous; qa/prod/dr require attestation (QA for quality, SRE for production readiness); Key takeaway: two layers of scanning, zero operator involvement in normal operations -->
---
## Slide 8 — The Decision Ledger
**Every automated decision is captured, immutable, queryable — and accountable.**
- **What is captured:** the chosen action, the confidence score, the alternatives considered, whether a human overrode it, and the outcome (backfilled once the apply completes). Every stage-gate attestation (QA, SRE) is captured with approver identity and the evidence presented.
- **"AI decisions" are really automated decisions** — deterministic scripts calculate a score and a band; the platform functions without AI, and a later LLM planner emits richer alternatives without breaking the schema.
- **The value is accountability, not the storage engine** — the ledger is append-only and tamper-evident; every decision is queryable for auditing, traceable to an outcome, and impossible to rewrite after the fact.
<div class="benefit">"autonomous" is defensible because every decision is immutable, queryable, and accountable — and the audience knows exactly what "automated" means here: deterministic scoring, not a black-box LLM.</div>
<!-- Speaker notes: Do not dwell on the storage substrate. The audience cares that the ledger is append-only, queryable, and tied to outcomes — not that it is a hash-chain in a SQLite file. The D-122 honesty point is restated without the decision ID: the platform's decisions are deterministic; the ledger captures that real path. -->
<!-- Transition: Decisions are captured — here is how stage-gate attestation keeps humans in accountability. -->
<!-- Talking points: "AI decisions" are really automated decisions — deterministic scripts calculate a score; the platform functions without AI; Do not dwell on the storage substrate — the value is accountability (immutable, queryable, traceable to outcome), not the database; Every stage-gate attestation is captured with approver identity and the evidence presented; When an LLM planner is added later, it emits richer alternatives without breaking the schema; Key takeaway: autonomous is defensible because every decision is immutable, queryable, accountable — and "automated" means deterministic scoring, not a black-box LLM -->
---
## Slide 9 — Attestation Matrix: QA
**The designed controls that keep humans at stage gates — QA concerns, freshness-validated.**
| Concern | Env | Freshness | Description |
|---------|-----|-----------|-------------|
| Functional correctness | qa | 24h | The application behaves as specified; evidence accepted from the consumer's UAT. |
| Performance baseline | qa | 7d | The deployment meets its performance envelope vs. the agreed baseline. |
| Security posture | qa | 24h | The deployment's security findings have been reviewed and accepted. |
<div class="benefit">QA signs off on quality before any promotion — the gate is explicit, not implicit.</div>
<!-- Speaker notes: The matrix is not a rubber stamp. Each concern has a freshness window and a plain-language description of what is being attested. The "operator-supplied" label from the prior deck was dropped — every concern now has a plain-language description. -->
<!-- Transition: QA is half the matrix — here are the production and DR controls. -->
<!-- Talking points: The matrix is not a rubber stamp — structured, freshness-validated; Each concern now has a plain-language description of what is being attested (the old "operator-supplied" label is gone); Three QA concerns: functional correctness (24h), performance baseline (7d), security posture (24h); Each concern has a freshness window — evidence older than the window does not satisfy the gate; Key takeaway: QA signs off on quality before any promotion — the gate is explicit, not implicit -->
---
## Slide 10 — Attestation Matrix: Prod/DR
**Production and DR controls — operational readiness, resilience, and disaster recovery.**
| Concern | Env | Freshness | Description |
|---------|-----|-----------|-------------|
| Operational readiness | prod | 30d | SRE confirms the deployment is operable: runbooks, dashboards, on-call. |
| Incident response | prod | 90d | The on-call path has been exercised; a working incident-response plan exists. |
| Capacity & cost | prod | 30d | Capacity headroom and monthly cost are within the agreed envelope. |
| Resilience: DR drill | prod | 180d | A DR drill has been run and recovery met the RTO. |
| Resilience: chaos | prod | 90d | A chaos exercise has been run and the deployment absorbed the failure. |
| Resilience: backup | prod | 30d | Backups are restorable and tested within the freshness window. |
| DR region deploy | dr | 180d | The DR region can be deployed and is reachable. |
Separation-of-duties on prod: the approver cannot be the same person who built the deployment.
<div class="benefit">the gate model is explicit — autonomy in operations, human in accountability, by design. The matrix is what makes autonomous operations safe enough to trust in production.</div>
<!-- Speaker notes: The prod/DR rows are the operational-readiness and resilience gates — SRE signs off on operability, incident response, capacity, and the three resilience checks (DR drill, chaos, backup). Separation-of-duties on prod is the rule that keeps the gate honest: the approver cannot be the same person who built the deployment. -->
<!-- Transition: You've seen how Nova works — the pipeline, the ledger, the attestation gates. Here is how Nova instruments itself so that every claim in this deck is traceable to a real signal. -->
<!-- Talking points: Seven prod/DR concerns: operational readiness, incident response, capacity & cost, DR drill, chaos, backup, DR region deploy; SRE signs off on operability (runbooks, dashboards, on-call), incident response, capacity, and the three resilience checks; Each concern has a freshness window — 30d/90d/180d depending on the control; SoD on prod: the approver can't be the same person who built it — the rule that keeps the gate honest; Key takeaway: autonomy in operations, human in accountability, by design — the matrix is what makes autonomous operations safe enough to trust in production -->
---
## Slide 11 — Telemetry & Live Ops
**Every metric in this deck is traceable to a real emitted signal — the live-ops dashboard makes operations visible in PowerBI.**
![h:480 class:tall](assets/png/telemetry-live-ops.png)
- **Platform components → CloudEvents envelope → event log + decision ledger + run records → collector → cold store → PowerBI views → live ops dashboard**
- **The live ops dashboard (PowerBI)** surfaces the four CTO-grade metrics (Lead Time, Vulnerability Count, MTTR, Cloud Spend) alongside trust metrics (Decision Ledger coverage, Attestation coverage) and efficiency metrics (touchless resolution, escalation frequency)
- **Every number is traceable to a signal** — when a CFO asks "where does this number come from?", the answer is a query against the cold store, not a Slack thread
<div class="benefit">the architecture is the trust substrate — leadership sees the same numbers the platform produces, in PowerBI, with full traceability. Operations become visible.</div>
<!-- Speaker notes: The value is not the plumbing — it is that the platform's metrics surface in a tool leadership already uses (PowerBI), and every number is traceable. The live-ops dashboard is where the "infrastructure operations become visible" theme lands concretely. -->
<!-- Transition: The architecture is sound — here is the measured proof. -->
<!-- Talking points: Deliberately minimal: Nova-native CloudEvents; no Kafka/Prometheus/ClickHouse; The live-ops dashboard is built in PowerBI on top of the exported views — leadership sees the same numbers the platform produces; Every number in the Proof slides is traceable to a signal — "where does this number come from?" → a query against the cold store; This is where the "infrastructure operations become visible" theme lands concretely; Key takeaway: the architecture is the trust substrate — operations become visible in PowerBI, with full traceability -->
---
## Slide 12 — Decision Ledger + Attestation Coverage
**By design, no change reaches production without a ledger entry and a human attestation — both queryable for auditing, with full traceability.**
- **Decision Ledger coverage: 100%** — every platform run emits a decision record with outcome backfill; no automated decision is ever lost
- **Attestation coverage: 100%** — every prod/dr promotion is attested by a human (QA for quality, SRE for production readiness), recorded with approver identity, separation-of-duties check, and the evidence matrix
- **No change to production without both** — the ledger entry and the human attestation are mandatory, enforced by the pipeline, not by policy
- **Full traceability** — a production change is traceable from the contract that declared intent, through the policy scan, the confidence score, the attestation, to the applied outcome
<div class="benefit">trust is provable — not a marketing claim, a queryable record. An auditor answers "who approved this, when, on what evidence?" in one query; a CTO answers "how many of last quarter's prod changes were touchless?" in one query.</div>
<!-- Speaker notes: The mandatory-by-design point is the one to land. The ledger + attestation are not a best-effort feature; they are a gate. No change reaches production without both. That is what makes the 100% numbers credible — they are enforced, not aspirational. -->
<!-- Transition: Trust is provable — here is the cost side of the ROI. -->
<!-- Talking points: Both 100% — no automated decision is ever lost; no prod/dr promotion lands without a human sign-off; The mandatory-by-design point: the ledger entry + the human attestation are a gate, not a best-effort feature; Easily queried: by run, by environment, by approver, by outcome — the audit trail is a query, not a forensic exercise; Key takeaway: trust is provable — not a marketing claim, a queryable record; no change to production without both the ledger entry and the human attestation -->
---
## Slide 13 — Cost & ROI
**The ROI formula and the cost estimates — grounded, with the production denominator honestly flagged.**
- **Cost estimates are pre-apply and offline** — the platform reads the terraform plan and estimates cost before anything is applied; a cost regression is caught before the spend happens
- **The ROI formula:**
`Platform ROI = (FTE hours saved × blended rate + cloud savings + avoided downtime) ÷ platform op cost`
- **The four CTO-grade metrics are the ROI proof:** Lead Time (PR → Prod), Infrastructure Vulnerability Count (trend), MTTR, Cloud Spend Reduction — all flow into PowerBI
- **Honest caveat:** derived metrics run on internal data today; the production-denominator activates with a pilot estate.
<div class="benefit">the ROI is not a black box — the formula is shown, the four metrics are committed, and the production-denominator caveat is stated up front. The CFO sees exactly what is real today and what activates with a pilot.</div>
<!-- Speaker notes: The formula is shown inline, not hidden. The "no fabrication" constraint in action: show the formula, show the caveat, do not pretend the production numbers exist. -->
<!-- Transition: The proof is grounded — here is what is honestly deferred, and why. -->
<!-- Talking points: The ROI formula is shown inline — not hidden in a footnote; The four CTO-grade metrics are the ROI proof — Lead Time, Vuln Count, MTTR, Cloud Spend; The N=0 caveat is stated explicitly: the formula is grounded; the production numbers activate with a pilot; Key takeaway: the ROI is not a black box — the formula is shown, the four metrics are committed, the production-denominator caveat is up front -->
---
## Slide 14 — What's Deferred — and Why
**Honesty about what is not measured yet — and the blocking work for each.**
These deferrals are measurement infrastructure, not the autonomy itself — the platform runs without an operator in normal operations.
| # | Deferred metric | Blocking work |
|---|-----------------|---------------|
| 1 | Live infra health, outbox write rate, SLA | Live AWS re-provisioning (currently torn down to zero-cost steady state) |
| 2 | Tamper-evident ledger checkpoints | Audit-ledger build-out (Object Lock + signed checkpoints) |
| 3 | Onboarding funnel (requested → granted) | Auto-grant implementation |
| 4 | Drift auto-reversal | Drift-detection scheduler (not yet built) |
| 5 | Live cost reconciliation | Live AWS re-provisioning + actual-spend feed |
| 6 | Predictive vs reactive ratio | ML anomaly-forecasting service (not yet built) |
<div class="benefit">the boundaries are explicit — what Nova measures today, and exactly what blocks the rest. The autonomy is real; the measurement gaps are documented with the work that unblocks each one.</div>
<!-- Speaker notes: The preempt is critical: these deferrals are measurement infrastructure, not autonomy. The platform runs without an operator in the loop. What is deferred is the evidence pipeline for live-infra health, drift, predictive remediation — not the autonomy itself. -->
<!-- Transition: The proof is honest — here is the roadmap from here to the targets. -->
<!-- Talking points: The preempt is critical: these deferrals are measurement infrastructure, not autonomy — the platform IS autonomous in operations; The blocking work is named in plain language (no decision IDs) — "live AWS re-provisioning", "drift-detection scheduler", "ML service"; Showing this to leadership demonstrates honesty, not weakness; Key takeaway: the autonomy is real; the measurement gaps are documented with the work that unblocks each one -->
---
## Slide 15 — Roadmap to the North Star
**The path from the grounded metrics to the 1218 month targets — each deferred metric has an unblock path and a timeframe.**
| Timeframe | Work | Unblocks |
|-----------|------|----------|
| Near-term | Live AWS re-provisioning | Live infra health, outbox write rate, live cost reconciliation, SLA |
| Near-term | Auto-grant implementation | Onboarding funnel (requested → granted) |
| Mid-term | Drift-detection scheduler | Drift auto-reversal |
| Mid-term | Audit-ledger build-out (Object Lock + signed checkpoints) | Tamper-evident ledger checkpoints |
| Mid-term | Hot-path activation (batch → near-real-time) | Live-ops dashboard freshness |
| Longer-term | ML anomaly-forecasting service | Predictive vs reactive ratio |
Re-evaluation triggers: each blocking piece of work lifts on its own schedule; the metrics layer evolves as each one lands.
<div class="benefit">every deferred metric has an unblock path — nothing is hand-waved; everything has a plan and a timeframe.</div>
<!-- Speaker notes: This is the bridge from "honestly deferred" to "here is how we get there." The roadmap uses timeframes, not status — most of it is not implemented yet, so a status column would be noise. -->
<!-- Transition: The unblock path is clear — here is the 12-month product arc. -->
<!-- Talking points: Each deferred metric has an unblock path and a timeframe — near-term, mid-term, longer-term; No status column: most of it is not implemented yet, so status would be noise; Re-evaluation triggers: each blocking piece of work lifts on its own schedule; Key takeaway: every deferred metric has a plan and a timeframe — nothing is hand-waved -->
---
## Slide 16 — 12-Month Product Roadmap
**The product arc from pilot activation to integration — four quarters, four outcomes.**
| Quarter | Theme | Board-level outcome |
|---------|-------|---------------------|
| **Q1** | Pilot Activation | Nova runs a real customer estate end-to-end, autonomously, with a measurable zero-touch rate. |
| **Q2** | Provable Trust | Every automated decision lands in a tamper-evident ledger; the CFO sees real cloud-spend reconciliation. |
| **Q3** | Compounding ROI | Quarter-over-quarter cloud spend drops; drift is detected and reversed without a human. |
| **Q4** | Integration & Predictive | AI agents deploy through Nova by default; the ML anomaly-forecasting service goes live. |
Grounded in the four strategic objectives (autonomy, provable trust, ROI, integration) and the deferred-metric unblock paths.
<div class="benefit">the 12-month product arc — each quarter activates a strategic objective and its corresponding board-level metric, from pilot activation through integration leadership.</div>
<!-- Speaker notes: The roadmap is organized by product outcome, not by technical milestone. Each quarter activates one strategic objective from the North Star. -->
<!-- Transition: Here is the quarter-by-quarter detail. -->
<!-- Talking points: This is the *product* roadmap, forward-looking only; Q1 Pilot Activation → Q2 Provable Trust → Q3 Compounding ROI → Q4 Integration & Predictive; Each quarter activates one strategic objective from the North Star; Key takeaway: the 12-month product arc — each quarter activates a strategic objective and its board-level metric -->
---
## Slide 17 — Quarter-by-Quarter Outcomes
| Quarter | Product theme | Key deliverable | Target metric |
|---------|---------------|-----------------|---------------|
| **Q1** | Pilot Activation | Re-provision live AWS; activate first pilot estate; onboarding auto-grant | Touchless ≥ 99% · Escalation < 0.1% · Accuracy ≥ 99.5% |
| **Q2** | Provable Trust | Tamper-evident ledger (Object Lock + signed checkpoints); daily checkpoints; live cost reconciliation | Decision Ledger Coverage 100% · Cost Savings ≥ 25% |
| **Q3** | Compounding ROI + Drift | Drift-detection scheduler; auto-reversal; pre-apply → actual-spend reconciliation on the pilot estate | Drift Auto-Reversal ≥ 95% · Spend Reduction ≥ 25% |
| **Q4** | Integration + Predictive | ML anomaly-forecasting; AI-agent intent surface; multi-cloud (Azure/GCP) preview | Predictive:Reactive ≥ 3:1 · AI-Agent Intent Share (first measurement) |
**Month-18 destination:** *"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.'"*
<div class="benefit">each quarter has a concrete deliverable, a target metric grounded in a strategic objective, and a path from "honestly deferred" to "shipped and measured."</div>
<!-- Speaker notes: Q1Q3 are committed (grounded pipeline + known unblock paths). Q4 targets are committed-deliverable, aspirational-metric — the ML service ships, the intent-share number is a first measurement (we do not control adoption rate). -->
<!-- Transition: Production-grade guidance is how Nova helps the citizen developer's AI agent meet the bar — here is the first half. -->
<!-- Talking points: Q1: three post-pilot metrics go live (Touchless ≥99%, Escalation <0.1%, Accuracy ≥99.5%) — denominator activates with the pilot; Q2: Decision Ledger Coverage was already grounded — tamper-evidence is the Q2 upgrade (local hash-chain → Object Lock + signed checkpoints); Q3: Drift Auto-Reversal ≥95% unblocks when the drift scheduler ships; Spend Reduction ≥25% measured against the pilot baseline; Q4: Predictive:Reactive ≥3:1 requires the ML forecasting service; AI-Agent Intent Share is a first measurement (aspirational-metric); Key takeaway: each quarter has a concrete deliverable, a target metric grounded in a strategic objective, and a path from deferred to shipped -->
---
## Slide 18 — Production-Grade Guidance via Atelier (1/2)
**Nova instructs the citizen developer's AI agent on production-grade engineering — a set of skills and an MCP server.**
- **Skills** — markdown files keyed to production-grade engineering domains (API, security, data, testing, observability, errors, DevOps, infrastructure-as-code, compliance); the skills extend the baseline catalog with Nova-specific production-grade principles
- **MCP server** — a plugin-registry, stdio server exposing four tools: `lookup_principle`, `list_domains`, `matrix_lookup`, `validate_against_principles`. The developer's AI agent (or any agentic SDLC platform) calls these tools to look up the principles that apply to its submission
- **The integration point is the same regardless of source** — whether the submission comes from an AI coding agent, an agentic SDLC platform, or a traditional IDE, the same skills and MCP server apply. This is how Nova makes the citizen developer production-grade without owning the PDLC
<div class="benefit">the citizen developer's AI agent is not unguided — Nova provides production-grade engineering principles as skills and as an MCP surface, so submissions arrive at the contract boundary already aligned with the platform's standards.</div>
<!-- Speaker notes: This is the first half of the Atelier story — the surface (skills + MCP). The next slide is what the surface catches that deterministic scanners cannot. -->
<!-- Transition: Here is what that guidance catches that deterministic scanners cannot. -->
<!-- Talking points: Nova instructs the citizen developer's AI agent via skills (markdown, keyed to engineering domains) + an MCP server (4 tools, plugin-registry, stdio); The integration point is the same regardless of source — AI agent, agentic SDLC, traditional IDE all get the same skills + MCP; This is how Nova makes the citizen developer production-grade without owning the PDLC; Key takeaway: the citizen developer's AI agent is not unguided — Nova provides engineering principles as skills + MCP -->
---
## Slide 19 — Production-Grade Guidance via Atelier (2/2)
**Agentic validation catches engineering-discipline gaps that deterministic scanners miss — and the validation is reproducible.**
- **Beyond deterministic scanners** — Wiz, Checkmarx, and Mend check policy and secrets; they do not check engineering discipline. The Atelier MCP server catches correctness, clarity, and observability gaps that deterministic tools cannot: "is this service observable?", "is this error path handled?", "is this API contract clear?"
- **Agentic validation, not a second policy engine** — the MCP server gives the AI agent the principles to validate against; the agent does the validation. The agent reasons about the submission against the principles, not a second static scan
- **Vendored for audit reproducibility** — Atelier is vendored at a pinned tag. A validation result is replayable against the exact principles that produced it, so an audit can reproduce a validation months later, not just trust a log line
<div class="benefit">the citizen developer's submission is checked for engineering discipline, not just policy compliance — and the check is reproducible for audit. That is what makes the submission production-grade, regardless of which upstream platform produced it.</div>
<!-- Speaker notes: The value is the gap deterministic scanners leave: engineering discipline. Policy scanners catch "is this S3 bucket public?"; the MCP server catches "is this service observable if that bucket fails?". The vendoring point is audit reproducibility — the validation is not a black box. -->
<!-- Transition: You've seen the problem, the solution, and the proof. Here is the recap and the ask. -->
<!-- Talking points: The value is the gap deterministic scanners leave: engineering discipline (Wiz/Checkmarx/Mend check policy/secrets, not discipline); The MCP server catches "is this service observable?", "is this error path handled?", "is this API contract clear?"; Vendored at a pinned tag → audit reproducibility — a validation result is replayable months later; Key takeaway: submissions are checked for engineering discipline, not just policy compliance — and the check is reproducible for audit -->
---
## Slide 20 — Recap + Ask
**The 4-beat recap + the business decision.**
**Recap:**
- **Problem:** product teams own infrastructure without the discipline and lifecycle planning it requires; bandwidth gaps and tribal knowledge leave operations exposed
- **Solution:** autonomous cloud delivery — operations become visible, trust is provable (deterministic scoring), humans at stage gates
- **Proof:** 100% ledger coverage, 100% attestation coverage, grounded ROI formula, four CTO-grade metrics flowing into PowerBI
- **Roadmap:** deferred metrics have unblock paths; the 12-month product arc activates one strategic objective per quarter
**The ask:** "Approve a pilot estate to activate the production-denominator metrics (Lead Time, Vulnerability Count, MTTR, Cloud Spend). Then approve the tamper-evident ledger build-out (S3 Object Lock + signed checkpoints). Together these move Nova from 'pipeline-ready' to 'production-proven.'"
<div class="benefit">a clear business decision — approve a pilot and the ledger build-out — with the confidence that every claim in this deck is grounded, derived, or honestly deferred.</div>
<!-- Speaker notes: The ask is a business decision, not insider language. "Approve a pilot estate" is a C-suite decision. "Approve the ledger build-out" is a budget decision. The recap reinforces the 4-beat arc — the audience leaves with the structure, not a pile of facts. -->
<!-- Talking points: Recap the 4-beat arc so the audience leaves with the structure; The ask is a business decision: approve a pilot estate + the tamper-evident ledger build-out; "Pipeline-ready" → "production-proven" is the value proposition; Key takeaway: approve a pilot + the ledger build-out to move from pipeline-ready to production-proven -->
---
<!-- _class: title -->
<!-- _paginate: false -->
## Appendix A1 — Metrics Glossary
| KPI | Definition | Status |
|-----|-----------|--------|
| Touchless Resolution Rate | runs without operational stage-gate block ÷ total | partial (Post-Pilot) |
| Human Escalation Frequency | operational stage-gate blocks ÷ total | partial (Post-Pilot) |
| Automated Decision Accuracy | decisions not followed by failure within 5min | partial (Post-Pilot) |
| MTTR (p95) | apply.failed → successful retry | grounded |
| Confidence-Gate Halt Rate | runs with band=block ÷ total | grounded |
| Provisioning Lead Time | run.completed run.started | grounded |
| Deployment Frequency | count(run.completed) per day | grounded |
| Cost Savings (pre-apply) | sum(delta_usd where delta < 0) | partial (live reconciliation deferred) |
| FTE Hours Saved | run count × manual baseline × rate | derived (N=0 caveat) |
| Platform ROI | (labor + cloud + avoided downtime) ÷ op cost | derived (N=0 caveat) |
| Decision Ledger Coverage | decisions with outcome ÷ total | grounded |
| Attestation Coverage | prod/dr attested ÷ total prod/dr | grounded |
| Policy Compliance Rate | 1 failed_assets ÷ total | grounded |
<div class="benefit">a reference for every metric mentioned in the deck.</div>
<!-- Talking points: Reference for every metric mentioned in the deck; Use if the audience asks "what does X mean?" -->
@@ -1,146 +0,0 @@
# Nova — The Autonomous Cloud Delivery Platform: Talking Points
> Step 4 of the 4-step deck process. Presenter cues that mirror the
> `<!-- Talking points: -->` comments in
> `nova-autonomous-cloud-delivery-marp.md` (the sole source of truth).
> 3-6 bullets per slide + key takeaway. Indexed by Marp slide #.
> v1.21 — REQ-245
---
### Slide 1 — The Problem
- Open with the shift: "you build it, you run it" put Terraform into product teams — ownership without discipline is destroying value
- Land the lifecycle-planning gap: resources authored for creation, not for patching/rollback → destructive changes
- Land the urgency: AI-era 0-day pace demands proactive scanning as code + at runtime, remediated at threat pace
- Call out tribal knowledge / the rockstar-operator problem — the platform should encode the discipline, not the person
- Do NOT frame this as "humans are the problem" — the problem is ownership without the discipline and tooling
- **Key takeaway:** the problem is infrastructure ownership without discipline; the answer is an autonomous platform that encodes the discipline
### Slide 2 — Nova's Vision
- Read the vision verbatim — "infrastructure operations become visible" is the operative phrase
- Emphasize "provable, not promised" — trust established by deterministic scripts; the platform functions without AI
- State the attestation model up front: QA for production, SRE for operational readiness
- **Key takeaway:** autonomous operations with provable trust — security, remediation velocity, reliability, lead time made visible, not promised
### Slide 3 — Strategic Objectives
- Objective #1: zero-touch operations — autonomy as the default, not the demo; stage-gate attestation (QA, SRE) remains human by design
- Objective #2 is the one to land carefully: trust = deterministic scoring, not an LLM; the platform functions without AI
- Objective #3: four CTO-grade metrics (Lead Time, Vuln Count, MTTR, Spend) — all flow into PowerBI
- Objective #4 is the integration thesis: Nova integrates with any upstream source; provides skills + MCP; all prod intents go through the same controls
- **Key takeaway:** the scope is explicit — Nova governs infra + delivery, integrates with any source through one contract, measures success on four CTO metrics
### Slide 4 — Anti-Goals (What Nova Is NOT)
- Not a general-purpose AI agent platform
- Not a system that removes humans from accountability — only from normal operations
- Not an upstream development platform (no product backlogs, IDE, code authorship)
- Not a replacement for the Product Development Lifecycle (PDLC)
- Anti-goals #3 and #4 protect the scope boundary — Nova will not become an IDE or a product-planning tool
- **Key takeaway:** the boundaries are explicit — Nova is purpose-built for infra ops + delivery, not a general-purpose AI agent or an upstream dev platform
### Slide 5 — Scope: Downstream of PDLC
- Nova governs infra + delivery only; the PDLC (backlog, code authorship, IDE) is upstream — Nova stays downstream of it
- Integration is only through the validated contract boundary
- Any upstream source (AI agent, agentic SDLC, dev platform) produces submissions subject to the same compliance standards
- Nova validates the submission, not the author
- **Key takeaway:** Nova is purpose-built for infrastructure operations; the scope boundary is clean and bounded
### Slide 6 — RACI: Who Owns What
- Four roles now: Citizen Developer, Platform, Quality Engineering, SRE
- Quality attestation is owned by Quality Engineering (not the Platform); Production readiness is owned by SRE
- The Platform runs the checks agentically but is never the Accountable party for the gate — that separation keeps the platform honest
- Production readiness is co-owned: the platform runs attestations; the citizen developer authorizes the promotion at the stage gate
- **Key takeaway:** you bring FRs + UAT; Nova provides NFRs + infra; QE guards the gate evidence; SRE signs off on production readiness
### Slide 7 — The Platform Pipeline
- Walk the pipeline left-to-right: contract → resolver → adapter → Checkov (static) → plan → Wiz (on plan) → confidence → gate → apply
- Two-stage scan: Checkov on static code BEFORE the plan (fail-fast dev feedback); Wiz on the plan (or Checkov as drop-in if no Wiz creds)
- Never both Wiz + Checkov on the plan — avoid duplicate noise
- Dev is autonomous; qa/prod/dr require attestation (QA for quality, SRE for production readiness)
- **Key takeaway:** two layers of scanning, zero operator involvement in normal operations
### Slide 8 — The Decision Ledger
- "AI decisions" are really automated decisions — deterministic scripts calculate a score; the platform functions without AI
- Do not dwell on the storage substrate — the value is accountability (immutable, queryable, traceable to outcome), not the database
- Every stage-gate attestation is captured with approver identity and the evidence presented
- When an LLM planner is added later, it emits richer alternatives without breaking the schema
- **Key takeaway:** autonomous is defensible because every decision is immutable, queryable, accountable — and "automated" means deterministic scoring, not a black-box LLM
### Slide 9 — Attestation Matrix: QA
- The matrix is not a rubber stamp — structured, freshness-validated
- Each concern now has a plain-language description of what is being attested (the old "operator-supplied" label is gone)
- Three QA concerns: functional correctness (24h), performance baseline (7d), security posture (24h)
- Each concern has a freshness window — evidence older than the window does not satisfy the gate
- **Key takeaway:** QA signs off on quality before any promotion — the gate is explicit, not implicit
### Slide 10 — Attestation Matrix: Prod/DR
- Seven prod/DR concerns: operational readiness, incident response, capacity & cost, DR drill, chaos, backup, DR region deploy
- SRE signs off on operability (runbooks, dashboards, on-call), incident response, capacity, and the three resilience checks
- Each concern has a freshness window — 30d/90d/180d depending on the control
- SoD on prod: the approver can't be the same person who built it — the rule that keeps the gate honest
- **Key takeaway:** autonomy in operations, human in accountability, by design — the matrix is what makes autonomous operations safe enough to trust in production
### Slide 11 — Telemetry & Live Ops
- Deliberately minimal: Nova-native CloudEvents; no Kafka/Prometheus/ClickHouse
- The live-ops dashboard is built in PowerBI on top of the exported views — leadership sees the same numbers the platform produces
- Every number in the Proof slides is traceable to a signal — "where does this number come from?" → a query against the cold store
- This is where the "infrastructure operations become visible" theme lands concretely
- **Key takeaway:** the architecture is the trust substrate — operations become visible in PowerBI, with full traceability
### Slide 12 — Decision Ledger + Attestation Coverage
- Both 100% — no automated decision is ever lost; no prod/dr promotion lands without a human sign-off
- The mandatory-by-design point: the ledger entry + the human attestation are a gate, not a best-effort feature
- Easily queried: by run, by environment, by approver, by outcome — the audit trail is a query, not a forensic exercise
- **Key takeaway:** trust is provable — not a marketing claim, a queryable record; no change to production without both the ledger entry and the human attestation
### Slide 13 — Cost & ROI
- The ROI formula is shown inline — not hidden in a footnote
- The four CTO-grade metrics are the ROI proof — Lead Time, Vuln Count, MTTR, Cloud Spend
- The N=0 caveat is stated explicitly: the formula is grounded; the production numbers activate with a pilot
- **Key takeaway:** the ROI is not a black box — the formula is shown, the four metrics are committed, the production-denominator caveat is up front
### Slide 14 — What's Deferred — and Why
- The preempt is critical: these deferrals are measurement infrastructure, not autonomy — the platform IS autonomous in operations
- The blocking work is named in plain language (no decision IDs) — "live AWS re-provisioning", "drift-detection scheduler", "ML service"
- Showing this to leadership demonstrates honesty, not weakness
- **Key takeaway:** the autonomy is real; the measurement gaps are documented with the work that unblocks each one
### Slide 15 — Roadmap to the North Star
- Each deferred metric has an unblock path and a timeframe — near-term, mid-term, longer-term
- No status column: most of it is not implemented yet, so status would be noise
- Re-evaluation triggers: each blocking piece of work lifts on its own schedule
- **Key takeaway:** every deferred metric has a plan and a timeframe — nothing is hand-waved
### Slide 16 — 12-Month Product Roadmap
- This is the *product* roadmap, forward-looking only
- Q1 Pilot Activation → Q2 Provable Trust → Q3 Compounding ROI → Q4 Integration & Predictive
- Each quarter activates one strategic objective from the North Star
- **Key takeaway:** the 12-month product arc — each quarter activates a strategic objective and its board-level metric
### Slide 17 — Quarter-by-Quarter Outcomes
- Q1: three post-pilot metrics go live (Touchless ≥99%, Escalation <0.1%, Accuracy ≥99.5%) — denominator activates with the pilot
- Q2: Decision Ledger Coverage was already grounded — tamper-evidence is the Q2 upgrade (local hash-chain → Object Lock + signed checkpoints)
- Q3: Drift Auto-Reversal ≥95% unblocks when the drift scheduler ships; Spend Reduction ≥25% measured against the pilot baseline
- Q4: Predictive:Reactive ≥3:1 requires the ML forecasting service; AI-Agent Intent Share is a first measurement (aspirational-metric)
- **Key takeaway:** each quarter has a concrete deliverable, a target metric grounded in a strategic objective, and a path from deferred to shipped
### Slide 18 — Production-Grade Guidance via Atelier (1/2)
- Nova instructs the citizen developer's AI agent via skills (markdown, keyed to engineering domains) + an MCP server (4 tools, plugin-registry, stdio)
- The integration point is the same regardless of source — AI agent, agentic SDLC, traditional IDE all get the same skills + MCP
- This is how Nova makes the citizen developer production-grade without owning the PDLC
- **Key takeaway:** the citizen developer's AI agent is not unguided — Nova provides engineering principles as skills + MCP
### Slide 19 — Production-Grade Guidance via Atelier (2/2)
- The value is the gap deterministic scanners leave: engineering discipline (Wiz/Checkmarx/Mend check policy/secrets, not discipline)
- The MCP server catches "is this service observable?", "is this error path handled?", "is this API contract clear?"
- Vendored at a pinned tag → audit reproducibility — a validation result is replayable months later
- **Key takeaway:** submissions are checked for engineering discipline, not just policy compliance — and the check is reproducible for audit
### Slide 20 — Recap + Ask
- Recap the 4-beat arc so the audience leaves with the structure
- The ask is a business decision: approve a pilot estate + the tamper-evident ledger build-out
- "Pipeline-ready" → "production-proven" is the value proposition
- **Key takeaway:** approve a pilot + the ledger build-out to move from pipeline-ready to production-proven
### Appendix A1 — Metrics Glossary
- Reference for every metric mentioned in the deck
- Use if the audience asks "what does X mean?"
File diff suppressed because one or more lines are too long
@@ -0,0 +1,321 @@
---
marp: true
theme: default
paginate: true
size: 16x9
header: "The Developer Experience"
footer: "Internal"
style: |
section {
font-family: "Akkurat Pro", "Helvetica Neue", "Arial", sans-serif;
font-size: 26px;
color: #1B1B1B;
}
h1 { color: #D6002A; font-size: 40px; margin-bottom: 0.3em; }
h2 { color: #D6002A; font-size: 32px; margin-bottom: 0.2em; }
section.title { background: #1B1B1B; color: #fff; border-top: 8px solid #D6002A; }
section.title h1 { color: #fff; }
table { font-size: 22px; width: 100%; }
th { background: #F0F0F0; }
blockquote { border-left: 4px solid #D6002A; color: #2E2E2E; font-size: 24px; }
pre { font-size: 16px; line-height: 1.3; }
code { font-size: 16px; }
img { display: block; margin: 0 auto; max-height: 280px; }
.badge {
display: inline-block; padding: 2px 8px; border-radius: 4px;
font-size: 16px; font-weight: 600;
}
.planned { background: #fef3c7; color: #78350f; }
---
<!-- _class: title -->
<!-- _paginate: false -->
# The Developer Experience
### Nova — The New Dawn of DevSecOps
<style>
section.title h1 { font-size: 44px; margin-bottom: 0.1em; }
section.title h3 { color: #F0F0F0; font-weight: 400; font-size: 22px; margin-top: 0; }
</style>
---
# Two consumer paths, one safety envelope
![w:1100](assets/png/developer-experience-01b-scope-boundary.png)
- **Technical developer** — owns app code + a contract + a thin CI definition
- **Citizen developer** — declares intent; an AI agent produces a contract that passes the **same** safety envelope
- **Upstream is anything** — IDE, agentic SDLC, or vibe coding. Nova doesn't care how the contract was produced
- **Nova is infrastructure only** — provisions and governs AWS resources. Application deployment is upstream
---
# The platform at a glance
![w:1100](assets/png/platform-architecture.png)
- **You own the left edge** — app code and a contract. That is the entire consumer surface
- **The platform owns the middle** — pipeline, catalog, adapter, environments, gates, evidence
- **Two surfaces, one pipeline, one evidence stream** — senior engineer and citizen dev converge on the same safety envelope
- **The bar rises automatically** — confidence signal + HITL gates scale with the target environment, not a ticket
---
# Three things. The entire consumer surface.
<img src="assets/png/developer-experience-02-what-dev-does.png" style="float: right; width: 38%; margin-left: 20px; margin-bottom: 10px;" />
- **1. App code** — the consumer's service, at the top level of the repo
- **2. A contract** — a single YAML file: id, name, environment, infrastructure
```yaml
id: msvc
name: microservice
environment: dev
infrastructure:
microservice:
version: "1.0.0"
inputs:
cpu: 256
memory: 512
desired_count: 2
port: 8080
```
- **3. A one-line CI definition** — a thin `uses:` wrapper pointing at a versioned platform workflow
- The developer does **not**: write modules, clone the platform repo, hold cloud credentials, or maintain a state backend
---
# See what the platform does, in real time
- **Streamed output by default** — the plan, policy results, and each check record flow to stdout
- **PR comments after every successful pipeline stage** — always know where you stand
- **Clear, explainable halt reasons** — a policy violation, an insufficient signal, or a missing attestation. **Never opaque.**
- **Connection strings posted as PR comments** — human-readable, no hunting. Runtime secrets go to encrypted Parameter Store, never to logs
- **Errors become GitHub issues, automatically** — a failed deploy opens an issue on the platform repo
---
# Pick from pre-built, security-reviewed blocks
![w:1100](assets/png/developer-experience-05-catalog.png)
- **Primitives** — single-purpose resources (S3, VPC, ECS, IAM, ALB, ECR, CloudFront, WAF, RDS)
- **Modules** — composed patterns (static site with CDN + WAF; microservice with VPC + ECS + ALB + ECR)
- **Validated examples per module**`simple.yaml` + `complex.yaml`, validated against the contract schema in CI
- **Auto-promotion of patterns** — after 3 observed usages <span class="badge planned">Planned</span>
---
# The bar rises automatically with sensitivity
![w:1000](assets/png/developer-experience-04-promotion-journey.png)
| Environment | What the platform adds | Maturity |
|---|---|---|
| dev | Confidence ≥ 0.50, fully autonomous | — |
| qa | QA human attestation + confidence ≥ 0.75 | <span class="badge planned">Planned</span> |
| prod | SRE human attestation + confidence ≥ 0.90 | <span class="badge planned">Planned</span> |
| dr | SRE human attestation + confidence ≥ 0.95 + DR drill | <span class="badge planned">Planned</span> |
- **No staging environment** — dev is the only autonomous environment
- **Separation of duties** — the QA approver cannot be the prod approver
---
# Tearing down is as gated as deploying
![w:1100](assets/png/developer-experience-07-decommission.png)
<style>
section { font-size: 22px; }
pre { font-size: 13px; line-height: 1.2; }
code { font-size: 13px; }
</style>
```yaml
uses: acdl/.github/workflows/deploy.yml@v1.12
with:
contract: .nova/contract.yml
mode: decommission
changeRequestId: "CHG0678912"
```
- **Validate the change request** — platform queries the CMDB; CR must be `approved` and match the consumer repo
- **Two SRE human-attestation gates** — disable protection → SRE approves → zero counts + destroy → second SRE approves
- **Per-stack encryption key enters a grace window** (default 30 days) so encrypted data remains recoverable
---
# You control when you absorb improvements
![w:1100](assets/png/developer-experience-08-semver.png)
- **Floating MAJOR + MINOR tags** (e.g. `@v1.12`) — automatically receive patch updates within the line
- **Semantic versioning with a clear contract:** interface → MAJOR, behavior → MINOR, lifecycle → PATCH
- **Pin to an exact version** for stability, or float on MAJOR only (`@v1`) to absorb new features on your own cadence
- **Unversioned references (`@main`, bare) are discouraged** — the versioned tag is the only immutability lever
- **Automated release job** computes the next semver on merge to main, creates the tag, and updates floating tags
---
# Fails gracefully, not opaquely
First impressions of a platform are made **when it fails for the first time.** The platform fails gracefully.
When no environment is bound, the platform emits a **user-friendly onboarding prompt** instead of failing opaquely:
1. That no environment is bound to their repo yet
2. What the platform will provision on their behalf (account, network, state, role)
3. The expected turnaround for the platform team to grant the environment
4. How to request an environment
The pipeline then **exits without attempting a deployment** — no partial state, no confusing errors.
<span class="badge planned">Citizen developer onboarding path: planned</span>
---
<!-- _class: title -->
<!-- _paginate: false -->
# The desired outcomes
- **Velocity without sacrificing safety** — speed in ergonomics, safety in unbypassable gates
- **Security, observability, compliance as platform defaults** — not per-team effort, not post-hoc remediation
- **Auditability as a byproduct, not a project** — every change traceable to a human attestation and a tamper-evident evidence event
- **Blast radius contained by design** — OIDC + ABAC, only your own tagged resources
- **The bottleneck moves off the platform team's ticket queue** — a merged change progresses without a platform engineer joining a thread
- **Infrastructure as a utility, not a craft** — consume, don't maintain
- **A path to the citizen developer** — same envelope, senior engineer or non-technical
---
<!-- _class: title -->
<!-- _paginate: false -->
# Appendix
**Contents:**
1. The Citizen Developer Experience (full)
2. No Platform Code, No Cloning (detail)
3. Local Reproducibility (detail)
4. The Road to the North Star (phased roadmap)
5. Glossary
6. Operating Model & Cost
7. Verified by Construction
---
# A1 — The Citizen Developer Experience
A non-technical consumer ships a production deployment **by declaring intent** — without authoring a workflow, a configuration file, or an infrastructure module.
- The consumer opens an issue describing what they need (e.g. "a web API for the pricing service")
- An AI agent maps the intent to a contract referencing a module from the **reviewed skill catalog**
- The contract enters the **same pipeline** and must clear the **same confidence gate** before promotion
**Guardrails that make this safe:**
- Skills are **versioned, signed, and reviewed for sensitive data before release** (Infra & Ops owns the review)
- Agents are **stateless** — all state lives in the platform; the platform trusts and **always verifies**
- The agent's trace and submission confidence are captured in the contract for review
<span class="badge planned">Skill catalog + real agent runtime: planned</span>
---
# A2 — No Platform Code, No Cloning
Consumers `uses:` a **versioned** central workflow. The platform fetches itself at run time. The consumer **never touches platform internals.**
![w:1000](assets/png/developer-experience-03-no-cloning.png)
- The consumer's CI definition is a thin wrapper — one `uses:` line
- The runner checks out the consumer repo, then checks out the platform repo into the workspace
- The platform installs its own runtime dependencies — the consumer installs nothing
- When the platform ships a fix, every consumer on a floating tag gets it on their next run
---
# A3 — Local Reproducibility
The entire CI pipeline runs **from the shell**, not just in CI.
- `scripts/run_ci.sh` mirrors the CI pipeline locally — the same three stages (lint → test → check-only) in sequence
- `scripts/run_platform.sh --check-only` runs the platform **offline** — no AWS, no policy engine, no outbox required. Validates a contract end-to-end before pushing
- `--plan-only` runs through the infrastructure plan without applying
- The CI and deploy pipelines are defined by **declarative contracts** (YAML instances validated against JSON Schemas) — a single source of truth that both workflows implement
---
<!-- _class: title -->
<!-- _paginate: false -->
# A4 — The Road to the North Star
*Proposed phasing — not formally planned.*
![w:1100](assets/png/road-to-north-star.png)
---
# A5 — Glossary
| Term | Meaning |
|---|---|
| **OIDC** | OpenID Connect — federation protocol for short-lived tokens, no long-lived credentials |
| **ABAC** | Attribute-Based Access Control — access scoped by resource tags + repo identity, not roles |
| **CMK** | Customer-Managed Key — per-stack encryption key, 90-day rotation, no shared keys |
| **CMDB** | Configuration Management Database — validates change requests for decommission |
| **RPO** | Recovery Point Objective — RPO = 0 means evidence is written synchronously, no data loss |
| **HITL** | Human-in-the-Loop — deliberate human attestation required for qa/prod/dr environments |
| **VCS** | Version Control System — the git hosting platform (GitHub, Gitea, GitLab) |
| **NFR** | Non-Functional Requirement — encryption, tagging, observability standards |
---
# A6 — Operating Model & Cost
<style>
section { font-size: 20px; }
table { font-size: 18px; }
</style>
Nova runs at **zero cloud cost** for day-to-day development. AWS spend was measured via Cost Explorer (`COST.md`, 2026-07-28):
| Metric | Value |
|--------|-------|
| Total spend (8 days) | **$0.001883** |
| Daily average | $0.000235 |
| Projected monthly | ~$0.007 |
| Peak day | 2026-07-27 ($0.000867) |
- **Local emulators are the primary tier** — the full pipeline runs in-process, no AWS credentials
- **Live-AWS verification is milestone-scoped, then torn down.** The pipeline now **defaults to plan-only** on every PR; `NOVA_LIFECYCLE_MODE=full` overrides to apply→destroy for milestone verification (REQ-134, v1.12).
- **Cost drivers** are spike-scoped: Terraform plan reads (free), S3 state storage (cents), DynamoDB outbox (cents). No running infrastructure between milestones.
**Pre-mortem (`PRE_MORTEM.md`):** the v1.10 decay incident (diff-scoped VERIFY missed 7 adapter defects) is the root pattern: *a claim outruns the verification that backs it.* Four forward failure modes + structural mitigations (regression-tested IAM baseline, mandatory teardown, verified-only deck claims, honest scope).
---
<!-- _class: title -->
<!-- _paginate: false -->
# A7 — Verified by Construction
<style>
section { font-size: 20px; }
</style>
Two architectural pillars make "Verified" a structural property, not a claim:
- **The stateless adapter (918 → ~80 lines).** The Terraform adapter was a 918-line monolith with 3 constant tables and 39 type-specific branches. It is now a ~80-line **stateless assembler**: it owns no module content — no resource shape, no nested HCL blocks, no defaults. Each L1 module ships a real `terraform/` module dir owning its shape, nested blocks, and defaults. The adapter reads the registry and emits `module "x" { source = ... }` blocks. A new module is a new terraform dir, not a code change. *(The v1.12 P67 fix closed a dedup defect for multi-resource L1s — ecs-service, alb; CAP-013 now Verified.)*
- **Pipeline-driven lifecycle testing.** A `modules-lifecycle` pipeline matrix-runs each L1 and L2 module's contracts through apply→modify→destroy against live AWS. **The "test" = the pipeline cell going green.** Defaults to **plan-only** on every PR (fast, no AWS mutation, no cost); `NOVA_LIFECYCLE_MODE=full` overrides to the real apply→destroy for milestone verification (REQ-134, v1.12). The regression gate (D-091) re-runs all 22 capabilities at milestone completion — **22/22 Verified** as of v1.12.
The v1.10 lesson is the negative space: a 918-line adapter with type-specific branches decayed silently. The ~80-line stateless adapter + the milestone regression gate are the structural fix.
@@ -0,0 +1,253 @@
# The Developer Experience — Talking Points
> **Companion to:** `the-developer-experience-marp.md` (11 main + Appendix TOC + 7 appendix = 19 slides)
> **Content source:** `the-developer-experience.md` (full source of truth with speaker notes)
> **Purpose:** Presenter-ready cues — 3-6 talking points per slide + the one key takeaway the audience should remember.
> **Audience:** Senior Leadership — CTO, Head of Cloud, Head of Infrastructure, Head of DevOps
---
## Slide 1 — Title
**Talking points:**
- Brief introduction — this deck covers *who uses the platform and how fast/safe they ship*, not the internal mechanics (that's the companion deck)
- Set the frame: velocity without sacrificing safety, and security/observability/compliance as platform defaults rather than per-team effort
- v1.12 re-verification: every "Testing" claim in this deck is now Verified — 22/22 capabilities via the v1.11 lifecycle pipeline (see A7)
**Key takeaway:** The consumer surface is intentionally tiny. The platform's surface is large and opinionated.
---
## Slide 2 — Two consumer paths, one safety envelope
**Talking points:**
- This is the scope-boundary slide — here's who uses the platform, and here's where Nova's responsibility starts and stops
- Two consumer paths converge on the same contract: **technical** developer writes the contract directly; **citizen** developer declares intent and an AI agent produces a contract that passes the same safety envelope
- Upstream is anything — your IDE, an agentic SDLC, or vibe coding on a laptop. Nova doesn't care how the contract was produced
- Nova is infrastructure only — it provisions and governs AWS resources. Application deployment is upstream of the contract
- The two surfaces are *parallel*, not a progression. A citizen developer doesn't "graduate" to the developer surface. There is no "citizen developer mode" with weaker checks
**Key takeaway:** Two consumer paths, one safety envelope. Nova is infra only — anything upstream is fair game.
---
## Slide 3 — The platform at a glance
**Talking points:**
- One-slide map — frame it from the left edge: "this is what you touch, this is what the platform owns for you"
- The leadership beat: the convergence — two surfaces, one pipeline, one evidence stream — is the design point that lets us expand who can ship safely without lowering the bar
- Don't walk every node — point to the contract boundary and say "the rest of this deck zooms into the developer-facing pieces"
- The bar rises automatically — the confidence signal and HITL gates scale with the target environment, not with a ticket
**Key takeaway:** You own the left edge (app + contract). The platform owns everything else, end to end.
---
## Slide 4 — Three things. The entire consumer surface.
**Talking points:**
- Hold this slide — the audience should sit with how small the consumer surface is. Three things: app code, a contract, a one-line CI definition
- The contract is a single YAML file: module, environment, inputs. That's the entire consumer-facing interface to production
- The contract example shows **infrastructure inputs** (cpu, memory, desired_count, port) — not an `image:` field. The consumer declares capacity and shape; the platform resolves the rest
- Walk the "does not" list quickly — no infrastructure modules, no platform repo cloning, no cloud credentials, no state backends. Every item is a category of toil the platform removes
- For the Head of DevOps: this is the lever for throughput — the bottleneck moves off the platform team's ticket queue
**Key takeaway:** Three things. That's the entire consumer-side surface. Everything else is the platform's job.
---
## Slide 5 — See what the platform does, in real time
**Talking points:**
- This directly answers "but developers hate platforms that hide what they're doing" — the platform is opinionated about *what* runs, not *opaque* about *that* it runs
- Streamed output by default — the plan, policy results, and each check record flow to stdout
- PR comments after every successful pipeline stage — a developer always knows where they stand without refreshing a dashboard
- Connection strings posted as PR comments — human-readable, no hunting. Runtime secrets go to encrypted Parameter Store (KMS-encrypted, namespaced), never to logs
- The "errors become GitHub issues" point is a DX win that also helps the platform team — every consumer failure is a tracked, queryable artifact, not a lost log line
- Clear, explainable halt reasons — a policy violation, an insufficient confidence signal, or a missing attestation. Never an opaque debugging exercise
**Key takeaway:** The platform closes the feedback loop — streamed output, PR comments, clear halt reasons, no secrets in logs.
---
## Slide 6 — Pick from pre-built, security-reviewed blocks
**Talking points:**
- The catalog is what makes "declare intent" practical — you can only declare a module that exists
- For leadership: the catalog is the leverage. One well-reviewed module serves every consumer; a fix to the module serves every consumer on the next run. This is the compounding asset
- Primitives are single-purpose resources (S3, VPC, ECS, IAM, ALB, ECR, CloudFront, WAF, RDS) — each with documented inputs/outputs and versioning
- Modules are composed patterns — a static site with CDN + WAF; a microservice with VPC + ECS + ALB + ECR
- Validated examples per module (`simple.yaml` + `complex.yaml`) are validated against the contract schema in CI — examples cannot drift from the schema silently
- Auto-promotion of patterns (after 3 observed usages) and compliance extension points (GDPR, SOX, SOC2, DORA) are on the roadmap
**Key takeaway:** You don't author infrastructure — you pick from pre-built, security-reviewed building blocks. The catalog is the compounding asset.
---
## Slide 7 — The bar rises automatically with sensitivity
**Talking points:**
- Promotion is a workflow choice, not a contract edit — a promotion can be reviewed as a *diff in the workflow*, not as a rewritten contract
- The DX win: the contract stays stable across environments; the safety win: the platform raises the threshold and attestation bar automatically based on the target environment
- The consumer can't bypass the gates — they pick *which* environment to target, and the platform applies the right bar
- No staging environment — the design deliberately removes the "staging is basically prod but not really" anti-pattern. Dev is the only autonomous environment
- Separation of duties is enforced — the QA approver cannot be the prod approver
- Be honest about maturity: dev is tested and pilot-ready; qa/prod/dr wiring is planned
**Key takeaway:** The bar rises automatically with sensitivity. The consumer picks the environment; the platform applies the right gate.
---
## Slide 8 — Tearing down is as gated as deploying
**Talking points:**
- The counter-argument to "deletion protection makes cleanup impossible" is this slide. Decommission is a first-class, gated, two-approval flow — not a lock with no key, and not an ungated `terraform destroy`
- The CMDB validation means decommission is auditable, not just possible — the platform queries the CMDB and asserts the CR is `approved` and matches the consumer repo
- Two SRE human-attestation gates: disable protection → SRE approves → zero all counts + destroy → a second SRE approves
- The per-stack encryption key enters a grace window (default 30 days) so encrypted data remains recoverable during decommission
- For the Head of Infrastructure: this is what makes deletion protection safe to ship by default — cleanup is a deliberate, gated path, not an impossible one
**Key takeaway:** Tearing down is as deliberate as deploying — two SRE attestation gates + CMDB-validated change request.
---
## Slide 9 — You control when you absorb improvements
**Talking points:**
- This is the "no surprise upgrades" story. Leadership hears two things: (1) consumers aren't forced to chase the platform, (2) the platform isn't forced to support N forks of every workflow
- Floating MAJOR + MINOR tags (e.g. `@v1.12`) — a consumer automatically receives patch updates within the line
- Semantic versioning with a clear contract: interface → MAJOR, behavior → MINOR, lifecycle → PATCH
- A consumer can pin to an exact version for maximum stability, or float on MAJOR only (`@v1`) to absorb new features on their own cadence
- Unversioned references (`@main`, bare) are discouraged — the versioned tag is the only immutability lever a consumer has
- The automated release job computes the next semver on merge to main, creates the tag, and updates the floating tags
**Key takeaway:** You control when you absorb platform improvements — no surprise upgrades, no forced forks.
---
## Slide 10 — Fails gracefully, not opaquely
**Talking points:**
- This looks like a small thing; it's actually a cultural one. The platform's posture is "help me get started," not "you should have known"
- For the Head of DevOps: this is what drives adoption. Platforms that fail opaquely on first run get routed around
- When no environment is bound, the platform emits a user-friendly onboarding prompt — not an opaque failure
- The prompt tells the consumer: no environment bound, what the platform will provision, expected turnaround, how to request an environment
- The pipeline then exits without attempting a deployment — no partial state, no confusing errors
- The citizen developer onboarding path is planned
**Key takeaway:** The platform fails gracefully, not opaquely — first impressions are made when it fails for the first time.
---
## Slide 11 — The desired outcomes
**Talking points:**
- Close on the strategic frame. The platform is not "a CI/CD tool" — it is the organizational lever for shipping safely at the pace the business demands, with the security and audit posture the regulators require
- Velocity without sacrificing safety: speed is in the ergonomics (a simple contract, a one-line `uses:`); safety is in the gates the consumer cannot bypass
- Security, observability, and compliance as platform defaults — not per-team effort, not post-hoc remediation
- Auditability as a byproduct, not a project — every production change is traceable to a human attestation and a tamper-evident evidence event
- The bottleneck moves off the platform team's ticket queue — a merged change progresses through lower environments without a platform engineer joining a thread
- A path to the citizen developer: the same safety envelope serves a senior engineer and a non-technical consumer
- Invite questions; the companion deck ("How the Platform Works") covers the internal mechanics in more depth
**Key takeaway:** Ship safely at the pace the business demands, with the security and audit posture the regulators require.
---
## Appendix TOC — Appendix
**Talking points:**
- These are backup slides for Q&A. Use them when the audience asks for the detail behind a main-slide claim
- Don't walk through them in the main talk unless time permits
- The appendix is indexed to match the Marp deck's A1-A7 structure
**Key takeaway:** Backup slides for Q&A — pull the relevant appendix slide when asked.
---
## A1 — The Citizen Developer Experience
**Talking points:**
- Be honest about maturity: the *mechanism* (agent → contract → same pipeline) is designed and the stub was proven in the v1.0 demo; the full skill catalog and real agent runtime are planned
- The "vibe coding on a laptop" framing is intentional — it meets the citizen developer where they already are, but every submission still passes the same safety envelope
- The design point matters to leadership now: we are building for a world where more of the org can ship safely, not where more of the org has to become a platform engineer
- Guardrails: skills are versioned, signed, reviewed for sensitive data; agents are stateless; the platform trusts and always verifies
- The agent's trace and submission confidence are captured in the contract (`profile: agentic`) for review
**Key takeaway:** A non-technical consumer ships by declaring intent — same pipeline, same safety envelope, no weaker checks.
---
## A2 — No Platform Code, No Cloning
**Talking points:**
- The Head of Cloud cares about this: there is no "platform code in every consumer repo" problem
- The version-pinned `uses:` line is the *only* coupling, and it's a coupling that updates itself within the line
- The runner checks out the consumer repo, then checks out the platform repo into the workspace — the consumer never clones the platform repo
- The platform installs its own runtime dependencies — the consumer installs nothing
- When the platform ships a fix, every consumer on a floating tag gets it on their next run — no per-repo upgrade project
**Key takeaway:** The consumer never touches platform internals. The versioned `uses:` line is the only coupling.
---
## A3 — Local Reproducibility
**Talking points:**
- This is the "no surprises before you push" story. A consumer can validate their contract offline, run the plan offline, and only push when they're confident
- `scripts/run_ci.sh` mirrors the CI pipeline locally — the same three stages (lint → test → check-only) in sequence
- `scripts/run_platform.sh --check-only` runs the platform offline — no AWS, no policy engine, no outbox required
- The same declarative contract drives both the local tooling and CI — there's no "works on my machine, fails in CI" gap
**Key takeaway:** The entire CI pipeline runs from the shell — no surprises before you push.
---
## A4 — The Road to the North Star
**Talking points:**
- This is a proposed phasing, not a formally committed plan — call that out explicitly
- Phase 1 is what's tested and Verified today (22/22 capabilities, torn down to zero-cost)
- Phase 2 is the next milestone (qa/prod/dr wiring)
- Phase 3 introduces the agentic surface (skill catalog + agents)
- Phase 4 is the north star: citizen developer GA on the same safety envelope
- Use this only when an audience member asks "how do you get from here to there"
**Key takeaway:** Proposed phasing — Phase 1 Verified, Phase 4 is the North Star (citizen developer GA).
---
## A5 — Glossary
**Talking points:**
- Keep this slide in your back pocket for the audience member who asks "what does ABAC actually mean?"
- Don't read it aloud
- All acronyms used in the deck are defined here
**Key takeaway:** Reference slide — don't read aloud.
---
## A6 — Operating Model & Cost
**Talking points:**
- The headline for the Head of Cloud / Finance: less than one cent over 8 days of active development; zero BAU cloud spend
- The lifecycle pipeline defaults to plan-only so the PR-time cost is zero; `NOVA_LIFECYCLE_MODE=full` overrides for milestone verification
- The pre-mortem is the credibility slide — we already asked "how does this fail?" and the mitigations are structural
- The v1.10 decay incident is disclosed honestly, not hidden — that disclosure IS the mitigation
- Cost drivers are spike-scoped: Terraform plan reads (free), S3 state storage (cents), DynamoDB outbox (cents). No running infrastructure between milestones
**Key takeaway:** Zero BAU cloud cost. Pre-mortemed failure modes with structural mitigations.
---
## A7 — Verified by Construction
**Talking points:**
- This is the deep-dive slide for the Head of Engineering / Architecture — the two pillars answer "how do you keep the decks honest?"
- The adapter is simple enough to reason about (a stateless assembler); the lifecycle pipeline is the automated verification that backs every "Testing" claim
- The v1.10 lesson is the negative space: a 918-line adapter with type-specific branches decayed silently because the VERIFY gate was diff-scoped
- The ~80-line stateless adapter + the milestone regression gate are the structural fix
- The plan-only default (v1.12) means verification runs on every PR at zero cost, with the full apply→destroy gated behind a CI variable override
**Key takeaway:** "Verified" is a structural property, not a claim — the stateless adapter + lifecycle pipeline make it so.
File diff suppressed because one or more lines are too long
@@ -0,0 +1,457 @@
# The Developer Experience
> **Subtitle:** Nova — The New Dawn of DevSecOps
> **Audience:** Senior Leadership, CTO, Head of Cloud, Head of Infrastructure, Head of DevOps
> **Length:** ~16 minutes · 11 main + Appendix TOC + 7 appendix = 19 slides
> **Purpose:** Sell the developer experience and the citizen developer experience to tech leadership — velocity without sacrificing safety, and security/observability/compliance as platform defaults rather than per-team effort.
> **Maturity framing:** "Testing" = works internally, dev pilot-ready. "Planned" = on the roadmap. "Agentic" = involves AI agents or autonomous decision-making.
> **Re-verification (2026-07-29):** Every "Testing" claim in this deck was re-verified in v1.10 Phase 54 (D-093) and again in v1.11 via the pipeline-driven lifecycle tests (P59P62). The headline E2E (contract → resolver → adapter → terraform init/validate/plan) passes against the live AWS account; the local emulating tier (Phase 53) runs the full E2E with no cloud credentials. **22/22 auto-verifiable capabilities Verified** (CAP-013 fixed in v1.12 P67 — the adapter's multi-resource L1 dedup defect is closed). The v1.11 lifecycle pipeline ran apply→modify→destroy against live AWS and was then torn down to zero-cost (D-096). See `.ciagent/CAPABILITY_INVENTORY.md` and `.ciagent/PRE_MORTEM.md`.
---
## Slide 1 — Title
**Nova — The New Dawn of DevSecOps.** Security as a seamless enabler of fast deployments — not a bottleneck, not a "no" department.
The consumer surface is intentionally tiny. The platform's surface is large and opinionated.
> **Speaker notes:** Brief introduction — this deck covers *who uses the platform and how fast/safe they ship*, not the internal mechanics (that's the companion deck). Set the frame: velocity without sacrificing safety, and security/observability/compliance as platform defaults rather than per-team effort.
---
## Slide 2 — Two consumer paths, one safety envelope
The platform serves **two kinds of consumer** through two coordinated paths — but both converge on the **same contract, the same policy envelope, and the same evidence stream.**
```mermaid
flowchart LR
subgraph UP ["Upstream — anything"]
direction TB
A["Technical dev\n(app code + contract)"]
B["Citizen dev\n(intent → AI agent\n→ contract)"]
end
subgraph ACDL ["Nova — infrastructure only"]
C["Same contract\nSame pipeline\nSame safety"]
D["Provision\nAWS resources"]
E["Evidence\nhash-chained"]
end
subgraph DOWN ["Downstream"]
F["AWS resources\nrunning"]
G["Consumer pipeline\ndeploys image"]
end
A --> C
B --> C
C --> D
C --> E
D --> F
F --> G
```
- **Technical developer** — owns app code + a contract + a thin CI definition.
- **Citizen developer** — declares intent in plain language; an AI agent produces a contract that passes the **same** safety envelope.
- **Upstream is anything** — IDE, agentic SDLC, or vibe coding. Nova doesn't care how the contract was produced.
- **Nova is infrastructure only** — it provisions and governs AWS resources. Application deployment is upstream.
> **Speaker notes:** This is the thesis of the deck. The two surfaces are *parallel*, not a progression — a citizen developer doesn't "graduate" to the developer surface. Both produce a contract; both get the same treatment. The scope boundary matters: anything upstream of the contract is out of Nova's concern. The leadership takeaway: we expand who can ship safely without lowering the bar.
---
## Slide 3 — The platform at a glance
One picture of the whole platform — what you touch, what the platform owns, and where the safety lives. The rest of this deck zooms into the developer-facing pieces.
```mermaid
flowchart TD
subgraph UP ["Consumer surfaces — upstream"]
direction LR
U1["Technical dev\napp code + contract"]
U2["Citizen dev\nintent → AI agent → contract"]
end
subgraph ACDL ["Nova — infrastructure only"]
direction TB
CS["Contract schema\n(validate + fail-fast)"]
subgraph PIPE ["Central pipeline — fixed stages, every deployment"]
direction LR
P1["Validate"] --> P2["Resolve\ntarget stack"] --> P3["Security\nchecks"] --> P4["Infra plan"] --> P5["Policy\nchecks"] --> P6["Confidence\nsignal"] --> P7["Evidence\nevent"] --> P8["Infra apply"]
end
CAT["Module catalog\nprimitives + modules\n(security-reviewed)"]
ADAPT["Engine adapter\n(stateless → Terraform)"]
ENV["Platform-managed\nenvironments\naccount · VPC · state · IAM"]
HITL["HITL gates\nqa · prod · dr"]
EVID["Evidence stream\nhash-chained outbox\n(RPO = 0)"]
CS --> PIPE
CAT --> P2
ADAPT --> P4
ADAPT --> P8
ENV --> P8
P6 --> HITL
HITL --> P8
P7 --> EVID
end
subgraph DOWN ["Downstream"]
direction LR
D1["AWS resources\nrunning\n(tagged, encrypted)"]
D2["Consumer pipeline\ndeploys image"]
end
U1 --> CS
U2 --> CS
P8 --> D1
D1 --> D2
```
- **You own the left edge** — app code and a contract. That is the entire consumer surface.
- **The platform owns everything in the middle** — the pipeline, the catalog, the adapter, the environments, the gates, the evidence.
- **Two surfaces, one pipeline, one evidence stream** — a senior engineer and a citizen developer converge on the same safety envelope.
- **The bar rises automatically** — the confidence signal and HITL gates scale with the target environment, not with a ticket.
> **Speaker notes:** This is the one-slide map. For a developer-experience audience, frame it from the left edge: "this is what you touch, this is what the platform owns for you." The leadership beat: the convergence — two surfaces, one pipeline, one evidence stream — is the design point that lets us expand who can ship safely without lowering the bar. Don't walk every node; point to the contract boundary and say "the rest of this deck zooms into the developer-facing pieces."
---
## Slide 4 — Three things. The entire consumer surface.
Three things. That is the entire consumer-side surface.
```yaml
id: msvc
name: microservice
environment: dev
infrastructure:
microservice:
version: "1.0.0"
inputs:
cpu: 256
memory: 512
desired_count: 2
port: 8080
```
1. **App code** — the consumer's service, at the top level of the repo
2. **A contract** — a single YAML file: id, name, environment, infrastructure
3. **A one-line CI definition** — a thin `uses:` wrapper pointing at a versioned platform workflow
The developer does **not**: write infrastructure modules, clone the platform repo, hold cloud credentials, or maintain a state backend.
> **Speaker notes:** Hold this slide. The audience should sit with how small the consumer surface is. Every item in the "does not" list is a category of toil the platform removes. The contract is the API — deliberately tiny so that it can be reviewed, validated, and audited. For the Head of DevOps: this is the lever for throughput — the bottleneck moves off the platform team's ticket queue.
---
## Slide 5 — See what the platform does, in real time
Developers see **what the platform is doing**, in real time.
- **Streamed output by default** — the plan, policy-check results, and each check record flow to stdout.
- **PR comments after every successful pipeline stage** — a developer always knows where they stand.
- **Clear, explainable halt reasons** — a policy violation, an insufficient confidence signal, or a missing attestation. **Never an opaque debugging exercise.**
- **Connection strings posted as PR comments** — human-readable, no hunting. Runtime secrets go to encrypted Parameter Store (KMS-encrypted, namespaced), never to logs.
- **Errors become GitHub issues, automatically** — a failed deploy opens an issue on the platform repo.
> **Speaker notes:** This directly answers "but developers hate platforms that hide what they're doing." The platform is opinionated about *what* runs, not *opaque* about *that* it runs. The PR-comment-after-each-stage pattern is a small thing that compounds into trust. The "errors become issues" point is a DX win that also helps the platform team — every consumer failure is a tracked, queryable artifact, not a lost log line.
---
## Slide 6 — Pick from pre-built, security-reviewed blocks
Developers pick from **pre-built, security-reviewed building blocks.**
```mermaid
flowchart LR
subgraph PRIM ["Primitives"]
direction TB
P1["S3"]
P2["VPC"]
P3["ECS"]
P4["IAM"]
P5["ALB"]
P6["ECR"]
P7["CloudFront"]
P8["WAF"]
P9["RDS"]
end
subgraph MOD ["Modules — composed patterns"]
direction TB
M1["Static site\nCDN + WAF + S3"]
M2["Microservice\nVPC + ECS + ALB + ECR"]
end
PRIM --> MOD
```
- **Primitives** — single-purpose resources (S3, VPC, ECS, IAM, ALB, ECR, CloudFront, WAF, RDS), each with documented inputs/outputs and versioning.
- **Modules** — composed patterns (a static site with CDN + WAF; a microservice with VPC + ECS + ALB + ECR).
- **Validated examples per module**`simple.yaml` + `complex.yaml`, validated against the contract schema in CI. Examples cannot drift from the schema silently.
- **Auto-promotion of patterns** — auto-promoted to the catalog after 3 observed usages. <span class="badge planned">Planned</span>
- **Compliance extension points** — each module lists where GDPR, SOX, SOC2, DORA controls will wire in. <span class="badge planned">Planned</span>
> **Speaker notes:** The catalog is what makes "declare intent" practical — you can only declare a module that exists. For leadership: the catalog is the leverage. One well-reviewed module serves every consumer; a fix to the module serves every consumer on the next run. This is the compounding asset.
---
## Slide 7 — The bar rises automatically with sensitivity
The contract is environment-agnostic. The platform raises the bar automatically.
```mermaid
flowchart LR
DEV["dev<br/>autonomous"] -->|raise the bar| QA["qa<br/>QA attests"]
QA -->|raise the bar| PROD["prod<br/>SRE attests"]
PROD -->|raise the bar| DR["dr<br/>SRE attests + DR drill"]
```
| Environment | What the platform adds | Maturity |
|---|---|---|
| dev | Confidence ≥ 0.50, fully autonomous | — |
| qa | QA human attestation + confidence ≥ 0.75 | <span class="badge planned">Planned</span> |
| prod | SRE human attestation + confidence ≥ 0.90 | <span class="badge planned">Planned</span> |
| dr | SRE human attestation + confidence ≥ 0.95 + DR drill reference | <span class="badge planned">Planned</span> |
- **No staging environment** — the design deliberately removes the "staging is basically prod but not really" anti-pattern.
- **Separation of duties is enforced** — the QA approver cannot be the prod approver.
- **Timeout discipline** — 1 business day = warn + escalate; 2 business days = auto-freeze + re-submit.
> **Speaker notes:** Promotion is a workflow choice, not a contract mutation — this matters because it means a promotion can be reviewed as a *diff in the workflow*, not as a rewritten contract. The DX win: the contract stays stable across environments; the safety win: the platform raises the threshold and attestation bar automatically based on the target environment. The consumer can't bypass the gates — they pick *which* environment to target, and the platform applies the right bar. Be honest about maturity: dev is tested and pilot-ready; qa/prod/dr wiring is planned.
---
## Slide 8 — Tearing down is as gated as deploying
Tearing down a stack is **as deliberate as deploying one.**
```mermaid
flowchart LR
A["Validate CR\n(CMDB)"]
B["Disable\nprevent_destroy"]
C["SRE\napprove"]
D["Zero counts\n+ destroy"]
E["SRE\napprove"]
F["Key enters\ngrace window"]
A --> B --> C --> D --> E --> F
```
```yaml
uses: acdl/.github/workflows/deploy.yml@v1.12
with:
contract: .nova/contract.yml
mode: decommission
changeRequestId: "CHG0678912"
```
A 2-step pipeline with **two SRE human-attestation gates**:
1. **Validate the change request** — the platform queries the CMDB and asserts the CR is `approved` and matches the consumer repo. No CR, no decommission.
2. **Disable deletion protection****SRE approves****Zero all counts + destroy** → **a second SRE approves.**
The per-stack encryption key enters a **grace window** (default 30 days) so encrypted data remains recoverable.
> **Speaker notes:** The counter-argument to "deletion protection makes cleanup impossible" is this slide. Decommission is a first-class, gated, two-approval flow — not a lock with no key, and not an ungated `terraform destroy`. For the Head of Infrastructure: the CMDB validation means decommission is auditable, not just possible.
---
## Slide 9 — You control when you absorb improvements
Consumers control **when** they absorb platform improvements.
```mermaid
flowchart LR
subgraph FLOAT ["@v1.12 — floating MAJOR+MINOR"]
direction LR
F1["v1.12.0"]
F2["v1.12.1"]
F3["v1.12.2"]
F1 --> F2 --> F3
end
subgraph PIN ["@v1.12.2 — pinned exact"]
direction LR
P1["v1.12.2"]
P2["v1.12.2"]
P3["v1.12.2"]
P1 --> P2 --> P3
end
subgraph MAJ ["@v1 — float MAJOR only"]
direction LR
M1["v1.12.0"]
M2["v1.13.0"]
M3["v1.14.0"]
M1 --> M2 --> M3
end
```
- **Floating MAJOR + MINOR tags** (e.g. `@v1.12`) — automatically receive patch updates within the line.
- **Semantic versioning with a clear contract:** interface → MAJOR, behavior → MINOR, lifecycle → PATCH.
- **Pin to an exact version** for maximum stability, or float on MAJOR only (`@v1`) to absorb new features on your own cadence.
- **Unversioned references (`@main`, bare) are discouraged** — the versioned tag is the only immutability lever.
- **Automated release job** computes the next semver on merge to main, creates the tag, and updates the floating tags.
> **Speaker notes:** This is the "no surprise upgrades" story. Leadership hears two things: (1) consumers aren't forced to chase the platform, (2) the platform isn't forced to support N forks of every workflow. The versioning discipline is what makes both true.
---
## Slide 10 — Fails gracefully, not opaquely
First impressions of a platform are made **when it fails for the first time.** The platform fails gracefully.
When no environment is bound, the platform emits a **user-friendly onboarding prompt** instead of failing opaquely. The prompt tells the consumer:
1. That no environment is bound to their repo yet.
2. What the platform will provision on their behalf (account, network, state, role).
3. The expected turnaround for the platform team to grant the environment.
4. How to request an environment.
The pipeline then **exits without attempting a deployment** — no partial state, no confusing errors.
<span class="badge planned">Citizen developer onboarding path: planned</span>
> **Speaker notes:** This looks like a small thing; it's actually a cultural one. The platform's posture is "help me get started," not "you should have known." For the Head of DevOps: this is what drives adoption. Platforms that fail opaquely on first run get routed around.
---
## Slide 11 — The desired outcomes
- **Velocity without sacrificing safety.** Speed is in the ergonomics; safety is in the gates the consumer cannot bypass.
- **Security, observability, and compliance as platform defaults** — not per-team effort, not post-hoc remediation.
- **Auditability as a byproduct, not a project.** Every production change is traceable to a human attestation and a tamper-evident evidence event.
- **Blast radius contained by design.** Zero-trust OIDC + ABAC means a consumer can only touch its own tagged resources.
- **The bottleneck moves off the platform team's ticket queue.** A merged change progresses through lower environments without a platform engineer joining a thread.
- **Infrastructure as a utility, not a craft.** Teams consume infrastructure, they don't maintain it.
- **A path to the citizen developer.** The same safety envelope serves a senior engineer and a non-technical consumer.
> **Speaker notes:** Close on the strategic frame. The platform is not "a CI/CD tool" — it is the organizational lever for shipping safely at the pace the business demands, with the security and audit posture the regulators require. Invite questions; the companion deck ("How the Platform Works") covers the internal mechanics in more depth.
---
## Appendix — Contents
For deep dives — these slides cover details omitted from the main 10.
1. **A1 — The Citizen Developer Experience** (full)
2. **A2 — No Platform Code, No Cloning** (detail)
3. **A3 — Local Reproducibility** (detail)
4. **A4 — The Road to the North Star** (phased roadmap)
5. **A5 — Glossary**
6. **A6 — Operating Model & Cost** (real AWS spend + pre-mortem)
7. **A7 — Verified by Construction** (the v1.11 architecture)
> **Speaker notes:** These are backup slides for Q&A. Use them when the audience asks for the detail behind a main-slide claim. Don't walk through them in the main talk unless time permits.
---
## A1 — The Citizen Developer Experience
A non-technical consumer ships a production deployment **by declaring intent** — without authoring a workflow, a configuration file, or an infrastructure module. Think of this as **vibe coding on a laptop** — the consumer describes what they want; an AI agent turns that into a contract that the platform treats identically to a senior engineer's.
- The consumer opens an issue describing what they need (e.g. "a web API for the pricing service").
- An AI agent maps the intent to a contract referencing a module from the **reviewed skill catalog.**
- The contract enters the **same pipeline** and must clear the **same confidence gate** before promotion.
**Guardrails that make this safe:**
- Skills are **versioned, signed, and reviewed for sensitive data before release** (Infra & Ops owns the review — it is the mandatory release gate).
- Agents are **stateless** — all state lives in the platform. The platform does not run the skill blindly; it trusts and **always verifies** on the platform side.
- The agent's trace and submission confidence are captured in the contract (`profile: agentic`), so a reviewer can see *how* the contract was produced.
- **Initial skill catalog:** web API, worker, scheduled job, static asset, basic observability bootstrap.
<span class="badge planned">Skill catalog + real agent runtime: planned</span>
> **Speaker notes:** Be honest about maturity: the *mechanism* (agent → contract → same pipeline) is designed and the stub was proven in the v1.0 demo; the full skill catalog and real agent runtime are planned. The "vibe coding on a laptop" framing is intentional — it meets the citizen developer where they already are, but every submission still passes the same safety envelope. The design point matters to leadership now: we are building for a world where more of the org can ship safely, not where more of the org has to become a platform engineer.
---
## A2 — No Platform Code, No Cloning
Consumers `uses:` a **versioned** central workflow. The platform fetches itself at run time. The consumer **never touches platform internals.**
```mermaid
flowchart LR
A["Consumer repo<br/>app + contract + 'uses:'"] -->|triggers on push to main| B["Platform runner"]
B -->|checks out the consumer repo| A
B -->|checks out the Nova platform repo<br/>into the workspace| C["Platform code<br/>(modules, adapters, schemas)"]
C --> B
B -->|runs the pipeline against<br/>the consumer's contract| D["Consumer's resources in AWS"]
```
- The consumer's CI definition is a thin wrapper — one `uses:` line pointing at a versioned tag.
- The runner checks out the consumer repo, then checks out the platform repo into the workspace.
- The platform installs its own runtime dependencies. The consumer installs nothing.
- The consumer **never clones the platform repo, never invokes platform scripts locally** (optional `--check-only` validation is available but not required for the happy path).
- When the platform ships a fix, every consumer on a floating MAJOR.MINOR tag gets it on their next run — no per-repo upgrade project.
> **Speaker notes:** The Head of Cloud cares about this: there is no "platform code in every consumer repo" problem. The version-pinned `uses:` line is the *only* coupling, and it's a coupling that updates itself within the line.
---
## A3 — Local Reproducibility
The entire CI pipeline runs **from the shell**, not just in CI.
- `scripts/run_ci.sh` mirrors the CI pipeline locally — the same three stages (lint → test → check-only) in sequence.
- `scripts/run_platform.sh --check-only` runs the platform **offline** — no AWS, no policy engine, no outbox required. Validates a contract end-to-end before pushing.
- `--plan-only` runs through the infrastructure plan without applying.
- The CI and deploy pipelines are defined by **declarative contracts** (YAML instances validated against JSON Schemas) — a single source of truth that both workflows implement.
> **Speaker notes:** This is the "no surprises before you push" story. A consumer can validate their contract offline, run the plan offline, and only push when they're confident. The same declarative contract drives both the local tooling and CI — there's no "works on my machine, fails in CI" gap.
---
## A4 — The Road to the North Star
*Proposed phasing — not formally planned.*
```mermaid
flowchart LR
P1["Phase 1<br/>Core platform<br/>(22/22 Verified)"] --> P2["Phase 2<br/>Safe promotion<br/>qa/prod/dr wiring"]
P2 --> P3["Phase 3<br/>Agentic surface<br/>(skill catalog + agents)"]
P3 --> P4["Phase 4<br/>North star<br/>citizen developer GA"]
```
> **Speaker notes:** This is a proposed phasing, not a formally committed plan — call that out explicitly. Phase 1 is what's tested and Verified today (22/22 capabilities, torn down to zero-cost). Phase 2 is the next milestone (qa/prod/dr wiring). Phase 3 introduces the agentic surface. Phase 4 is the north star: citizen developer GA on the same safety envelope. Use this only when an audience member asks "how do you get from here to there."
---
## A5 — Glossary
| Term | Meaning |
|---|---|
| **OIDC** | OpenID Connect — federation protocol for short-lived tokens, no long-lived credentials |
| **ABAC** | Attribute-Based Access Control — access scoped by resource tags + repo identity, not roles |
| **CMK** | Customer-Managed Key — per-stack encryption key, 90-day rotation, no shared keys |
| **CMDB** | Configuration Management Database — validates change requests for decommission |
| **RPO** | Recovery Point Objective — RPO = 0 means evidence is written synchronously, no data loss |
| **HITL** | Human-in-the-Loop — deliberate human attestation required for qa/prod/dr environments |
| **VCS** | Version Control System — the git hosting platform (GitHub, Gitea, GitLab) |
| **NFR** | Non-Functional Requirement — encryption, tagging, observability standards |
> **Speaker notes:** Keep this slide in your back pocket for the audience member who asks "what does ABAC actually mean?" Don't read it aloud.
---
## A6 — Operating Model & Cost
Nova runs at **zero cloud cost** for day-to-day development. The v1.0→v1.10 AWS spend was measured directly via Cost Explorer (`COST.md`, 2026-07-28):
| Metric | Value |
|--------|-------|
| Total spend (8 days) | **$0.001883** |
| Daily average | $0.000235 |
| Projected monthly | ~$0.007 |
| Peak day | 2026-07-27 ($0.000867 — v1.10 regression + verify run) |
- **Local emulators are the primary tier** — the full pipeline runs in-process, no AWS credentials, no Checkov, no DynamoDB.
- **Live-AWS verification is milestone-scoped, then torn down.** The v1.11 lifecycle pipeline ran apply→modify→destroy for every module, then tore down to zero-cost steady state (D-096 — teardown mandatory before milestone COMPLETE). The lifecycle pipeline now **defaults to plan-only** on every PR (fast, no AWS mutation, no cost); a CI variable (`NOVA_LIFECYCLE_MODE=full`) overrides to the real apply→destroy for milestone verification (REQ-134, v1.12).
- **Cost drivers** are spike-scoped: Terraform plan reads (free), S3 state storage (cents), DynamoDB outbox (cents). No running infrastructure between milestones.
**Pre-mortem (`PRE_MORTEM.md`):** the project's failure modes were pre-mortemed before the leadership pitch. The v1.10 decay incident (diff-scoped VERIFY missed 7 adapter defects — decks advertised capability that wasn't reproducible) is the root pattern: *a claim outruns the verification that backs it.* Four forward failure modes + structural mitigations (regression-tested IAM baseline, mandatory teardown, verified-only deck claims, honest scope).
> **Speaker notes:** The headline for the Head of Cloud / Finance: less than one cent over 8 days of active development; zero BAU cloud spend; the lifecycle pipeline defaults to plan-only so the PR-time cost is zero. The pre-mortem is the credibility slide — we already asked "how does this fail?" and the mitigations are structural.
---
## A7 — Verified by Construction (the v1.11 architecture)
v1.11 rebuilt the platform on two architectural pillars that make "Verified" a structural property, not a claim:
- **The stateless adapter (918 → ~80 lines).** The Terraform adapter was a 918-line monolith with 3 constant tables and 39 type-specific branches. It is now a ~80-line **stateless assembler**: it owns no module content. Each L1 module ships a real `terraform/` module dir owning its resource shape, nested blocks, and defaults. A new module is a new terraform dir, not a code change. *(The v1.12 P67 fix closed a dedup defect for multi-resource L1s — ecs-service, alb; CAP-013 now Verified.)*
- **Pipeline-driven lifecycle testing.** A `modules-lifecycle` pipeline matrix-runs each L1 and L2 module's contracts through apply→modify→destroy against live AWS. **The "test" = the pipeline cell going green.** Defaults to **plan-only** on every PR (fast, no AWS mutation, no cost); `NOVA_LIFECYCLE_MODE=full` runs the real apply→destroy for milestone verification (REQ-134, v1.12). The regression gate (D-091) re-runs all 22 capabilities at milestone completion — 22/22 Verified as of v1.12.
> **Speaker notes:** This is the deep-dive slide for the Head of Engineering / Architecture. The two pillars answer "how do you keep the decks honest?" The adapter is simple enough to reason about (a stateless assembler); the lifecycle pipeline is the automated verification that backs every "Testing" claim. The v1.10 lesson is the negative space: a 918-line adapter with type-specific branches decayed silently. The ~80-line stateless adapter + the milestone regression gate are the structural fix. The plan-only default (v1.12) means verification runs on every PR at zero cost, with the full apply→destroy gated behind a CI variable override.
-97
View File
@@ -1,97 +0,0 @@
# RACI — Who Owns What
> This page is the citizen-developer-facing copy.
Nova's delivery lifecycle has four roles. This page clarifies who owns
what — so the citizen developer knows what they bring, what the platform
provides, what quality engineering guards, and what is co-owned with SRE.
## The Four Roles
### Citizen Developer (CD)
That's you — the consumer (technical developer L3A or non-technical L3B).
You are **Responsible** for all **Functional Requirements (FRs)** and
**User Acceptance Testing (UAT)**. You produce the FRs + UAT via your AI
coding agent, an upstream agentic SDLC platform, or any upstream
development platform. **The source does not matter** — all are subject to
the same compliance standards (the submission-readiness gate, the
contract schema, the policy envelope, the immutable audit stream). Nova
validates the submission, not the author.
### Platform (Nova)
Nova is **Responsible** for all **Non-Functional Requirements (NFRs)**,
**Infrastructure** (cloud resource lifecycle, state, IAM), and
**Production deployments to cloud** (the apply path, the pipeline, the
release mechanics).
### Quality Engineering (QE)
Quality Engineering is **Responsible** for the platform-side quality
checks: policy enforcement, confidence scoring, schema validation, and
the functional/contract/non-functional evidence that feeds attestation.
QE owns the **quality** of what the platform produces — the gate
evidence, not the gate decision.
### SRE — co-owned with you
Production readiness is **co-owned**. SRE owns operational readiness:
the operational attestation (incident response, capacity, resilience,
DR). The platform performs the QA + SRE attestations agentically (it
runs the confidence signal, the policy checks, the
separation-of-duties). The citizen developer **oversees and triggers**
the actual release — the human attestation at the stage gate is your
authorization. The platform runs the checks; you authorize the promotion.
This is the "autonomy in operations, human at stage gates" model.
## The Matrix
| Work Category | Citizen Developer | Platform | Quality Engineering | SRE |
|---|---|---|---|---|
| **Functional Requirements (FRs)** | **R/A** | C | I | I |
| **User Acceptance Testing (UAT)** | **R/A** | C | I | I |
| **Non-Functional Requirements (NFRs)** | I | **R/A** | C | C |
| **Infrastructure (cloud, state, IAM)** | I | **R/A** | I | C |
| **QA (policy, confidence, schema checks)** | C | R | **R/A** | I |
| **Production deployment to cloud** | I | **R/A** | C | C |
| **Quality attestation (QA sign-off)** | **A** | R | **R** | I |
| **Production readiness (SRE sign-off)** | **A** | R | C | **R** |
**Key:** **R** = Responsible (does the work) · **A** = Accountable (owns
the outcome, sign-off) · **C** = Consulted · **I** = Informed.
## What This Means in Practice
**You (Citizen Developer) bring:**
- Your application code + a contract that declares intent.
- Your FRs (what the application does).
- Your UAT (you accept the deployment when it meets your FRs).
**Nova (Platform) provides:**
- The NFRs (security, observability, compliance — baked into the
pipeline, not your concern).
- The infrastructure (cloud resources, state management, IAM scoping).
- The production deployment (the apply path, the pipeline, the release).
**Quality Engineering guards:**
- The policy enforcement, confidence scoring, schema validation.
- The quality attestation evidence that feeds the stage gates.
**You co-own production readiness with SRE:**
- Nova + SRE run the attestations (QA quality sign-off, SRE operational
readiness).
- You authorize the promotion at the stage gate. No promotion happens
without your recorded attestation.
## Compliance Standards Apply Equally
Your FRs + UAT may come from any source — an AI coding agent, an
agentic SDLC platform, or a traditional IDE. Nova does not
differentiate. All submissions pass through the same gate
(`schemas/submission-readiness.schema.json`): tags, environment
metadata, policy preconditions, profile markers. The compliance
standards are the same regardless of how the code was authored. This is
by design: the audit trail is the same, the policy envelope is the
same, the evidence stream is the same. The source does not matter; the
submission does.
-72
View File
@@ -1,72 +0,0 @@
# Scope — Nova is Downstream of PDLC
> This page is the citizen-developer-facing copy.
## The Boundary
The **Product Development Lifecycle (PDLC)** is **upstream** of Nova. The
PDLC includes:
- Product backlog / roadmap planning
- Code authorship (via AI coding agent, IDE, or agentic SDLC platform)
- Sprint planning / issue tracking
- Application business logic
- IDE workflows / developer experience
Nova never reaches into the PDLC. Nova's domain is **infrastructure +
delivery only**. 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, and
all intents to deploy to production go through the same rigorous
controls, quality gates, attestation, and evidence stream.
## What Nova Does
Nova governs the downstream half:
- **Contract ingestion** — the validated entry point
- **Submission-readiness gate** — what is acceptable to start
(`schemas/submission-readiness.schema.json`)
- **Policy enforcement** — the confidence signal, Checkov, tagging
- **Cloud resource lifecycle** — Terraform plan/apply, state, IAM
- **Environment progression** — dev (autonomous) → qa (QA attestation) →
prod (SRE attestation) → dr (SRE attestation)
- **Immutable audit + attestation** — the Decision Ledger, the evidence
stream, the HITL gates
## The Integration Point
Integration between the PDLC and Nova is **only** through the validated,
published contract boundary:
```
PDLC (upstream) Nova (downstream)
───────────────── ─────────────────
product backlog contract ingestion
code authorship (AI agent / IDE / SDLC) → submission-readiness gate
sprint planning → policy enforcement
application business logic → cloud resource lifecycle
→ environment progression (dev→qa→prod→dr)
→ immutable audit + attestation
```
The citizen developer's AI coding agent, an upstream agentic SDLC
platform, or any upstream development platform may all produce
submissions. **The source does not matter** — all are subject to the
same compliance standards. Nova validates the submission, not the
author.
## What Nova is Not
- Not an upstream development platform (no product backlogs, IDE, code
authorship).
- Not a general-purpose AI agent platform (autonomy is narrow, bounded
by policy envelopes).
- Not a legacy infrastructure bridge (no VMs/bare metal/OS).
- Not a permissive delivery highway (no escape hatches past confidence
or HITL).
- Not a mutable audit log (VCS history ≠ regulatory evidence).
These anti-goals (from `docs/vision.md` §7 and Core Tenet #2) are
promoted here from buried tenets to an unmissable scope statement.
-88
View File
@@ -1,88 +0,0 @@
# Skills — Production-Grade Guidance for the Citizen Developer
> **Source of truth (v1.18, REQ-221, REQ-222).** The Nova skill catalog
> extends the BA.A 5-skill catalog (web API, worker, scheduled job, static
> asset, basic observability bootstrap) with Atelier-derived production-
> grade engineering principles. Each skill is a markdown file under
> `skills/` keyed to an Atelier domain path.
## How the Citizen Developer's AI Agent Consumes Skills
1. **Before completing a task**, read the relevant skill file(s) that
match the task's domain.
2. **Run `review/agent-checklist.md`** (from Atelier) before finishing —
the checklist items are the gate between "the code is written" and
"the task is done."
3. **Use the Atelier MCP server** (`mcp/atelier/server.py`, P5) for
agentic validation — the `atelier.validate_against_principles` tool
catches correctness/clarity/simplicity/observability gaps that
deterministic scanners (Wiz, Checkmarx, Mend) cannot.
## The 9 Skills
| Skill | Atelier Source | Core Principles | BA.A Mapping |
|---|---|---|---|
| [`api.md`](../skills/api.md) | `domains/api/` | C1, C2, C6 | web API |
| [`security.md`](../skills/security.md) | `domains/security/` | C1 | cross-cutting (all 5) |
| [`data.md`](../skills/data.md) | `domains/data/` | C1, C4, C6 | web API, worker, scheduled job |
| [`testing.md`](../skills/testing.md) | `domains/testing/` | C1, C5 | UAT (citizen-dev RACI) |
| [`observability.md`](../skills/observability.md) | `domains/observability/` | C7 | basic observability bootstrap |
| [`errors.md`](../skills/errors.md) | `domains/errors/` | C1, C7 | web API, worker, scheduled job |
| [`devops.md`](../skills/devops.md) | `domains/devops/` | C5, C7, C8 | scheduled job, worker |
| [`infrastructure-as-code.md`](../skills/infrastructure-as-code.md) | `domains/infrastructure-as-code/` | C1, C5, C8 | static asset |
| [`compliance.md`](../skills/compliance.md) | `domains/compliance/` | C1, C5 | cross-cutting (all 5) |
## Atelier Provenance
The skills are derived from [Atelier](https://example.com/atelier)
— a first-principles docs-as-code engineering framework with 8 core
principles (C1C8) and 19 domains, each with 10 derived P-rules. The
skills distill the citizen-developer-relevant subset of each domain's
first-principles, link to the agent-checklist triggers, and map to the
existing BA.A catalog.
Atelier is vendored under `mcp/atelier/vendor/` (pinned tag) for
audit reproducibility — an agentic validation result is replayable
against the exact principles that produced it.
## The 8 Core Principles (from Atelier)
| # | Principle | One-line |
|---|---|---|
| C1 | Correctness | The system does what it is supposed to do, and nothing else. |
| C2 | Clarity | The intent of the code is obvious to its reader. |
| C3 | Simplicity | The solution is as simple as possible, and no simpler. |
| C4 | Locality | Decisions and their consequences live near each other. |
| C5 | Reversibility | Every decision can be undone, and the cost of undoing is known. |
| C6 | Composability | Parts combine into wholes, and the parts are reusable. |
| C7 | Observability | The system's behavior is visible to those who must understand it. |
| C8 | Economy | The system uses no more resources than the task requires. |
Precedence: C1 > C2 > C3 > C4 > C5 > C6 > C7 > C8. Correctness is never
sacrificed.
## Reference-Only Domains (cited inside skills, not elevated to skill files)
These 4 Atelier domains are relevant to a citizen developer but are cited
inside the 9 skills above rather than getting their own skill file:
- **Performance** (`domains/performance/`) — cited in `observability.md` +
`devops.md` (bounded operations, timeouts, N+1)
- **Documentation** (`domains/documentation/`) — the runbook requirement
(W3.E prod mandatory) is the documentation skill in practice
- **Concurrency** (`domains/concurrency/`) — cited in `errors.md` +
`devops.md` (bounded queues, cancellation, timeout)
- **AI/ML** (`domains/ai-ml/`) — scope: engineering discipline (data
versioning, evaluation, serving, drift), not algorithm design
## Excluded Domains (not relevant to Nova citizen developer)
6 Atelier domains are excluded from the Nova skill catalog (not relevant
to a citizen developer building on Nova's infrastructure platform):
- UI/UX — Nova has no frontend (frontend-engineer deactivated, PERSONAS.md)
- Kubernetes — Nova is AWS-only this milestone (NORTH_STAR Non-Goal #7)
- GitOps + Operators — future roadmap (no GitOps reconciler today)
- Edge — not in scope (Nova is cloud, not edge)
- Messaging — not in scope (Nova deploys infra, not message brokers)
- i18n — application-level concern, not infrastructure
-150
View File
@@ -1,150 +0,0 @@
# Submission Readiness — What is Acceptable to Start
> **Source of truth:** `schemas/submission-readiness.schema.json` (v1.18,
> REQ-217). The validator is `core/submission_readiness.py` (REQ-218),
> invoked as `python3 -m core.lambda.contract_ingestor --check-readiness
> <submission.json>` (D-133).
Nova's submission-readiness gate defines what is **acceptable to start**.
It is a superset gate *above* contract-schema validity: the contract schema
(`schemas/contract.schema.json`) defines the **shape** (id / name /
environment / infrastructure); the readiness schema defines the **gate**
(tags, per-env mandatory metadata, policy preconditions, profile markers,
appSource). Both must pass before ingestion proceeds.
## How It Works
```
citizen developer submits
contract.schema.json validation (shape) ← the existing check
submission-readiness.schema.json (gate) ← the new check
├── contractId present (non-empty)
├── environment valid (dev/qa/prod/dr)
├── tags: all 5 Nova tags present (D-054)
├── policyPreconditions declared
├── profile: developer or agentic
│ └── if agentic: naturalLanguageIntent + confidenceAtSubmission + agentTrace
├── appSource: repo + ref (for runtime fetch)
└── per-env mandatory (W3.E):
dev → stack + environment
qa → + validation.e2eSuite + validation.loadTest
prod → + runbook + dashboard + oncall
dr → + drDrillRef
ready → proceed to contract ingestion
not ready → reject with citizen-developer-facing error (reason code)
```
## Reason Codes
When a submission is not ready, the validator returns one or more reason
codes. These are citizen-developer-facing — no stack traces.
| Code | Meaning |
|---|---|
| `MISSING_TAGS:<tag1>,<tag2>` | One or more required Nova tags are absent |
| `ENV_MISSING_MANDATORY:<env>:<field>` | A per-env mandatory field (W3.E) is missing |
| `AGENTIC_MISSING_INTENT:<marker>` | profile=agentic but a required marker is absent |
| `MISSING_APP_SOURCE` | appSource (repo + ref) is missing |
| `POLICY_PRECONDITION_MISSING` | No policy preconditions declared |
| `CONTRACT_SCHEMA_INVALID:<detail>` | The contract shape failed contract.schema.json |
| `READINESS_SCHEMA_INVALID:<detail>` | The submission failed the readiness schema |
## Good Example
```json
{
"contractId": "uuid-1234",
"id": "webapi",
"name": "Customer Web API",
"environment": "dev",
"tags": {
"nova:owner": "consumer-repo",
"nova:contract": "uuid-1234",
"nova:environment": "dev",
"nova:cost-center": "nova-default",
"nova:ref": "CHG0678912"
},
"policyPreconditions": {
"public-ingress": false,
"encryption_enabled": true,
"deletion_protection": true
},
"profile": "developer",
"appSource": {
"repo": "consumer/web-api",
"ref": "main"
},
"infrastructure": {
"static-assets": {
"inputs": {
"bucket_name": "webapi-assets"
}
}
}
}
```
Result: **READY** — passes the shape + the gate.
## Rejected Examples
### Missing Tags
```json
{
"contractId": "uuid-1234",
"environment": "dev",
"tags": {
"nova:owner": "consumer-repo"
},
"policyPreconditions": {"public-ingress": false},
"profile": "developer",
"appSource": {"repo": "consumer/repo", "ref": "main"}
}
```
Result: `NOT READY — MISSING_TAGS:nova:contract,nova:environment,nova:cost-center,nova:ref`
### Agentic Missing Intent
```json
{
"contractId": "uuid-1234",
"environment": "qa",
"tags": { "nova:owner": "x", "nova:contract": "x", "nova:environment": "qa", "nova:cost-center": "x", "nova:ref": "x" },
"policyPreconditions": {"public-ingress": false},
"profile": "agentic",
"appSource": {"repo": "x", "ref": "x"},
"validation": {"e2eSuite": true, "loadTest": true}
}
```
Result: `NOT READY — AGENTIC_MISSING_INTENT:naturalLanguageIntent; AGENTIC_MISSING_INTENT:confidenceAtSubmission; AGENTIC_MISSING_INTENT:agentTrace`
### Env Missing Mandatory (prod without runbook)
```json
{
"contractId": "uuid-1234",
"environment": "prod",
"tags": { "nova:owner": "x", "nova:contract": "x", "nova:environment": "prod", "nova:cost-center": "x", "nova:ref": "x" },
"policyPreconditions": {"public-ingress": false},
"profile": "developer",
"appSource": {"repo": "x", "ref": "x"}
}
```
Result: `NOT READY — ENV_MISSING_MANDATORY:prod:runbook; ENV_MISSING_MANDATORY:prod:dashboard; ENV_MISSING_MANDATORY:prod:oncall`
## Compliance-Standard Equivalence
The submission-readiness gate applies **equally** to all upstream sources.
Whether the citizen developer's submission originated from an AI coding
agent, an agentic SDLC platform, or a traditional development platform —
the same tags, the same env mandatory, the same policy preconditions, the
same profile markers are required. The source does not matter; the
submission does. This is the RACI compliance-standard equivalence note
(`docs/raci.md`) made machine-checkable.
+1 -1
View File
@@ -15,7 +15,7 @@ Consumers declare intent; the platform delivers safe production deployment throu
## 3. Core Tenets ## 3. Core Tenets
* **Operations are Declared, Not Executed.** Consumers define what they need — workload shape, dependencies, non-functional requirements, policy constraints. The platform handles reconciliation, provisioning, and environment progression. The execution burden moves from the human to the platform. * **Operations are Declared, Not Executed.** Consumers define what they need — workload shape, dependencies, non-functional requirements, policy constraints. The platform handles reconciliation, provisioning, and environment progression. The execution burden moves from the human to the platform.
* **The Delivery Lifecycle is a Sovereign Boundary.** The platform governs the infrastructure and delivery engine. It does not reach into upstream product or software development lifecycles. Integration happens exclusively through validated, published contracts. * **The Delivery Lifecycle is a Sovereign Boundary.** The platform governs the infrastructure and delivery engine. It does not penetrate upstream product or software development lifecycles. Integration happens exclusively through validated, published contracts.
* **Lower Environments are Autonomous; Higher Environments are Attested.** Progression through lower environments proceeds through zero-touch agentic automation. Promotion to higher-stakes environments requires deliberate human attestation — not as a rubber stamp, but as a policy-mandated act of accountability. * **Lower Environments are Autonomous; Higher Environments are Attested.** Progression through lower environments proceeds through zero-touch agentic automation. Promotion to higher-stakes environments requires deliberate human attestation — not as a rubber stamp, but as a policy-mandated act of accountability.
* **Safety is Computed, Not Assumed.** Every delivery action produces a measurable, explainable confidence signal aggregating policy conformance, validation evidence, and historical behavior. The signal is the platform's certified answer to "is this safe to proceed?" Reliance on operator instinct or tenure is not a substitute. * **Safety is Computed, Not Assumed.** Every delivery action produces a measurable, explainable confidence signal aggregating policy conformance, validation evidence, and historical behavior. The signal is the platform's certified answer to "is this safe to proceed?" Reliance on operator instinct or tenure is not a substitute.
* **Infrastructure is Consumed, Not Maintained.** Compute is abstract, containerized, or serverless. The platform does not manage node, OS, or bare-metal lifecycles. Infrastructure is treated as a utility, not a craft. * **Infrastructure is Consumed, Not Maintained.** Compute is abstract, containerized, or serverless. The platform does not manage node, OS, or bare-metal lifecycles. Infrastructure is treated as a utility, not a craft.

Some files were not shown because too many files have changed in this diff Show More