Compare commits
93 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 96d4677fac | |||
| 863484e681 | |||
| 7f4b79593a | |||
| 35e3de401e | |||
| 814d45b211 | |||
| 0f0d9b9145 | |||
| e6ee79402b | |||
| 4b6c3a12d8 | |||
| 56dab4fdfb | |||
| ed387a4f54 | |||
| ac18c98385 | |||
| ba816f69ae | |||
| 2e519743b5 | |||
| 36c8ae9a80 | |||
| ec53302014 | |||
| 7e6ed25ea9 | |||
| f753353ad4 | |||
| f020178c15 | |||
| 5a75075616 | |||
| 42c579f7b8 | |||
| ab7171236a | |||
| fe635c17d5 | |||
| d069654367 | |||
| 25427250ad | |||
| eca1181716 | |||
| 0920550ae5 | |||
| d8240588c9 | |||
| 5dc97673e5 | |||
| 956cf91ce0 | |||
| a7a93d95d1 | |||
| afca994511 | |||
| e63c0cb36e | |||
| 3512261051 | |||
| 14c11027a8 | |||
| e07a210c70 | |||
| 9b8ab75b85 | |||
| 9bc37301ba | |||
| 5476f8eb24 | |||
| 863f482f9c | |||
| 66b13a6d0c | |||
| 485d105bcd | |||
| df426afd6a | |||
| 9114227ef1 | |||
| c9ace0af6e | |||
| a47c16245a | |||
| 74e9d4d887 | |||
| 818e285fac | |||
| b8fbd995a9 | |||
| ea44fdb9d6 | |||
| e14818875c | |||
| f496dd9c24 | |||
| 0d22b89a7b | |||
| 75e9e479db | |||
| d199204367 | |||
| 6a64b2b337 | |||
| 9274b4b87f | |||
| 25ddc894c2 | |||
| 156431c80a | |||
| 631244458f | |||
| 81b731ed17 | |||
| cc6071ee53 | |||
| d1ff6934c6 | |||
| ccbccb02ac | |||
| 574e6cb189 | |||
| 358aa62c3a | |||
| ff416777f9 | |||
| 94891af6ee | |||
| 072ac83ef6 | |||
| c6036ca433 | |||
| 38eb01d266 | |||
| 0404988465 | |||
| dbca694f55 | |||
| 71f0f1a05d | |||
| ce751313a7 | |||
| 5b5e24d535 | |||
| 85c500e45a | |||
| 301aa2c8d8 | |||
| 707a7dbe9b | |||
| e7866fda84 | |||
| 2efed26bb6 | |||
| 5c07e29b90 | |||
| aa868c97ef | |||
| e4a9915891 | |||
| 0ca383dae6 | |||
| ed5ea90654 | |||
| 2273009b95 | |||
| 0d2cbdb423 | |||
| b418d429b5 | |||
| dcba380b52 | |||
| f0bc3be92c | |||
| 0b79b16715 | |||
| 90624be63f | |||
| be51fc15fa |
@@ -879,3 +879,67 @@ config entry in `config.json` (`strategic_direction_file:
|
|||||||
".ciagent/NORTH_STAR.md"`) that the run workflow reads at SPECIFY. This
|
".ciagent/NORTH_STAR.md"`) that the run workflow reads at SPECIFY. This
|
||||||
ensures the strategic direction survives across milestones without
|
ensures the strategic direction survives across milestones without
|
||||||
being overwritten by status updates.
|
being overwritten by status updates.
|
||||||
|
|
||||||
|
### §12.7 — Policy Engine Registry (v1.25, REQ-291)
|
||||||
|
|
||||||
|
The policy-engine abstraction is first-class: a swappable `PolicyEngine`
|
||||||
|
protocol so the engine may change without touching the confidence
|
||||||
|
signal, the pipeline, or the `PolicyCheckResult` schema. This is the
|
||||||
|
**swap boundary** that keeps the platform's compliance posture
|
||||||
|
replaceable (Strategic Objective #2 — provable trust via a replaceable
|
||||||
|
substrate, not a vendor lock-in).
|
||||||
|
|
||||||
|
```
|
||||||
|
contract.yml ─┐ ┌─→ list[PolicyCheckResult] ─┐
|
||||||
|
stack IR ─────┼─→ PolicyEngine.evaluate ├─→ list[PolicyCheckResult] ─┼─→ confidence_signal
|
||||||
|
plan JSON ────┤ (protocol) └─→ list[PolicyCheckResult] ─┘ (engine-agnostic,
|
||||||
|
PCR list ─────┘ unchanged)
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
┌─ KyvernoJsonEngine (shells to `kj scan`; engine: "kyverno")
|
||||||
|
└─ OpaEngine (future — same protocol; engine: "opa")
|
||||||
|
|
||||||
|
checkov/wiz ──→ raw findings ──→ (merged PCR list is the meta-policy payload)
|
||||||
|
```
|
||||||
|
|
||||||
|
**The protocol (`core/policy_engine.py`):**
|
||||||
|
```python
|
||||||
|
class PolicyEngine(Protocol):
|
||||||
|
@property
|
||||||
|
def name(self) -> str: ...
|
||||||
|
def is_configured(self) -> bool: ...
|
||||||
|
def evaluate(self, payload, policy_dir: Path, contract_id: str) -> list[dict]: ...
|
||||||
|
```
|
||||||
|
|
||||||
|
**The registry** reads `config.json.policy.engine` (default
|
||||||
|
`"kyverno-json"`) and returns the active engine. A `NullEngine` is the
|
||||||
|
fallback when the `policy` key is absent (emits `SKIPPED` PCRs —
|
||||||
|
backward compatibility for tests that don't set the key). The
|
||||||
|
confidence signal is **untouched** — it already consumes
|
||||||
|
`list[PolicyCheckResult]` engine-agnostically (§12.6). v1.25 only
|
||||||
|
changes *who produces* the PCR list, not *what* the list is.
|
||||||
|
|
||||||
|
**Engine enum reuse (D-116):** kyverno-json PCR records carry
|
||||||
|
`engine: "kyverno"` (no new enum value). The `engine` field records the
|
||||||
|
policy-engine *family*, not the specific binary. The K8s Kyverno adapter
|
||||||
|
and the kyverno-json engine are distinguished by `ruleId` prefix
|
||||||
|
(`KYVERNO_` vs `KJ_`) and `evidence` payload shape (`namespace`/`kind`
|
||||||
|
vs `assertion`/`jmespath`).
|
||||||
|
|
||||||
|
**Defense-in-depth (D-119):** the declarative meta-policy
|
||||||
|
`block-on-any-critical` (asserts no PCR has `severity: critical` +
|
||||||
|
`result: fail`) is the *source of truth* for "critical = block". The
|
||||||
|
`confidence_signal.py` `PENALTY["critical"]: None` hard-override stays
|
||||||
|
as the *imperative* safety net — the meta-policy runs *before* the
|
||||||
|
confidence signal (produces PCRs that flow in), the hard-override runs
|
||||||
|
*inside* it (the last gate). Removing the hard-override would make the
|
||||||
|
"critical = block" guarantee depend on a single policy file — a
|
||||||
|
regression in provable trust.
|
||||||
|
|
||||||
|
**Graceful degradation (D-120):** `KyvernoJsonEngine.is_configured()`
|
||||||
|
returns false when `which kj` is absent → `evaluate()` returns a single
|
||||||
|
`SKIPPED` PCR (`ruleId: "KJ_ENGINE_NOT_CONFIGURED"`). The platform
|
||||||
|
functions without the binary (the "platform functions without AI /
|
||||||
|
deterministic scripts" tenet holds — kyverno-json is deterministic, not
|
||||||
|
AI; the `is_configured()` guard ensures the platform runs even when the
|
||||||
|
binary is not installed).
|
||||||
|
|||||||
@@ -0,0 +1,66 @@
|
|||||||
|
# Nova — The Autonomous Cloud Delivery Platform: Autonomy Defensibility Brief
|
||||||
|
|
||||||
|
> Strategic direction, leadership metrics & unified story
|
||||||
|
> Last refined: v1.21 — reframe from "no-humans" to "autonomous operations"
|
||||||
|
|
||||||
|
## The thesis
|
||||||
|
|
||||||
|
Nova is the autonomous infrastructure layer that lets product teams
|
||||||
|
ship without engaging an operator, and lets executives trust the
|
||||||
|
platform not because it never fails but because every decision is
|
||||||
|
captured, scored, and accountable.
|
||||||
|
|
||||||
|
**Autonomy in operations; human at stage gates.** Normal operations —
|
||||||
|
provisioning, healing, remediation — run without an operator in the
|
||||||
|
loop. Human attestation remains required at stage gates: QA signs off
|
||||||
|
for production, SRE greenlights based on operational readiness. The
|
||||||
|
absence of an operator in the loop is never the absence of a record.
|
||||||
|
|
||||||
|
## Grounded proof (measurable today)
|
||||||
|
|
||||||
|
| Proof | Source | Status |
|
||||||
|
|-------|--------|--------|
|
||||||
|
| Capabilities verified, none broken (live-AWS caps honestly skipped, resources torn down to zero-cost steady state) | regression report | grounded |
|
||||||
|
| Decision Ledger captures 100% of automated decisions with outcome backfill | decision ledger store | grounded |
|
||||||
|
| Attestation coverage: 100% of prod/dr promotions attested by a human | attestation gates + outbox | grounded |
|
||||||
|
| Confidence-gated policy engine (deterministic, not an LLM) — weighted inputs, band outcome | confidence signal | grounded |
|
||||||
|
| Attestation matrix with separation-of-duties on prod | attestation matrix + separation-of-duties | grounded |
|
||||||
|
| Pre-apply cost estimates (offline) | cost adapter | grounded |
|
||||||
|
| Test suite passes | test results | grounded |
|
||||||
|
|
||||||
|
## Deferred proof (measurable when blocking work lifts)
|
||||||
|
|
||||||
|
| Proof | Blocking work | Unblock requirement |
|
||||||
|
|-------|----------------|---------------------|
|
||||||
|
| Touchless resolution rate across production estates | 0 consumers today | Pilot estate activation |
|
||||||
|
| Live infrastructure health (ECS, ALB, RPS) | Live AWS torn down | Live AWS re-provisioning |
|
||||||
|
| Onboarding funnel: requested → granted | Auto-grant not built | Auto-grant implementation |
|
||||||
|
| Drift auto-reversal rate | No drift scheduler | Drift detection scheduler |
|
||||||
|
| Predictive vs reactive ratio | No emitter | ML anomaly-forecasting service |
|
||||||
|
| Tamper-evident ledger checkpoints (S3 Object Lock + JWS) | Audit ledger build-out | Audit ledger build-out |
|
||||||
|
|
||||||
|
## Anti-claims (what Nova is NOT)
|
||||||
|
|
||||||
|
1. **Nova's decisions are NOT made by an LLM.** They are made by a
|
||||||
|
confidence-gated policy engine: deterministic scripts calculate a
|
||||||
|
score, and a band outcome gates the action. The platform functions
|
||||||
|
without AI. The Decision Ledger captures this real decision path —
|
||||||
|
not a fabricated "AI agent." When an LLM planner is added, it will
|
||||||
|
emit richer `alternatives_considered` without schema breakage.
|
||||||
|
2. **Nova does NOT remove humans from accountability.** Only from
|
||||||
|
normal operations. Every stage-gate promotion (qa/prod/dr) requires
|
||||||
|
a human attestation recorded with approver identity,
|
||||||
|
separation-of-duties check, and the evidence matrix.
|
||||||
|
3. **Nova is NOT for legacy, untagged, or freeform infrastructure.** It
|
||||||
|
requires Terraform-managed, policy-aligned, fully-tagged inputs.
|
||||||
|
4. **Nova does NOT fabricate metrics.** Every metric is grounded (cites
|
||||||
|
a source), derived (documented formula), or deferred (cites the
|
||||||
|
blocking work). No fabricated numbers in any deck slide or metrics
|
||||||
|
entry (the "no fabrication" hard constraint).
|
||||||
|
|
||||||
|
## What "won" looks like
|
||||||
|
|
||||||
|
By month 18, Nova is the layer enterprise leadership points to when
|
||||||
|
they say *"we don't have an infrastructure ops team anymore, and the
|
||||||
|
audit trail is stronger than it ever was"* — and it is the layer their
|
||||||
|
AI engineering teams reach for first when an agent needs to deploy.
|
||||||
@@ -1,12 +1,22 @@
|
|||||||
{
|
{
|
||||||
"phase": 1,
|
"phase": 2,
|
||||||
"stage": "execute",
|
"stage": "complete",
|
||||||
"milestone": "v1.19",
|
"milestone": "v1.25",
|
||||||
"phase_role": "execution",
|
"phase_role": "execution",
|
||||||
"attempts": 0,
|
"attempts": 0,
|
||||||
"updated_at": "2026-08-06T19:30:00Z",
|
"updated_at": "2026-08-12T17:15:00Z",
|
||||||
|
"project": "acdl",
|
||||||
"milestone_complete": false,
|
"milestone_complete": false,
|
||||||
"tag": null,
|
"tag_line": "v1.24.x",
|
||||||
"requirements": ["REQ-229"],
|
"tag": "v1.24.2",
|
||||||
"notes": "v1.19 P1 complete: scripts/sync_to_nova.sh replaces sync_to_gl.sh. Manual-only 2nd-release pipeline into ~/nova (separate GitLab repo, consumer/platform-team audience). Consumer subset rsync, 13 fixed-order domain commits via positional -m, conventional-commit validation. 8 new tests pass (TestSyncToNovaScript). Pre-existing test_attestation_event_emission failure (env-dependent: NOVA_ATTESTATION_SIGNING_KEY_ID unset, D-089) reproduced on clean main — unrelated. Next: P2 final-review-ship -> tag v1.18.0."
|
"next_tag": "v1.24.3",
|
||||||
|
"release": {
|
||||||
|
"forge": "gitea",
|
||||||
|
"releases_created": true,
|
||||||
|
"release_ids": {"v1.24.0": 640, "v1.24.1": 641, "v1.24.2": 642},
|
||||||
|
"phase_release_id": 642
|
||||||
|
},
|
||||||
|
"requirements": ["REQ-291", "REQ-292", "REQ-293", "REQ-294", "REQ-295", "REQ-296", "REQ-297", "REQ-298", "REQ-299", "REQ-308", "REQ-309"],
|
||||||
|
"tests": {"total": 119, "passed": 119, "skipped": 7, "failed": 0},
|
||||||
|
"notes": "v1.25 P2 (contract+stack-IR policies) complete. Tag v1.24.2 (gitea release id 642). 5 requirements (REQ-295..299). 7 contract+stack-IR policies. Resolver wired (pre+post resolve). Phase 02 branch deleted. Next: P3 plan-JSON + meta-orchestration + pipeline wiring."
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,164 @@
|
|||||||
|
# CLARIFY — v1.25 kyverno-json Unified Policy Engine
|
||||||
|
|
||||||
|
> **Autonomy:** full. Ambiguities are auto-resolved with assumption logging
|
||||||
|
> per `config.json autonomy.level: "full"` and
|
||||||
|
> `autonomy.decision_confidence_threshold: 0.6`. No human escalation.
|
||||||
|
|
||||||
|
## Ambiguities Identified
|
||||||
|
|
||||||
|
### A1 — kyverno-json install path (pip / go install / pinned binary release)
|
||||||
|
|
||||||
|
**Ambiguity:** kyverno-json is a Go project, not a Python package. Three
|
||||||
|
install paths exist: (a) `pip install` — not possible (no PyPI package);
|
||||||
|
(b) `go install github.com/kyverno/kyverno-json/cmd/kj@latest` — requires
|
||||||
|
Go toolchain in the CI image; (c) download a pinned binary release from
|
||||||
|
GitHub releases — no Go toolchain needed, but release artifacts are
|
||||||
|
platform-specific and must be checksummed.
|
||||||
|
|
||||||
|
**Resolution (auto, confidence 0.85):** `go install` (option b). A
|
||||||
|
`scripts/install-kyverno-json.sh` helper runs
|
||||||
|
`go install github.com/kyverno/kyverno-json/cmd/kj@latest` and prints
|
||||||
|
`kj version`. The CI image (`.github/workflows/ci.yml` +
|
||||||
|
`.gitea/workflows/ci.yml`) installs Go + kj when
|
||||||
|
`config.json.policy.engine == "kyverno-json"`; the install is cached via
|
||||||
|
the existing Go module cache. Rationale: `go install` is the upstream-
|
||||||
|
blessed path, tracks the latest stable release, avoids per-platform
|
||||||
|
binary management, and the project already accepts Go-based tooling
|
||||||
|
(checkov pulls Go-built transitive deps via pip). When `which kj` is
|
||||||
|
absent, `KyvernoJsonEngine.is_configured()` returns false → `SKIPPED`
|
||||||
|
PCR (mirrors the Wiz adapter pattern) — the platform functions without
|
||||||
|
the binary. Captured in REQ-293, REQ-294. Decision ID: D-115.
|
||||||
|
|
||||||
|
### A2 — `engine` enum value: new `"kyverno-json"` vs reuse `"kyverno"`
|
||||||
|
|
||||||
|
**Ambiguity:** `schemas/policy_check_result.schema.json` already lists
|
||||||
|
`engine: ["checkov", "kyverno", "opa", "wiz"]`. kyverno-json is a
|
||||||
|
distinct runtime from the K8s Kyverno admission controller, but both
|
||||||
|
are "Kyverno." Two options: (a) add a new `"kyverno-json"` enum value
|
||||||
|
— requires schema change + checkov/wiz adapter test regression check;
|
||||||
|
(b) reuse `"kyverno"` and distinguish by `ruleId` prefix.
|
||||||
|
|
||||||
|
**Resolution (auto, confidence 0.80):** Reuse `"kyverno"` (option b).
|
||||||
|
Adding `"kyverno-json"` would force a schema change + a test sweep for
|
||||||
|
no semantic gain — the `engine` field records the policy engine family,
|
||||||
|
not the specific binary. kyverno-json PCR records carry `engine:
|
||||||
|
"kyverno"` and `ruleId` prefixed `KJ_<policy_name>` (e.g.
|
||||||
|
`KJ_REQUIRE_TAGGING_STANDARD`), while the K8s adapter uses `KYVERNO_`
|
||||||
|
prefixes (e.g. `KYVERNO_INACTIVE_TF_STACK`). The two are distinguishable
|
||||||
|
in audit/telemetry by `ruleId` prefix and `evidence` payload shape (the
|
||||||
|
K8s adapter's evidence has `namespace`/`kind`; kyverno-json's has
|
||||||
|
`assertion`/`jmespath`). No schema change. Captured in REQ-293.
|
||||||
|
Decision ID: D-116.
|
||||||
|
|
||||||
|
### A3 — Do checkov/wiz adapters change their signatures to feed kyverno-json?
|
||||||
|
|
||||||
|
**Ambiguity:** The unified-orchestrator model places kyverno-json "on
|
||||||
|
top of" checkov/wiz. Two interpretations: (a) checkov/wiz now emit a
|
||||||
|
"raw findings" intermediate (not PCR) that kyverno-json meta-policies
|
||||||
|
consume — requires changing `adapt() -> list[PolicyCheckResult]` to
|
||||||
|
`adapt() -> list[RawFinding]`; (b) checkov/wiz keep emitting PCRs as
|
||||||
|
today, and the meta-policies in `adapters/kyverno-json/policies/meta/`
|
||||||
|
consume the **merged** PCR list as their payload.
|
||||||
|
|
||||||
|
**Resolution (auto, confidence 0.90):** Option (b). The existing
|
||||||
|
`adapt() -> list[PolicyCheckResult]` signatures are unchanged. The
|
||||||
|
meta-policies consume the merged PCR list (checkov + wiz + kyverno-json
|
||||||
|
plan-JSON policies) as their input payload. This preserves the
|
||||||
|
`PolicyCheckResult` schema as the single inter-adapter contract
|
||||||
|
(ARCHITECTURE.md §12.6), avoids a new "RawFinding" type, and means
|
||||||
|
the existing checkov/wiz adapter tests pass unchanged. The meta-policy
|
||||||
|
`block-on-any-critical.json` iterates the merged list; the
|
||||||
|
`tagging-rules-agree.json` meta-policy cross-checks the Checkov
|
||||||
|
`NOVA_TAG_NAMING` result against the kyverno-json
|
||||||
|
`KJ_REQUIRE_TAGGING_STANDARD` result by `resourceRef`. Captured in
|
||||||
|
REQ-303, D-117. Decision ID: D-117.
|
||||||
|
|
||||||
|
### A4 — `NOVA_TAG_NAMING` Checkov rule: rewrite as kyverno-json policy, keep, or both?
|
||||||
|
|
||||||
|
**Ambiguity:** The Checkov custom rule
|
||||||
|
`adapters/terraform/policy/custom_rules/nova_tagging.py` enforces the
|
||||||
|
Nova tagging standard over Terraform HCL (static scan + plan scan). The
|
||||||
|
kyverno-json milestone adds `require-tagging-standard.json` over the
|
||||||
|
resolved Stack IR. Three options: (a) rewrite — replace the Checkov
|
||||||
|
rule with the kyverno-json policy (loses Checkov's HCL-level coverage
|
||||||
|
and the `--external-checks-dir` integration); (b) keep Checkov only —
|
||||||
|
don't add a kyverno-json policy (the Stack IR is already the input to
|
||||||
|
terraform, so the Checkov rule catches it); (c) both — keep the
|
||||||
|
Checkov rule as the source of truth for HCL-level scanning AND add the
|
||||||
|
kyverno-json policy for IR-level coverage, with a meta-policy that
|
||||||
|
asserts the two agree.
|
||||||
|
|
||||||
|
**Resolution (auto, confidence 0.82):** Option (c) — both, with a
|
||||||
|
cross-check meta-policy. The Checkov rule stays the source of truth
|
||||||
|
for `terraform_plan` scanning (it reads HCL resource blocks directly);
|
||||||
|
the kyverno-json policy covers the Stack IR dict (which is the input
|
||||||
|
*before* terraform, so it catches IR-level violations that the
|
||||||
|
terraform adapter might mask via defaults). The P3 meta-policy
|
||||||
|
`tagging-rules-agree.json` asserts the two engines agree on every
|
||||||
|
resource; divergence emits an `error` PCR (defense-in-depth against
|
||||||
|
rule drift — if the two engines disagree, the operator must
|
||||||
|
investigate before proceeding). This is the only case in v1.25 where
|
||||||
|
two engines evaluate the same concern; it is intentional — the
|
||||||
|
tagging standard is the highest-impact rule (v1.8 D-tagging-standard,
|
||||||
|
v1.10 re-verification) and merits redundancy. Captured in REQ-297,
|
||||||
|
REQ-303, REQ-299. Decision ID: D-118.
|
||||||
|
|
||||||
|
### A5 — Critical-override: delegate to declarative meta-policy or keep hard-override?
|
||||||
|
|
||||||
|
**Ambiguity:** `core/confidence_signal.py` lines 144-157 hardcode
|
||||||
|
`PENALTY["critical"]: None` — a critical-severity `fail` PCR forces
|
||||||
|
`score = 0, band = block` regardless of the weighted-sum inputs. The
|
||||||
|
v1.25 meta-policy `block-on-any-critical.json` makes this declarative
|
||||||
|
(asserts no PCR in the merged list has `severity: critical` +
|
||||||
|
`result: fail`). Two options: (a) fully delegate — remove the
|
||||||
|
hard-override, rely on the meta-policy to emit a critical `fail` PCR
|
||||||
|
that the existing penalty logic then blocks; (b) keep both — the
|
||||||
|
meta-policy is the declarative source of truth, the hard-override is
|
||||||
|
defense-in-depth.
|
||||||
|
|
||||||
|
**Resolution (auto, confidence 0.88):** Option (b) — keep both. The
|
||||||
|
meta-policy is the *declarative* statement ("Nova blocks on any
|
||||||
|
critical finding from any engine"); the hard-override is the
|
||||||
|
*imperative* safety net that ensures a critical PCR can never slip
|
||||||
|
through even if the meta-policy is misconfigured or the
|
||||||
|
`PolicyEngineRegistry` returns a `NullEngine`. This is
|
||||||
|
defense-in-depth, not redundancy-for-its-own-sake: the meta-policy
|
||||||
|
runs *before* the confidence signal (it produces PCRs that flow in),
|
||||||
|
the hard-override runs *inside* the confidence signal (it is the last
|
||||||
|
gate). Removing the hard-override would make the platform's
|
||||||
|
"critical = block" guarantee depend on a single declarative policy
|
||||||
|
file — a regression in the provable-trust posture (Strategic
|
||||||
|
Objective #2). Captured in REQ-303, PROJECT.md hard-constraints.
|
||||||
|
Decision ID: D-119.
|
||||||
|
|
||||||
|
### A6 — Does kyverno-json break the "platform functions without AI" tenet?
|
||||||
|
|
||||||
|
**Ambiguity:** NORTH_STAR.md Strategic Objective #2: "the platform
|
||||||
|
functions without AI — 'AI decisions' are really automated decisions."
|
||||||
|
kyverno-json is a deterministic policy engine (no ML), but it is a new
|
||||||
|
runtime dependency. Does adding it violate the tenet?
|
||||||
|
|
||||||
|
**Resolution (auto, confidence 0.95):** No — kyverno-json is
|
||||||
|
deterministic, not AI. The tenet distinguishes "AI decisions" (LLM-
|
||||||
|
driven, non-reproducible) from "automated decisions" (rule-driven,
|
||||||
|
reproducible). kyverno-json is the latter — the same policy + payload
|
||||||
|
produces the same result on every run. It is *more* aligned with the
|
||||||
|
tenet than the current imperative Python in `core/env_transition.py`
|
||||||
|
and `core/regression_verify.py`, because the policy is declarative
|
||||||
|
(visible, auditable, version-controlled) rather than imperative (logic
|
||||||
|
hidden in function bodies). The `is_configured()` guard ensures the
|
||||||
|
platform functions without the binary (graceful skip), so the tenet
|
||||||
|
holds even in environments where kyverno-json is not installed.
|
||||||
|
Captured in PROJECT.md hard-constraints + RESEARCH.md G-Q1.
|
||||||
|
Decision ID: D-120.
|
||||||
|
|
||||||
|
## Summary
|
||||||
|
|
||||||
|
6 ambiguities identified; 6 auto-resolved at full autonomy (no human
|
||||||
|
escalation). All resolutions are binding and recorded as D-115..D-120.
|
||||||
|
The resolutions are captured in PROJECT.md hard-constraints,
|
||||||
|
REQUIREMENTS.md v1.25 sections, and will be referenced in RESEARCH.md +
|
||||||
|
PLAN.md. No PROJECT.md or REQUIREMENTS.md structural changes beyond the
|
||||||
|
v1.25 sections added in SPECIFY — the resolutions are already embedded
|
||||||
|
in the requirement text (REQ-293, REQ-297, REQ-303, etc.) via the
|
||||||
|
"Decision" annotations.
|
||||||
+199
-880
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,157 @@
|
|||||||
|
# IDEATE — v1.25 kyverno-json Unified Policy Engine
|
||||||
|
|
||||||
|
> **Autonomy:** full. 3-tier ideation per `config.json ideation.enabled:
|
||||||
|
> true`. `cross_project.enabled: false` → cross-project tier scoped to
|
||||||
|
> single-project (deferred ideas only, no cross-project candidates
|
||||||
|
> accepted). `confidence_threshold: 0.6`, `max_ideas: 20`.
|
||||||
|
> Categories: security, quality, architecture, coverage, improvement.
|
||||||
|
|
||||||
|
## Tier 1 — Mechanical (pattern-driven, codebase-grounded)
|
||||||
|
|
||||||
|
### I1 — Regression-gate-as-policy ✅ ACCEPTED (REQ-304, REQ-305)
|
||||||
|
|
||||||
|
**Category:** quality, coverage
|
||||||
|
**Confidence:** 0.90
|
||||||
|
**Pattern:** imperative check → declarative policy (the milestone's
|
||||||
|
core thesis applied to Nova's own regression gate).
|
||||||
|
**Source:** `core/regression_verify.py` (CAP-013, CAP-023, CAP-024)
|
||||||
|
are imperative Python checks. The milestone makes compliance
|
||||||
|
declarative; Nova's own capability regression should follow.
|
||||||
|
**Idea:** Port the three capability checks into
|
||||||
|
`adapters/kyverno-json/policies/regression/` as declarative policies
|
||||||
|
over the capability-inventory JSON frontmatter. The imperative
|
||||||
|
`regression_verify.py` stays (it drives the CI gate); the policies are
|
||||||
|
the declarative mirror that makes capability regression auditable as a
|
||||||
|
policy artifact.
|
||||||
|
**Accepted into:** REQ-304 (policies), REQ-305 (tests). Phase P4.
|
||||||
|
|
||||||
|
### I2 — Contract-shape validation as policy ✅ ACCEPTED (REQ-295)
|
||||||
|
|
||||||
|
**Category:** security, architecture
|
||||||
|
**Confidence:** 0.92
|
||||||
|
**Pattern:** jsonschema constraint → declarative policy (same constraint,
|
||||||
|
different language, Nova posture on top).
|
||||||
|
**Source:** `schemas/contract.schema.json` required/pattern/enum.
|
||||||
|
**Idea:** The 4 contract policies (`require-id-pattern`,
|
||||||
|
`require-env-in-enum`, `require-infrastructure-min-1`, `forbid-unknown-
|
||||||
|
fields`) are the declarative equivalent of the jsonschema constraints —
|
||||||
|
they let Nova apply its own compliance posture (e.g. forbid a specific
|
||||||
|
env for a specific consumer) on top of schema validity without editing
|
||||||
|
the jsonschema.
|
||||||
|
**Accepted into:** REQ-295. Phase P2.
|
||||||
|
|
||||||
|
### I3 — Stack-IR imperative rules → declarative policies ✅ ACCEPTED (REQ-297)
|
||||||
|
|
||||||
|
**Category:** security, architecture
|
||||||
|
**Confidence:** 0.88
|
||||||
|
**Pattern:** imperative Python rule → declarative kyverno-json policy.
|
||||||
|
**Source:** `adapters/terraform/policy/custom_rules/nova_tagging.py`
|
||||||
|
(tagging), the v1.0 demo `public-ingress: true` rule, the v1.8
|
||||||
|
D-encryption-default rule.
|
||||||
|
**Idea:** Port the three highest-impact imperative rules into
|
||||||
|
declarative kyverno-json policies over the resolved Stack IR. The
|
||||||
|
tagging rule is a cross-check (D-118 — both engines, agree meta-policy);
|
||||||
|
public-ingress and encryption-by-default are kyverno-json only (the IR
|
||||||
|
is the earliest point these can be caught).
|
||||||
|
**Accepted into:** REQ-297. Phase P2.
|
||||||
|
|
||||||
|
## Tier 2 — Backend-enriched (signal-driven)
|
||||||
|
|
||||||
|
### I4 — Plan-JSON Checkov RULE_MAP → kyverno-json mirrors ✅ ACCEPTED (REQ-300)
|
||||||
|
|
||||||
|
**Category:** security, coverage
|
||||||
|
**Confidence:** 0.85
|
||||||
|
**Pattern:** existing engine rule → declarative mirror in the new engine
|
||||||
|
(defense-in-depth against engine drift).
|
||||||
|
**Source:** `checkov_adapter.py:RULE_MAP` (CKV_AWS_41/45/46, CKV_AWS_1/40,
|
||||||
|
CKV_AWS_7/33).
|
||||||
|
**Idea:** Port the 6 Checkov rules over `terraform_plan` into declarative
|
||||||
|
kyverno-json policies over `terraform show -json` output. The Checkov
|
||||||
|
rules stay the source of truth for HCL scanning; the kyverno-json
|
||||||
|
policies are mirrors (different rule language, same plan JSON). Defense-
|
||||||
|
in-depth: if Checkov and kyverno-json disagree on the same plan, the
|
||||||
|
divergence is visible (two PCRs with different results for the same
|
||||||
|
resource).
|
||||||
|
**Accepted into:** REQ-300. Phase P3.
|
||||||
|
|
||||||
|
### I5 — Meta-policy over the merged PCR list ✅ ACCEPTED (REQ-303)
|
||||||
|
|
||||||
|
**Category:** architecture, quality
|
||||||
|
**Confidence:** 0.90
|
||||||
|
**Pattern:** the policy result list is itself a policy target (the most
|
||||||
|
novel use of kyverno-json in v1.25).
|
||||||
|
**Source:** `core/confidence_signal.py` PENALTY hardcode (critical
|
||||||
|
override), the D-118 tagging cross-check.
|
||||||
|
**Idea:** `block-on-any-critical` (declarative "critical = block") +
|
||||||
|
`tagging-rules-agree` (Checkov vs kj agree). The meta-policies consume
|
||||||
|
the merged PCR list as their payload. The critical-block meta-policy is
|
||||||
|
the declarative source of truth; the `confidence_signal.py` hard-override
|
||||||
|
stays as defense-in-depth (D-119).
|
||||||
|
**Accepted into:** REQ-303. Phase P3.
|
||||||
|
|
||||||
|
### I6 — Env-transition destroy as a declarative policy ❌ DEFERRED
|
||||||
|
|
||||||
|
**Category:** improvement
|
||||||
|
**Confidence:** 0.55 (below threshold — deferred, not rejected)
|
||||||
|
**Pattern:** imperative lifecycle Python → declarative policy.
|
||||||
|
**Source:** `core/env_transition.py` (v1.24 detect-and-destroy).
|
||||||
|
**Idea:** The v1.24 env-transition destroy logic (detect env change via
|
||||||
|
DynamoDB, destroy prior env, fail-closed) is imperative Python. A
|
||||||
|
declarative kyverno-json policy could assert "if `environment` changed
|
||||||
|
on a stable `contract.id`, a destroy event MUST precede the apply" —
|
||||||
|
turning the lifecycle enforcement into an auditable policy artifact.
|
||||||
|
**Reason deferred:** The env-transition logic is *stateful* (DynamoDB
|
||||||
|
queries, terraform state inspection) — kyverno-json policies are
|
||||||
|
*stateless* (payload in, PCRs out). A policy can assert the *contract*
|
||||||
|
shape (the env value is valid) but not the *lifecycle* (the prior env
|
||||||
|
was destroyed). The stateful check stays in `core/env_transition.py`;
|
||||||
|
a future milestone could emit a `nova.env.destroyed` event that a
|
||||||
|
kyverno-json policy then asserts is present in the evidence stream
|
||||||
|
(event-as-policy). Recorded as a future-idea, not a v1.25 requirement.
|
||||||
|
|
||||||
|
### I7 — Drift detection as policy ❌ DEFERRED
|
||||||
|
|
||||||
|
**Category:** security, coverage
|
||||||
|
**Confidence:** 0.40 (below threshold — deferred)
|
||||||
|
**Pattern:** scheduled job → policy over the drift report.
|
||||||
|
**Source:** NORTH_STAR.md Non-Goal #4 (drift detection scheduled job,
|
||||||
|
deferred — D-096 + no scheduler).
|
||||||
|
**Idea:** A kyverno-json policy over a terraform drift report could
|
||||||
|
assert "no drifted resources" declaratively. But drift detection itself
|
||||||
|
requires a scheduled `terraform plan -detailed-exitcode` job, which is
|
||||||
|
deferred (no scheduler). The policy is the easy part; the emitter is the
|
||||||
|
blocking dependency.
|
||||||
|
**Reason deferred:** Blocked by D-096 + no scheduler (same as NORTH_STAR
|
||||||
|
Non-Goal #4). The policy shape is documented for when the emitter ships.
|
||||||
|
|
||||||
|
## Tier 3 — Cross-project (deferred — single project)
|
||||||
|
|
||||||
|
### I8 — Cross-project policy sharing ❌ DEFERRED (config)
|
||||||
|
|
||||||
|
**Category:** improvement
|
||||||
|
**Confidence:** N/A
|
||||||
|
**Pattern:** policies shared across projects in a multi-project org.
|
||||||
|
**Source:** `config.json ideation.cross_project.enabled: false`.
|
||||||
|
**Idea:** In a multi-project org, kyverno-json policies could be shared
|
||||||
|
across projects (a tagging standard policy applies to all projects).
|
||||||
|
**Reason deferred:** ACDL is single-project (`active_projects: ["acdl"]`).
|
||||||
|
Cross-project ideation is disabled in config. Recorded for when the
|
||||||
|
org grows.
|
||||||
|
|
||||||
|
## Summary
|
||||||
|
|
||||||
|
- 5 ideas accepted (I1..I5) → already captured as REQ-295, REQ-297,
|
||||||
|
REQ-300, REQ-303, REQ-304, REQ-305.
|
||||||
|
- 3 ideas deferred (I6, I7, I8) with documented blocking reasons.
|
||||||
|
- 0 ideas rejected (below-threshold ideas are deferred, not rejected —
|
||||||
|
they may activate when their blockers lift).
|
||||||
|
- The accepted ideas are the **quality improvement** the user asked for
|
||||||
|
("ideate and explore how it can be used within the Nova platform to
|
||||||
|
improve quality of the platform checks"): I1 (regression-gate-as-
|
||||||
|
policy) is the headline quality improvement; I4 + I5 are the defense-
|
||||||
|
in-depth coverage improvements; I2 + I3 are the architecture
|
||||||
|
improvements (imperative → declarative).
|
||||||
|
- No new requirements added beyond REQ-291..309 (the accepted ideas are
|
||||||
|
already scoped into the existing requirements). The IDEATE pass
|
||||||
|
validated the requirement set rather than expanding it — the ideas
|
||||||
|
were anticipated in the SPECIFY stage and explicitly captured.
|
||||||
+57
-36
@@ -1,7 +1,7 @@
|
|||||||
# NORTH_STAR — Nova
|
# NORTH_STAR — Nova
|
||||||
|
|
||||||
> **Status:** Draft (pending interactive GRILL → final)
|
> **Status:** Draft (pending interactive GRILL → final)
|
||||||
> **Milestone:** v1.17 — Strategic Direction, Leadership Metrics & Unified Story
|
> **Milestone:** v1.21 — Nova Deck Refinement & Pipeline Hardening
|
||||||
> **Owner:** Product Owner
|
> **Owner:** Product Owner
|
||||||
> **Purpose:** Durable strategic intent. Read by CIAgent in every future
|
> **Purpose:** Durable strategic intent. Read by CIAgent in every future
|
||||||
> `/ci-run` so the platform's direction survives across milestones. This
|
> `/ci-run` so the platform's direction survives across milestones. This
|
||||||
@@ -14,7 +14,7 @@
|
|||||||
|
|
||||||
## Vision
|
## Vision
|
||||||
|
|
||||||
> **Infrastructure operations become invisible. Every environment
|
> **Infrastructure operations become visible. Every environment
|
||||||
> provisioned, every incident healed, every risk remediated — by an
|
> provisioned, every incident healed, every risk remediated — by an
|
||||||
> autonomous system whose trustworthiness is provable, not promised.
|
> autonomous system whose trustworthiness is provable, not promised.
|
||||||
> Human attestation remains required at stage gates — QA signs off for
|
> Human attestation remains required at stage gates — QA signs off for
|
||||||
@@ -22,9 +22,12 @@
|
|||||||
> operator is never in the loop of normal operations.**
|
> operator is never in the loop of normal operations.**
|
||||||
|
|
||||||
Nova is the autonomous infrastructure layer that lets product teams ship
|
Nova is the autonomous infrastructure layer that lets product teams ship
|
||||||
without engaging an operator, and lets executives trust the AI not because
|
without engaging an operator, and lets executives trust the platform not
|
||||||
it never fails but because every decision is captured, scored, and
|
because it never fails but because every decision is captured, scored,
|
||||||
accountable.
|
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.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -38,46 +41,64 @@ human by design; operational escalations (AI confidence too low to
|
|||||||
proceed) are the failure mode we drive toward zero. Everything else
|
proceed) are the failure mode we drive toward zero. Everything else
|
||||||
collapses if autonomy isn't real.
|
collapses if autonomy isn't real.
|
||||||
|
|
||||||
**2. Establish provable trust in AI decisions.**
|
**2. Establish provable trust in automated decisions.**
|
||||||
Build the audit substrate — Decision Ledger, confidence scoring, circuit
|
Trust is established by deterministic scripts that calculate a score and
|
||||||
breakers, blast-radius controls — that turns "autonomous" from a
|
a band outcome that gates the action — the platform functions without AI.
|
||||||
marketing claim into a defensible one. Trust is the moat. Features can be
|
"AI decisions" are really automated decisions. The audit substrate —
|
||||||
copied; an immutable, queryable decision history cannot.
|
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.**
|
**3. Deliver compounding, quantifiable ROI for customers.**
|
||||||
Each quarter on Nova must reduce cloud spend, free engineering hours, and
|
Each quarter on Nova must show measurable improvement on four CTO-grade
|
||||||
avoid downtime measurably. If the CFO can't point to a number that
|
metrics, all of which flow into PowerBI views and are captured by the
|
||||||
improves quarter-over-quarter, Nova fails its commercial test, regardless
|
telemetry pipeline:
|
||||||
of how clever the AI is.
|
|
||||||
|
|
||||||
**4. Become the default substrate for agentic infrastructure consumption.**
|
- **Lead Time** — from PR merge to production deployment (downward trend).
|
||||||
AI agents are already becoming the largest consumers of cloud
|
- **Infrastructure Vulnerability Count** — open findings on deployed
|
||||||
infrastructure. Nova must be the platform through which those agents
|
resources (downward trend, demonstrating that proactive scanning +
|
||||||
declare, deploy, and verify infrastructure — not a vendor scrambling into
|
remediation keeps up with the AI-era 0-day pace).
|
||||||
that market two quarters late.
|
- **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 (5 — what Nova is fundamentally NOT)
|
## Anti-Goals (4 — what Nova is fundamentally NOT)
|
||||||
|
|
||||||
1. **Not a Terraform, Kubernetes, or hyperscaler competitor.** We
|
1. **Not a general-purpose AI agent platform.** We are purpose-built for
|
||||||
orchestrate them. Replacing them is the most expensive possible
|
|
||||||
distraction from the value we create.
|
|
||||||
2. **Not a general-purpose AI agent platform.** We are purpose-built for
|
|
||||||
infrastructure operations. Breadth here produces shallow tools; depth
|
infrastructure operations. Breadth here produces shallow tools; depth
|
||||||
here wins the category.
|
here wins the category.
|
||||||
3. **Not a system that removes humans from accountability.** Only from
|
2. **Not a system that removes humans from accountability.** Only from
|
||||||
operations. Every AI decision lands in an immutable ledger. Every
|
normal operations. Every automated decision lands in an immutable
|
||||||
stage-gate promotion (qa/prod/dr) requires a human attestation recorded
|
ledger. Every stage-gate promotion (qa/prod/dr) requires a human
|
||||||
with approver identity, separation-of-duties check, and the 8-concern
|
attestation recorded with approver identity, separation-of-duties
|
||||||
evidence matrix. The absence of an operator is never the absence of a
|
check, and the evidence matrix. The absence of an operator in the
|
||||||
record.
|
loop is never the absence of a record.
|
||||||
4. **Not for legacy, untagged, or freeform infrastructure.** Nova requires
|
3. **Not an upstream development platform.** Nova does not own the
|
||||||
Terraform-managed, policy-aligned, fully-tagged inputs. We optimize for
|
product backlog, IDE workflows, code authorship, or application
|
||||||
the disciplined 95%, not the chaotic 5%.
|
business logic. The PDLC is upstream; Nova integrates with it through
|
||||||
5. **Not sold to operators.** Nova is sold to leadership on outcomes —
|
a validated contract boundary — Nova never reaches into it.
|
||||||
cost, velocity, risk. Selling to operators inverts the incentive and
|
4. **Not a replacement for the Product Development Lifecycle (PDLC).**
|
||||||
breaks the autonomy thesis.
|
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.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
+109
-113
@@ -1,136 +1,132 @@
|
|||||||
---
|
---
|
||||||
project: acdl
|
project: acdl
|
||||||
milestone: v1.18
|
milestone: v1.25
|
||||||
generated_at: 2026-08-06
|
generated_at: 2026-08-12
|
||||||
generator: lead-developer
|
generator: lead-developer
|
||||||
verification_toolchain:
|
verification_toolchain:
|
||||||
typecheck: "python3 -m py_compile core/submission_readiness.py mcp/atelier/server.py && python3 -m jsonschema schemas/submission-readiness.schema.json"
|
typecheck: "python3 -m py_compile core/policy_engine.py adapters/kyverno-json/kyverno_json_engine.py tests/test_policy_engine.py tests/test_kyverno_json_engine.py"
|
||||||
test: "pytest tests/test_submission_readiness.py tests/test_atelier_mcp.py # REQ-220 + REQ-225"
|
test: "pytest tests/test_policy_engine.py tests/test_kyverno_json_engine.py tests/test_adapter.py tests/test_contract_resolver.py tests/test_confidence_signal.py tests/test_checkov_adapter.py tests/test_kyverno_adapter.py tests/test_pipeline.py -v"
|
||||||
build: "bash scripts/render_deck.sh docs/presentations/nova-no-humans-platform-marp.md # HTML + PPTX (D-142)"
|
lint: "ruff check core/policy_engine.py adapters/kyverno-json/ 2>/dev/null || python3 -m py_compile core/policy_engine.py"
|
||||||
note: |
|
note: |
|
||||||
v1.18 adds the Citizen Developer & Production-Grade Guidance surface:
|
v1.25 is the kyverno-json Unified Policy Engine milestone — a feat
|
||||||
submission-readiness gate, Atelier-derived skills, the Atelier MCP server
|
milestone. Four active personas: lead-developer (coordination +
|
||||||
(plugin-registry, stdio), and PPTX-as-first-class-artifact deck automation.
|
docs + ARCHITECTURE.md §12.7), backend-engineer (core/policy_engine.py
|
||||||
Three active personas: lead-developer (coordination + decks + RACI/scope
|
protocol + registry + contract_resolver.py wiring + run_platform.sh
|
||||||
docs), backend-engineer (MCP server + submission-readiness validator +
|
Step 5 + pipeline tests), policy-engineer (adapters/kyverno-json/
|
||||||
render/attach scripts), data-engineer (submission-readiness schema if it
|
engine + policies across all 4 target dirs + meta-policies + policy
|
||||||
touches contract storage / DynamoDB shape). frontend-engineer stays
|
tests + adapter README + STANDARDS.md policy-authoring section),
|
||||||
deactivated (v1.18 has no frontend; decks are markdown = lead-developer
|
data-engineer (config.json policy object + schemas/README.md note +
|
||||||
territory). The MCP plugin-registry is a backend pattern, so a separate
|
capability-inventory JSON fixture for regression policies).
|
||||||
mcp-engineer persona is NOT added — it folds into backend-engineer.
|
frontend-engineer stays deactivated (no UI). The policy-engineer is a
|
||||||
|
new custom persona created for this milestone's policy domain (see
|
||||||
|
RESEARCH.md §4 — kyverno-json + JMESPath is a distinct framework from
|
||||||
|
backend-engineer's fastify/hono).
|
||||||
---
|
---
|
||||||
|
|
||||||
# ACDL — Persona Roster (v1.18 Citizen Developer & Production-Grade Guidance)
|
# ACDL — Persona Roster (v1.25 kyverno-json Unified Policy Engine)
|
||||||
|
|
||||||
> v1.18 roster. Three active personas + one deactivated. The MCP server
|
> v1.25 roster. Four active personas + one deactivated. This is a feat
|
||||||
> plugin-registry (D-140) is a backend pattern, not a new persona — it
|
> milestone: the work is a swappable policy-engine protocol + a new
|
||||||
> folds into backend-engineer. v1.17 precedent (frontend-engineer
|
> adapter + policies across 4 Nova artifacts + pipeline wiring + docs.
|
||||||
> deactivated, decks are markdown = lead-developer territory) is upheld.
|
> The policy-engineer is a new custom persona — kyverno-json + JMESPath
|
||||||
|
> is a specialized domain that doesn't fit backend-engineer's
|
||||||
|
> fastify/hono frameworks or data-engineer's drizzle/postgresql.
|
||||||
|
|
||||||
## Active personas
|
## Active personas
|
||||||
|
|
||||||
### lead-developer
|
### lead-developer
|
||||||
- **Domain:** coordination
|
- **Domain:** coordination + docs
|
||||||
- **Active:** true
|
- **Frameworks:** []
|
||||||
- **Phase-specific:** false
|
- **Constraints:** ["pragmatic", "battle-tested defaults", "docs match code", "swap boundary is the moat"]
|
||||||
- **Frameworks:** [] (no framework — owns process + narrative, not code)
|
|
||||||
- **Constraints:** ["pragmatic", "battle-tested defaults", "no fabrication (NORTH_STAR honesty model)"]
|
|
||||||
- **Territory:**
|
- **Territory:**
|
||||||
- `docs/presentations/**` (Step 1/2/4 markdown + the deck automation trigger)
|
- `.ciagent/ARCHITECTURE.md` (§12.7 Policy Engine Registry — NEW)
|
||||||
- `.ciagent/**` (PROJECT, ROADMAP, REQUIREMENTS, RESEARCH, PLAN, GRILL, PERSONAS, REVIEW, CHECKPOINT)
|
- `.ciagent/PROJECT.md` (v1.25 section)
|
||||||
- `PROJECT.md` (RACI matrix + PDLC-scope statement, REQ-215/216)
|
- `.ciagent/REQUIREMENTS.md` (v1.25 section)
|
||||||
- `ROADMAP.md`
|
- `.ciagent/ROADMAP.md` (v1.25 section)
|
||||||
- `REQUIREMENTS.md`
|
- `.ciagent/PLAN.md`, `.ciagent/RESEARCH.md`, `.ciagent/CLARIFY.md`,
|
||||||
- `docs/raci.md` (REQ-215)
|
`.ciagent/GRILL.md`, `.ciagent/PERSONAS.md`
|
||||||
- `docs/scope.md` (REQ-216)
|
- `docs/METRICS.md` (swappable engine narrative — REQ-307)
|
||||||
- `docs/skills.md` (REQ-222 — the index page, not the skill files themselves)
|
- **Reason:** Owns the milestone coordination + the architecture
|
||||||
- `docs/submission-readiness.md` (REQ-219 — citizen-developer-facing copy; co-owned with backend-engineer for the reason-code catalog)
|
narrative. The swap boundary (PolicyEngine protocol) is the moat per
|
||||||
- **Reason:** Owns CIAgent metadata, the milestone narrative, the RACI +
|
Strategic Objective #2 — the lead-developer owns the boundary
|
||||||
PDLC-scope statements (REQ-215/216), the deck (21 slides, S&P theme
|
description in ARCHITECTURE.md §12.7 and the docs/METRICS.md note.
|
||||||
regression check vs P1, CAP-024), the skills index page (REQ-222), and
|
No Python policy code (backend-engineer + policy-engineer territory).
|
||||||
the citizen-developer-facing submission-readiness doc (REQ-219). Is
|
No UI (frontend-engineer deactivated).
|
||||||
the only persona that touches `.ciagent/**` and the deck markdown.
|
|
||||||
- **Phase-specific flag:** none (active for all of P0–P7).
|
|
||||||
|
|
||||||
### backend-engineer
|
### backend-engineer
|
||||||
- **Domain:** backend
|
- **Domain:** backend (Python + bash + pipeline wiring)
|
||||||
- **Active:** true
|
- **Frameworks:** ["boto3", "terraform"]
|
||||||
- **Phase-specific:** false
|
- **Constraints:** ["api-first", "strict-typing", "engine-agnostic confidence signal", "fail-soft when kj absent"]
|
||||||
- **Frameworks:** ["mcp (Python SDK v2)", "pydantic", "jsonschema", "urllib"]
|
|
||||||
- **Constraints:** ["api-first", "strict-typing", "plugin-registry extensible (D-140)", "stdio now / HTTP-ready (D-135)", "no stack traces to citizen developers (REQ-218)"]
|
|
||||||
- **Territory:**
|
- **Territory:**
|
||||||
- `mcp/atelier/server.py` (REQ-223)
|
- `core/policy_engine.py` (NEW — PolicyEngine Protocol + PolicyEngineRegistry + NullEngine)
|
||||||
- `mcp/atelier/plugins/**/*.py` (REQ-223 — principles.py, validation.py)
|
- `core/contract_resolver.py` (MODIFIED — invoke registry pre/post resolve)
|
||||||
- `mcp/atelier/vendor/**` (REQ-224 — vendored Atelier snapshot)
|
- `scripts/run_platform.sh` (MODIFIED — Step 5 kyverno-json parallel pass)
|
||||||
- `mcp/atelier/VERSION.md` + `mcp/atelier/README.md` (REQ-224)
|
- `scripts/install-kyverno-json.sh` (NEW)
|
||||||
- `scripts/update_atelier_vendor.sh` (REQ-224)
|
- `tests/test_policy_engine.py` (NEW — protocol conformance, registry, NullEngine)
|
||||||
- `core/submission_readiness.py` (REQ-218 — the validator, invoked as `contract_ingestor.py --check-readiness`)
|
- `tests/test_run_platform_plan_json_policies.py` (NEW — script-substring assertion)
|
||||||
- `scripts/render_deck.sh` (REQ-228 — HTML + PPTX render)
|
- `.github/workflows/ci.yml` + `.gitea/workflows/ci.yml` (MODIFIED — Go + kj install)
|
||||||
- `scripts/attach_release_asset.py` (REQ-228 — Gitea release asset upload)
|
- **Reason:** Owns the Python protocol layer + the pipeline wiring. The
|
||||||
- `tests/test_atelier_mcp.py` (REQ-225)
|
`PolicyEngine` Protocol + `PolicyEngineRegistry` are Python structural-
|
||||||
- `tests/test_submission_readiness.py` (REQ-220)
|
typing constructs (PEP 544) — backend-engineer's strict-typing
|
||||||
- `docs/submission-readiness.md` (REQ-219 — reason-code catalog section; co-owned with lead-developer for the narrative)
|
constraint. The `contract_resolver.py` wiring + `run_platform.sh`
|
||||||
- **Reason:** Owns the MCP server (plugin-registry, stdio, vendored
|
Step 5 are backend territory. Does NOT write kyverno-json policy
|
||||||
Atelier), the submission-readiness validator (extends
|
files (policy-engineer territory) — only the Python that *invokes* the
|
||||||
`contract_ingestor.py --check-readiness`, D-133), the render/attach
|
engine. Does NOT modify the confidence signal (it already consumes
|
||||||
scripts (D-142 trigger), and the two new test files. The MCP
|
`list[PolicyCheckResult]` engine-agnostically — PROJECT.md hard-
|
||||||
plugin-registry (D-140) is a backend pattern — no separate
|
constraint).
|
||||||
mcp-engineer persona is created; backend-engineer owns it.
|
|
||||||
- **Phase-specific flag:** none (active for P1 deck-render, P3 validator,
|
### policy-engineer
|
||||||
P5 MCP server, P6 scripts).
|
- **Domain:** policy (declarative compliance rules)
|
||||||
|
- **Frameworks:** ["kyverno-json", "jmespath", "kyverno ValidatingPolicy"]
|
||||||
|
- **Constraints:** ["declarative-policies", "no-imperative-rules", "schema-validated", "severity-via-annotation", "assertion-trees-not-foreach"]
|
||||||
|
- **Territory:**
|
||||||
|
- `adapters/kyverno-json/` (NEW — engine impl + __init__.py + README)
|
||||||
|
- `adapters/kyverno-json/kyverno_json_engine.py` (NEW — KyvernoJsonEngine)
|
||||||
|
- `adapters/kyverno-json/policies/` (NEW — all 4 target dirs: contract/, stack-ir/, plan-json/, meta/, regression/)
|
||||||
|
- `adapters/kyverno-json/policies/_smoke.json` (NEW)
|
||||||
|
- `adapters/README.md` (MODIFIED — new adapter row + PolicyEngine Protocol section)
|
||||||
|
- `tests/test_kyverno_json_engine.py` (NEW — PCR schema validity, defensive parsing)
|
||||||
|
- `tests/test_stack_ir_policies.py` (NEW)
|
||||||
|
- `tests/test_plan_json_policies.py` (NEW)
|
||||||
|
- `tests/test_meta_policies.py` (NEW)
|
||||||
|
- `tests/test_regression_policies.py` (NEW)
|
||||||
|
- `tests/fixtures/stack_ir/`, `tests/fixtures/plan_json/`, `tests/fixtures/capability_inventory.json` (NEW)
|
||||||
|
- `modules/STANDARDS.md` (MODIFIED — Policy authoring standard section — REQ-307)
|
||||||
|
- **Reason:** The policy-engineer owns the declarative policy artifacts.
|
||||||
|
kyverno-json's `ValidatingPolicy` + assertion trees + JMESPath is a
|
||||||
|
distinct framework from backend-engineer's fastify/hono and requires
|
||||||
|
its own constraints: no imperative rules (everything is an assertion
|
||||||
|
tree), severity via the `nova.cloudinit.dev/severity` annotation (not
|
||||||
|
in the engine adapter), no `forEach` (use the `~` modifier). The
|
||||||
|
adapter pattern (engine ↔ protocol ↔ registry) is backend-engineer
|
||||||
|
territory, but the policy *content* and the engine *translation*
|
||||||
|
(`_to_pcr()`) are policy-engineer territory because they require
|
||||||
|
kyverno-json output-shape knowledge. Created per RESEARCH.md §4 — this
|
||||||
|
is a phase-spanning persona (active for P1..P4), not phase-specific.
|
||||||
|
|
||||||
### data-engineer
|
### data-engineer
|
||||||
- **Domain:** data
|
- **Domain:** data (config schema + structured fixtures)
|
||||||
- **Active:** true
|
- **Frameworks:** ["jsonschema", "yaml"]
|
||||||
- **Phase-specific:** false
|
- **Constraints:** ["schema-first", "type-safe config", "backward-compatible additions"]
|
||||||
- **Frameworks:** ["jsonschema", "dynamodb (item shape)"]
|
|
||||||
- **Constraints:** ["schema-first", "superset-gate NOT duplicate (PROJECT.md hard constraint)", "W3.E per-env mandatory table is the source of truth"]
|
|
||||||
- **Territory:**
|
- **Territory:**
|
||||||
- `schemas/**` (REQ-217 — `submission-readiness.schema.json` is the new schema; existing schemas untouched)
|
- `.ciagent/config.json` (MODIFIED — new `policy` object: engine + policy_root)
|
||||||
- `core/lambda/contract_ingestor.py` (the `--check-readiness` subcommand wiring, D-133 — the validator is in `core/submission_readiness.py` but the ingestor dispatches to it; co-owned with backend-engineer)
|
- `schemas/policy_check_result.schema.json` (READ-ONLY — no change per D-116)
|
||||||
- **Reason:** Owns the submission-readiness JSON Schema (REQ-217) — it
|
- `schemas/README.md` (MODIFIED — note engine: "kyverno" shared by K8s adapter + kj)
|
||||||
is a schema artifact, data-engineer territory. The schema is a
|
- `tests/fixtures/capability_inventory.json` (NEW — clean + drifted inventory fixtures for regression policies)
|
||||||
*superset gate above* `contract.schema.json`, not a duplicate (it
|
- **Reason:** The `config.json.policy` object is a schema-first addition
|
||||||
references contract fields, does not redefine them). The
|
(new top-level key with `engine` + `policy_root` fields). The
|
||||||
per-env-mandatory table comes from W3.E (the locked decision). The
|
capability-inventory JSON fixtures for the regression-gate policies
|
||||||
ingestor wiring is co-owned with backend-engineer (the dispatch point
|
(REQ-304) are structured data — the data-engineer owns the fixture
|
||||||
is backend; the schema it validates against is data).
|
shape. The `policy_check_result.schema.json` is read-only (D-116 — no
|
||||||
- **Phase-specific flag:** none (active for P3 schema + ingestor wiring).
|
enum change); the data-engineer documents the `engine: "kyverno"`
|
||||||
|
sharing in `schemas/README.md`. No migrations (no database). No Python
|
||||||
|
(backend-engineer + policy-engineer territory).
|
||||||
|
|
||||||
## Deactivated personas
|
## Deactivated personas
|
||||||
|
|
||||||
### frontend-engineer
|
### frontend-engineer
|
||||||
- **Active:** false
|
- **active:** false
|
||||||
- **Domain:** frontend
|
- **Reason:** ACDL has no frontend (no package.json — confirmed in
|
||||||
- **Frameworks:** ["react", "next.js"] (inert — no territory)
|
config.json personas.personas[frontend-engineer].reason). v1.25 adds
|
||||||
- **Constraints:** ["component-first", "server-components", "minimal-client-js"] (inert)
|
no UI work — the policy engine is backend + policy artifacts only.
|
||||||
- **Territory:** [] (no territory in v1.18)
|
Deactivated per the v1.15+ convention.
|
||||||
- **Reason:** v1.18 has no frontend; decks are markdown (lead-developer
|
|
||||||
territory); deactivated per PERSONAS.md v1.17 precedent. v1.18's
|
|
||||||
observability stays PowerBI / external (Out of Scope: "A Nova-built
|
|
||||||
frontend / dashboard"). The MCP server exposes tools to an AI agent,
|
|
||||||
not a web UI. No reactivation trigger in this milestone.
|
|
||||||
|
|
||||||
## Roster decisions
|
|
||||||
|
|
||||||
### D-143 (0.90): Fold mcp-engineer into backend-engineer
|
|
||||||
The MCP plugin-registry (D-140: `plugins/<name>.py register(mcp)`) is a
|
|
||||||
backend code pattern — Python modules, type hints, stdio transport,
|
|
||||||
urllib for the Gitea asset API. It shares nothing with the data domain
|
|
||||||
(schemas/DynamoDB) and is not a new engineering discipline. Creating a
|
|
||||||
separate `mcp-engineer` persona would fragment ownership of the server +
|
|
||||||
its tests + the render/attach scripts (all backend). **Decision:** fold
|
|
||||||
into backend-engineer. backend-engineer's `frameworks` list gains
|
|
||||||
`mcp (Python SDK v2)`. Confidence 0.90 — the only counter-argument is
|
|
||||||
that MCP is a distinct protocol skill, but the SDK v2 API surface
|
|
||||||
(`@mcp.tool()` + type hints) is small and well within backend-engineer's
|
|
||||||
range (it's the same Pydantic/FastAPI-style pattern the persona already
|
|
||||||
knows).
|
|
||||||
|
|
||||||
### Territory-overlap resolution (co-ownership)
|
|
||||||
|
|
||||||
| Path | Primary | Co-owner | Why |
|
|
||||||
|------|---------|----------|-----|
|
|
||||||
| `docs/submission-readiness.md` | lead-developer (narrative + examples) | backend-engineer (reason-code catalog, REQ-218 codes) | The doc is citizen-developer-facing copy (lead) but the reason-code catalog (MISSING_TAGS, ENV_MISSING_MANDATORY, AGENTIC_MISSING_INTENT, MISSING_APP_SOURCE, POLICY_PRECONDITION_MISSING) is backend (it mirrors the validator's return codes). |
|
|
||||||
| `core/lambda/contract_ingestor.py` | backend-engineer (dispatch wiring) | data-engineer (the schema it validates against) | D-133 places the `--check-readiness` subcommand on the ingestor (backend dispatch), but the readiness schema it loads is data-engineer territory. |
|
|
||||||
| `schemas/submission-readiness.schema.json` | data-engineer (schema artifact) | backend-engineer (the validator must match it) | The schema is data-engineer's; the validator (REQ-218) is backend-engineer's and must stay in sync with it. |
|
|
||||||
+323
-900
File diff suppressed because it is too large
Load Diff
+299
-7
@@ -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 penetrate upstream product/SDLC.
|
governs infra and delivery; it does not reach into 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
|
||||||
@@ -66,7 +66,7 @@ traceable to a human attestation and an immutable evidence stream.
|
|||||||
|
|
||||||
The **Product Development Lifecycle (PDLC)** — product backlog, code
|
The **Product Development Lifecycle (PDLC)** — product backlog, code
|
||||||
authorship, IDE workflows, sprint planning, application business logic —
|
authorship, IDE workflows, sprint planning, application business logic —
|
||||||
is **upstream** of Nova. Nova never penetrates the PDLC. Nova's domain is
|
is **upstream** of Nova. Nova never reaches into the PDLC. Nova's domain is
|
||||||
**infrastructure + delivery only**: environment progression, cloud
|
**infrastructure + delivery only**: environment progression, cloud
|
||||||
resource lifecycle, operational security/observability NFRs, policy
|
resource lifecycle, operational security/observability NFRs, policy
|
||||||
enforcement, immutable audit lineage, and the two consumer surfaces
|
enforcement, immutable audit lineage, and the two consumer surfaces
|
||||||
@@ -711,7 +711,7 @@ is acceptable to start**. Five user-directed inputs drive the milestone:
|
|||||||
`sp-theme.json` survived; only the Marp CSS theme was lost.
|
`sp-theme.json` survived; only the Marp CSS theme was lost.
|
||||||
|
|
||||||
2. **PDLC-upstream scope made explicit.** Core Tenet #2 already states the
|
2. **PDLC-upstream scope made explicit.** Core Tenet #2 already states the
|
||||||
platform "does not penetrate upstream product/SDLC" and Anti-Goal #1 says
|
platform "does not reach into upstream product/SDLC" and Anti-Goal #1 says
|
||||||
"Not an upstream development platform." v1.18 promotes this from a
|
"Not an upstream development platform." v1.18 promotes this from a
|
||||||
buried tenet to a dedicated, unmissable scope statement in PROJECT.md +
|
buried tenet to a dedicated, unmissable scope statement in PROJECT.md +
|
||||||
`docs/scope.md` + a deck slide: **the PDLC (Product Development
|
`docs/scope.md` + a deck slide: **the PDLC (Product Development
|
||||||
@@ -1266,16 +1266,29 @@ P7 review+audit+ship). Tags on the v1.16.x line: `v1.16.0` (P0) →
|
|||||||
|
|
||||||
- **Pillar A — Strategic Direction.** A durable, PO-authored
|
- **Pillar A — Strategic Direction.** A durable, PO-authored
|
||||||
`.ciagent/NORTH_STAR.md` encodes the platform's vision, 4 strategic
|
`.ciagent/NORTH_STAR.md` encodes the platform's vision, 4 strategic
|
||||||
objectives, 5 anti-goals, v1.17 non-goals, 12–18mo targets (with a
|
objectives, anti-goals, v1.17 non-goals, 12–18mo targets (with a
|
||||||
grounding column), and success criteria. CIAgent reads it in every
|
grounding column), and success criteria. CIAgent reads it in every
|
||||||
future `/ci-run` so the direction survives across milestones. The
|
future `/ci-run` so the direction survives across milestones. The
|
||||||
attestation clarification is reflected: human attestation required at
|
attestation clarification is reflected: human attestation required at
|
||||||
stage gates (QA for production, SRE for operational readiness); autonomy
|
stage gates (QA for production, SRE for operational readiness); autonomy
|
||||||
in operations, not in accountability.
|
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
|
- **Pillar B — Leadership Metrics + PowerBI.** Instrument Nova to
|
||||||
collect, aggregate, and surface leadership-grade metrics that prove the
|
collect, aggregate, and surface leadership-grade metrics that prove the
|
||||||
"no-humans" autonomous-infrastructure value proposition. Nova-native
|
"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
|
minimal tech (CloudEvents 1.0 envelope, JSONL event log, SQLite cold
|
||||||
store, hash-chained Decision Ledger via `outbox_writer.py` extension)
|
store, hash-chained Decision Ledger via `outbox_writer.py` extension)
|
||||||
+ Infracost for pre-apply cost estimates. Hybrid model: existing
|
+ Infracost for pre-apply cost estimates. Hybrid model: existing
|
||||||
@@ -1334,7 +1347,7 @@ constraints or user-directed scope). New v1.18 decisions:
|
|||||||
| 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-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-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. |
|
| 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 (active — Nova 2nd-Release Sync)
|
## Objective for Milestone v1.19 (complete — Nova 2nd-Release Sync)
|
||||||
|
|
||||||
> **NFR-only chore milestone.** Ships a patch on the v1.18.x line (tag
|
> **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
|
> `v1.18.0`). Single execution phase. Establishes the manual-only "2nd
|
||||||
@@ -1406,3 +1419,282 @@ wrong commit standard, wrong repo.
|
|||||||
| 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-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-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. |
|
| 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` (P1–P5) →
|
||||||
|
`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` (P1–P5) →
|
||||||
|
> `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).
|
||||||
|
|
||||||
|
## v1.25 — kyverno-json Unified Policy Engine
|
||||||
|
|
||||||
|
> **Active milestone.** Feature milestone (the primary compliance/policy
|
||||||
|
> tool becomes kyverno-json, implemented behind a swappable adapter).
|
||||||
|
> Branch: `milestone/v1.25-kyverno-json`. Tags run on the **v1.24.x**
|
||||||
|
> patch line: `v1.24.0` (P0) → `v1.24.1..v1.24.4` (P1–P4) → `v1.24.5`
|
||||||
|
> (P5 final = milestone release).
|
||||||
|
|
||||||
|
[Nova](https://github.com/kyverno/kyverno-json) `kyverno-json` is a
|
||||||
|
runtime from the Kyverno ecosystem that applies Kyverno policies to
|
||||||
|
**any JSON or YAML payload** — not just Kubernetes manifests. This
|
||||||
|
milestone makes kyverno-json the **primary tool of choice for
|
||||||
|
compliance / policy checks** in Nova, implemented as an **adapter**
|
||||||
|
(the `PolicyEngine` protocol) so the platform may one day replace it
|
||||||
|
with something else (e.g. OPA) without touching the confidence signal
|
||||||
|
or the pipeline.
|
||||||
|
|
||||||
|
### Why
|
||||||
|
|
||||||
|
Nova's policy posture today is split across three engines with three
|
||||||
|
different rule languages and three adapter shapes:
|
||||||
|
|
||||||
|
- **Checkov** (`adapters/terraform/policy/checkov_adapter.py`) — the
|
||||||
|
runtime scanner over `terraform_plan` JSON; carries the
|
||||||
|
`NOVA_TAG_NAMING` custom rule. Imperative YAML+Python rules.
|
||||||
|
- **Wiz** (`adapters/wiz/wiz_adapter.py`) — security findings from the
|
||||||
|
Wiz API; inactive unless credentials are present.
|
||||||
|
- **Kyverno (K8s)** (`adapters/kyverno/kyverno_adapter.py`) — translates
|
||||||
|
Kyverno `PolicyReport` results; **inactive for Terraform-only stacks**
|
||||||
|
(the platform emits Terraform, not K8s manifests — D-053).
|
||||||
|
|
||||||
|
All three emit the same `schemas/policy_check_result.schema.json` shape
|
||||||
|
that `core/confidence_signal.py` consumes engine-agnostically. The
|
||||||
|
*contract* is already right; the *orchestration* is fragmented. There is
|
||||||
|
no single place where "what Nova considers compliant" is declared —
|
||||||
|
tagging lives in a Checkov custom rule, public-ingress in Checkov's
|
||||||
|
`RULE_MAP`, env-transition destroy in `core/env_transition.py`
|
||||||
|
(imperative Python), and capability regression in
|
||||||
|
`core/regression_verify.py` (imperative Python). Each is a different
|
||||||
|
language, each drifts independently, and the K8s Kyverno adapter can't
|
||||||
|
help because it only speaks to K8s manifests.
|
||||||
|
|
||||||
|
`kyverno-json` fixes this: one declarative policy language (Kyverno
|
||||||
|
policies with JMESPath assertions) that applies to **any** Nova
|
||||||
|
artifact — the consumer contract, the resolved Stack IR, the
|
||||||
|
Terraform plan JSON, and even the PolicyCheckResult list itself
|
||||||
|
(meta-validation). It becomes the **unified orchestrator** of compliance
|
||||||
|
checks, while Checkov and Wiz remain as raw-finding adapters that feed
|
||||||
|
*into* kyverno-json meta-policies (so Nova-specific posture rules sit
|
||||||
|
on top of, not beside, the scanner findings).
|
||||||
|
|
||||||
|
### What the milestone delivers
|
||||||
|
|
||||||
|
- **Swappable `PolicyEngine` protocol** (`core/policy_engine.py`) — a
|
||||||
|
Python Protocol + registry selected from `config.json` (`policy.engine`,
|
||||||
|
default `"kyverno-json"`). `KyvernoJsonEngine` implements it (shells
|
||||||
|
to the `kyverno-json` CLI); a future `OpaEngine` implements the same
|
||||||
|
protocol. The confidence signal and pipeline never import the engine
|
||||||
|
directly — they go through the registry.
|
||||||
|
- **`KyvernoJsonEngine` adapter** (`adapters/kyverno-json/`) —
|
||||||
|
`evaluate(payload, policies) -> list[PolicyCheckResult]` translates
|
||||||
|
kyverno-json native output to the existing PCR schema. Mirrors the
|
||||||
|
Checkov/Wiz adapter pattern. `is_configured()` guard skips gracefully
|
||||||
|
when the `kyverno-json` binary is absent (same pattern as the Wiz
|
||||||
|
adapter — emits `SKIPPED`, never breaks the pipeline).
|
||||||
|
- **Policies over all four Nova artifacts** under
|
||||||
|
`adapters/kyverno-json/policies/`:
|
||||||
|
- `contract/` — consumer contract JSON (shape + env-promotion rules).
|
||||||
|
- `stack-ir/` — resolved Target Stack IR (tagging standard,
|
||||||
|
public-ingress, encryption-by-default — ports of the v1.0/v1.8
|
||||||
|
imperative rules into declarative policies).
|
||||||
|
- `plan-json/` — `terraform show -json` output (plaintext secrets,
|
||||||
|
IAM wildcards, KMS references — ports of Checkov's `RULE_MAP`).
|
||||||
|
- `meta/` — policies over the merged PolicyCheckResult list itself
|
||||||
|
(e.g. `block-on-any-critical` — the single declarative source of
|
||||||
|
truth for "critical = block", with the existing
|
||||||
|
`confidence_signal.py` hard-override kept as defense-in-depth).
|
||||||
|
- **`run_platform.sh` Step 5 wiring** — Checkov/Wiz still run and emit
|
||||||
|
raw PCRs; `KyvernoJsonEngine.evaluate()` runs plan-JSON policies in
|
||||||
|
parallel; both PCR lists merge into the confidence signal's `policy`
|
||||||
|
input. No change to `core/confidence_signal.py` (it already consumes
|
||||||
|
`list[PolicyCheckResult]` engine-agnostically).
|
||||||
|
- **Regression-gate-as-policy** (P4 — quality improvement from the
|
||||||
|
IDEATE pass): the capability checks in
|
||||||
|
`core/regression_verify.py` (CAP-013, CAP-023, CAP-024) become
|
||||||
|
declarative kyverno-json policies over the capability-inventory JSON
|
||||||
|
frontmatter. Capability regression becomes an audit artifact, not
|
||||||
|
imperative Python.
|
||||||
|
- **`policy-engineer` persona** (custom, added in RESEARCH) — owns the
|
||||||
|
policy territory; declarative-policies constraint; kyverno-json +
|
||||||
|
JMESPath frameworks.
|
||||||
|
|
||||||
|
**Phase count:** 6 (P0 pre-execution + 4 execution + 1 final).
|
||||||
|
|
||||||
|
**Hard constraints:**
|
||||||
|
- DO NOT change `schemas/policy_check_result.schema.json` shape in a way
|
||||||
|
that breaks existing adapters — the contract is the moat. The
|
||||||
|
`engine` enum already includes `"kyverno"` and `"opa"`; v1.25 records
|
||||||
|
carry `engine: "kyverno"` (no new enum value — decision in CLARIFY).
|
||||||
|
- DO NOT remove Checkov or Wiz adapters — they remain as raw-finding
|
||||||
|
sources feeding into kyverno-json meta-policies.
|
||||||
|
- DO NOT remove the `confidence_signal.py` `PENALTY["critical"]: None`
|
||||||
|
hard-override — it stays as defense-in-depth behind the declarative
|
||||||
|
`block-on-any-critical` meta-policy (decision in CLARIFY).
|
||||||
|
- DO NOT change `core/confidence_signal.py`'s input contract — it
|
||||||
|
already consumes `list[PolicyCheckResult]`; v1.25 only changes *who
|
||||||
|
produces* that list, not *what* the list is.
|
||||||
|
- The platform must function with `kyverno-json` absent — `is_configured()`
|
||||||
|
returns false → `SKIPPED` records → confidence signal proceeds (no
|
||||||
|
hard dependency that breaks the "platform functions without AI /
|
||||||
|
deterministic scripts" tenet — kyverno-json is deterministic, not AI).
|
||||||
|
|
||||||
|
### Requirements
|
||||||
|
|
||||||
|
New requirements REQ-291..REQ-309 — see `REQUIREMENTS.md` §v1.25.
|
||||||
|
Summary: engine protocol + registry (REQ-291,292), kyverno-json engine
|
||||||
|
impl (REQ-293,294), contract policies (REQ-295,296), stack-IR policies
|
||||||
|
(REQ-297,298,299), plan-JSON policies + pipeline wiring (REQ-300,301,302),
|
||||||
|
meta-policies (REQ-303), regression-gate policies (REQ-304,305), docs +
|
||||||
|
adapter README (REQ-306,307), tests (REQ-308,309).
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
+407
-2317
File diff suppressed because it is too large
Load Diff
+452
-2
@@ -28,6 +28,8 @@
|
|||||||
- **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 4–11; 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 4–11; 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 (complete, tag `v1.23.4`):** 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. 287 tests pass. Feature milestone; tags on v1.23.x line.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -1756,7 +1758,7 @@ locked (D-133..D-142).
|
|||||||
Ship tag at milestone COMPLETE: `v1.17.7` (feature milestone; final patch
|
Ship tag at milestone COMPLETE: `v1.17.7` (feature milestone; final patch
|
||||||
IS the release). **DONE.**
|
IS the release). **DONE.**
|
||||||
|
|
||||||
## v1.19 (active — Nova 2nd-Release Sync, tag line `v1.18.x`)
|
## v1.19 (complete — Nova 2nd-Release Sync, tag line `v1.18.x`)
|
||||||
|
|
||||||
> **NFR-only chore milestone.** Single execution phase. Establishes the
|
> **NFR-only chore milestone.** Single execution phase. Establishes the
|
||||||
> manual-only "2nd release" pipeline `~/acdl → ~/nova` (GitLab
|
> manual-only "2nd release" pipeline `~/acdl → ~/nova` (GitLab
|
||||||
@@ -1787,7 +1789,7 @@ IS the release). **DONE.**
|
|||||||
### Phase P2 — final-review-ship (Final Phase)
|
### Phase P2 — final-review-ship (Final Phase)
|
||||||
- **Description:** Final review + audit + milestone ship. Merge to main, tag
|
- **Description:** Final review + audit + milestone ship. Merge to main, tag
|
||||||
`v1.18.0` (first patch on the v1.18.x line), create Gitea release.
|
`v1.18.0` (first patch on the v1.18.x line), create Gitea release.
|
||||||
- **Status:** pending
|
- **Status:** complete
|
||||||
- **Depends on:** [P1]
|
- **Depends on:** [P1]
|
||||||
- **Requirements:** REQ-229
|
- **Requirements:** REQ-229
|
||||||
- **Success Criteria:**
|
- **Success Criteria:**
|
||||||
@@ -1796,3 +1798,451 @@ IS the release). **DONE.**
|
|||||||
`main`.
|
`main`.
|
||||||
- Tag `v1.18.0` created; release notes summarize REQ-229.
|
- Tag `v1.18.0` created; release notes summarize REQ-229.
|
||||||
- Milestone branches deleted; CHECKPOINT cleared.
|
- 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).
|
||||||
|
|
||||||
|
## v1.25 (active, tag line `v1.24.x`): kyverno-json Unified Policy Engine
|
||||||
|
|
||||||
|
`kyverno-json` — a Kyverno-ecosystem runtime that applies Kyverno policies
|
||||||
|
to **any** JSON/YAML payload — becomes Nova's **primary compliance /
|
||||||
|
policy tool**, implemented behind a swappable `PolicyEngine` adapter so
|
||||||
|
OPA (or any other engine) can replace it one day. The unified-orchestrator
|
||||||
|
model: Checkov and Wiz remain as raw-finding adapters feeding *into*
|
||||||
|
kyverno-json meta-policies; the confidence signal is untouched (it already
|
||||||
|
consumes `list[PolicyCheckResult]` engine-agnostically). Policies cover
|
||||||
|
all four Nova artifacts: consumer contract JSON, resolved Stack IR,
|
||||||
|
Terraform plan JSON, and the merged PCR list itself (meta-validation).
|
||||||
|
The K8s-only Kyverno adapter stays documentation-only (D-053); the
|
||||||
|
kyverno-json engine and the K8s adapter are siblings, not replacements.
|
||||||
|
Quality improvement from the IDEATE pass: capability regression checks
|
||||||
|
(`core/regression_verify.py` CAP-013/023/024) become declarative
|
||||||
|
kyverno-json policies. New `policy-engineer` persona owns the policy
|
||||||
|
territory. 19 requirements (REQ-291..309), 6 phases (P0 + P1..P4 + P5
|
||||||
|
final). Tags: `v1.24.0` (P0) → `v1.24.5` (P5 = milestone release).
|
||||||
|
|
||||||
|
### Phase P1 — engine-core (planned, tag v1.24.1)
|
||||||
|
- REQ-291: `core/policy_engine.py` — `PolicyEngine` Protocol +
|
||||||
|
`PolicyEngineRegistry` (selects engine from `config.json.policy.engine`).
|
||||||
|
- REQ-292: `config.json` gains `policy` object
|
||||||
|
(`engine: "kyverno-json"`, `policy_root`).
|
||||||
|
- REQ-293: `adapters/kyverno-json/kyverno_json_engine.py` —
|
||||||
|
`KyvernoJsonEngine` (shells to `kj scan`; translates native output →
|
||||||
|
PCR; `is_configured()` guards on `which kj`).
|
||||||
|
- REQ-294: `adapters/kyverno-json/__init__.py` + `_smoke.json` policy +
|
||||||
|
`scripts/install-kyverno-json.sh` + CI image install.
|
||||||
|
- REQ-308: `tests/test_policy_engine.py` — protocol conformance,
|
||||||
|
registry, NullEngine fallback.
|
||||||
|
- REQ-309: `tests/test_kyverno_json_engine.py` — PCR schema validity,
|
||||||
|
defensive parsing, `pytest.skip` when kj absent.
|
||||||
|
|
||||||
|
### Phase P2 — contract + stack-IR policies (planned, tag v1.24.2)
|
||||||
|
- REQ-295: `adapters/kyverno-json/policies/contract/` — 4 policies over
|
||||||
|
consumer contract JSON (id-pattern, env-enum, infra-min-1,
|
||||||
|
forbid-unknown-fields).
|
||||||
|
- REQ-296: `core/contract_resolver.py` invokes the engine pre-resolve
|
||||||
|
(contract policies) — early-fail, confidence signal decides the gate.
|
||||||
|
- REQ-297: `adapters/kyverno-json/policies/stack-ir/` — 3 policies over
|
||||||
|
resolved Stack IR (tagging-standard, public-ingress, encryption-by-
|
||||||
|
default — ports of v1.0/v1.8 imperative rules).
|
||||||
|
- REQ-298: `core/contract_resolver.py` invokes the engine post-resolve
|
||||||
|
(stack-IR policies); additive — existing tests pass.
|
||||||
|
- REQ-299: `tests/test_stack_ir_policies.py` + fixtures (passing + failing
|
||||||
|
IR; skip when kj absent).
|
||||||
|
|
||||||
|
### Phase P3 — plan-JSON policies + meta-orchestration + pipeline wiring (planned, tag v1.24.3)
|
||||||
|
- REQ-300: `adapters/kyverno-json/policies/plan-json/` — 3 policies over
|
||||||
|
`terraform show -json` (plaintext-secrets, iam-wildcard, kms-reference
|
||||||
|
— ports of `checkov_adapter.py:RULE_MAP`).
|
||||||
|
- REQ-301: `run_platform.sh` Step 5 gains a parallel kyverno-json pass;
|
||||||
|
both PCR lists (checkov/wiz + kj) concatenate into the confidence
|
||||||
|
signal's `policy` input; skips gracefully when `which kj` is false.
|
||||||
|
- REQ-302: `tests/test_plan_json_policies.py` + fixtures;
|
||||||
|
`tests/test_run_platform_plan_json_policies.py` (script-substring
|
||||||
|
assertion).
|
||||||
|
- REQ-303: `adapters/kyverno-json/policies/meta/` —
|
||||||
|
`block-on-any-critical.json` (declarative critical-block; the
|
||||||
|
`confidence_signal.py` hard-override stays as defense-in-depth) +
|
||||||
|
`tagging-rules-agree.json` (asserts Checkov + kj agree on tagging).
|
||||||
|
`tests/test_meta_policies.py`.
|
||||||
|
|
||||||
|
### Phase P4 — regression-gate policies + docs (planned, tag v1.24.4)
|
||||||
|
- REQ-304: `adapters/kyverno-json/policies/regression/` — 3 policies over
|
||||||
|
capability-inventory JSON (CAP-013/023/024) — declarative mirrors of
|
||||||
|
`core/regression_verify.py` checks.
|
||||||
|
- REQ-305: `tests/test_regression_policies.py` + fixtures (clean +
|
||||||
|
drifted inventory); regression gate still 287/287 baseline.
|
||||||
|
- REQ-306: `adapters/README.md` (new adapter row + PolicyEngine Protocol
|
||||||
|
section) + `adapters/kyverno-json/README.md`.
|
||||||
|
- REQ-307: `.ciagent/ARCHITECTURE.md` §12.7 (Policy Engine Registry) +
|
||||||
|
`schemas/README.md` + `modules/STANDARDS.md` (policy-authoring
|
||||||
|
standard) + `docs/METRICS.md` (swappable engine narrative).
|
||||||
|
|
||||||
|
### Phase P5 — final review + audit + milestone ship (Final Phase, tag v1.24.5)
|
||||||
|
- Multi-persona code review across P1..P4 (lead-developer, backend-
|
||||||
|
engineer, data-engineer, policy-engineer). Auto-fix P0; flag P1+.
|
||||||
|
- Audit: reconstruction test (git log ↔ `.ciagent/`), branch hygiene,
|
||||||
|
commit discipline.
|
||||||
|
- Milestone ship: merge `phase/05-final-review-ship` →
|
||||||
|
`milestone/v1.25-kyverno-json` → `main`; tag `v1.24.5` (= the v1.25
|
||||||
|
release per prev-minor tagging rule); create Gitea release with full
|
||||||
|
milestone summary; delete all milestone branches.
|
||||||
|
- Update `REQUIREMENTS.md` (mark REQ-291..309 complete), `ROADMAP.md`
|
||||||
|
(mark v1.25 complete), `NORTH_STAR.md` (note Strategic Objective #2 —
|
||||||
|
provable trust via a replaceable policy-engine substrate).
|
||||||
|
- **Requirements:** REQ-291..309 (19 requirements).
|
||||||
|
|||||||
+75
-123
@@ -1,135 +1,87 @@
|
|||||||
# ACDL v1.10 — Verify (milestone gate)
|
# VERIFY — P1 engine-core (v1.25)
|
||||||
|
|
||||||
> Verify date: 2026-07-27. Verifier: ci-verifier. Milestone: v1.10 (complete, tag `v1.10.0`).
|
> 4-layer verify gate: structural, behavioral, security, quality.
|
||||||
> Scope: 4 phases (52–55), 5 commits (772ac72..2697775), 22 files, +2281/-256 lines.
|
> Phase: P1. Requirements: REQ-291..294, 308, 309. Result: PASS.
|
||||||
|
|
||||||
## Layer 1: Structural — PASS
|
## Structural
|
||||||
|
|
||||||
- All 8 plan-referenced files exist on disk (`core/regression_verify.py`,
|
- `core/policy_engine.py` exists, implements `PolicyEngine` Protocol
|
||||||
`core/local_emulators.py`, `scripts/run_regression.sh`,
|
(PEP 544, `@runtime_checkable`), `PolicyEngineRegistry` with
|
||||||
`tests/test_verify_regression_mode.py`,
|
`register()` + `get_engine()`, `NullEngine` fallback.
|
||||||
`tests/test_local_emulating_adapters.py`,
|
- `adapters/kyverno-json/kyverno_json_engine.py` exists, exports
|
||||||
`.ciagent/CAPABILITY_INVENTORY.md`, `REGRESSION_REPORT.md`,
|
`KyvernoJsonEngine` with `name`, `is_configured()`, `evaluate()`.
|
||||||
`REGRESSION_REPORT.json`).
|
- `adapters/kyverno-json/__init__.py` loads the engine by file path
|
||||||
- All imports resolve (`py_compile` + runtime import OK).
|
(the dir name has a hyphen — not a valid Python package name).
|
||||||
- No TODO/FIXME/HACK/stub placeholders in new code (the `LocalLambdaStub`
|
- `adapters/kyverno-json/policies/_smoke.json` exists (trivial policy
|
||||||
is a legitimate local emulator, not a placeholder).
|
for round-trip validation).
|
||||||
- All declared exports exist (`run_regression`, `write_report`,
|
- `scripts/install-kyverno-json.sh` exists (go install kj@latest).
|
||||||
`CAPABILITY_REGISTRY`, `RegressionReport`, `CapabilityResult`,
|
- `.ciagent/config.json` has the `policy` object
|
||||||
`FlatFileOutbox`, `LocalEcsEmulator`, `LocalS3StateBackend`,
|
(`engine: kyverno-json`, `policy_root`).
|
||||||
`LocalLambdaStub`, `run_local_e2e`, `is_local_tier`).
|
- `.gitea/workflows/ci.yml` + `.github/workflows/ci.yml` have the
|
||||||
|
Go + kj install step (best-effort, tests skip when kj absent).
|
||||||
|
- `tests/test_policy_engine.py` (10 tests) +
|
||||||
|
`tests/test_kyverno_json_engine.py` (16 tests) exist.
|
||||||
|
|
||||||
## Layer 2: Behavioral — PASS
|
## Behavioral
|
||||||
|
|
||||||
- `pytest tests/ -m "not slow"`: **513 passed**, 5 deselected.
|
- `pytest tests/test_policy_engine.py tests/test_kyverno_json_engine.py`:
|
||||||
- `pytest tests/ -m slow`: **5 passed** (2 local E2E + 3 regression
|
**24 passed, 2 skipped** (kj not installed — expected;
|
||||||
integration incl. live-AWS terraform plan).
|
`pytest.skip("kj not installed")`).
|
||||||
- **Total: 518 passed, 0 failed.**
|
- `NullEngine` satisfies the `PolicyEngine` Protocol (G-Q8a —
|
||||||
- Requirement coverage: REQ-112 (P52), REQ-113 (P53), REQ-114 (P54),
|
`isinstance(NullEngine(), PolicyEngine)` is True). Proves the swap
|
||||||
REQ-115 (P55) — all 4 marked `complete`.
|
boundary is real without implementing OPA.
|
||||||
- Regression gate: `bash scripts/run_regression.sh` → **16/16
|
- `KyvernoJsonEngine.is_configured()` returns `False` when
|
||||||
capabilities Verified** (12 local + 4 live-AWS). Milestone gate open.
|
`which kj` is absent → `evaluate()` returns a single
|
||||||
|
`KJ_ENGINE_NOT_CONFIGURED` SKIPPED PCR (distinct `ruleId` from
|
||||||
|
NullEngine's `NULL_ENGINE_INACTIVE` — G-Q4).
|
||||||
|
- PCR records validate against `schemas/policy_check_result.schema.json`
|
||||||
|
(via `jsonschema.validate` in tests).
|
||||||
|
- Defensive parsing: malformed kyverno-json output → `error` PCR
|
||||||
|
(`KJ_ENGINE_ERROR`), never an exception.
|
||||||
|
- Severity annotation reading (G-Q10a): policies with
|
||||||
|
`nova.cloudinit.dev/severity: high` produce PCRs with `severity: high`;
|
||||||
|
policies without the annotation default to `info`.
|
||||||
|
- Registry: `get_engine()` returns the configured engine; unknown
|
||||||
|
engine name raises `KeyError`; `policy` key absent → `NullEngine`.
|
||||||
|
- No regression: `pytest tests/test_confidence_signal.py
|
||||||
|
tests/test_adapter.py tests/test_checkov_adapter.py
|
||||||
|
tests/test_kyverno_adapter.py tests/test_contract_resolver.py` —
|
||||||
|
**132 passed** (unchanged).
|
||||||
|
|
||||||
## Layer 3: Security (STRIDE) — PASS
|
## Security
|
||||||
|
|
||||||
| Threat | Risk | Disposition |
|
- No new secrets, no new network calls in the engine core (the engine
|
||||||
|--------|------|-------------|
|
shells to a local binary; the binary makes no network calls for
|
||||||
| Spoofing | Local Lambda stub patches `_get_dynamodb`/`_get_secrets_client`; opt-in via `ACDL_LOCAL_TIER=1`, never in prod | Accept (low) |
|
`scan`).
|
||||||
| Tampering | Flat-file outbox hash-chain verification detects tampering | Accept (low) |
|
- `is_configured()` guard ensures the platform runs without the binary
|
||||||
| Repudiation | Regression report records per-capability status + timestamps | Accept (low) |
|
(no hard dependency that could be exploited as a DoS vector).
|
||||||
| Info Disclosure | Creds read into env vars, never logged (0 cred strings in reports); ECS binds 127.0.0.1 only | Accept (low) |
|
- The engine writes the payload to a temp file (`tempfile.NamedTemporaryFile`)
|
||||||
| Denial of Service | Local ECS emulator: free port, daemon thread, clean destroy | Accept (low) |
|
and unlinks it in a `finally` block (no leftover payload on disk).
|
||||||
| Elevation of Privilege | `urllib.urlopen` patched to fake response (no network egress); no eval/exec/subprocess in adapter | Accept (low) |
|
- No `shell=True` in the `subprocess.run` call (command is a list —
|
||||||
|
no shell injection surface).
|
||||||
|
|
||||||
All threats low-severity; auto-accepted per
|
## Quality
|
||||||
`config.json security.auto_accept_low_severity=true`.
|
|
||||||
|
|
||||||
## Layer 4: Quality (multi-persona) — PASS
|
- `python3 -m py_compile` passes on all new Python files.
|
||||||
|
- The `PolicyEngine` Protocol is minimal (3 members) — the swap
|
||||||
|
boundary is the moat (NORTH_STAR Strategic Objective #2).
|
||||||
|
- The `NullEngine` proves a second implementation exists (structural
|
||||||
|
conformance) — the OPA swap is a known quantity (RESEARCH §4.2).
|
||||||
|
- Tests use `pytest.skip` when `which kj` is absent, so the CI matrix
|
||||||
|
passes with or without the binary (the suite is green in both cases).
|
||||||
|
|
||||||
| Persona | Finding | Verdict |
|
## Must-have checklist
|
||||||
|---------|---------|---------|
|
|
||||||
| Correctness | 7 adapter defects fixed; each traceable to a terraform validate/plan error | PASS |
|
|
||||||
| Testing | 518 tests pass; 24 new tests. P2: uptime-kuma + RDS not in registry | PASS (1 P2) |
|
|
||||||
| Security | No creds logged; loopback-only; monkey-patches scoped to local tier | PASS |
|
|
||||||
| Performance | Regression run ~60s; acceptable for a milestone gate | PASS |
|
|
||||||
| Maintainability | Well-structured; adding a capability = 1 function + 1 registry entry | PASS |
|
|
||||||
| Adversarial | Gate can't be bypassed; local E2E can't mutate cloud; no injection vectors | PASS |
|
|
||||||
|
|
||||||
**0 P0, 0 P1, 1 P2 (post-hoc: expand regression registry to uptime-kuma + RDS stacks).**
|
- [x] `PolicyEngine` Protocol + `PolicyEngineRegistry` + `NullEngine`
|
||||||
|
(REQ-291)
|
||||||
|
- [x] `config.json.policy` object (REQ-292)
|
||||||
|
- [x] `KyvernoJsonEngine` adapter (REQ-293)
|
||||||
|
- [x] `__init__.py` + `_smoke.json` + `install-kyverno-json.sh` + CI
|
||||||
|
install (REQ-294)
|
||||||
|
- [x] `test_policy_engine.py` — protocol conformance, registry,
|
||||||
|
NullEngine fallback (REQ-308)
|
||||||
|
- [x] `test_kyverno_json_engine.py` — PCR schema validity, defensive
|
||||||
|
parsing, skip-without-kj (REQ-309)
|
||||||
|
|
||||||
## Verdict
|
**Verdict: PASS** — all P1 must-haves met, no regressions, 24 new
|
||||||
|
tests pass (2 skip-without-kj), 132 existing tests unchanged.
|
||||||
**VERIFY PASS** — all 4 layers pass. The v1.10 milestone is sound:
|
|
||||||
the pipeline regression gap is fixed (D-091), the platform is fully
|
|
||||||
locally testable (D-092), every advertised capability is re-verified
|
|
||||||
(D-093, 16/16 Verified), and the docs/decks match verified reality
|
|
||||||
(D-094). 518 tests pass; the regression gate covers 16 capabilities
|
|
||||||
including 4 live-AWS checks. 0 P0, 0 P1, 1 P2 post-hoc. Ready to ship.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
# ACDL — Verify (grill deliverable, commit ac11c01)
|
|
||||||
|
|
||||||
> Verify date: 2026-07-27. Verifier: ci-verifier. Scope: the grill
|
|
||||||
> deliverable (`.ciagent/GRILL.md`, phase 0, status `grill`) added in
|
|
||||||
> commit `ac11c01` since the v1.10 audit PASS (`ab477b3`). Docs-only;
|
|
||||||
> no code, no tests, no schema changes.
|
|
||||||
|
|
||||||
## Layer 1: Structural — PASS
|
|
||||||
|
|
||||||
- `.ciagent/GRILL.md` exists on disk (18250 bytes).
|
|
||||||
- No imports to resolve (markdown docs file).
|
|
||||||
- No TODO/FIXME/HACK/stub placeholders in the report.
|
|
||||||
- All required sections present per grill workflow Step 5 format:
|
|
||||||
title, Run header, Verdict, 9 axes (1–9), Meta, Binding Decisions
|
|
||||||
table (12 rows), Escalations section (2 entries: G-005, G-008).
|
|
||||||
- Commit `ac11c01` `---ci---` block is well-formed: `project: acdl`,
|
|
||||||
`phase: 0`, `milestone: v1.10`, `status: grill`, 12 decision ids
|
|
||||||
(G-001..G-012), 2 escalation lines.
|
|
||||||
|
|
||||||
## Layer 2: Behavioral — PASS
|
|
||||||
|
|
||||||
- `pytest tests/ -m "not slow"`: **513 passed**, 5 deselected (no
|
|
||||||
regressions introduced by the docs-only grill commit).
|
|
||||||
- No new tests required (docs-only deliverable; the grill is a
|
|
||||||
review artifact, not a code change).
|
|
||||||
- Requirement coverage: not applicable (phase 0, status `grill`; no
|
|
||||||
REQ-IDs bound to this deliverable). The grill's binding decisions
|
|
||||||
(G-001..G-012) are advisory and do not modify REQUIREMENTS.md per
|
|
||||||
grill workflow Step 7.
|
|
||||||
|
|
||||||
## Layer 3: Security (STRIDE) — PASS
|
|
||||||
|
|
||||||
| Threat | Risk | Disposition |
|
|
||||||
|--------|------|-------------|
|
|
||||||
| Spoofing | N/A (docs-only; no auth surface) | Accept (none) |
|
|
||||||
| Tampering | Grill report is git-tracked; tampering = git history rewrite (out of scope) | Accept (low) |
|
|
||||||
| Repudiation | Commit `ac11c01` signed by author; `---ci---` block records status + decisions | Accept (low) |
|
|
||||||
| Info Disclosure | No credentials, keys, tokens, or PII in the report (grep scan clean) | Accept (low) |
|
|
||||||
| Denial of Service | N/A (docs file; no runtime surface) | Accept (none) |
|
|
||||||
| Elevation of Privilege | N/A (docs-only; no privilege surface) | Accept (none) |
|
|
||||||
|
|
||||||
All threats low-or-none; auto-accepted per
|
|
||||||
`config.json security.auto_accept_low_severity=true`.
|
|
||||||
|
|
||||||
## Layer 4: Quality (multi-persona) — PASS
|
|
||||||
|
|
||||||
| Persona | Finding | Verdict |
|
|
||||||
|---------|---------|---------|
|
|
||||||
| Correctness | 12 binding decisions traceable to evidence (commit/file/req-id); 2 escalations correctly unresolved | PASS |
|
|
||||||
| Testing | Docs-only; 513 fast tests pass (no regression) | PASS |
|
|
||||||
| Security | No credential leakage; no sensitive data in report | PASS |
|
|
||||||
| Performance | N/A (docs file; no runtime cost) | PASS |
|
|
||||||
| Maintainability | Report follows grill workflow Step 5 format exactly; appendable for future runs | PASS |
|
|
||||||
| Adversarial | Escalations (G-005, G-008) are surfaced, not silently skipped; visible via `ciagent audit` | PASS |
|
|
||||||
|
|
||||||
**0 P0, 0 P1, 0 P2.**
|
|
||||||
|
|
||||||
## Verdict (grill deliverable)
|
|
||||||
|
|
||||||
**VERIFY PASS** — all 4 layers pass. The grill deliverable is a
|
|
||||||
well-formed docs-only artifact. 513 fast tests pass (no regression).
|
|
||||||
No credential leakage. 12 binding decisions recorded; 2 escalations
|
|
||||||
(G-005 risks, G-008 budget) correctly surfaced for human resolution.
|
|
||||||
The grill does not modify PROJECT.md, ROADMAP.md, or REQUIREMENTS.md
|
|
||||||
(per grill workflow Step 7).
|
|
||||||
@@ -8,7 +8,7 @@
|
|||||||
],
|
],
|
||||||
"active_project": "acdl",
|
"active_project": "acdl",
|
||||||
"active_projects": ["acdl"],
|
"active_projects": ["acdl"],
|
||||||
"active_milestone": "v1.19",
|
"active_milestone": "v1.25",
|
||||||
"autonomy": {
|
"autonomy": {
|
||||||
"level": "full",
|
"level": "full",
|
||||||
"escalation_hooks": ["deploy", "delete_data", "merge_to_main"],
|
"escalation_hooks": ["deploy", "delete_data", "merge_to_main"],
|
||||||
@@ -209,5 +209,9 @@
|
|||||||
"enabled": true,
|
"enabled": true,
|
||||||
"persist": true
|
"persist": true
|
||||||
},
|
},
|
||||||
"strategic_direction_file": ".ciagent/NORTH_STAR.md"
|
"strategic_direction_file": ".ciagent/NORTH_STAR.md",
|
||||||
|
"policy": {
|
||||||
|
"engine": "kyverno-json",
|
||||||
|
"policy_root": "adapters/kyverno-json/policies"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,24 @@
|
|||||||
|
=== 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% /
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
{"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}}
|
||||||
+18
-1
@@ -1,4 +1,4 @@
|
|||||||
# ACDL CI Pipeline — Gitea Actions (dev environment)
|
# Nova CI Pipeline (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)
|
||||||
@@ -63,6 +63,23 @@ jobs:
|
|||||||
- name: Install test dependencies
|
- name: Install test dependencies
|
||||||
run: pip install -r requirements-test.txt
|
run: pip install -r requirements-test.txt
|
||||||
|
|
||||||
|
- name: Install kyverno-json (kj) for policy-engine tests
|
||||||
|
run: |
|
||||||
|
# v1.25: kyverno-json is the primary policy engine. Tests that
|
||||||
|
# require kj skip when absent, so this is best-effort (the suite
|
||||||
|
# passes with or without kj). Install is cached via the Go
|
||||||
|
# module cache (~/.cache/go-build + ~/go/pkg/mod).
|
||||||
|
if command -v go >/dev/null 2>&1; then
|
||||||
|
go install github.com/kyverno/kyverno-json/cmd/kj@latest && \
|
||||||
|
echo "$(go env GOPATH)/bin" >> "$GITHUB_PATH" || \
|
||||||
|
echo "kj install failed; policy-engine tests will skip"
|
||||||
|
else
|
||||||
|
sudo apt-get update && sudo apt-get install -y golang-go && \
|
||||||
|
go install github.com/kyverno/kyverno-json/cmd/kj@latest && \
|
||||||
|
echo "$(go env GOPATH)/bin" >> "$GITHUB_PATH" || \
|
||||||
|
echo "kj install failed; policy-engine tests will skip"
|
||||||
|
fi
|
||||||
|
|
||||||
- name: Run pytest
|
- name: Run pytest
|
||||||
run: python3 -m pytest tests/ -v --tb=short
|
run: python3 -m pytest tests/ -v --tb=short
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
# ACDL Reusable Deploy Workflow — Gitea Actions (dev environment)
|
# Nova Reusable Deploy Workflow (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: acdl/.gitea/workflows/deploy.yml@v1.9 (Gitea)
|
# uses: nova/.github/workflows/deploy.yml@v1.19
|
||||||
# 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. Gitea pending
|
# Override (where OIDC is unavailable, e.g. pending
|
||||||
# go-gitea/gitea#36988): set NOVA_AWS_ACCESS_KEY_ID + NOVA_AWS_SECRET_ACCESS_KEY
|
# upstream forge OIDC support): 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.
|
||||||
@@ -110,6 +110,8 @@ jobs:
|
|||||||
|
|
||||||
- name: Run the platform pipeline
|
- name: Run the platform pipeline
|
||||||
working-directory: ${{ github.workspace }}
|
working-directory: ${{ github.workspace }}
|
||||||
|
env:
|
||||||
|
NOVA_CONSUMER_REPO: ${{ github.repository }}
|
||||||
run: |
|
run: |
|
||||||
MODE_FLAG=""
|
MODE_FLAG=""
|
||||||
case "${{ inputs.mode }}" in
|
case "${{ inputs.mode }}" in
|
||||||
@@ -155,7 +157,7 @@ jobs:
|
|||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: nova-terraform
|
name: nova-terraform
|
||||||
path: /tmp/acdl_platform_run_v18/tf/*.tf
|
path: /tmp/nova_platform_run/tf/*.tf
|
||||||
if-no-files-found: warn
|
if-no-files-found: warn
|
||||||
|
|
||||||
- name: Upload platform log
|
- name: Upload platform log
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
# ACDL Modules Lifecycle Pipeline — Gitea Actions (dev environment)
|
# Nova Modules Lifecycle Pipeline (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 .gitea/workflows/ and .github/workflows/).
|
# in .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,
|
||||||
|
|||||||
@@ -0,0 +1,43 @@
|
|||||||
|
# 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
|
||||||
+10
-15
@@ -1,35 +1,30 @@
|
|||||||
# GitHub Workflows — Nova Platform CI/CD Catalog
|
# GitHub Workflows — Nova Platform CI/CD Catalog
|
||||||
|
|
||||||
This directory contains the 7 GitHub Actions workflows for the Nova
|
This directory contains the GitHub Actions workflows for the Nova
|
||||||
platform. 3 are byte-identical Gitea mirrors (generated from
|
platform. 3 are generated from `workflows-src/<name>`; 4 are GitHub-only.
|
||||||
`workflows-src/` by `scripts/sync_workflows.py`, P8/REQ-172); 4 are
|
|
||||||
GitHub-only (Gitea act_runner feature gaps).
|
|
||||||
|
|
||||||
## Shared workflows (byte-identical Gitea + GitHub)
|
## Shared workflows (generated from source)
|
||||||
|
|
||||||
These 3 are generated from `workflows-src/<name>` by
|
These 3 are generated from `workflows-src/<name>`. Run `python3 scripts/sync_workflows.py --check` to verify
|
||||||
`scripts/sync_workflows.py`; the `.gitea/workflows/<name>` mirror is kept
|
|
||||||
byte-identical. Run `python3 scripts/sync_workflows.py --check` to verify
|
|
||||||
no drift.
|
no drift.
|
||||||
|
|
||||||
| Workflow | Trigger | Inputs | Required Secrets | Purpose |
|
| Workflow | Trigger | Inputs | Required Secrets | Purpose |
|
||||||
|----------|---------|--------|------------------|---------|
|
|----------|---------|--------|------------------|---------|
|
||||||
| `ci.yml` | `pull_request: [main]` | — | — | Lint + test + check-only (runs on every PR) |
|
| `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: acdl/.github/workflows/deploy.yml@v1.15`) |
|
| `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) |
|
| `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 (no Gitea mirror)
|
## GitHub-only workflows
|
||||||
|
|
||||||
These 4 have no Gitea counterpart (Gitea act_runner lacks the features
|
These 4 have no counterpart (the dev forge lacks the features
|
||||||
they require — reusable workflows, matrix `needs`, release API). See
|
they require — reusable workflows, matrix `needs`, release API).
|
||||||
`.gitea/workflows/README.md` for the limitation rationale.
|
|
||||||
|
|
||||||
| Workflow | Trigger | Inputs | Required Secrets | Purpose |
|
| Workflow | Trigger | Inputs | Required Secrets | Purpose |
|
||||||
|----------|---------|--------|------------------|---------|
|
|----------|---------|--------|------------------|---------|
|
||||||
| `platform-test.yml` | `pull_request: [main]` | — | — | Lint + unit + integration + schema-validation (replaces `ci.yml` for PRs) |
|
| `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) |
|
| `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) |
|
| `patterns-plan.yml` | `pull_request: [main]` | — | `NOVA_AWS_*` | Plan-only for all L2 modules (matrix) |
|
||||||
| `release.yml` | `push: [main]` | — | `NOVA_GITEA_TOKEN` (for Gitea release API) | Semver tag + MAJOR.MINOR/MAJOR floating-tag maintenance + release creation on merge to main |
|
| `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`)
|
## Reusable deploy workflow (`deploy.yml`)
|
||||||
|
|
||||||
@@ -38,7 +33,7 @@ Consumer repos invoke the deploy workflow via a versioned tag:
|
|||||||
```yaml
|
```yaml
|
||||||
jobs:
|
jobs:
|
||||||
deploy:
|
deploy:
|
||||||
uses: acdl/.github/workflows/deploy.yml@v1.15
|
uses: nova/.github/workflows/deploy.yml@v1.19
|
||||||
with:
|
with:
|
||||||
contract: .nova/contract.yml
|
contract: .nova/contract.yml
|
||||||
environment: dev
|
environment: dev
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
# ACDL CI Pipeline — Gitea Actions (dev environment)
|
# Nova CI Pipeline (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)
|
||||||
@@ -63,6 +63,21 @@ jobs:
|
|||||||
- name: Install test dependencies
|
- name: Install test dependencies
|
||||||
run: pip install -r requirements-test.txt
|
run: pip install -r requirements-test.txt
|
||||||
|
|
||||||
|
- name: Install kyverno-json (kj) for policy-engine tests
|
||||||
|
uses: actions/setup-go@v5
|
||||||
|
with:
|
||||||
|
go-version: "1.22"
|
||||||
|
cache: false
|
||||||
|
|
||||||
|
- name: Install kj binary
|
||||||
|
run: |
|
||||||
|
# v1.25: kyverno-json is the primary policy engine. Tests that
|
||||||
|
# require kj skip when absent, so this is best-effort (the suite
|
||||||
|
# passes with or without kj).
|
||||||
|
go install github.com/kyverno/kyverno-json/cmd/kj@latest && \
|
||||||
|
echo "$(go env GOPATH)/bin" >> "$GITHUB_PATH" || \
|
||||||
|
echo "kj install failed; policy-engine tests will skip"
|
||||||
|
|
||||||
- name: Run pytest
|
- name: Run pytest
|
||||||
run: python3 -m pytest tests/ -v --tb=short
|
run: python3 -m pytest tests/ -v --tb=short
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
# ACDL Reusable Deploy Workflow — Gitea Actions (dev environment)
|
# Nova Reusable Deploy Workflow (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: acdl/.gitea/workflows/deploy.yml@v1.9 (Gitea)
|
# uses: nova/.github/workflows/deploy.yml@v1.19
|
||||||
# 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. Gitea pending
|
# Override (where OIDC is unavailable, e.g. pending
|
||||||
# go-gitea/gitea#36988): set NOVA_AWS_ACCESS_KEY_ID + NOVA_AWS_SECRET_ACCESS_KEY
|
# upstream forge OIDC support): 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.
|
||||||
@@ -110,6 +110,8 @@ jobs:
|
|||||||
|
|
||||||
- name: Run the platform pipeline
|
- name: Run the platform pipeline
|
||||||
working-directory: ${{ github.workspace }}
|
working-directory: ${{ github.workspace }}
|
||||||
|
env:
|
||||||
|
NOVA_CONSUMER_REPO: ${{ github.repository }}
|
||||||
run: |
|
run: |
|
||||||
MODE_FLAG=""
|
MODE_FLAG=""
|
||||||
case "${{ inputs.mode }}" in
|
case "${{ inputs.mode }}" in
|
||||||
@@ -155,7 +157,7 @@ jobs:
|
|||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: nova-terraform
|
name: nova-terraform
|
||||||
path: /tmp/acdl_platform_run_v18/tf/*.tf
|
path: /tmp/nova_platform_run/tf/*.tf
|
||||||
if-no-files-found: warn
|
if-no-files-found: warn
|
||||||
|
|
||||||
- name: Upload platform log
|
- name: Upload platform log
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
# ACDL Modules Lifecycle Pipeline — Gitea Actions (dev environment)
|
# Nova Modules Lifecycle Pipeline (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 .gitea/workflows/ and .github/workflows/).
|
# in .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,
|
||||||
|
|||||||
@@ -0,0 +1,43 @@
|
|||||||
|
# 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
|
||||||
@@ -41,3 +41,4 @@ metrics/lifecycle/
|
|||||||
*.crt
|
*.crt
|
||||||
*.jks
|
*.jks
|
||||||
*.keystore.coverage
|
*.keystore.coverage
|
||||||
|
.coverage
|
||||||
|
|||||||
@@ -219,23 +219,9 @@ bash scripts/run_ci.sh --quiet # suppress per-stage banners
|
|||||||
|
|
||||||
### Reusable deploy workflow
|
### Reusable deploy workflow
|
||||||
|
|
||||||
The deployment pipeline is defined by a **central deployment pipeline
|
Consumer repos invoke the deploy pipeline via `.github/workflows/deploy.yml`
|
||||||
contract** (`pipelines/contract.yml`, validated against
|
(a reusable GitHub Actions workflow, versioned tag `nova/.github/workflows/deploy.yml@v1.19`).
|
||||||
`schemas/deploy-pipeline.schema.json`) and exposed to consumer repos as a
|
See the [Consumer guide](docs/consumer-guide.md) for the end-to-end happy path.
|
||||||
**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)
|
||||||
|
|
||||||
@@ -310,12 +296,6 @@ 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).
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
"""Nova kyverno-json adapter package (v1.25, REQ-294).
|
||||||
|
|
||||||
|
The directory name ``kyverno-json`` has a hyphen, so it is not a valid
|
||||||
|
Python package name and cannot be imported via ``import
|
||||||
|
adapters.kyverno-json``. The ``PolicyEngineRegistry`` loads the engine
|
||||||
|
by file path (``importlib.util.spec_from_file_location``). This
|
||||||
|
``__init__`` is a convenience for direct-script use and for ``pip
|
||||||
|
install -e .`` style discovery if the package is ever renamed.
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
def _load_engine():
|
||||||
|
import importlib.util
|
||||||
|
import os
|
||||||
|
engine_path = os.path.join(os.path.dirname(os.path.abspath(__file__)),
|
||||||
|
"kyverno_json_engine.py")
|
||||||
|
spec = importlib.util.spec_from_file_location("kyverno_json_engine", engine_path)
|
||||||
|
if spec is None or spec.loader is None:
|
||||||
|
raise ImportError(f"could not load {engine_path}")
|
||||||
|
mod = importlib.util.module_from_spec(spec)
|
||||||
|
spec.loader.exec_module(mod)
|
||||||
|
return mod.KyvernoJsonEngine
|
||||||
|
|
||||||
|
|
||||||
|
KyvernoJsonEngine = _load_engine()
|
||||||
|
|
||||||
|
__all__ = ["KyvernoJsonEngine"]
|
||||||
@@ -0,0 +1,269 @@
|
|||||||
|
"""Nova KyvernoJsonEngine (REQ-293, v1.25).
|
||||||
|
|
||||||
|
Implements the ``PolicyEngine`` protocol (``core/policy_engine.py``)
|
||||||
|
by shelling to the ``kj`` CLI (``kyverno-json``). Translates native
|
||||||
|
kyverno-json scan output to Nova ``PolicyCheckResult`` dicts
|
||||||
|
(``schemas/policy_check_result.schema.json``).
|
||||||
|
|
||||||
|
Engine enum reuse (D-116): records carry ``engine: "kyverno"`` (no new
|
||||||
|
enum value). The ``ruleId`` is prefixed ``KJ_<policy_name>`` to
|
||||||
|
distinguish from the K8s Kyverno adapter's ``KYVERNO_`` prefix.
|
||||||
|
|
||||||
|
Severity (RESEARCH §2.6, G-Q10a): kyverno-json does not natively assign
|
||||||
|
severities. Each Nova policy declares its severity via a
|
||||||
|
``metadata.annotations["nova.cloudinit.dev/severity"]`` field. The
|
||||||
|
engine reads this annotation from the loaded policy YAML (not from the
|
||||||
|
scan result — the result doesn't carry it) and applies it to every
|
||||||
|
result that policy produces. Default when absent: ``"info"``.
|
||||||
|
|
||||||
|
Graceful degradation (D-120): ``is_configured()`` returns ``False`` when
|
||||||
|
``which kj`` is absent → ``evaluate()`` returns a single SKIPPED PCR
|
||||||
|
(``ruleId: KJ_ENGINE_NOT_CONFIGURED``). The platform functions without
|
||||||
|
the binary.
|
||||||
|
|
||||||
|
Defensive parsing: any kyverno-json output that doesn't match the
|
||||||
|
expected shape produces an ``error`` PCR, never an exception. The
|
||||||
|
engine is read-only against a local policy dir + a temp payload file.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import datetime
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
import shutil
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
|
import tempfile
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import Any, Union
|
||||||
|
|
||||||
|
import yaml
|
||||||
|
|
||||||
|
|
||||||
|
Payload = Union[dict, list, str]
|
||||||
|
|
||||||
|
SEVERITY_DEFAULT = "info"
|
||||||
|
SEVERITY_ANNOTATION = "nova.cloudinit.dev/severity"
|
||||||
|
|
||||||
|
RESULT_MAP = {
|
||||||
|
"pass": "pass",
|
||||||
|
"fail": "fail",
|
||||||
|
"error": "error",
|
||||||
|
"skip": "skipped",
|
||||||
|
"skipped": "skipped",
|
||||||
|
"warn": "skipped",
|
||||||
|
"warning": "skipped",
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def _iso8601_now() -> str:
|
||||||
|
return datetime.datetime.now(datetime.timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ")
|
||||||
|
|
||||||
|
|
||||||
|
def _which_kj() -> str | None:
|
||||||
|
"""Return the path to ``kj`` if on PATH, else ``None``."""
|
||||||
|
return shutil.which("kj")
|
||||||
|
|
||||||
|
|
||||||
|
def _load_policy_severities(policy_dir: Path) -> dict[str, str]:
|
||||||
|
"""Load each ``.json``/``.yaml``/``.yml`` policy in ``policy_dir``
|
||||||
|
(non-recursive) and return ``{policy_name: severity}``.
|
||||||
|
|
||||||
|
kyverno-json policies are Kubernetes-style ``ValidatingPolicy``
|
||||||
|
resources. The severity is read from
|
||||||
|
``metadata.annotations["nova.cloudinit.dev/severity"]``. Policies
|
||||||
|
in subdirectories (e.g. ``contract/``, ``stack-ir/``) are loaded
|
||||||
|
when the caller passes that subdirectory as ``policy_dir``.
|
||||||
|
"""
|
||||||
|
severities: dict[str, str] = {}
|
||||||
|
if not policy_dir.is_dir():
|
||||||
|
return severities
|
||||||
|
for entry in sorted(os.listdir(policy_dir)):
|
||||||
|
if entry.startswith("_") or entry.startswith("."):
|
||||||
|
continue
|
||||||
|
full = policy_dir / entry
|
||||||
|
if not full.is_file():
|
||||||
|
continue
|
||||||
|
if entry.endswith((".json", ".yaml", ".yml")):
|
||||||
|
try:
|
||||||
|
with open(full, "r", encoding="utf-8") as fh:
|
||||||
|
doc = yaml.safe_load(fh)
|
||||||
|
if not isinstance(doc, dict):
|
||||||
|
continue
|
||||||
|
name = doc.get("metadata", {}).get("name") or entry.rsplit(".", 1)[0]
|
||||||
|
ann = doc.get("metadata", {}).get("annotations", {}) or {}
|
||||||
|
sev = ann.get(SEVERITY_ANNOTATION, SEVERITY_DEFAULT)
|
||||||
|
severities[name] = str(sev).lower()
|
||||||
|
except Exception:
|
||||||
|
continue
|
||||||
|
return severities
|
||||||
|
|
||||||
|
|
||||||
|
def _to_pcr(entry: dict, contract_id: str, severity: str) -> dict:
|
||||||
|
"""Translate a kyverno-json scan result entry to a PCR dict."""
|
||||||
|
policy_name = entry.get("policy", "") or "UNKNOWN"
|
||||||
|
rule_name = entry.get("rule", "") or ""
|
||||||
|
rule_id = f"KJ_{policy_name}"
|
||||||
|
if rule_name:
|
||||||
|
rule_id = f"{rule_id}/{rule_name}"
|
||||||
|
result_raw = entry.get("result", "skip")
|
||||||
|
result = RESULT_MAP.get(str(result_raw).lower(), "error")
|
||||||
|
message = entry.get("message", "") or ""
|
||||||
|
resource = entry.get("resource", "")
|
||||||
|
if not resource and entry.get("name"):
|
||||||
|
kind = entry.get("kind", "")
|
||||||
|
ns = entry.get("namespace", "")
|
||||||
|
resource = f"{kind}/{ns}/{entry.get('name')}" if kind else entry.get("name", "")
|
||||||
|
return {
|
||||||
|
"contractId": contract_id,
|
||||||
|
"evaluatedAt": _iso8601_now(),
|
||||||
|
"engine": "kyverno",
|
||||||
|
"ruleId": rule_id,
|
||||||
|
"severity": severity,
|
||||||
|
"result": result,
|
||||||
|
"message": message,
|
||||||
|
"evidence": {
|
||||||
|
"resource": resource,
|
||||||
|
"policy": policy_name,
|
||||||
|
"rule": rule_name,
|
||||||
|
"namespace": entry.get("namespace", ""),
|
||||||
|
"kind": entry.get("kind", ""),
|
||||||
|
"name": entry.get("name", ""),
|
||||||
|
},
|
||||||
|
"resourceRef": resource,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def _skipped_not_configured(contract_id: str) -> dict:
|
||||||
|
return {
|
||||||
|
"contractId": contract_id,
|
||||||
|
"evaluatedAt": _iso8601_now(),
|
||||||
|
"engine": "kyverno",
|
||||||
|
"ruleId": "KJ_ENGINE_NOT_CONFIGURED",
|
||||||
|
"severity": "info",
|
||||||
|
"result": "skipped",
|
||||||
|
"message": (
|
||||||
|
"kyverno-json engine not configured — `which kj` returned no path. "
|
||||||
|
"Install via scripts/install-kyverno-json.sh. The platform proceeds "
|
||||||
|
"with a neutral SKIPPED policy input (is_configured() guard, D-120)."
|
||||||
|
),
|
||||||
|
"evidence": {},
|
||||||
|
"resourceRef": "",
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def _error_pcr(contract_id: str, message: str) -> dict:
|
||||||
|
return {
|
||||||
|
"contractId": contract_id,
|
||||||
|
"evaluatedAt": _iso8601_now(),
|
||||||
|
"engine": "kyverno",
|
||||||
|
"ruleId": "KJ_ENGINE_ERROR",
|
||||||
|
"severity": "info",
|
||||||
|
"result": "error",
|
||||||
|
"message": message,
|
||||||
|
"evidence": {},
|
||||||
|
"resourceRef": "",
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class KyvernoJsonEngine:
|
||||||
|
"""``PolicyEngine`` impl that shells to the ``kj`` CLI."""
|
||||||
|
|
||||||
|
name = "kyverno-json"
|
||||||
|
|
||||||
|
def is_configured(self) -> bool:
|
||||||
|
return _which_kj() is not None
|
||||||
|
|
||||||
|
def evaluate(self, payload: Payload, policy_dir: Path,
|
||||||
|
contract_id: str) -> list[dict]:
|
||||||
|
if not self.is_configured():
|
||||||
|
return [_skipped_not_configured(contract_id)]
|
||||||
|
kj = _which_kj()
|
||||||
|
policy_dir = Path(policy_dir)
|
||||||
|
if not policy_dir.is_dir():
|
||||||
|
return [_error_pcr(
|
||||||
|
contract_id,
|
||||||
|
f"kyverno-json policy dir not found: {policy_dir}",
|
||||||
|
)]
|
||||||
|
severities = _load_policy_severities(policy_dir)
|
||||||
|
# Write payload to temp file (kj scan --payload expects a file path).
|
||||||
|
payload_tmp = tempfile.NamedTemporaryFile(
|
||||||
|
mode="w", suffix=".json", delete=False, encoding="utf-8"
|
||||||
|
)
|
||||||
|
try:
|
||||||
|
json.dump(payload, payload_tmp)
|
||||||
|
payload_tmp.flush()
|
||||||
|
payload_tmp.close()
|
||||||
|
cmd = [
|
||||||
|
kj, "scan",
|
||||||
|
"--policy", str(policy_dir),
|
||||||
|
"--payload", payload_tmp.name,
|
||||||
|
"--output", "json",
|
||||||
|
]
|
||||||
|
try:
|
||||||
|
proc = subprocess.run(
|
||||||
|
cmd, capture_output=True, text=True, timeout=60,
|
||||||
|
)
|
||||||
|
except subprocess.TimeoutExpired:
|
||||||
|
return [_error_pcr(contract_id, "kyverno-json scan timed out (60s)")]
|
||||||
|
if proc.returncode not in (0, 1):
|
||||||
|
return [_error_pcr(
|
||||||
|
contract_id,
|
||||||
|
f"kyverno-json scan exited {proc.returncode}: {proc.stderr[:200]}",
|
||||||
|
)]
|
||||||
|
try:
|
||||||
|
out = json.loads(proc.stdout) if proc.stdout.strip() else {}
|
||||||
|
except json.JSONDecodeError as e:
|
||||||
|
return [_error_pcr(
|
||||||
|
contract_id,
|
||||||
|
f"kyverno-json output not JSON: {e}",
|
||||||
|
)]
|
||||||
|
return self._translate(out, contract_id, severities)
|
||||||
|
finally:
|
||||||
|
try:
|
||||||
|
os.unlink(payload_tmp.name)
|
||||||
|
except OSError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
def _translate(self, out: dict, contract_id: str,
|
||||||
|
severities: dict[str, str]) -> list[dict]:
|
||||||
|
results = out.get("results", []) if isinstance(out, dict) else []
|
||||||
|
if not isinstance(results, list):
|
||||||
|
results = []
|
||||||
|
pcrs: list[dict] = []
|
||||||
|
for entry in results:
|
||||||
|
if not isinstance(entry, dict):
|
||||||
|
continue
|
||||||
|
policy_name = entry.get("policy", "") or "UNKNOWN"
|
||||||
|
severity = severities.get(policy_name, SEVERITY_DEFAULT)
|
||||||
|
pcrs.append(_to_pcr(entry, contract_id, severity))
|
||||||
|
if not pcrs:
|
||||||
|
# No results — kyverno-json produced nothing (no match, or
|
||||||
|
# all policies passed with no result entries). Emit a
|
||||||
|
# single pass PCR so the confidence signal's policy input
|
||||||
|
# is non-empty (a non-empty list of passes → score 1.0).
|
||||||
|
pcrs.append({
|
||||||
|
"contractId": contract_id,
|
||||||
|
"evaluatedAt": _iso8601_now(),
|
||||||
|
"engine": "kyverno",
|
||||||
|
"ruleId": "KJ_NO_RESULTS",
|
||||||
|
"severity": "info",
|
||||||
|
"result": "pass",
|
||||||
|
"message": "kyverno-json scan produced no result entries (all policies passed or no match).",
|
||||||
|
"evidence": {},
|
||||||
|
"resourceRef": "",
|
||||||
|
})
|
||||||
|
return pcrs
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
if len(sys.argv) < 4:
|
||||||
|
print(
|
||||||
|
"usage: kyverno_json_engine.py <payload.json> <policy_dir> <contract-id>",
|
||||||
|
file=sys.stderr,
|
||||||
|
)
|
||||||
|
sys.exit(2)
|
||||||
|
with open(sys.argv[1], "r", encoding="utf-8") as fh:
|
||||||
|
pl = json.load(fh)
|
||||||
|
engine = KyvernoJsonEngine()
|
||||||
|
out = engine.evaluate(pl, Path(sys.argv[2]), sys.argv[3])
|
||||||
|
print(json.dumps(out, indent=2))
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
{
|
||||||
|
"apiVersion": "json.kyverno.io/v1alpha1",
|
||||||
|
"kind": "ValidatingPolicy",
|
||||||
|
"metadata": {
|
||||||
|
"name": "require-contract-id",
|
||||||
|
"annotations": {
|
||||||
|
"nova.cloudinit.dev/severity": "high",
|
||||||
|
"title.policy.kyverno.io": "Require contract id"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"spec": {
|
||||||
|
"rules": [
|
||||||
|
{
|
||||||
|
"name": "require-id",
|
||||||
|
"validate": {
|
||||||
|
"message": "contract id is required",
|
||||||
|
"assert": {
|
||||||
|
"all": [
|
||||||
|
{
|
||||||
|
"check": {
|
||||||
|
"id": "{{ to_string(@) }}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
{
|
||||||
|
"apiVersion": "json.kyverno.io/v1alpha1",
|
||||||
|
"kind": "ValidatingPolicy",
|
||||||
|
"metadata": {
|
||||||
|
"name": "forbid-unknown-fields",
|
||||||
|
"annotations": {
|
||||||
|
"nova.cloudinit.dev/severity": "low",
|
||||||
|
"title.policy.kyverno.io": "Contract has only schema-allowed fields"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"spec": {
|
||||||
|
"rules": [
|
||||||
|
{
|
||||||
|
"name": "no-unknown-fields",
|
||||||
|
"validate": {
|
||||||
|
"message": "contract may only contain id, name, environment, infrastructure (schema-allowed fields)",
|
||||||
|
"assert": {
|
||||||
|
"all": [
|
||||||
|
{
|
||||||
|
"check": {
|
||||||
|
"(length(keys(@)) == `4`)": true,
|
||||||
|
"keys(@)": "(contains(['id','name','environment','infrastructure'], @))"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
{
|
||||||
|
"apiVersion": "json.kyverno.io/v1alpha1",
|
||||||
|
"kind": "ValidatingPolicy",
|
||||||
|
"metadata": {
|
||||||
|
"name": "require-env-in-enum",
|
||||||
|
"annotations": {
|
||||||
|
"nova.cloudinit.dev/severity": "high",
|
||||||
|
"title.policy.kyverno.io": "Contract environment is one of dev/qa/prod/dr"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"spec": {
|
||||||
|
"rules": [
|
||||||
|
{
|
||||||
|
"name": "env-enum",
|
||||||
|
"validate": {
|
||||||
|
"message": "contract.environment must be one of dev, qa, prod, dr",
|
||||||
|
"assert": {
|
||||||
|
"all": [
|
||||||
|
{
|
||||||
|
"check": {
|
||||||
|
"environment": "(contains(['dev','qa','prod','dr'], @))"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
{
|
||||||
|
"apiVersion": "json.kyverno.io/v1alpha1",
|
||||||
|
"kind": "ValidatingPolicy",
|
||||||
|
"metadata": {
|
||||||
|
"name": "require-id-pattern",
|
||||||
|
"annotations": {
|
||||||
|
"nova.cloudinit.dev/severity": "high",
|
||||||
|
"title.policy.kyverno.io": "Contract id matches operational acronym pattern"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"spec": {
|
||||||
|
"rules": [
|
||||||
|
{
|
||||||
|
"name": "id-pattern",
|
||||||
|
"validate": {
|
||||||
|
"message": "contract.id must match ^[a-z][a-z0-9-]{2,5}$ (3-6 char operational acronym)",
|
||||||
|
"assert": {
|
||||||
|
"all": [
|
||||||
|
{
|
||||||
|
"check": {
|
||||||
|
"id": "(regex_match('^[a-z][a-z0-9-]{2,5}$', @))"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
{
|
||||||
|
"apiVersion": "json.kyverno.io/v1alpha1",
|
||||||
|
"kind": "ValidatingPolicy",
|
||||||
|
"metadata": {
|
||||||
|
"name": "require-infrastructure-min-1",
|
||||||
|
"annotations": {
|
||||||
|
"nova.cloudinit.dev/severity": "medium",
|
||||||
|
"title.policy.kyverno.io": "Contract declares at least one infrastructure entry"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"spec": {
|
||||||
|
"rules": [
|
||||||
|
{
|
||||||
|
"name": "infra-min-1",
|
||||||
|
"validate": {
|
||||||
|
"message": "contract.infrastructure must have at least one module entry",
|
||||||
|
"assert": {
|
||||||
|
"all": [
|
||||||
|
{
|
||||||
|
"check": {
|
||||||
|
"infrastructure": "(length(keys(@)) > `0`)"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
{
|
||||||
|
"apiVersion": "json.kyverno.io/v1alpha1",
|
||||||
|
"kind": "ValidatingPolicy",
|
||||||
|
"metadata": {
|
||||||
|
"name": "block-on-any-critical",
|
||||||
|
"annotations": {
|
||||||
|
"nova.cloudinit.dev/severity": "critical",
|
||||||
|
"title.policy.kyverno.io": "Block on any critical-fail policy result (declarative source of truth)"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"spec": {
|
||||||
|
"rules": [
|
||||||
|
{
|
||||||
|
"name": "no-critical-fail",
|
||||||
|
"validate": {
|
||||||
|
"message": "No PolicyCheckResult in the merged list may have severity: critical + result: fail. The confidence_signal.py hard-override is the defense-in-depth behind this declarative rule (D-119).",
|
||||||
|
"assert": {
|
||||||
|
"all": [
|
||||||
|
{
|
||||||
|
"check": {
|
||||||
|
"~.[]": {
|
||||||
|
"(severity == 'critical' && result == 'fail')": false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
{
|
||||||
|
"apiVersion": "json.kyverno.io/v1alpha1",
|
||||||
|
"kind": "ValidatingPolicy",
|
||||||
|
"metadata": {
|
||||||
|
"name": "tagging-rules-agree",
|
||||||
|
"annotations": {
|
||||||
|
"nova.cloudinit.dev/severity": "medium",
|
||||||
|
"title.policy.kyverno.io": "Checkov NOVA_TAG_NAMING and kj KJ_REQUIRE_TAGGING_STANDARD agree per resource"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"spec": {
|
||||||
|
"rules": [
|
||||||
|
{
|
||||||
|
"name": "no-tagging-divergence",
|
||||||
|
"validate": {
|
||||||
|
"message": "For every resource, the Checkov NOVA_TAG_NAMING result and the kyverno-json KJ_REQUIRE_TAGGING_STANDARD result must agree. Divergence emits an error PCR (D-118, defense-in-depth against rule drift).",
|
||||||
|
"assert": {
|
||||||
|
"all": [
|
||||||
|
{
|
||||||
|
"check": {
|
||||||
|
"~.[?(ruleId == 'NOVA_TAG_NAMING')]": {
|
||||||
|
"result->ckv_result": {},
|
||||||
|
"($ckv_result == 'fail')": false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"check": {
|
||||||
|
"~.[?(ruleId == 'KJ_REQUIRE_TAGGING_STANDARD')]": {
|
||||||
|
"result->kj_result": {},
|
||||||
|
"($kj_result == 'fail')": false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
{
|
||||||
|
"apiVersion": "json.kyverno.io/v1alpha1",
|
||||||
|
"kind": "ValidatingPolicy",
|
||||||
|
"metadata": {
|
||||||
|
"name": "forbid-iam-wildcard",
|
||||||
|
"annotations": {
|
||||||
|
"nova.cloudinit.dev/severity": "high",
|
||||||
|
"title.policy.kyverno.io": "No IAM wildcard Actions or Resources"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"spec": {
|
||||||
|
"rules": [
|
||||||
|
{
|
||||||
|
"name": "no-wildcard-action",
|
||||||
|
"validate": {
|
||||||
|
"message": "IAM policy Action must not be '*' (ports CKV_AWS_1/40)",
|
||||||
|
"assert": {
|
||||||
|
"all": [
|
||||||
|
{
|
||||||
|
"check": {
|
||||||
|
"planned_values.root_module.~.resources": {
|
||||||
|
"(type == 'aws_iam_policy' && contains(values.policy_document.Statement[].Action, '*'))": false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "no-wildcard-resource",
|
||||||
|
"validate": {
|
||||||
|
"message": "IAM policy Resource must not be '*' (ports CKV_AWS_1/40)",
|
||||||
|
"assert": {
|
||||||
|
"all": [
|
||||||
|
{
|
||||||
|
"check": {
|
||||||
|
"planned_values.root_module.~.resources": {
|
||||||
|
"(type == 'aws_iam_policy' && contains(values.policy_document.Statement[].Resource, '*'))": false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
{
|
||||||
|
"apiVersion": "json.kyverno.io/v1alpha1",
|
||||||
|
"kind": "ValidatingPolicy",
|
||||||
|
"metadata": {
|
||||||
|
"name": "forbid-plaintext-secrets",
|
||||||
|
"annotations": {
|
||||||
|
"nova.cloudinit.dev/severity": "high",
|
||||||
|
"title.policy.kyverno.io": "No plaintext secrets in the terraform plan"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"spec": {
|
||||||
|
"rules": [
|
||||||
|
{
|
||||||
|
"name": "no-plaintext-db-password",
|
||||||
|
"validate": {
|
||||||
|
"message": "aws_db_instance.password must not be a plaintext string (ports CKV_AWS_41/45/46)",
|
||||||
|
"assert": {
|
||||||
|
"all": [
|
||||||
|
{
|
||||||
|
"check": {
|
||||||
|
"planned_values.root_module.~.resources": {
|
||||||
|
"(type == 'aws_db_instance' && contains(keys(values), 'password') && !contains(['${...}', ''], values.password))": false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
{
|
||||||
|
"apiVersion": "json.kyverno.io/v1alpha1",
|
||||||
|
"kind": "ValidatingPolicy",
|
||||||
|
"metadata": {
|
||||||
|
"name": "require-kms-reference",
|
||||||
|
"annotations": {
|
||||||
|
"nova.cloudinit.dev/severity": "medium",
|
||||||
|
"title.policy.kyverno.io": "KMS keys referenced by alias, not inline key material"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"spec": {
|
||||||
|
"rules": [
|
||||||
|
{
|
||||||
|
"name": "kms-by-alias",
|
||||||
|
"validate": {
|
||||||
|
"message": "aws_kms_key resources should reference a customer-managed key alias, not inline key material (ports CKV_AWS_7/33)",
|
||||||
|
"assert": {
|
||||||
|
"all": [
|
||||||
|
{
|
||||||
|
"check": {
|
||||||
|
"planned_values.root_module.~.resources": {
|
||||||
|
"(type == 'aws_kms_key' && !contains(keys(values), 'key_id') && !contains(keys(values), 'kms_key_id'))": false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
{
|
||||||
|
"apiVersion": "json.kyverno.io/v1alpha1",
|
||||||
|
"kind": "ValidatingPolicy",
|
||||||
|
"metadata": {
|
||||||
|
"name": "forbid-public-ingress",
|
||||||
|
"annotations": {
|
||||||
|
"nova.cloudinit.dev/severity": "high",
|
||||||
|
"title.policy.kyverno.io": "No resource has public ingress enabled"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"spec": {
|
||||||
|
"rules": [
|
||||||
|
{
|
||||||
|
"name": "no-public-ingress",
|
||||||
|
"identifier": "id",
|
||||||
|
"validate": {
|
||||||
|
"message": "public_ingress: true is not allowed on any resource (v1.0 demo rule, now declarative)",
|
||||||
|
"assert": {
|
||||||
|
"all": [
|
||||||
|
{
|
||||||
|
"check": {
|
||||||
|
"~.resources": {
|
||||||
|
"(inputs.public_ingress || `false`)": false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,57 @@
|
|||||||
|
{
|
||||||
|
"apiVersion": "json.kyverno.io/v1alpha1",
|
||||||
|
"kind": "ValidatingPolicy",
|
||||||
|
"metadata": {
|
||||||
|
"name": "require-encryption-by-default",
|
||||||
|
"annotations": {
|
||||||
|
"nova.cloudinit.dev/severity": "high",
|
||||||
|
"title.policy.kyverno.io": "S3 buckets and EBS volumes carry encryption config"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"spec": {
|
||||||
|
"rules": [
|
||||||
|
{
|
||||||
|
"name": "s3-encryption",
|
||||||
|
"identifier": "id",
|
||||||
|
"match": {
|
||||||
|
"any": [
|
||||||
|
{"type": "aws:s3:bucket"}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"validate": {
|
||||||
|
"message": "S3 buckets must declare encryption config (inputs.bucket_encryption or inputs.kms_key_id)",
|
||||||
|
"assert": {
|
||||||
|
"all": [
|
||||||
|
{
|
||||||
|
"check": {
|
||||||
|
"(contains(keys(inputs), 'bucket_encryption') || contains(keys(inputs), 'kms_key_id'))": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "ebs-encryption",
|
||||||
|
"identifier": "id",
|
||||||
|
"match": {
|
||||||
|
"any": [
|
||||||
|
{"type": "aws:ebs:volume"}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"validate": {
|
||||||
|
"message": "EBS volumes must declare encryption (inputs.encrypted or inputs.kms_key_id)",
|
||||||
|
"assert": {
|
||||||
|
"all": [
|
||||||
|
{
|
||||||
|
"check": {
|
||||||
|
"(contains(keys(inputs), 'encrypted') || contains(keys(inputs), 'kms_key_id'))": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
{
|
||||||
|
"apiVersion": "json.kyverno.io/v1alpha1",
|
||||||
|
"kind": "ValidatingPolicy",
|
||||||
|
"metadata": {
|
||||||
|
"name": "require-tagging-standard",
|
||||||
|
"annotations": {
|
||||||
|
"nova.cloudinit.dev/severity": "medium",
|
||||||
|
"title.policy.kyverno.io": "All resources carry required Nova tags"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"spec": {
|
||||||
|
"rules": [
|
||||||
|
{
|
||||||
|
"name": "require-nova-tags",
|
||||||
|
"identifier": "id",
|
||||||
|
"validate": {
|
||||||
|
"message": "Every taggable resource must carry nova:owner, nova:contract, nova:environment, nova:cost-center tags",
|
||||||
|
"assert": {
|
||||||
|
"all": [
|
||||||
|
{
|
||||||
|
"check": {
|
||||||
|
"~.resources": {
|
||||||
|
"(contains(keys(tags || `[]`), 'nova:owner'))": true,
|
||||||
|
"(contains(keys(tags || `[]`), 'nova:contract'))": true,
|
||||||
|
"(contains(keys(tags || `[]`), 'nova:environment'))": true,
|
||||||
|
"(contains(keys(tags || `[]`), 'nova:cost-center'))": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -115,6 +115,9 @@ def adapt(stack_instance, out_dir):
|
|||||||
environment = stack.get("environment", "dev")
|
environment = stack.get("environment", "dev")
|
||||||
account_id = env.get_env("AWS_ACCOUNT_ID", "581513795199")
|
account_id = env.get_env("AWS_ACCOUNT_ID", "581513795199")
|
||||||
state_bucket = f"nova-tfstate-{account_id}-us-east-1"
|
state_bucket = f"nova-tfstate-{account_id}-us-east-1"
|
||||||
|
# State key is env-scoped (v1.24 REQ-287): the {environment} segment lets
|
||||||
|
# the env-transition detect-and-destroy step target the PRIOR env's state
|
||||||
|
# without affecting the new env. No orphan path on environment promotion.
|
||||||
terraform_tf = (
|
terraform_tf = (
|
||||||
'terraform {\n'
|
'terraform {\n'
|
||||||
' required_version = ">= 1.9, < 1.10"\n'
|
' required_version = ">= 1.9, < 1.10"\n'
|
||||||
|
|||||||
@@ -186,8 +186,37 @@ 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__":
|
||||||
if len(sys.argv) != 3:
|
import argparse
|
||||||
print("usage: wiz_adapter.py <wiz_issues.json> <contract-id>", file=sys.stderr)
|
parser = argparse.ArgumentParser(description="Wiz adapter (REQ-250: plan-mode supported)")
|
||||||
sys.exit(2)
|
parser.add_argument("wiz_json", nargs="?", help="wiz_issues.json (legacy positional mode)")
|
||||||
print(json.dumps(adapt(sys.argv[1], sys.argv[2]), indent=2))
|
parser.add_argument("contract_id_pos", nargs="?", help="contract-id (legacy positional mode)")
|
||||||
|
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>")
|
||||||
@@ -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 Gitea Actions scheduled workflow)
|
- **Async worker + DLQ:** a Lambda (or a forge 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` (Gitea/GitHub username of the QA approver; populated on
|
- `approver_qa` (CI 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 `gitea.actor` / `github.actor`. v1.9's
|
to the prod-dispatch CI 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-
|
||||||
|
|||||||
@@ -488,6 +488,25 @@ def resolve(contract_path, repo_root=None, environment_override=None):
|
|||||||
# Validate contract against schema
|
# Validate contract against schema
|
||||||
jsonschema.validate(contract, contract_schema)
|
jsonschema.validate(contract, contract_schema)
|
||||||
|
|
||||||
|
# v1.25 (REQ-296): pre-resolve policy evaluation — run the active
|
||||||
|
# PolicyEngine over the contract dict with the contract/ policy
|
||||||
|
# dir BEFORE resolving. Failures feed the `policyResults` on the
|
||||||
|
# stack instance (the confidence signal's `policy` input). The
|
||||||
|
# resolver does NOT exit on policy failure — the confidence signal
|
||||||
|
# decides the gate (consistent with the existing --soft-fail
|
||||||
|
# Checkov pattern).
|
||||||
|
contract_pcrs: list = []
|
||||||
|
try:
|
||||||
|
from core.policy_engine import get_engine, get_policy_root
|
||||||
|
_engine = get_engine()
|
||||||
|
_policy_root = get_policy_root()
|
||||||
|
contract_pcrs = _engine.evaluate(
|
||||||
|
contract, _policy_root / "contract", contract.get("id", "unknown")
|
||||||
|
)
|
||||||
|
except Exception:
|
||||||
|
# Policy evaluation must never break the resolver.
|
||||||
|
contract_pcrs = []
|
||||||
|
|
||||||
# Interpolation (D-081): expand ${env.<field>} + ${contract.<field>}
|
# Interpolation (D-081): expand ${env.<field>} + ${contract.<field>}
|
||||||
# tokens AFTER schema validation (the schema sees raw tokens, which are
|
# tokens AFTER schema validation (the schema sees raw tokens, which are
|
||||||
# valid strings) and BEFORE IR resolution (the resolver sees concrete
|
# valid strings) and BEFORE IR resolution (the resolver sees concrete
|
||||||
@@ -590,6 +609,12 @@ def resolve(contract_path, repo_root=None, environment_override=None):
|
|||||||
"data_sources": all_data_sources,
|
"data_sources": all_data_sources,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# v1.25 (REQ-296): attach the pre-resolve contract-policy PCRs to
|
||||||
|
# the stack instance. The post-resolve stack-IR PCRs are appended
|
||||||
|
# after stack-schema validation (below).
|
||||||
|
if contract_pcrs:
|
||||||
|
stack_instance["policyResults"] = list(contract_pcrs)
|
||||||
|
|
||||||
# Add the human-readable title
|
# Add the human-readable title
|
||||||
if contract.get("name"):
|
if contract.get("name"):
|
||||||
stack_instance["stack"]["title"] = contract["name"]
|
stack_instance["stack"]["title"] = contract["name"]
|
||||||
@@ -606,6 +631,28 @@ def resolve(contract_path, repo_root=None, environment_override=None):
|
|||||||
stack_schema = _load_schema(os.path.join(repo_root, "schemas", "stack.schema.json"))
|
stack_schema = _load_schema(os.path.join(repo_root, "schemas", "stack.schema.json"))
|
||||||
jsonschema.validate(stack_instance, stack_schema)
|
jsonschema.validate(stack_instance, stack_schema)
|
||||||
|
|
||||||
|
# v1.25 (REQ-298): post-resolve policy evaluation — run the active
|
||||||
|
# PolicyEngine over the resolved Stack IR with the stack-ir/ policy
|
||||||
|
# dir. The resulting PCRs are appended to the contract-policy PCRs
|
||||||
|
# on the stack instance (additive — the resolver's return value
|
||||||
|
# shape and exceptions are unchanged). The confidence signal
|
||||||
|
# consumes the merged list as its `policy` input.
|
||||||
|
try:
|
||||||
|
from core.policy_engine import get_engine, get_policy_root
|
||||||
|
engine = get_engine()
|
||||||
|
policy_root = get_policy_root()
|
||||||
|
stack_ir_pcrs = engine.evaluate(
|
||||||
|
stack_instance, policy_root / "stack-ir", contract.get("id", "unknown")
|
||||||
|
)
|
||||||
|
stack_instance.setdefault("policyResults", []).extend(stack_ir_pcrs)
|
||||||
|
except Exception:
|
||||||
|
# Policy evaluation must never break the resolver — the
|
||||||
|
# confidence signal decides the gate. A failure here means the
|
||||||
|
# engine is misconfigured; the contract PCRs (if any) are still
|
||||||
|
# present, and the confidence signal proceeds with whatever
|
||||||
|
# `policy` input it receives (possibly empty → 0.5 neutral).
|
||||||
|
pass
|
||||||
|
|
||||||
return stack_instance
|
return stack_instance
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,159 @@
|
|||||||
|
"""Nova Environment Transition — detect prior env + record applied env.
|
||||||
|
|
||||||
|
When a consumer edits the `environment:` field on a stable contract `id`
|
||||||
|
(Shape A promotion), the platform must destroy the prior environment's
|
||||||
|
resources before building the new environment. This module provides the
|
||||||
|
DynamoDB query logic to detect the prior environment and record the
|
||||||
|
applied environment after a successful apply.
|
||||||
|
|
||||||
|
Source of truth: the `nova-contracts` DynamoDB table (PK `consumerRepo`,
|
||||||
|
SK `contractId#submittedAt`), written by `core/lambda/contract_ingestor.py`.
|
||||||
|
|
||||||
|
detect_prior_env() queries the table for the last-applied environment for
|
||||||
|
a given consumerRepo + contractId. If it differs from the new env, the
|
||||||
|
prior env name is returned (so the pipeline can destroy it). If no record
|
||||||
|
exists (first deploy or Shape B per-env caller), returns None.
|
||||||
|
|
||||||
|
record_applied_env() writes a `#LAST_APPLIED` record after a successful
|
||||||
|
apply, so the next run's detect step has a source of truth.
|
||||||
|
|
||||||
|
Failures to reach DynamoDB (local/CI mode without the table) log a warning
|
||||||
|
and return None (conservative — no false-positive destroys). This is the
|
||||||
|
no-orphan-path guarantee: if we can't confirm a prior env, we don't
|
||||||
|
destroy, but we also don't silently proceed in a way that orphans — the
|
||||||
|
record step ensures future runs have the data.
|
||||||
|
|
||||||
|
CLI:
|
||||||
|
python3 core/env_transition.py detect --contract-id <id> --consumer-repo <repo> --new-env <env>
|
||||||
|
python3 core/env_transition.py record --contract-id <id> --consumer-repo <repo> --env <env>
|
||||||
|
"""
|
||||||
|
|
||||||
|
import datetime
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
try:
|
||||||
|
import boto3
|
||||||
|
except ImportError:
|
||||||
|
boto3 = None
|
||||||
|
|
||||||
|
TABLE_NAME = os.environ.get("CONTRACTS_TABLE", "nova-contracts")
|
||||||
|
REGION = os.environ.get("AWS_DEFAULT_REGION", "us-east-1")
|
||||||
|
LAST_APPLIED_SUFFIX = "#LAST_APPLIED"
|
||||||
|
|
||||||
|
|
||||||
|
def _get_table():
|
||||||
|
"""Return the DynamoDB table resource, or raise if boto3 unavailable."""
|
||||||
|
if boto3 is None:
|
||||||
|
raise RuntimeError("boto3 is required for env_transition")
|
||||||
|
session = boto3.Session(region_name=REGION)
|
||||||
|
dyn = session.resource("dynamodb")
|
||||||
|
return dyn.Table(TABLE_NAME)
|
||||||
|
|
||||||
|
|
||||||
|
def detect_prior_env(contract_id: str, consumer_repo: str, new_env: str) -> Optional[str]:
|
||||||
|
"""Query the nova-contracts table for the last-applied env.
|
||||||
|
|
||||||
|
Returns the prior env name if it differs from new_env, else None.
|
||||||
|
Failures to reach DynamoDB log a warning and return None (conservative).
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
table = _get_table()
|
||||||
|
sk_prefix = f"{contract_id}{LAST_APPLIED_SUFFIX}#"
|
||||||
|
resp = table.query(
|
||||||
|
KeyConditionExpression="consumerRepo = :repo AND begins_with(#sk, :prefix)",
|
||||||
|
FilterExpression="#status = :status",
|
||||||
|
ExpressionAttributeNames={
|
||||||
|
"#sk": "contractId#submittedAt",
|
||||||
|
"#status": "status",
|
||||||
|
},
|
||||||
|
ExpressionAttributeValues={
|
||||||
|
":repo": consumer_repo,
|
||||||
|
":prefix": sk_prefix,
|
||||||
|
":status": "applied",
|
||||||
|
},
|
||||||
|
ScanIndexForward=False,
|
||||||
|
Limit=1,
|
||||||
|
)
|
||||||
|
items = resp.get("Items", [])
|
||||||
|
if not items:
|
||||||
|
return None
|
||||||
|
prior_env = items[0].get("environment")
|
||||||
|
if prior_env and prior_env != new_env:
|
||||||
|
return prior_env
|
||||||
|
return None
|
||||||
|
except Exception as exc:
|
||||||
|
sys.stderr.write(
|
||||||
|
f"WARNING: env_transition.detect_prior_env: could not query "
|
||||||
|
f"DynamoDB table {TABLE_NAME} — {type(exc).__name__}: {exc}. "
|
||||||
|
f"Assuming no prior env (conservative). This is expected in "
|
||||||
|
f"local/CI mode without the nova-contracts table.\n"
|
||||||
|
)
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def record_applied_env(contract_id: str, consumer_repo: str, env: str) -> bool:
|
||||||
|
"""Write a LAST_APPLIED record to the nova-contracts table.
|
||||||
|
|
||||||
|
Called after a successful apply. Idempotent (writes a new timestamped
|
||||||
|
record each time; the detect step reads the latest by ScanIndexForward).
|
||||||
|
Returns True on success, False on failure (non-fatal — the pipeline
|
||||||
|
should not halt if the record write fails).
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
table = _get_table()
|
||||||
|
ts = datetime.datetime.now(datetime.timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ")
|
||||||
|
sk = f"{contract_id}{LAST_APPLIED_SUFFIX}#{ts}"
|
||||||
|
table.put_item(
|
||||||
|
Item={
|
||||||
|
"consumerRepo": consumer_repo,
|
||||||
|
"contractId#submittedAt": sk,
|
||||||
|
"contractId": contract_id,
|
||||||
|
"environment": env,
|
||||||
|
"status": "applied",
|
||||||
|
"appliedAt": ts,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
return True
|
||||||
|
except Exception as exc:
|
||||||
|
sys.stderr.write(
|
||||||
|
f"WARNING: env_transition.record_applied_env: could not write to "
|
||||||
|
f"DynamoDB table {TABLE_NAME} — {type(exc).__name__}: {exc}. "
|
||||||
|
f"The apply succeeded but the last-applied env record was not "
|
||||||
|
f"persisted. Future env-transition detection may not work.\n"
|
||||||
|
)
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def main(argv):
|
||||||
|
import argparse
|
||||||
|
|
||||||
|
parser = argparse.ArgumentParser(description="Nova env-transition detect/record")
|
||||||
|
sub = parser.add_subparsers(dest="command", required=True)
|
||||||
|
|
||||||
|
p_detect = sub.add_parser("detect", help="Detect prior env for a contract")
|
||||||
|
p_detect.add_argument("--contract-id", required=True)
|
||||||
|
p_detect.add_argument("--consumer-repo", required=True)
|
||||||
|
p_detect.add_argument("--new-env", required=True)
|
||||||
|
|
||||||
|
p_record = sub.add_parser("record", help="Record the applied env for a contract")
|
||||||
|
p_record.add_argument("--contract-id", required=True)
|
||||||
|
p_record.add_argument("--consumer-repo", required=True)
|
||||||
|
p_record.add_argument("--env", required=True)
|
||||||
|
|
||||||
|
args = parser.parse_args(argv[1:])
|
||||||
|
|
||||||
|
if args.command == "detect":
|
||||||
|
prior = detect_prior_env(args.contract_id, args.consumer_repo, args.new_env)
|
||||||
|
print(json.dumps({"prior_env": prior}))
|
||||||
|
return 0 if prior is None else 0
|
||||||
|
elif args.command == "record":
|
||||||
|
ok = record_applied_env(args.contract_id, args.consumer_repo, args.env)
|
||||||
|
print(json.dumps({"recorded": ok}))
|
||||||
|
return 0 if ok else 1
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
sys.exit(main(sys.argv))
|
||||||
+4
-4
@@ -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 (`gitea.actor` / `github.actor`) to the
|
Records the approver identity (the CI actor (GITHUB_ACTOR or FORGE_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
|
||||||
@@ -29,7 +29,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 (`gitea.actor` / `github.actor`).
|
approver: the approver's username (the CI actor (GITHUB_ACTOR or FORGE_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 +41,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/GITEA_ACTOR unset)")
|
return (False, f"no approver identity for {env} (GITHUB_ACTOR/FORGE_ACTOR unset)")
|
||||||
|
|
||||||
attr = _approver_attr(env)
|
attr = _approver_attr(env)
|
||||||
if not attr:
|
if not attr:
|
||||||
@@ -88,7 +88,7 @@ def attest(contract_id: str, env: str, approver: str,
|
|||||||
|
|
||||||
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("GITEA_ACTOR")
|
return os.environ.get("GITHUB_ACTOR") or os.environ.get("FORGE_ACTOR")
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
|||||||
+15
-15
@@ -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.
|
||||||
|
|
||||||
## Gitea-specific gate mechanics (D-042)
|
## Forge-specific gate mechanics (D-042)
|
||||||
|
|
||||||
Gitea has **no Environments API** and ignores `environment:` blocks
|
The dev forge 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 `gitea.actor` is the QA approver.
|
run's `CI actor` is the QA approver.
|
||||||
- **prod gate:** `workflow_dispatch` with `approve_prod: true`;
|
- **prod gate:** `workflow_dispatch` with `approve_prod: true`;
|
||||||
`gitea.actor` is the SRE approver.
|
`CI 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 = `gitea.actor` of the dispatch run
|
The approver identity of record = `CI actor` of the dispatch run
|
||||||
(D-042). There is no other approval-identity signal in Gitea. The real
|
(D-042). There is no other approval-identity signal in the dev forge. The real
|
||||||
OIDC path (blocked on go-gitea/gitea#36988) does not change this —
|
OIDC path (blocked on upstream forge OIDC support) 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 `github.actor` of the `workflow_dispatch`
|
On GitHub, the equivalent is `CI 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 Gitea + GitHub workflows.
|
byte-identical across forges.
|
||||||
|
|
||||||
## Reviewer routing (ARCHITECTURE.md §10.2)
|
## Reviewer routing (ARCHITECTURE.md §10.2)
|
||||||
|
|
||||||
Gitea CODEOWNERS routes the right reviewer to the right gate:
|
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:** a Gitea `on: schedule` workflow (runs hourly) that
|
**Implementation:** an `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 `gitea.actor` (or `github.actor`)
|
from the `workflow_dispatch` run's `CI 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's `gitea.actor` from the
|
from the outbox and the new SRE approver identity 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 = `gitea.actor` of the `workflow_dispatch`
|
- **D-042** — approver identity = `CI actor` of the `workflow_dispatch`
|
||||||
run; no Environments API in Gitea. On GitHub, `github.actor`.
|
run; no Environments API in the dev forge.
|
||||||
- **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
|
||||||
|
|||||||
@@ -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 Gitea API root (e.g. https://git.cloudinit.dev/api/v1) for Gitea.
|
# to a compatible forge API root (e.g. https://forge.example.com/api/v1).
|
||||||
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():
|
||||||
"""P1-9: Detect whether the API base is GitHub or Gitea.
|
"""Detect whether the API base is GitHub or a compatible forge.
|
||||||
|
|
||||||
Gitea API roots contain '/api/v1'; GitHub's is 'api.github.com'.
|
Compatible forge 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 "gitea"
|
return "generic_forge"
|
||||||
return "github"
|
return "github"
|
||||||
|
|
||||||
|
|
||||||
def _issues_search_url(owner, repo, encoded_query):
|
def _issues_search_url(owner, repo, encoded_query):
|
||||||
"""P1-9: Build the issue search URL based on forge type.
|
"""Build the issue search URL based on forge type.
|
||||||
|
|
||||||
GitHub uses /search/issues?q=...; Gitea uses /repos/{owner}/{repo}/issues?...
|
GitHub uses /search/issues?q=...; compatible forges use /repos/{owner}/{repo}/issues?...
|
||||||
with query params (no /search/issues endpoint).
|
with query params (no /search/issues endpoint).
|
||||||
"""
|
"""
|
||||||
if _forge_type() == "gitea":
|
if _forge_type() == "generic_forge":
|
||||||
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 for both GitHub + Gitea)."""
|
"""URL for creating an issue (same pattern across forges)."""
|
||||||
return f"{GITHUB_API_BASE}/repos/{owner}/{repo}/issues"
|
return f"{GITHUB_API_BASE}/repos/{owner}/{repo}/issues"
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,212 @@
|
|||||||
|
"""Nova Policy Engine Registry (REQ-291, v1.25).
|
||||||
|
|
||||||
|
The swappable policy-engine abstraction. A Python Protocol (PEP 544)
|
||||||
|
defines the engine contract; a registry selects the active engine from
|
||||||
|
``config.json``'s ``policy.engine`` key. This is the **swap boundary**
|
||||||
|
(ARCHITECTURE.md §12.7) — the confidence signal and pipeline never
|
||||||
|
import an engine directly; they go through the registry. A future
|
||||||
|
``OpaEngine`` implements the same protocol without touching the
|
||||||
|
confidence signal, the PCR schema, or the pipeline.
|
||||||
|
|
||||||
|
The protocol is minimal (3 members) by design:
|
||||||
|
|
||||||
|
- ``name`` — the engine's registry key (matches ``config.json.policy.engine``).
|
||||||
|
- ``is_configured()`` — returns False when the engine's binary is absent
|
||||||
|
(the registry's caller must skip gracefully, emitting SKIPPED PCRs).
|
||||||
|
- ``evaluate(payload, policy_dir, contract_id)`` — runs the engine's
|
||||||
|
policies over ``payload`` and returns a ``list[dict]`` where each dict
|
||||||
|
conforms to ``schemas/policy_check_result.schema.json``.
|
||||||
|
|
||||||
|
A ``NullEngine`` is the fallback when the ``policy`` key is absent from
|
||||||
|
``config.json`` (backward compatibility for tests that don't set the
|
||||||
|
key — it emits a single SKIPPED PCR so the confidence signal proceeds
|
||||||
|
with a neutral ``policy`` input).
|
||||||
|
|
||||||
|
Engine enum reuse (D-116): kyverno-json PCR records carry
|
||||||
|
``engine: "kyverno"`` (no new enum value). The ``engine`` field records
|
||||||
|
the policy-engine *family*, not the specific binary. The K8s Kyverno
|
||||||
|
adapter and the kyverno-json engine are distinguished by ``ruleId``
|
||||||
|
prefix (``KYVERNO_`` vs ``KJ_``).
|
||||||
|
"""
|
||||||
|
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import Any, Callable, Protocol, Union, runtime_checkable
|
||||||
|
|
||||||
|
import datetime
|
||||||
|
|
||||||
|
|
||||||
|
def _iso8601_now() -> str:
|
||||||
|
return datetime.datetime.now(datetime.timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ")
|
||||||
|
|
||||||
|
|
||||||
|
Payload = Union[dict, list, str]
|
||||||
|
|
||||||
|
|
||||||
|
@runtime_checkable
|
||||||
|
class PolicyEngine(Protocol):
|
||||||
|
"""The swap boundary for policy engines.
|
||||||
|
|
||||||
|
Implementations: ``KyvernoJsonEngine`` (adapters/kyverno-json/),
|
||||||
|
``NullEngine`` (this module), future ``OpaEngine``.
|
||||||
|
"""
|
||||||
|
|
||||||
|
@property
|
||||||
|
def name(self) -> str: ...
|
||||||
|
|
||||||
|
def is_configured(self) -> bool: ...
|
||||||
|
|
||||||
|
def evaluate(self, payload: Payload, policy_dir: Path,
|
||||||
|
contract_id: str) -> list[dict]: ...
|
||||||
|
|
||||||
|
|
||||||
|
def _skipped_pcr(rule_id: str, message: str, contract_id: str) -> dict:
|
||||||
|
return {
|
||||||
|
"contractId": contract_id,
|
||||||
|
"evaluatedAt": _iso8601_now(),
|
||||||
|
"engine": "kyverno",
|
||||||
|
"ruleId": rule_id,
|
||||||
|
"severity": "info",
|
||||||
|
"result": "skipped",
|
||||||
|
"message": message,
|
||||||
|
"evidence": {},
|
||||||
|
"resourceRef": "",
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class NullEngine:
|
||||||
|
"""Fallback when ``config.json.policy`` is absent.
|
||||||
|
|
||||||
|
Emits a single SKIPPED PCR with ``ruleId: NULL_ENGINE_INACTIVE`` so
|
||||||
|
the confidence signal's ``policy`` input is non-null (the per-input
|
||||||
|
score for a single SKIPPED PCR is 1.0 — skipped counts as pass per
|
||||||
|
``core/confidence_signal.py:84-89``). This keeps existing tests
|
||||||
|
passing when the ``policy`` key is not set.
|
||||||
|
"""
|
||||||
|
|
||||||
|
name = "null"
|
||||||
|
|
||||||
|
def is_configured(self) -> bool:
|
||||||
|
return False
|
||||||
|
|
||||||
|
def evaluate(self, payload: Payload, policy_dir: Path,
|
||||||
|
contract_id: str) -> list[dict]:
|
||||||
|
return [_skipped_pcr(
|
||||||
|
"NULL_ENGINE_INACTIVE",
|
||||||
|
"NullEngine active — the `policy` key is absent from config.json. "
|
||||||
|
"No policy engine is configured; the confidence signal proceeds with "
|
||||||
|
"a neutral SKIPPED policy input.",
|
||||||
|
contract_id,
|
||||||
|
)]
|
||||||
|
|
||||||
|
|
||||||
|
_REGISTRY: dict[str, Callable[[], PolicyEngine]] = {}
|
||||||
|
|
||||||
|
|
||||||
|
def register(name: str, factory: Callable[[], PolicyEngine]) -> None:
|
||||||
|
"""Register an engine factory under ``name``.
|
||||||
|
|
||||||
|
The factory is called lazily by ``get_engine()`` so an engine's
|
||||||
|
binary dependency (e.g. ``kj``) is not required at import time.
|
||||||
|
"""
|
||||||
|
_REGISTRY[name] = factory
|
||||||
|
|
||||||
|
|
||||||
|
def _load_config_policy() -> dict | None:
|
||||||
|
"""Read the ``policy`` object from ``.ciagent/config.json``.
|
||||||
|
|
||||||
|
Returns ``None`` when the file is absent or the ``policy`` key is
|
||||||
|
missing (the caller falls back to ``NullEngine``).
|
||||||
|
"""
|
||||||
|
repo_root = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||||
|
cfg = os.path.join(repo_root, ".ciagent", "config.json")
|
||||||
|
if not os.path.isfile(cfg):
|
||||||
|
return None
|
||||||
|
try:
|
||||||
|
with open(cfg, "r", encoding="utf-8") as fh:
|
||||||
|
data = json.load(fh)
|
||||||
|
except (json.JSONDecodeError, OSError):
|
||||||
|
return None
|
||||||
|
return data.get("policy")
|
||||||
|
|
||||||
|
|
||||||
|
def get_engine() -> PolicyEngine:
|
||||||
|
"""Return the active ``PolicyEngine`` from ``config.json``.
|
||||||
|
|
||||||
|
Reads ``config.json.policy.engine`` (default ``"kyverno-json"``).
|
||||||
|
Falls back to ``NullEngine`` when the ``policy`` key is absent
|
||||||
|
(backward compatibility). Raises ``KeyError`` for an unknown engine
|
||||||
|
name (a typo in config — fail loud, not silent).
|
||||||
|
"""
|
||||||
|
policy_cfg = _load_config_policy()
|
||||||
|
if policy_cfg is None:
|
||||||
|
return NullEngine()
|
||||||
|
engine_name = policy_cfg.get("engine", "kyverno-json")
|
||||||
|
factory = _REGISTRY.get(engine_name)
|
||||||
|
if factory is None:
|
||||||
|
raise KeyError(
|
||||||
|
f"Unknown policy engine '{engine_name}' in config.json. "
|
||||||
|
f"Registered engines: {sorted(_REGISTRY.keys()) or ['(none)']}. "
|
||||||
|
f"Set policy.engine to a registered name or install the engine adapter."
|
||||||
|
)
|
||||||
|
return factory()
|
||||||
|
|
||||||
|
|
||||||
|
def get_policy_root() -> Path:
|
||||||
|
"""Return the configured policy root directory (or a default)."""
|
||||||
|
policy_cfg = _load_config_policy()
|
||||||
|
if policy_cfg is None:
|
||||||
|
return Path("adapters/kyverno-json/policies")
|
||||||
|
root = policy_cfg.get("policy_root", "adapters/kyverno-json/policies")
|
||||||
|
repo_root = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||||
|
if os.path.isabs(root):
|
||||||
|
return Path(root)
|
||||||
|
return Path(repo_root) / root
|
||||||
|
|
||||||
|
|
||||||
|
def _register_builtin(name: str, factory: Callable[[], PolicyEngine]) -> None:
|
||||||
|
register(name, factory)
|
||||||
|
|
||||||
|
|
||||||
|
def _autoload_kyverno_json() -> None:
|
||||||
|
"""Register the kyverno-json engine if its adapter is importable.
|
||||||
|
|
||||||
|
The adapter directory uses a hyphen (``adapters/kyverno-json/``),
|
||||||
|
so a plain ``import`` is not possible. Load the module by file path
|
||||||
|
via ``importlib.util``. Lazy import so ``core/policy_engine.py``
|
||||||
|
does not require ``adapters/kyverno-json/`` at import time (the
|
||||||
|
adapter imports ``yaml``, which may be unavailable in minimal test
|
||||||
|
envs).
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
import importlib.util
|
||||||
|
repo_root = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||||
|
adapter_path = os.path.join(
|
||||||
|
repo_root, "adapters", "kyverno-json", "kyverno_json_engine.py"
|
||||||
|
)
|
||||||
|
if not os.path.isfile(adapter_path):
|
||||||
|
return
|
||||||
|
spec = importlib.util.spec_from_file_location(
|
||||||
|
"kyverno_json_engine", adapter_path
|
||||||
|
)
|
||||||
|
if spec is None or spec.loader is None:
|
||||||
|
return
|
||||||
|
mod = importlib.util.module_from_spec(spec)
|
||||||
|
spec.loader.exec_module(mod)
|
||||||
|
engine_cls = getattr(mod, "KyvernoJsonEngine")
|
||||||
|
_register_builtin("kyverno-json", engine_cls)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
_autoload_kyverno_json()
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
eng = get_engine()
|
||||||
|
print(json.dumps({
|
||||||
|
"engine": eng.name,
|
||||||
|
"is_configured": eng.is_configured(),
|
||||||
|
"policy_root": str(get_policy_root()),
|
||||||
|
}, indent=2))
|
||||||
@@ -593,32 +593,30 @@ def _check_cap_023_metrics_collector() -> Tuple[Status, str]:
|
|||||||
|
|
||||||
|
|
||||||
def _check_cap_024_deck_structure() -> Tuple[Status, str]:
|
def _check_cap_024_deck_structure() -> Tuple[Status, str]:
|
||||||
"""CAP-024: unified deck structure (v1.17).
|
"""CAP-024: unified deck structure (v1.17 + v1.21 refinement).
|
||||||
|
|
||||||
Verifies the unified deck source of truth exists, has 12-20 slides
|
Verifies the unified deck source of truth exists, has 18 main slides
|
||||||
(## Slide N), has the x3 arc (arc preview + recap), and per-slide
|
(## Slide N) + 1 appendix, has the recap+ask closing, and per-slide
|
||||||
benefit callouts.
|
benefit callouts. v1.21 renamed the deck + restructured to a 4-beat arc.
|
||||||
"""
|
"""
|
||||||
import os
|
import os
|
||||||
deck_path = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))),
|
deck_path = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))),
|
||||||
"docs", "presentations", "nova-no-humans-platform.md")
|
"docs", "presentations", "nova-autonomous-cloud-delivery.md")
|
||||||
if not os.path.isfile(deck_path):
|
if not os.path.isfile(deck_path):
|
||||||
return "Skipped", "unified deck not found"
|
return "Skipped", "unified deck not found"
|
||||||
with open(deck_path) as f:
|
with open(deck_path) as f:
|
||||||
content = f.read()
|
content = f.read()
|
||||||
slide_count = content.count("## Slide ")
|
slide_count = content.count("## Slide ")
|
||||||
if slide_count < 12 or slide_count > 20:
|
if slide_count < 18 or slide_count > 19:
|
||||||
return "Broken", f"deck has {slide_count} slides (expected 12-20)"
|
return "Broken", f"deck has {slide_count} main slides (expected 18-19)"
|
||||||
has_arc_preview = "Arc Preview" in content
|
|
||||||
has_recap = "Recap + Ask" in content
|
has_recap = "Recap + Ask" in content
|
||||||
has_benefit = content.count("Benefit:") >= 10
|
has_benefit = content.count("Benefit:") >= 10
|
||||||
if not (has_arc_preview and has_recap and has_benefit):
|
if not (has_recap and has_benefit):
|
||||||
missing = []
|
missing = []
|
||||||
if not has_arc_preview: missing.append("arc preview")
|
|
||||||
if not has_recap: missing.append("recap+ask")
|
if not has_recap: missing.append("recap+ask")
|
||||||
if not has_benefit: missing.append("per-slide benefit callouts")
|
if not has_benefit: missing.append("per-slide benefit callouts")
|
||||||
return "Broken", f"deck missing: {missing}"
|
return "Broken", f"deck missing: {missing}"
|
||||||
return "Verified", f"deck has {slide_count} slides, x3 arc present, per-slide benefits present"
|
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).
|
||||||
|
|||||||
@@ -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 `gitea.actor` / `github.actor`.
|
contractId, compares to the prod-dispatch the CI 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.
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,5 @@
|
|||||||
# Nova Metrics Catalog
|
# Nova Metrics Catalog
|
||||||
|
|
||||||
> v1.17 — Strategic Direction, Leadership Metrics & Unified Story (REQ-195)
|
|
||||||
> Generated: 2026-08-04
|
|
||||||
|
|
||||||
This is the canonical catalog of every executive KPI in Nova's
|
This is the canonical catalog of every executive KPI in Nova's
|
||||||
leadership metrics layer. Each metric carries a **status**:
|
leadership metrics layer. Each metric carries a **status**:
|
||||||
|
|||||||
@@ -1,7 +1,5 @@
|
|||||||
# Nova Deferred Metrics Activation Roadmap
|
# Nova Deferred Metrics Activation Roadmap
|
||||||
|
|
||||||
> v1.17 — Strategic Direction, Leadership Metrics & Unified Story (REQ-210)
|
|
||||||
> Generated: 2026-08-04
|
|
||||||
|
|
||||||
This document lists all 8 deferred metrics + the onboarding-funnel
|
This document lists all 8 deferred metrics + the onboarding-funnel
|
||||||
"granted" half, with their blocking decisions, unblock requirements,
|
"granted" half, with their blocking decisions, unblock requirements,
|
||||||
|
|||||||
@@ -1,7 +1,5 @@
|
|||||||
# Nova Metrics Views — PowerBI Data Dictionary
|
# Nova Metrics Views — PowerBI Data Dictionary
|
||||||
|
|
||||||
> v1.17 — Strategic Direction, Leadership Metrics & Unified Story (REQ-190, REQ-209)
|
|
||||||
> Generated: 2026-08-04
|
|
||||||
|
|
||||||
This document is the column-level data dictionary for the PowerBI export
|
This document is the column-level data dictionary for the PowerBI export
|
||||||
views in `metrics/powerbi/`. Each fact/dimension table and placeholder
|
views in `metrics/powerbi/`. Each fact/dimension table and placeholder
|
||||||
|
|||||||
@@ -1,270 +0,0 @@
|
|||||||
# 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-*`.
|
|
||||||
@@ -1,177 +0,0 @@
|
|||||||
# 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 P2–P4, 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 P2–P4 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 P2–P4 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 P2–P4 — 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 P3–P4 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 P3–P4 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 P2–P4.)*
|
|
||||||
2. **Before P5 — env vars:** rename `ACDL_*` CI secrets / workflow `env:`
|
|
||||||
blocks / local `.env.secrets` to `NOVA_*`. *(Incremental during P2–P4;
|
|
||||||
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.
|
|
||||||
@@ -1,67 +0,0 @@
|
|||||||
# Nova — The No-Humans Infrastructure Platform: Thesis Defensibility Brief
|
|
||||||
|
|
||||||
> v1.17 — Strategic Direction, Leadership Metrics & Unified Story (REQ-213)
|
|
||||||
> Generated: 2026-08-04
|
|
||||||
|
|
||||||
## The thesis
|
|
||||||
|
|
||||||
Nova is the autonomous infrastructure layer that lets product teams
|
|
||||||
ship without engaging an operator, and lets executives trust the AI
|
|
||||||
not because it never fails but because every decision is captured,
|
|
||||||
scored, and accountable.
|
|
||||||
|
|
||||||
**Autonomy in operations; human at stage gates.** The operator is
|
|
||||||
removed from the loop of normal operations. Human attestation remains
|
|
||||||
required at stage gates — QA signs off for production, SRE greenlights
|
|
||||||
based on operational readiness. The absence of an operator is never
|
|
||||||
the absence of a record.
|
|
||||||
|
|
||||||
## Grounded proof (measurable today)
|
|
||||||
|
|
||||||
| Proof | Source | Status |
|
|
||||||
|-------|--------|--------|
|
|
||||||
| 18 capabilities verified, 4 honestly skipped (0 broken) | `REGRESSION_REPORT.json` | grounded |
|
|
||||||
| Decision Ledger captures 100% of AI decisions with outcome backfill | `metrics/decision_ledger.db` | grounded (this milestone) |
|
|
||||||
| Attestation Coverage: 100% of prod/dr promotions attested by a human | `hitl_gates.py` + outbox `approver_*` | grounded |
|
|
||||||
| Confidence-gated policy engine (not an LLM) — 6 weighted inputs, band outcome | `confidence_signal.py` | grounded |
|
|
||||||
| 8-concern attestation matrix with separation-of-duties on prod | `attestation_matrix.py` + `separation_of_duties.py` | grounded |
|
|
||||||
| Pre-apply cost estimates (Infracost, offline) | `infracost_adapter.py` | grounded |
|
|
||||||
| Test suite passes (~656 tests) | `metrics/test-results.xml` | grounded |
|
|
||||||
|
|
||||||
## Deferred proof (measurable when blocking decisions lift)
|
|
||||||
|
|
||||||
| Proof | Blocking Decision | Unblock Requirement |
|
|
||||||
|-------|-------------------|---------------------|
|
|
||||||
| Touchless Resolution Rate ≥99% across production estates | 0 consumers today | Pilot estate activation |
|
|
||||||
| Live infrastructure health (ECS, ALB, RPS) | D-096 | Live AWS re-provisioning |
|
|
||||||
| Onboarding funnel: requested → granted | D-113/D-114/D-119 | Auto-grant implementation |
|
|
||||||
| Drift auto-reversal rate ≥95% | D-096 + no scheduler | Drift detection scheduler |
|
|
||||||
| Predictive vs reactive ratio ≥3:1 | future emitter | ML anomaly-forecasting service |
|
|
||||||
| Tamper-evident ledger checkpoints (S3 Object Lock + JWS) | D-083 | Audit ledger build-out |
|
|
||||||
|
|
||||||
## Anti-claims (what Nova is NOT)
|
|
||||||
|
|
||||||
1. **Nova's "AI" is NOT an LLM planner.** It is a confidence-gated
|
|
||||||
policy engine (confidence_signal + HITL gate). The Decision Ledger
|
|
||||||
captures this real decision path — not a fabricated "AI agent" that
|
|
||||||
doesn't exist yet (D-122). 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
|
|
||||||
operations. Every stage-gate promotion (qa/prod/dr) requires a human
|
|
||||||
attestation recorded with approver identity, separation-of-duties
|
|
||||||
check, and the 8-concern evidence matrix (NORTH_STAR Anti-Goal #3).
|
|
||||||
3. **Nova is NOT for legacy, untagged, or freeform infrastructure.** It
|
|
||||||
requires Terraform-managed, policy-aligned, fully-tagged inputs
|
|
||||||
(NORTH_STAR Anti-Goal #4).
|
|
||||||
4. **Nova does NOT fabricate metrics.** Every metric is grounded (cites
|
|
||||||
a source file), derived (documented formula), or deferred (cites a
|
|
||||||
blocking decision ID). No fabricated numbers in any deck slide or
|
|
||||||
METRICS.md 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 default
|
|
||||||
substrate their AI engineering teams reach for first when an agent needs
|
|
||||||
to deploy.
|
|
||||||
+3
-3
@@ -1,6 +1,6 @@
|
|||||||
# Nova Onboarding — No-Humans Request Path (v1.16, REQ-182..184)
|
# Nova Onboarding — Autonomous Request Path (v1.16, REQ-182..184)
|
||||||
|
|
||||||
The v1.16 milestone implements the **request path** of the no-humans
|
The v1.16 milestone implements the **request path** of the autonomous
|
||||||
onboarding flow (D-113). A consumer can submit an onboarding request
|
onboarding flow (D-113). A consumer can submit an onboarding request
|
||||||
without contacting the platform team; the platform generates an
|
without contacting the platform team; the platform generates an
|
||||||
environment binding + (in a future milestone) provisions the AWS resources.
|
environment binding + (in a future milestone) provisions the AWS resources.
|
||||||
@@ -77,7 +77,7 @@ milestone (D-113).
|
|||||||
only (D-114); live apply is deferred.
|
only (D-114); live apply is deferred.
|
||||||
- **OIDC trust policy** — the onboarding Terraform uses a placeholder
|
- **OIDC trust policy** — the onboarding Terraform uses a placeholder
|
||||||
OIDC provider; real OIDC federation is blocked on
|
OIDC provider; real OIDC federation is blocked on
|
||||||
go-gitea/gitea#36988 (carries forward from v1.1).
|
upstream forge OIDC support (carries forward from v1.1).
|
||||||
|
|
||||||
## See also
|
## See also
|
||||||
|
|
||||||
|
|||||||
@@ -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.13`); patch fixes flow within the tag, breaking changes land
|
(e.g. `@v1.19`); 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.
|
||||||
|
|||||||
+57
-21
@@ -19,7 +19,7 @@ definitions.
|
|||||||
|
|
||||||
```mermaid
|
```mermaid
|
||||||
flowchart LR
|
flowchart LR
|
||||||
A["your repo<br/>(app code + contracts + CI definitions)"] -->|uses: acdl/.github/workflows/deploy.yml@v1.13| B
|
A["your repo<br/>(app code + contracts + CI definitions)"] -->|uses: nova/.github/workflows/deploy.yml@v1.19| B
|
||||||
B["platform runners<br/>(modules + pipelines + adapters + schemas)"] -->|contract -> resolver -> stack -> adapter<br/>-> security checks -> infrastructure plan -> policy checks<br/>-> confidence -> apply -> evidence event| C
|
B["platform runners<br/>(modules + pipelines + adapters + schemas)"] -->|contract -> resolver -> stack -> adapter<br/>-> security checks -> infrastructure plan -> policy checks<br/>-> confidence -> apply -> 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. `acdl/pipelines/contract.yml@v1.13`). Version
|
and MINOR tags** (e.g. `nova/pipelines/contract.yml@v1.19`). 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
|
||||||
`acdl/pipelines/contract.yml`.
|
`nova/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: acdl/.github/workflows/deploy.yml@v1.13`.
|
your repo the right to `uses: nova/.github/workflows/deploy.yml@v1.19`.
|
||||||
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: acdl/.github/workflows/deploy.yml@v1.13
|
uses: nova/.github/workflows/deploy.yml@v1.19
|
||||||
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 |
|
||||||
|-------|------|----------|-------------|
|
|-------|------|----------|-------------|
|
||||||
| `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). |
|
| `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. |
|
||||||
| `module` | string | yes | Module name from the registry — any primitive or module (e.g. `static-assets`, `microservice`, `s3`). See the [module catalog](modules/). |
|
| `name` | string | yes | Full human-readable stack name. Becomes `stack.title`: the display name in PR comments, evidence records, and dashboards. |
|
||||||
| `environment` | string | yes | The platform-managed environment to deploy to (e.g. `dev`). See [Environments](environments/). |
|
| `environment` | string | yes | The platform-managed environment to deploy to (`dev`, `qa`, `prod`, or `dr`). See [Environments](environments/). |
|
||||||
| `inputs` | object | yes | Module-specific inputs (see the module's README). |
|
| `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. |
|
||||||
|
|
||||||
### Module inputs
|
### Module inputs
|
||||||
|
|
||||||
@@ -177,14 +177,15 @@ on:
|
|||||||
branches: [main]
|
branches: [main]
|
||||||
jobs:
|
jobs:
|
||||||
deploy:
|
deploy:
|
||||||
uses: acdl/.github/workflows/deploy.yml@v1.13
|
uses: nova/.github/workflows/deploy.yml@v1.19
|
||||||
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: acdl/.github/workflows/deploy.yml@v1.13`
|
1. The platform runner resolves `uses: nova/.github/workflows/deploy.yml@v1.19`
|
||||||
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 —
|
||||||
@@ -229,7 +230,7 @@ flowchart TD
|
|||||||
S5["policy checks<br/>(adapter -> PolicyCheckResult)"] --> S6
|
S5["policy checks<br/>(adapter -> PolicyCheckResult)"] --> S6
|
||||||
S6["confidence<br/>score + band (dev >= 0.50)"] --> S7
|
S6["confidence<br/>score + band (dev >= 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/>(dev only)"]
|
S8["infrastructure apply<br/>(autonomous in dev;<br/>higher envs apply after HITL)"]
|
||||||
```
|
```
|
||||||
|
|
||||||
1. **validate-contract** — validates your contract YAML against the contract
|
1. **validate-contract** — validates your contract YAML against the contract
|
||||||
@@ -250,9 +251,10 @@ 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** (dev only) — the infrastructure plan is applied,
|
8. **infrastructure apply** (autonomous in dev; higher environments apply
|
||||||
creating the resources in your AWS account. An evidence event for the
|
after HITL attestation) — the infrastructure plan is applied, creating
|
||||||
apply is recorded.
|
the resources in your AWS account. An evidence event for the apply is
|
||||||
|
recorded.
|
||||||
|
|
||||||
## Step 6 — What gets created
|
## Step 6 — What gets created
|
||||||
|
|
||||||
@@ -289,7 +291,14 @@ push your container image to the ECR repo the platform created.
|
|||||||
|
|
||||||
## Step 8 — Promote to qa / prod
|
## Step 8 — Promote to qa / prod
|
||||||
|
|
||||||
Change `environment` in your contract (the infrastructure stays the same):
|
There are **two supported promotion shapes**. Both are valid; pick the one
|
||||||
|
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
|
||||||
@@ -301,10 +310,32 @@ 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
|
||||||
@@ -326,8 +357,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.yaml` | The reference example contract (uses `@v1.13`). |
|
| Sample contract | `contracts/static-assets.yml` | The reference example contract (used with caller workflow `@v1.19`). |
|
||||||
| Sample contract | `contracts/microservice.yaml` | The microservice example contract (uses `@v1.13`). |
|
| Sample contract | `contracts/microservice.yml` | The microservice example contract (used with caller workflow `@v1.19`). |
|
||||||
| 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. |
|
||||||
@@ -353,7 +384,7 @@ destruction:
|
|||||||
use `mode: decommission` with the `changeRequestId` input:
|
use `mode: decommission` with the `changeRequestId` input:
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
uses: acdl/.github/workflows/deploy.yml@v1.13
|
uses: nova/.github/workflows/deploy.yml@v1.19
|
||||||
with:
|
with:
|
||||||
contract: .nova/contract.yml
|
contract: .nova/contract.yml
|
||||||
mode: decommission
|
mode: decommission
|
||||||
@@ -395,6 +426,11 @@ 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
|
||||||
@@ -421,7 +457,7 @@ name: static-assets
|
|||||||
```
|
```
|
||||||
|
|
||||||
**Shape 2 — single contract + `environment` workflow input:** the
|
**Shape 2 — single contract + `environment` workflow input:** the
|
||||||
reusable deploy workflow (`acdl/.github/workflows/deploy.yml@v1.13`)
|
reusable deploy workflow (`nova/.github/workflows/deploy.yml@v1.19`)
|
||||||
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:
|
||||||
@@ -436,7 +472,7 @@ on: workflow_dispatch:
|
|||||||
required: true
|
required: true
|
||||||
jobs:
|
jobs:
|
||||||
deploy-qa:
|
deploy-qa:
|
||||||
uses: acdl/.github/workflows/deploy.yml@v1.13
|
uses: nova/.github/workflows/deploy.yml@v1.19
|
||||||
with:
|
with:
|
||||||
environment: qa
|
environment: qa
|
||||||
contract: .nova/contract.yml
|
contract: .nova/contract.yml
|
||||||
|
|||||||
@@ -78,10 +78,3 @@ 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.
|
|
||||||
@@ -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. `acdl/.github/workflows/deploy.yml@v1.13`).
|
(floating MAJOR + MINOR, e.g. `nova/.github/workflows/deploy.yml@v1.19`).
|
||||||
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
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ tag** in a consumer's CI workflow definition:
|
|||||||
```yaml
|
```yaml
|
||||||
jobs:
|
jobs:
|
||||||
deploy:
|
deploy:
|
||||||
uses: acdl/.github/workflows/deploy.yml@v1.13
|
uses: nova/.github/workflows/deploy.yml@v1.19
|
||||||
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
|
||||||
`acdl/.github/workflows/deploy.yml` — `main` is constantly updated and can
|
`nova/.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
|
||||||
|
|||||||
+189
-263
@@ -2,229 +2,184 @@
|
|||||||
|
|
||||||
Leadership-facing presentation decks for the Nova platform.
|
Leadership-facing presentation decks for the Nova platform.
|
||||||
|
|
||||||
## The 4-step slide creation process
|
## The 3-step slide creation process
|
||||||
|
|
||||||
Every presentation in this folder is produced by the same four-step process.
|
Every presentation in this folder is produced by the same three-step
|
||||||
**Never edit the Marp deck, the PPTX, or the talking points directly** —
|
process. **Never edit the rendered HTML, either PPTX, or the talking
|
||||||
always start from the full markdown source of truth (Step 1), synthesize the
|
points directly** — always start from the Marp deck source of truth
|
||||||
Marp deck (Step 2), export to HTML + PPTX (Step 3), then distill the talking
|
(Step 1), render it (Step 2), then distill the talking points (Step 3).
|
||||||
points (Step 4). This keeps a reviewable, plain-text source of truth for
|
This keeps a reviewable, plain-text source of truth for every deck and a
|
||||||
every deck and a presenter-ready cue sheet for delivery.
|
presenter-ready cue sheet for delivery.
|
||||||
|
|
||||||
```
|
```
|
||||||
Step 1: full markdown Step 2: Marp deck Step 3: HTML + PPTX Step 4: Talking points
|
Step 1: Author the deck Step 2: Render Step 3: Talking points
|
||||||
(source of truth) ──► (lean, 10 slides) ──► (rendered) ──► (presenter cues)
|
(source of truth) ──► (HTML + dual PPTX) ──► (presenter cues)
|
||||||
*.md *-marp.md *.html / *.pptx *-talking-points.md
|
*-marp.md *.html *-talking-points.md
|
||||||
+ speaker notes + embedded PNG diagrams + 3-6 bullets per slide
|
+ ## Slide N — Title + mermaid PNGs + 3-6 bullets per slide
|
||||||
+ mermaid code blocks + Marp frontmatter + key takeaway per slide
|
+ <!-- Speaker notes: --> + MARP PPTX (image-of-slide) + key takeaway per slide
|
||||||
+ maturity badges + indexed by Marp slide #
|
+ <!-- Talking points: --> + python PPTX (structured) + indexed by slide #
|
||||||
+ no speaker notes + content distilled from Step 1
|
+ <div class="benefit"> + base64-inlined HTML + content distilled from
|
||||||
|
+ embedded PNG diagrams (self-contained) the Marp deck
|
||||||
```
|
```
|
||||||
|
|
||||||
### Step 1 — Full markdown (source of truth)
|
### Step 1 — Author the deck (source of truth)
|
||||||
|
|
||||||
**File convention:** `<deck-name>.md` (e.g. `how-the-platform-works.md`).
|
**File convention:** `<deck-name>-marp.md` (e.g.
|
||||||
|
`nova-autonomous-cloud-delivery-marp.md`).
|
||||||
|
|
||||||
Write the complete deck as a standard markdown file. This is the **source of
|
This is the **sole source of truth** — the Marp deck that is both authored
|
||||||
truth** — it contains:
|
and rendered. 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 for fonts, colors, tables, badges.
|
block carrying the S&P palette (`#D6002A` red, `#1B1B1B` black, the
|
||||||
- **No speaker notes.** The Marp deck is what the audience sees; the
|
`section.title` rule). The styling is **inline** — no standalone theme
|
||||||
speaker notes live only in the Step 1 source of truth.
|
CSS is loaded at render time.
|
||||||
- **Mermaid diagrams → PNG images.** Marp does not render mermaid fenced
|
- Every slide as an `## Slide N — Title` (or `## Appendix A1 — Title`) H2
|
||||||
blocks natively. Extract each mermaid block from Step 1 into a `.mmd`
|
section. The H1 title slide precedes slide 1.
|
||||||
source file under `assets/mmd/`, render it to PNG under `assets/png/`,
|
- Tight bullets with leadership-relevant content.
|
||||||
and embed it with ``.
|
- **Speaker notes** as `<!-- Speaker notes: ... -->` HTML comments at the
|
||||||
- **`<!-- _class: title -->` + `<!-- _paginate: false -->`** on title and
|
end of each slide. Marp excludes HTML comments from the rendered slide;
|
||||||
closing slides for the dark-background title style.
|
they are for authors/presenters only.
|
||||||
- **Maturity badges** using inline spans:
|
- **Talking points** as `<!-- Talking points: ... -->` HTML comments (also
|
||||||
`<span class="badge planned">Planned</span>`
|
excluded from rendering — Step 3 mirrors them into a standalone cue
|
||||||
- **Tighter prose** than Step 1 — strip the speaker-note nuance; keep the
|
sheet).
|
||||||
leadership-relevant selling points.
|
- **Benefit callouts** as `<div class="benefit">...</div>` (styled by the
|
||||||
|
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 `` (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.
|
||||||
|
|
||||||
### Step 3 — Render to HTML and PPTX
|
**Why the Marp deck 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 HTML and PPTX are *derived
|
||||||
|
artifacts* — if a fact is wrong, fix it here and re-run Step 2.
|
||||||
|
|
||||||
Both formats are derived from the Marp deck. **HTML is committed to the repo**
|
> **`nova-sp-theme.css` is RETIRED from render.** The standalone theme
|
||||||
(viewable in any browser, self-contained with base64-embedded images). **PPTX
|
> stylesheet under `assets/nova-sp-theme.css` is kept as a **reference
|
||||||
is uploaded to the Gitea release** as a downloadable attachment (binary, not
|
> only** and is **not loaded at render time**. The live styling is the
|
||||||
committed to git).
|
> inline `style:` block in the `-marp.md` frontmatter. Do NOT pass the CSS
|
||||||
|
> via `--theme`; it is not in the render path.
|
||||||
|
|
||||||
#### HTML export (committed to repo)
|
### Step 2 — Render (HTML + dual PPTX)
|
||||||
|
|
||||||
|
`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
|
||||||
CHROME_PATH=/root/.cache/ms-playwright/chromium-1217/chrome-linux64/chrome \
|
bash scripts/render_slides.sh nova-autonomous-cloud-delivery
|
||||||
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 — no `--allow-local-files`
|
Both the HTML and both PPTX files are committed to the repo; the MARP
|
||||||
needed for self-contained output, but it's required when the Marp deck
|
PPTX is also attached to the phase's release via
|
||||||
references local PNG assets. The resulting HTML is a single self-contained
|
`scripts/attach_release_asset.py`.
|
||||||
file that renders the full deck with the S&P Global Energy theme.
|
|
||||||
|
|
||||||
**Re-render the HTML whenever the Marp source changes.** The HTML files are
|
#### Dual-PPTX output
|
||||||
committed artifacts, not generated on-the-fly — they must be re-rendered and
|
|
||||||
re-committed when the Marp deck is updated.
|
|
||||||
|
|
||||||
#### PPTX export (uploaded to Gitea release)
|
| PPTX | File | Render | Purpose |
|
||||||
|
|---|---|---|---|
|
||||||
|
| **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 |
|
||||||
|
|
||||||
```bash
|
### Step 3 — Talking points (presenter cues)
|
||||||
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. As of v1.18 (REQ-228, D-141), PPTX
|
|
||||||
files **are committed to the repo** as first-class binary artifacts (no LFS)
|
|
||||||
and are also attached to the phase's Gitea release via
|
|
||||||
`scripts/attach_release_asset.py`. The render + commit + attach pipeline is
|
|
||||||
automated by `scripts/render_deck.sh`.
|
|
||||||
|
|
||||||
### Step 4 — Talking points (presenter cues)
|
|
||||||
|
|
||||||
**File convention:** `<deck-name>-talking-points.md` (e.g.
|
**File convention:** `<deck-name>-talking-points.md` (e.g.
|
||||||
`how-the-platform-works-talking-points.md`).
|
`nova-autonomous-cloud-delivery-talking-points.md`).
|
||||||
|
|
||||||
Distill the source of truth (Step 1) into presenter-ready cues, indexed by
|
Distill the deck's `<!-- Talking points: -->` HTML comments into
|
||||||
the Marp deck (Step 2) slide structure:
|
presenter-ready cues, indexed by the Marp deck (Step 1) 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 11 main + Appendix TOC + appendix slide structure exactly. The Marp deck
|
deck's 20 main + 1 appendix slide structure exactly.
|
||||||
provides the indexing and context (what the audience sees); the source
|
- **3-6 talking point bullets per slide** — punchy, actionable cues
|
||||||
markdown provides the content (the speaker notes, the detail, the nuance).
|
distilled from the Marp deck's `<!-- Talking points: -->` comments.
|
||||||
- **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 slides
|
- **No content duplication** — the talking points reference the Marp
|
||||||
for visual context and the source markdown for full detail. They don't
|
slides for visual context.
|
||||||
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
|
||||||
├── how-the-platform-works.md ← Step 1: full source of truth
|
├── 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-marp.md ← Step 2: Marp deck (11 main + TOC + 8 appendix = 20)
|
├── nova-autonomous-cloud-delivery.html ← Step 2: rendered HTML (committed, S&P inline style, base64-inlined images)
|
||||||
├── how-the-platform-works.html ← Step 3: rendered HTML (committed)
|
├── nova-autonomous-cloud-delivery.pptx ← Step 2: MARP PPTX (image-of-slide, primary release attachment)
|
||||||
├── how-the-platform-works-talking-points.md ← Step 4: presenter cues (20 sections)
|
├── nova-autonomous-cloud-delivery-python.pptx ← Step 2: python-pptx (structured, editable)
|
||||||
├── the-developer-experience.md ← Step 1: full source of truth
|
├── nova-autonomous-cloud-delivery-talking-points.md ← Step 3: presenter cues (21 sections)
|
||||||
├── 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)
|
||||||
│ ├── platform-works-01-contract-driven.mmd
|
│ └── ... (per-slide .mmd files)
|
||||||
│ ├── platform-works-02-frictions.mmd
|
└── png/ ← rendered mermaid PNGs (committed, S&P-themed, 2x, transparent)
|
||||||
│ ├── 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
|
||||||
|
|
||||||
### Appendix structure
|
### Slide structure
|
||||||
|
|
||||||
Each Marp deck has **11 main slides + an Appendix TOC + appendix slides**. The
|
Each Marp deck has **1 title slide + 20 main slides + 1 appendix slide = 22
|
||||||
main 11 are the presentation; the appendix is for deep dives and Q&A backup.
|
rendered slides** (21 `## ` sections + the H1 title slide). The main 20
|
||||||
The platform-works deck has 8 appendix slides (A1–A8); the developer-experience
|
are the presentation; the appendix is for Q&A backup. (v1.22 split slides
|
||||||
deck has 7 appendix slides (A1–A7). Both include an Appendix TOC slide.
|
3 and 8 to relieve overflow, increasing the main count from 18 to 20.)
|
||||||
|
|
||||||
- **Main slides** (1-11): the story arc, high-impact, minimal text,
|
- **Title slide** (H1): `<!-- _class: title -->` + `<!-- _paginate: false -->`
|
||||||
visual-heavy. These are what the audience sees during the talk.
|
for the dark-background title style (S&P-red top border on black).
|
||||||
- **Appendix slides** (TOC + A1..An): detail-heavy slides moved out of the
|
- **Main slides** (1-20): the story arc — Problem → Solution → Proof →
|
||||||
main 10 to preserve the narrative flow. The appendix starts with a TOC
|
Roadmap + Ask. These are what the audience sees during the talk.
|
||||||
slide listing the contents, followed by detail slides and a glossary.
|
- **Appendix slide** (A1): the Metrics Glossary — detail-heavy reference
|
||||||
- **The Road to the North Star** is a required appendix slide in both decks
|
for Q&A.
|
||||||
— 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.
|
|
||||||
|
|
||||||
### Maturity framing
|
### Honesty framing
|
||||||
|
|
||||||
Every capability claim in a deck is tagged with a `Planned` badge when the item is on the roadmap but not yet implemented:
|
Every capability claim in the deck is grounded, derived, or honestly
|
||||||
|
deferred with its blocking work named in plain language. Internal
|
||||||
| Badge | Meaning |
|
provenance (decision IDs, requirement IDs, internal file paths) is kept
|
||||||
|---|---|
|
out of the audience-facing slide bodies — those live in the `.ciagent/`
|
||||||
| `Planned` | On the roadmap, not yet implemented |
|
files only (and may appear inside `<!-- ... -->` speaker-note comments,
|
||||||
|
which Marp excludes from the rendered slide). When in doubt, check
|
||||||
This is non-negotiable for a leadership audience: never present a roadmap
|
`.ciagent/ROADMAP.md` and the milestone status in `.ciagent/PROJECT.md`.
|
||||||
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
|
||||||
|
|
||||||
@@ -236,105 +191,72 @@ 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.
|
||||||
- **Zero-trust, security, observability, auditability, DX, citizen
|
- **Security, remediation velocity, reliability, lead time, observability,
|
||||||
developer** are the themes — not implementation details.
|
citizen developer** are the themes — not implementation details.
|
||||||
|
- **"Infrastructure operations become visible"** is the recurring theme
|
||||||
|
across the deck.
|
||||||
|
|
||||||
### Diagrams
|
### Diagrams
|
||||||
|
|
||||||
Mermaid diagrams in the Step 1 source use the repo's existing `flowchart`
|
Mermaid diagrams are authored as `assets/mmd/*.mmd` source files and
|
||||||
style (renders on GitHub/Pages). For the Marp deck (Step 2):
|
rendered to PNG under `assets/png/`:
|
||||||
|
|
||||||
1. Extract the mermaid block into `assets/mmd/<deck>-<slide>-<name>.mmd`.
|
1. Author the mermaid block as `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. A 9-node sequential `flowchart TD` renders as a tall thin
|
illegibility.
|
||||||
strip — restructure it as 2-row subgraphs or `flowchart LR`.
|
3. Render with a 2x scale factor and transparent background for crisp
|
||||||
3. Render with a 2x scale factor and transparent background for crisp slides.
|
slides (`scripts/render_slides.sh` does this with the S&P theme JSON).
|
||||||
4. Embed with `` (or `h:320` for tall images).
|
4. Embed with `` (or `h:480 class:tall`
|
||||||
|
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 all mermaid diagrams to PNG
|
### Render the deck (HTML + dual PPTX + inlined images)
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
cd docs/presentations/assets
|
bash scripts/render_slides.sh nova-autonomous-cloud-delivery
|
||||||
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
|
|
||||||
```
|
```
|
||||||
|
|
||||||
The `puppeteer-config.json` passes `--no-sandbox` to the headless browser
|
This renders all mermaid PNGs, the HTML (with base64-inlined images), the
|
||||||
(required when running as root in this environment). The `--configFile
|
MARP PPTX, and the python-pptx PPTX, and stages them for commit. Both
|
||||||
mmd/sp-theme.json` applies the S&P Global Red/Black/White theme (dark
|
HTML and both PPTX files are committed to the repo; the MARP PPTX is also
|
||||||
`#1B1B1B` accent nodes with `#D6002A` red borders, white supporting nodes,
|
attached to the phase's release.
|
||||||
`#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. **Write the full markdown** as `<deck-name>.md` following the
|
1. **Author the Marp deck** as `<deck-name>-marp.md` — frontmatter
|
||||||
`## Slide N — Title` + `> **Speaker notes:**` structure. This is the
|
(`marp: true`, `theme: default`, `paginate: true`, `size: 16x9`, an
|
||||||
source of truth.
|
inline `style:` block with the S&P palette), `## Slide N — Title`
|
||||||
2. **Extract any mermaid diagrams** into `assets/mmd/<deck-name>-<slide>-<name>.mmd`
|
sections, `<!-- Speaker notes: -->` + `<!-- Talking points: -->` HTML
|
||||||
and render them to `assets/png/` (command above).
|
comments, and `<div class="benefit">` callouts. This is the sole source
|
||||||
3. **Synthesize the Marp deck** as `<deck-name>-marp.md` with frontmatter,
|
of truth.
|
||||||
no speaker notes, embedded PNGs, and maturity badges.
|
2. **Author any mermaid diagrams** as `assets/mmd/<deck-name>-<slide>-<name>.mmd`
|
||||||
4. **Render to HTML** with `--allow-local-files` and commit the HTML to
|
(Step 2 renders them to `assets/png/`).
|
||||||
`docs/presentations/<deck-name>.html`.
|
3. **Render** via `bash scripts/render_slides.sh <deck-name>` — this
|
||||||
5. **Render to PPTX** with `--allow-local-files` and upload to the Gitea
|
produces the HTML (base64-inlined), the MARP PPTX, and the python-pptx
|
||||||
release (do not commit PPTX to git).
|
PPTX, and stages all of them (plus the PNGs) for commit.
|
||||||
6. **Distill the talking points** as `<deck-name>-talking-points.md` — one
|
4. **Distill the talking points** as `<deck-name>-talking-points.md` — one
|
||||||
section per Marp slide, 3-6 talking point bullets + key takeaway, content
|
section per Marp slide, 3-6 talking point bullets + key takeaway,
|
||||||
distilled from the source markdown (Step 1), indexed by the Marp deck
|
content distilled from the Marp deck's `<!-- Talking points: -->`
|
||||||
(Step 2) slide structure.
|
comments, indexed by the Marp deck slide structure.
|
||||||
7. **Verify** the PPTX slide count and that media files are embedded:
|
5. **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('<output>.pptx') as z:
|
with zipfile.ZipFile('docs/presentations/<deck-name>.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')
|
||||||
@@ -343,13 +265,17 @@ attachments to the Gitea release.
|
|||||||
|
|
||||||
## Current decks
|
## Current decks
|
||||||
|
|
||||||
| Deck | Source of truth (Step 1) | Marp deck (Step 2) | Rendered HTML + PPTX (Step 3) | Talking points (Step 4) | Slides | Audience |
|
| Deck | Source of truth (Step 1) | Rendered HTML + dual PPTX (Step 2) | Talking points (Step 3) | Slides | Audience |
|
||||||
|---|---|---|---|---|---|---|
|
|---|---|---|---|---|---|
|
||||||
| Nova — The No-Humans Infrastructure Platform | `nova-no-humans-platform.md` | `nova-no-humans-platform-marp.md` | `nova-no-humans-platform.html` + `.pptx` (committed + release-attached) | `nova-no-humans-platform-talking-points.md` | 19 main + 2 appendix (21) | CTO, Head of Cloud, Head of Infra, Head of DevOps |
|
| 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 |
|
||||||
|
|
||||||
> **v1.18 (D-130):** the two legacy decks (How the Platform Works + The
|
> **v1.23:** the slide creation process collapsed from 4 steps to 3 — the
|
||||||
> Developer Experience) were consolidated into a single unified narrative
|
> plain `<deck-name>.md` was deleted; `<deck-name>-marp.md` is now the
|
||||||
> deck with a 5-act arc (Problem → Vision → How → Proof → Roadmap). v1.18
|
> sole source of truth. The standalone `nova-sp-theme.css` was retired
|
||||||
> (REQ-226) adds 3 slides (17 Scope, 18 RACI, 19 Atelier) → 21 total. The
|
> from render (the live styling is the inline `style:` block in the
|
||||||
> S&P Global Energy theme is restored (REQ-214, P1). PPTX is committed to
|
> `-marp.md` frontmatter; the CSS file is retained as a reference only).
|
||||||
> git + attached to the Gitea release (REQ-228, D-141).
|
> 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.
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
%%{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
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
%%{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
|
||||||
@@ -0,0 +1,136 @@
|
|||||||
|
/* 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.
|
After Width: | Height: | Size: 36 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 63 KiB |
@@ -0,0 +1,435 @@
|
|||||||
|
---
|
||||||
|
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.**
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
- **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.**
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
- **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 12–18 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: Q1–Q3 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?" -->
|
||||||
Binary file not shown.
@@ -0,0 +1,146 @@
|
|||||||
|
# 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
Binary file not shown.
@@ -1,386 +0,0 @@
|
|||||||
---
|
|
||||||
marp: true
|
|
||||||
theme: default
|
|
||||||
paginate: true
|
|
||||||
size: 16x9
|
|
||||||
header: 'Nova — The No-Humans Infrastructure Platform'
|
|
||||||
footer: 'Act %{page}/5 — v1.17'
|
|
||||||
style: |
|
|
||||||
section {
|
|
||||||
font-family: "Akkurat Pro", "Helvetica Neue", "Arial", sans-serif;
|
|
||||||
font-size: 22px;
|
|
||||||
color: #1B1B1B;
|
|
||||||
}
|
|
||||||
h1 { color: #D6002A; font-size: 34px; margin-bottom: 0.3em; }
|
|
||||||
h2 { color: #D6002A; font-size: 26px; margin-bottom: 0.2em; }
|
|
||||||
section.title { background: #1B1B1B; color: #fff; border-top: 8px solid #D6002A; }
|
|
||||||
section.title h1 { color: #fff; }
|
|
||||||
table { font-size: 18px; width: 100%; }
|
|
||||||
th { background: #F0F0F0; }
|
|
||||||
blockquote { border-left: 4px solid #D6002A; color: #2E2E2E; font-size: 20px; }
|
|
||||||
img { display: block; margin: 0 auto; max-height: 320px; }
|
|
||||||
.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; }
|
|
||||||
---
|
|
||||||
|
|
||||||
<!-- _class: title -->
|
|
||||||
<!-- _paginate: false -->
|
|
||||||
|
|
||||||
# Nova — The No-Humans Infrastructure Platform
|
|
||||||
|
|
||||||
**Shifting from Operational Overhead to Strategic Value**
|
|
||||||
|
|
||||||
v1.18 — Citizen Developer & Production-Grade Guidance
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Slide 1 — Arc Preview
|
|
||||||
|
|
||||||
**This deck proves Nova is the no-humans infrastructure platform — and shows you the metrics that make the claim defensible.**
|
|
||||||
|
|
||||||
**Today:** 18 capabilities verified, 0 consumer estates in production.
|
|
||||||
|
|
||||||
**The 5-act arc:**
|
|
||||||
1. **Problem** — why the operator is the bottleneck
|
|
||||||
2. **Vision** — Nova's strategic direction (NORTH_STAR)
|
|
||||||
3. **How** — the pipeline, Decision Ledger, attestation gates
|
|
||||||
4. **Proof** — grounded metrics that make the claim defensible
|
|
||||||
5. **Roadmap** — deferred metrics with unblock paths + the ask + scope + RACI
|
|
||||||
|
|
||||||
**Benefit:** you leave knowing which claims are proven today, which are pipeline-ready, and which are deferred with a documented unblock path — no marketing, just grounded evidence.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Slide 2 — The No-Humans Imperative
|
|
||||||
|
|
||||||
**Why the operator is the bottleneck — and why removing them from operations (not accountability) is the imperative.**
|
|
||||||
|
|
||||||
- **The cost of humans-in-the-loop:** L1/L2 ops hours, escalation latency, the trust gap
|
|
||||||
- **The operator is the bottleneck:** provisioning takes days, not minutes
|
|
||||||
- **The attestation model:** autonomy in operations, human at stage gates
|
|
||||||
- Cites `docs/NO_HUMANS_THESIS.md`
|
|
||||||
|
|
||||||
**Benefit:** you now know the problem framing — autonomy in operations, human at stage gates, is the path forward.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Slide 3 — Nova's Vision
|
|
||||||
|
|
||||||
> **Infrastructure operations become invisible. 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.**
|
|
||||||
|
|
||||||
- Autonomy in operations, not in accountability
|
|
||||||
- Cites `docs/NO_HUMANS_THESIS.md`
|
|
||||||
|
|
||||||
**Benefit:** you now know the destination — invisible operations with provable trust, not promised trust.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Slide 4 — Strategic Objectives + Anti-Goals
|
|
||||||
|
|
||||||
**4 Strategic Objectives:**
|
|
||||||
1. **Zero-touch operations** — autonomy as the default, not the demo
|
|
||||||
2. **Provable trust in AI decisions** — Decision Ledger, confidence scoring, circuit breakers
|
|
||||||
3. **Compounding, quantifiable ROI** — each quarter must reduce spend, free hours, avoid downtime
|
|
||||||
4. **Default substrate for agentic consumption** — the platform AI agents reach for first
|
|
||||||
|
|
||||||
**5 Anti-Goals (what Nova is NOT):**
|
|
||||||
1. Not a hyperscaler competitor
|
|
||||||
2. Not a general-purpose AI platform
|
|
||||||
3. Not removing humans from accountability
|
|
||||||
4. Not for legacy, untagged, or freeform infrastructure
|
|
||||||
5. Not sold to operators
|
|
||||||
|
|
||||||
**Benefit:** you now know the scope boundaries — Nova is purpose-built for infrastructure operations, sold to leadership on outcomes.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Slide 5 — 12–18 Month Targets
|
|
||||||
|
|
||||||
**Current-milestone targets (grounded/derived):**
|
|
||||||
|
|
||||||
| Domain | Target | Status |
|
|
||||||
|---|---|---|
|
|
||||||
| MTTR (p95) | < 60s | grounded |
|
|
||||||
| Cloud Spend Reduction | ≥ 25% | partial (CUR deferred D-096) |
|
|
||||||
| L1/L2 Ops Hours Avoided | ≥ 70% | derived (N internal runs) |
|
|
||||||
| Platform ROI | ≥ 250% | derived (formula; N=0 caveat) |
|
|
||||||
| Decision Ledger Coverage | 100% | grounded |
|
|
||||||
| Attestation Coverage | 100% | grounded |
|
|
||||||
|
|
||||||
**Post-Pilot targets (pipeline grounded; 0 consumers today):**
|
|
||||||
|
|
||||||
| Domain | Target | Status |
|
|
||||||
|---|---|---|
|
|
||||||
| Touchless Resolution Rate | ≥ 99% | partial |
|
|
||||||
| Human Escalation Frequency | < 0.1% | partial |
|
|
||||||
| AI Decision Accuracy | ≥ 99.5% | partial |
|
|
||||||
|
|
||||||
**Deferred:** Predictive vs Reactive ≥3:1 <span class="badge planned">Planned</span> · Drift Auto-Reversal ≥95% <span class="badge planned">Planned</span>
|
|
||||||
|
|
||||||
**Benefit:** you now know the destination numbers — and which are measurable today vs deferred honestly.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Slide 6 — The Platform Pipeline
|
|
||||||
|
|
||||||
**How intent becomes verified infrastructure without an operator.**
|
|
||||||
|
|
||||||
Contract → Resolver → Adapter → Terraform Plan → Checkov (Policy) → Confidence Signal → HITL Gate → Apply → Evidence
|
|
||||||
|
|
||||||
- Dev: autonomous (no HITL gate)
|
|
||||||
- qa/prod/dr: attested (human sign-off required)
|
|
||||||
- Grounded in `run_platform.sh` + `contract_resolver.py` + `confidence_signal.py`
|
|
||||||
|
|
||||||
**Benefit:** you now know the path from intent to evidence — and where the human appears (stage gates only).
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Slide 7 — The Decision Ledger
|
|
||||||
|
|
||||||
**Every AI decision captured with confidence, alternatives, and outcome.**
|
|
||||||
|
|
||||||
- `outbox_writer.py` → SQLite append-only hash-chain table
|
|
||||||
- `ai.decision.made`: decision_id=run_id, chosen_action=band, confidence=score, alternatives=perInput, human_override=HITL block
|
|
||||||
- `attestation.recorded`: qa/prod/dr sign-offs
|
|
||||||
- D-121, D-122, D-132. Honors D-083 (no S3 Object Lock/JWS — local hash-chain)
|
|
||||||
|
|
||||||
**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."
|
|
||||||
|
|
||||||
**Benefit:** you now know why 'autonomous' is defensible — every decision is immutable, queryable, and accountable. And you know exactly what 'AI' means here: a confidence-gated policy engine, not a black-box LLM.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Slide 8 — The 8-Concern Attestation Matrix
|
|
||||||
|
|
||||||
**Designed controls that keep humans at stage gates.**
|
|
||||||
|
|
||||||
| Concern | Env | Freshness | Type |
|
|
||||||
|---------|-----|-----------|------|
|
|
||||||
| functional_correctness | qa | 24h | operator-supplied |
|
|
||||||
| performance_baseline | qa | 7d | operator-supplied |
|
|
||||||
| security_posture | qa | 24h | operator-supplied |
|
|
||||||
| operational_readiness | prod | 30d | operator-supplied |
|
|
||||||
| incident_response | prod | 90d | operator-supplied |
|
|
||||||
| capacity_cost | prod | 30d | operator-supplied |
|
|
||||||
| resilience_dr_drill | prod | 180d | operator-supplied |
|
|
||||||
| dr_region_deploy | dr | 180d | operator-supplied |
|
|
||||||
|
|
||||||
- Offline-testable concerns run for real; operator-supplied concerns accept signed evidence
|
|
||||||
- Separation-of-duties on prod
|
|
||||||
- Grounded in `attestation_matrix.py` + `hitl_gates.py`
|
|
||||||
|
|
||||||
**Benefit:** you now know the gate model — autonomy in operations, human in accountability, by design.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Slide 9 — Telemetry Architecture
|
|
||||||
|
|
||||||
**How Nova instruments itself — CloudEvents envelope, cold store, PowerBI export.**
|
|
||||||
|
|
||||||
Platform → CloudEvents 1.0 → `metrics/events.jsonl` + `metrics/decision_ledger.db` + `metrics/runs/` → Collector → `metrics/nova_metrics.db` (SQLite cold store) → `metrics/powerbi/` (CSV/JSON) → PowerBI
|
|
||||||
|
|
||||||
- D-120 (Nova-native), D-125 (hybrid), D-126 (cold-only)
|
|
||||||
- <span class="badge planned">Planned</span>: Hot-path (live ops dashboard) — D-126
|
|
||||||
|
|
||||||
**Benefit:** you now know that every metric in this deck is traceable to a real emitted event — the architecture IS the trust substrate. When a CFO asks 'where does this number come from?', the answer is a file path, not a Slack thread.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Slide 10 — Capability Health + Confidence Distribution
|
|
||||||
|
|
||||||
**Grounded proof: capability health and confidence distribution from real runs.**
|
|
||||||
|
|
||||||
| Status | Count |
|
|
||||||
|--------|-------|
|
|
||||||
| Verified | 18 |
|
|
||||||
| Skipped | 4 |
|
|
||||||
| Broken | 0 |
|
|
||||||
| Decayed | 0 |
|
|
||||||
|
|
||||||
- 4 Skipped = live-AWS caps (CAP-013..016), honestly skipped (D-096 teardown), not a failure
|
|
||||||
- Source: `.ciagent/REGRESSION_REPORT.json`
|
|
||||||
|
|
||||||
**Benefit:** you now know the platform is verified — 18 capabilities pass, 4 are honestly skipped, 0 broken.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Slide 11 — Decision Ledger + Attestation Coverage
|
|
||||||
|
|
||||||
**Trust metrics — both 100%.**
|
|
||||||
|
|
||||||
- **Decision Ledger Coverage:** 100% of platform runs emit `ai.decision.made` with outcome backfill
|
|
||||||
- **Attestation Coverage:** 100% of prod/dr promotions attested by a human
|
|
||||||
- **AI Decision Accuracy:** decisions not followed by apply.failed/incident within 5min
|
|
||||||
- Trust snapshot: `metrics/TRUST_SNAPSHOT.md` with chain-integrity verdict
|
|
||||||
- <span class="badge planned">Planned</span>: Tamper-Evident Ledger Checkpoints (D-083)
|
|
||||||
|
|
||||||
**Benefit:** you now know the trust is provable — not a marketing claim, a queryable record.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Slide 12 — Zero-Touch Efficiency
|
|
||||||
|
|
||||||
**Touchless resolution, human escalation, and MTTR.**
|
|
||||||
|
|
||||||
- **Touchless Resolution Rate:** runs without operational HITL block ÷ total (attestation gates excluded)
|
|
||||||
- **Human Escalation Frequency:** operational HITL blocks only (confidence-driven; attestation sign-offs excluded)
|
|
||||||
- **MTTR (platform-run):** apply.failed → successful retry (D-131)
|
|
||||||
|
|
||||||
**Post-Pilot caveat:** computed on N internal runs today; production-denominator activates when a pilot estate runs.
|
|
||||||
|
|
||||||
**Benefit:** you now know the zero-touch efficiency is measurable — the pipeline works today on internal runs, and the denominator expands to production estates when a pilot activates.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Slide 13 — Cost & ROI
|
|
||||||
|
|
||||||
**Cost estimates and the ROI formula — with honest caveats.**
|
|
||||||
|
|
||||||
- **Cost Estimates via Infracost:** pre-apply, grounded (reads plan JSON, offline)
|
|
||||||
- **ROI formula:** `Platform ROI = (FTE hours saved × blended rate + cloud savings + avoided downtime) ÷ platform op cost`
|
|
||||||
- **N=0 caveat:** "Computed on N internal runs today; production-denominator activates post-pilot. The formula is grounded; the production numbers are not yet."
|
|
||||||
- <span class="badge planned">Planned</span>: Live CUR Reconciliation (D-096)
|
|
||||||
|
|
||||||
**Benefit:** you now know the ROI formula — and you know it's computed on internal runs today, not fabricated production numbers.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Slide 14 — What's Deferred — and Why
|
|
||||||
|
|
||||||
**Honesty about what isn't measured yet.**
|
|
||||||
|
|
||||||
**To be clear:** these deferrals are *measurement infrastructure*, not whether the platform runs without humans. The platform IS autonomous in operations. What's deferred is the *evidence pipeline* for certain metrics — not the autonomy itself.
|
|
||||||
|
|
||||||
| # | Deferred Metric | Blocking Decision |
|
|
||||||
|---|----------------|-------------------|
|
|
||||||
| 1 | Live Infrastructure Health | D-096 |
|
|
||||||
| 2 | Live Outbox Write Rate | D-096 |
|
|
||||||
| 3 | Tamper-Evident Ledger Checkpoints | D-083 |
|
|
||||||
| 4 | Onboarding Funnel (granted) | D-113/D-114/D-119 |
|
|
||||||
| 5 | Drift Auto-Reversal | D-096 + no scheduler |
|
|
||||||
| 6 | Live CUR Reconciliation | D-096 |
|
|
||||||
| 7 | SLA / Unplanned Downtime | D-096 |
|
|
||||||
| 8 | Predictive vs Reactive | future emitter |
|
|
||||||
|
|
||||||
**Benefit:** you now know the boundaries — what Nova measures today, and exactly what blocks the rest. The autonomy is real; the measurement gaps are documented.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Slide 15 — Roadmap to the North Star
|
|
||||||
|
|
||||||
**The path from v1.17's grounded metrics to the 12–18 month targets.**
|
|
||||||
|
|
||||||
- Each deferred metric → blocking decision → unblock requirement → candidate milestone
|
|
||||||
- Hot-path activation (post-D-096, Nova-native only, D-120)
|
|
||||||
- Re-evaluation triggers: D-096 lift, D-083 lift, onboarding-grant lift
|
|
||||||
|
|
||||||
From `docs/METRICS_DEFERRED_ROADMAP.md`.
|
|
||||||
|
|
||||||
**Benefit:** you now know the path — every deferred metric has an unblock requirement and a candidate milestone. Nothing is hand-waved; everything has a plan.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Slide 16 — Recap + Ask
|
|
||||||
|
|
||||||
**The 5-act recap + the business decision.**
|
|
||||||
|
|
||||||
**Recap:**
|
|
||||||
- **Problem:** operator is the bottleneck; autonomy in operations, human at stage gates
|
|
||||||
- **Vision:** invisible operations with provable trust (NORTH_STAR)
|
|
||||||
- **How:** pipeline + Decision Ledger + 8-concern attestation matrix
|
|
||||||
- **Proof:** 18V+4S, 100% ledger coverage, 100% attestation, grounded ROI formula
|
|
||||||
- **Roadmap:** deferred metrics have unblock paths
|
|
||||||
|
|
||||||
**The ask:** "Approve a pilot estate to activate the production-denominator metrics (Touchless Resolution, Human Escalation, AI Decision Accuracy), and approve the tamper-evident ledger build-out (D-083 lift) to move from local hash-chain to S3 Object Lock + JWS. These two decisions move Nova from 'pipeline-ready' to 'production-proven.'"
|
|
||||||
|
|
||||||
**Benefit:** you leave with a clear business decision to make — approve a pilot + the ledger build-out — and the confidence that every claim in this deck is grounded, derived, or honestly deferred.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Slide 17 — Scope: Downstream of PDLC
|
|
||||||
|
|
||||||
**Nova governs infrastructure + delivery. The PDLC (product backlog, code authorship, IDE) is upstream — Nova never penetrates it.**
|
|
||||||
|
|
||||||
- **The PDLC is upstream:** product backlog, code authorship (AI agent / IDE / agentic SDLC), sprint planning, application business logic
|
|
||||||
- **Nova is downstream:** contract ingestion → submission-readiness gate → policy → cloud lifecycle → environment progression → audit + attestation
|
|
||||||
- **Integration is only through the contract boundary:** the citizen developer's AI coding agent, an upstream agentic SDLC, or any dev platform may all produce submissions — the source does not matter as all are subject to the same compliance standards
|
|
||||||
- Nova validates the submission, not the author
|
|
||||||
- Cites `docs/scope.md` + `PROJECT.md` § Scope
|
|
||||||
|
|
||||||
**Benefit:** you now know the scope boundary — Nova is purpose-built for infrastructure operations, not product development; integration is through one validated contract.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Slide 18 — RACI: Who Owns What
|
|
||||||
|
|
||||||
**Three roles, one matrix — the citizen developer owns FRs + UAT, the platform owns NFRs + infra + QA + prod deploy, release management is co-owned.**
|
|
||||||
|
|
||||||
| Work Category | Citizen Dev | Platform | Release Mgmt |
|
|
||||||
|---|---|---|---|
|
|
||||||
| 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) | C | **R/A** | I |
|
|
||||||
| Production deployment to cloud | I | **R/A** | C |
|
|
||||||
| Release attestation (QA + SRE) | **A** | R | **R** |
|
|
||||||
|
|
||||||
- **Compliance-standard equivalence:** FRs + UAT may come from any upstream source (AI agent, agentic SDLC, dev platform) — all pass the same submission-readiness gate
|
|
||||||
- **Release co-ownership:** the platform runs the attestations agentically; the citizen developer oversees and triggers the actual release (human at the stage gate)
|
|
||||||
- Cites `docs/raci.md` + `PROJECT.md` § RACI Matrix
|
|
||||||
|
|
||||||
**Benefit:** you now know exactly what you bring (FRs + UAT), what Nova provides (NFRs + infra + QA + prod deploy), and what you co-own (the release attestation).
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Slide 19 — Production-Grade Guidance via Atelier
|
|
||||||
|
|
||||||
**Nova instructs the citizen developer's AI agent on production-grade engineering — skills + an MCP server with agentic validation beyond deterministic scanners.**
|
|
||||||
|
|
||||||
- **Skills (9):** markdown files under `skills/` keyed to Atelier domain paths (api, security, data, testing, observability, errors, devops, infrastructure-as-code, compliance) — extending the BA.A 5-skill catalog
|
|
||||||
- **MCP server:** `mcp/atelier/server.py` (plugin-registry, stdio) — 4 tools: `lookup_principle`, `list_domains`, `matrix_lookup`, `validate_against_principles`
|
|
||||||
- **Agentic validation:** catches C1 correctness + C2 clarity + C7 observability gaps that Wiz/Checkmarx/Mend cannot — deterministic tools check policy/secrets; the MCP server checks engineering discipline
|
|
||||||
- **Vendored Atelier** (pinned tag v0.3.6): audit reproducibility — a validation result is replayable against the exact principles that produced it
|
|
||||||
- Cites `docs/skills.md` + `mcp/atelier/README.md`
|
|
||||||
|
|
||||||
**Benefit:** you now know the citizen developer is not unguided — Nova provides production-grade engineering principles via skills + an MCP server, so the AI agent's submissions meet the same standards regardless of upstream source.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
<!-- _class: title -->
|
|
||||||
<!-- _paginate: false -->
|
|
||||||
|
|
||||||
## Appendix A1 — Metrics Glossary
|
|
||||||
|
|
||||||
| KPI | Definition | Status |
|
|
||||||
|-----|-----------|--------|
|
|
||||||
| Touchless Resolution Rate | runs without operational HITL block ÷ total | partial (Post-Pilot) |
|
|
||||||
| Human Escalation Frequency | operational HITL blocks ÷ total | partial (Post-Pilot) |
|
|
||||||
| AI 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 (Infracost) | sum(delta_usd where delta < 0) | partial (CUR 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 |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
<!-- _class: title -->
|
|
||||||
<!-- _paginate: false -->
|
|
||||||
|
|
||||||
## Appendix A2 — Operating Model & Cost
|
|
||||||
|
|
||||||
- **Cost figures** from `COST.md`: $0.001883 over 8 days, ~$0.007/month, S3-dominated, zero BAU compute
|
|
||||||
- **Zero-cost steady state:** all resources torn down post-v1.11 (D-096); the platform runs offline
|
|
||||||
- References the pre-mortem (`PRE_MORTEM.md`: v1.10 decay root cause + structural mitigations)
|
|
||||||
|
|
||||||
**Benefit:** you now know the operating cost is negligible — and the structural mitigation that prevents decay.
|
|
||||||
@@ -1,134 +0,0 @@
|
|||||||
# Nova — The No-Humans Infrastructure Platform: Talking Points
|
|
||||||
|
|
||||||
> Step 4 of the 4-step deck process. Presenter cues distilled from the
|
|
||||||
> source of truth (`nova-no-humans-platform.md`). 3-6 bullets per slide
|
|
||||||
> + key takeaway. Indexed by Marp slide #.
|
|
||||||
> v1.17 — REQ-196, REQ-197
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### Slide 1 — Arc Preview
|
|
||||||
- Open with the stake line: "18 capabilities verified, 0 consumer estates in production"
|
|
||||||
- Preview the 5-act arc so the audience knows the structure
|
|
||||||
- Set the honesty frame: "this is an evidence deck, not a hype deck"
|
|
||||||
- **Key takeaway:** you'll leave knowing what's proven, what's pipeline-ready, and what's deferred
|
|
||||||
|
|
||||||
### Slide 2 — The No-Humans Imperative
|
|
||||||
- The operator is the bottleneck: days vs. minutes for provisioning
|
|
||||||
- Key reframing: "no-humans" = no human in normal operations; stage-gate attestation is human by design
|
|
||||||
- Cite the no-humans thesis doc
|
|
||||||
- **Key takeaway:** autonomy in operations, human at stage gates
|
|
||||||
|
|
||||||
### Slide 3 — Nova's Vision
|
|
||||||
- Read the vision statement verbatim — it's precise
|
|
||||||
- Emphasize "provable, not promised" — the difference between marketing and defensible
|
|
||||||
- State the attestation model up front to prevent mishearing
|
|
||||||
- **Key takeaway:** invisible operations with provable trust
|
|
||||||
|
|
||||||
### Slide 4 — Strategic Objectives + Anti-Goals
|
|
||||||
- The 4 objectives are the "what"; the 5 anti-goals are the "what NOT"
|
|
||||||
- Anti-goal #3 (not removing humans from accountability) reinforces slide 3
|
|
||||||
- Anti-goal #5 (not sold to operators) explains why this deck is for leadership
|
|
||||||
- **Key takeaway:** purpose-built for infra ops, sold to leadership on outcomes
|
|
||||||
|
|
||||||
### Slide 5 — 12–18 Month Targets
|
|
||||||
- The three-section split (current / post-pilot / deferred) IS the honesty model
|
|
||||||
- "Partial" means the pipeline works but the denominator is zero (0 consumers)
|
|
||||||
- The Post-Pilot targets are committed; the numbers fill when a pilot runs
|
|
||||||
- **Key takeaway:** which numbers are real today vs. deferred honestly
|
|
||||||
|
|
||||||
### Slide 6 — The Platform Pipeline
|
|
||||||
- Walk the pipeline left-to-right: contract → resolver → adapter → plan → policy → confidence → gate → apply
|
|
||||||
- Key insight: dev is autonomous; qa/prod/dr require attestation
|
|
||||||
- The confidence signal is the "AI" — 6-input weighted score, not an LLM
|
|
||||||
- **Key takeaway:** the path from intent to evidence, with humans at stage gates only
|
|
||||||
|
|
||||||
### Slide 7 — The Decision Ledger
|
|
||||||
- The D-122 honesty sentence is critical: "Nova's AI is the confidence-gated policy engine, not an LLM"
|
|
||||||
- The ledger is the moat: features can be copied, an immutable decision history cannot
|
|
||||||
- Every decision has outcome backfill from apply.completed
|
|
||||||
- **Key takeaway:** autonomous is defensible because every decision is immutable, queryable, accountable
|
|
||||||
|
|
||||||
### Slide 8 — The 8-Concern Attestation Matrix
|
|
||||||
- The matrix is not a rubber stamp — it's structured, freshness-validated, SoD-enforced
|
|
||||||
- Offline-testable concerns run for real; operator-supplied concerns accept signed evidence
|
|
||||||
- SoD on prod: the approver can't be the same person who built it
|
|
||||||
- **Key takeaway:** autonomy in operations, human in accountability, by design
|
|
||||||
|
|
||||||
### Slide 9 — Telemetry Architecture
|
|
||||||
- Deliberately minimal (Nova-native, no Kafka/Prometheus/ClickHouse)
|
|
||||||
- Every number in the Proof act is traceable to a file path
|
|
||||||
- The hot path is deferred (D-126) — cold store is sufficient for batch
|
|
||||||
- **Key takeaway:** the architecture IS the trust substrate — "where does this number come from?" → file path
|
|
||||||
|
|
||||||
### Slide 10 — Capability Health
|
|
||||||
- 18V+4S is the single most important proof point
|
|
||||||
- The 4 Skipped are live-AWS caps — honestly skipped (D-096), not broken
|
|
||||||
- When live AWS is re-provisioned, they reactivate
|
|
||||||
- **Key takeaway:** the platform works, and we're honest about what we can't test
|
|
||||||
|
|
||||||
### Slide 11 — Decision Ledger + Attestation Coverage
|
|
||||||
- Both 100% — no AI decision is ever lost; no prod/dr promotion lands without a human sign-off
|
|
||||||
- The trust snapshot has a chain-integrity verdict (the ledger hasn't been tampered with)
|
|
||||||
- D-083 (S3 Object Lock + JWS) is the next step for the ledger
|
|
||||||
- **Key takeaway:** trust is provable — not a marketing claim, a queryable record
|
|
||||||
|
|
||||||
### Slide 12 — Zero-Touch Efficiency
|
|
||||||
- The Post-Pilot caveat is the honesty model: pipeline works, denominator is zero
|
|
||||||
- This is NOT a fabricated "99% touchless" claim
|
|
||||||
- The numbers fill when a pilot runs
|
|
||||||
- **Key takeaway:** the measurement works; the numbers activate with a pilot
|
|
||||||
|
|
||||||
### Slide 13 — Cost & ROI
|
|
||||||
- The ROI formula is shown inline — not hidden in a footnote
|
|
||||||
- The N=0 caveat is stated explicitly
|
|
||||||
- This is the "no fabrication" constraint in action
|
|
||||||
- **Key takeaway:** the formula is ready; the production denominator activates with a pilot
|
|
||||||
|
|
||||||
### Slide 14 — What's Deferred — and Why
|
|
||||||
- The preempt is critical: deferrals are measurement infrastructure, not autonomy
|
|
||||||
- The platform IS autonomous in operations; what's deferred is the evidence pipeline
|
|
||||||
- Showing this to leadership demonstrates honesty, not weakness
|
|
||||||
- **Key takeaway:** the autonomy is real; the measurement gaps are documented
|
|
||||||
|
|
||||||
### Slide 15 — Roadmap to the North Star
|
|
||||||
- Every deferred metric has a specific unblock requirement and a candidate milestone
|
|
||||||
- The re-evaluation triggers ensure the metrics layer evolves
|
|
||||||
- Nothing is hand-waved; everything has a plan
|
|
||||||
- **Key takeaway:** the path from "honestly deferred" to "here's how we get there"
|
|
||||||
|
|
||||||
### Slide 16 — Recap + Ask
|
|
||||||
- Recap the 5-act arc so the audience leaves with the structure
|
|
||||||
- The ask is a business decision: approve a pilot + the 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
|
|
||||||
|
|
||||||
### Slide 17 — Scope: Downstream of PDLC
|
|
||||||
- Nova governs infra + delivery only; the PDLC (product backlog, code authorship, IDE) is upstream
|
|
||||||
- Integration is only through the validated contract boundary
|
|
||||||
- Any upstream source (AI agent, agentic SDLC, dev platform) may produce submissions — all subject to the same compliance standards
|
|
||||||
- Nova validates the submission, not the author
|
|
||||||
- **Key takeaway:** Nova is purpose-built for infrastructure operations, not product development; the scope boundary is clean
|
|
||||||
|
|
||||||
### Slide 18 — RACI: Who Owns What
|
|
||||||
- Citizen Developer owns FRs + UAT (via any upstream source — AI agent, SDLC, dev platform — all pass the same gate)
|
|
||||||
- Platform owns NFRs + infra + QA + prod deploy
|
|
||||||
- Release Management is co-owned: platform runs attestations agentically, citizen developer oversees + triggers the release (human at stage gate)
|
|
||||||
- The compliance-standard equivalence is the key: the source does not matter; the submission does
|
|
||||||
- **Key takeaway:** you bring FRs + UAT; Nova provides NFRs + infra + QA + prod deploy; the release is co-owned with you at the stage gate
|
|
||||||
|
|
||||||
### Slide 19 — Production-Grade Guidance via Atelier
|
|
||||||
- Nova instructs the citizen developer's AI agent via skills (9 markdown files) + an MCP server (4 tools, plugin-registry, stdio)
|
|
||||||
- The MCP server provides agentic validation beyond deterministic scanners — catches correctness, clarity, observability gaps that Wiz/Checkmarx/Mend cannot
|
|
||||||
- Atelier is vendored (pinned tag) for audit reproducibility — a validation result is replayable
|
|
||||||
- This is how Nova ensures the citizen developer's submissions meet production-grade standards regardless of upstream source
|
|
||||||
- **Key takeaway:** the citizen developer is not unguided — Nova provides engineering principles via skills + MCP, so every submission meets the same standards
|
|
||||||
|
|
||||||
### Appendix A1 — Metrics Glossary
|
|
||||||
- Reference for every metric mentioned in the deck
|
|
||||||
- Use if the audience asks "what does X mean?"
|
|
||||||
|
|
||||||
### Appendix A2 — Operating Model & Cost
|
|
||||||
- The operating cost is negligible (~$0.007/month)
|
|
||||||
- The zero-cost steady state (D-096 teardown) is the structural mitigation
|
|
||||||
- References the pre-mortem for the decay-prevention story
|
|
||||||
File diff suppressed because one or more lines are too long
@@ -1,417 +0,0 @@
|
|||||||
# Nova — The No-Humans Infrastructure Platform
|
|
||||||
|
|
||||||
> **Source of truth** (Step 1 of the 4-step deck process).
|
|
||||||
> Unified narrative deck merging `how-the-platform-works` + `the-developer-experience`.
|
|
||||||
> 5-act arc: Problem → Vision → How → Proof → Roadmap.
|
|
||||||
> x3 structure at deck level (opening = arc preview, body = tell them, closing = recap + ask)
|
|
||||||
> AND per slide (opens with what it covers, delivers, closes with benefit callout).
|
|
||||||
> Act indicator in the Marp footer: `Act N/5: <act name>`.
|
|
||||||
>
|
|
||||||
> **Honesty model:** every metric cited is grounded (cites a source file),
|
|
||||||
> derived (documented formula), or deferred (cites a blocking decision ID).
|
|
||||||
> No fabricated numbers. Deferred metrics marked `<span class="badge planned">Planned</span>`.
|
|
||||||
>
|
|
||||||
> v1.17 — Strategic Direction, Leadership Metrics & Unified Story (REQ-196, REQ-197)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Slide 1 — Arc Preview (the "what I'm going to tell you" deck-level opening)
|
|
||||||
|
|
||||||
This deck proves Nova is the no-humans infrastructure platform — and shows you the metrics that make the claim defensible.
|
|
||||||
|
|
||||||
**Today:** 18 capabilities verified, 0 consumer estates in production. This deck shows what's proven, what's pipeline-ready, and what's honestly deferred.
|
|
||||||
|
|
||||||
The 5-act arc:
|
|
||||||
1. **Problem** — why the operator is the bottleneck
|
|
||||||
2. **Vision** — Nova's strategic direction (NORTH_STAR)
|
|
||||||
3. **How** — the pipeline, Decision Ledger, attestation gates
|
|
||||||
4. **Proof** — grounded metrics that make the claim defensible
|
|
||||||
5. **Roadmap** — deferred metrics with unblock paths + the ask
|
|
||||||
|
|
||||||
> **Benefit:** you leave this deck knowing which claims are proven today, which are pipeline-ready, and which are deferred with a documented unblock path — no marketing, just grounded evidence.
|
|
||||||
|
|
||||||
> **Speaker notes:** The stake line (18V + 0 consumers) sets the honesty frame. The audience knows from slide 1 that this is not a hype deck — it's an evidence deck. The arc preview orients them for the next 15 slides.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Slide 2 — The No-Humans Imperative
|
|
||||||
|
|
||||||
This slide shows why the operator is the bottleneck — and why removing them from operations (not accountability) is the imperative.
|
|
||||||
|
|
||||||
- **The cost of humans-in-the-loop:** L1/L2 ops hours, escalation latency, the trust gap (autonomous claims without proof)
|
|
||||||
- **The operator is the bottleneck:** provisioning takes days, not minutes; escalations pile up; the trust gap means "autonomous" is a marketing claim, not a defensible one
|
|
||||||
- **The attestation model:** autonomy in operations, human at stage gates — not "no humans ever"
|
|
||||||
- Cites `docs/NO_HUMANS_THESIS.md` (the thesis, grounded proof, deferred proof, anti-claims)
|
|
||||||
|
|
||||||
> **Benefit:** you now know the problem framing — autonomy in operations, human at stage gates, is the path forward.
|
|
||||||
|
|
||||||
> **Speaker notes:** The key reframing: "no-humans" means no human in the loop of *normal operations*. Stage-gate attestation (QA for production, SRE for operational readiness) remains human by design. This is not about removing humans from accountability — only from operations.
|
|
||||||
|
|
||||||
> **Transition:** "Having defined the problem, here is Nova's strategic direction toward solving it."
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Slide 3 — Nova's Vision
|
|
||||||
|
|
||||||
This slide states Nova's vision — infrastructure operations become invisible, with provable trust.
|
|
||||||
|
|
||||||
> **Infrastructure operations become invisible. 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.**
|
|
||||||
|
|
||||||
- The attestation model: human attestation required at stage gates (QA for production, SRE for operational readiness); autonomy in operations, not in accountability
|
|
||||||
- Cites `docs/NO_HUMANS_THESIS.md` (the thesis, grounded proof, deferred proof, anti-claims incl. D-122 honesty)
|
|
||||||
|
|
||||||
> **Benefit:** you now know the destination — invisible operations with provable trust, not promised trust. And you know the attestation model: humans at stage gates, not in the ops loop.
|
|
||||||
|
|
||||||
> **Speaker notes:** The vision is ambitious but precise. "Provable, not promised" is the key phrase — it's the difference between a marketing claim and a defensible one. The attestation clarification is stated up front so the audience doesn't mishear "no-humans" as "no accountability."
|
|
||||||
|
|
||||||
> **Transition:** "The vision is ambitious — here are the 4 strategic objectives that make it concrete."
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Slide 4 — Strategic Objectives + Anti-Goals
|
|
||||||
|
|
||||||
This slide pairs what Nova is building toward (4 objectives) with what Nova refuses to build (5 anti-goals).
|
|
||||||
|
|
||||||
**4 Strategic Objectives:**
|
|
||||||
1. **Demonstrate production-grade zero-touch operations** — autonomy as the default, not the demo
|
|
||||||
2. **Establish provable trust in AI decisions** — Decision Ledger, confidence scoring, circuit breakers, blast-radius controls
|
|
||||||
3. **Deliver compounding, quantifiable ROI** — each quarter must reduce spend, free hours, avoid downtime measurably
|
|
||||||
4. **Become the default substrate for agentic infrastructure consumption** — the platform AI agents reach for first
|
|
||||||
|
|
||||||
**5 Anti-Goals (what Nova is NOT):**
|
|
||||||
1. Not a Terraform, Kubernetes, or hyperscaler competitor
|
|
||||||
2. Not a general-purpose AI agent platform
|
|
||||||
3. Not a system that removes humans from accountability
|
|
||||||
4. Not for legacy, untagged, or freeform infrastructure
|
|
||||||
5. Not sold to operators
|
|
||||||
|
|
||||||
From `NORTH_STAR.md`.
|
|
||||||
|
|
||||||
> **Benefit:** you now know the scope boundaries — Nova is purpose-built for infrastructure operations, sold to leadership on outcomes, and explicitly not a general-purpose AI platform or a hyperscaler competitor.
|
|
||||||
|
|
||||||
> **Speaker notes:** The anti-goals are as important as the objectives. They tell the audience what Nova will NOT be distracted by. Anti-goal #3 (not removing humans from accountability) reinforces the attestation model from slide 3.
|
|
||||||
|
|
||||||
> **Transition:** "The objectives are committed to measurable targets — here is the 12–18 month scorecard, with honest grounding status."
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Slide 5 — 12–18 Month Targets (the scorecard)
|
|
||||||
|
|
||||||
This slide shows the committed targets — numbers a board member can repeat back — with their grounding status.
|
|
||||||
|
|
||||||
**Current-milestone targets (grounded or derived this milestone):**
|
|
||||||
|
|
||||||
| Domain | Target | Status |
|
|
||||||
|---|---|---|
|
|
||||||
| MTTR (p95) | < 60 seconds | grounded (platform-run) |
|
|
||||||
| Cloud Spend Reduction | ≥ 25% on pilot estates | partial (Infracost grounded; CUR deferred D-096) |
|
|
||||||
| L1/L2 Ops Hours Avoided | ≥ 70% of pre-Nova FTE | derived (N internal runs; prod activates post-pilot) |
|
|
||||||
| Platform ROI | ≥ 250% annually | derived (formula; N internal runs caveat) |
|
|
||||||
| Decision Ledger Coverage | 100% of AI actions | grounded (this milestone builds it) |
|
|
||||||
| Attestation Coverage | 100% of prod/dr promotions | grounded |
|
|
||||||
|
|
||||||
**Post-Pilot targets (pipeline grounded; denominator activates with a pilot estate):**
|
|
||||||
|
|
||||||
| Domain | Target | Status |
|
|
||||||
|---|---|---|
|
|
||||||
| Touchless Resolution Rate | ≥ 99% | partial (pipeline grounded; 0 consumers today) |
|
|
||||||
| Human Escalation Frequency | < 0.1% | partial (pipeline grounded; 0 consumers today) |
|
|
||||||
| AI Decision Accuracy | ≥ 99.5% | partial (pipeline grounded; 0 consumers today) |
|
|
||||||
|
|
||||||
**Deferred targets:** Predictive vs Reactive ≥3:1 <span class="badge planned">Planned</span> · Drift Auto-Reversal ≥95% <span class="badge planned">Planned</span>
|
|
||||||
|
|
||||||
> **Benefit:** you now know the destination numbers — and which ones are measurable today vs deferred honestly. The Post-Pilot targets are committed; the pipeline works; the numbers fill when a pilot estate runs.
|
|
||||||
|
|
||||||
> **Speaker notes:** The three-section split (current / post-pilot / deferred) is the honesty model. The "partial" status means the measurement pipeline is grounded but the denominator is zero (0 consumers). This is the same honesty as Cloud Spend (Infracost grounded, CUR deferred). A board member can see exactly which numbers are real today and which are waiting for a pilot.
|
|
||||||
|
|
||||||
> **Transition:** "The targets are committed — here is how Nova works to achieve them."
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Slide 6 — The Platform Pipeline
|
|
||||||
|
|
||||||
This slide shows the contract-to-evidence pipeline — how intent becomes verified infrastructure without an operator.
|
|
||||||
|
|
||||||
```mermaid
|
|
||||||
graph LR
|
|
||||||
A[Contract] --> B[Resolver]
|
|
||||||
B --> C[Adapter]
|
|
||||||
C --> D[Terraform Plan]
|
|
||||||
D --> E[Checkov Policy]
|
|
||||||
E --> F[Confidence Signal]
|
|
||||||
F --> G{HITL Gate}
|
|
||||||
G -->|dev: autonomous| H[Apply]
|
|
||||||
G -->|qa/prod/dr: attested| H
|
|
||||||
H --> I[Evidence + Outbox]
|
|
||||||
```
|
|
||||||
|
|
||||||
- Contract → resolver → adapter → terraform plan → Checkov (policy) → confidence signal → HITL gate (dev autonomous; qa/prod/dr attested) → apply → evidence
|
|
||||||
- Grounded in `scripts/run_platform.sh` + `core/contract_resolver.py` + `adapters/terraform/adapter.py` + `core/confidence_signal.py`
|
|
||||||
|
|
||||||
> **Benefit:** you now know the path from intent to evidence — and where the human appears (stage gates only, not in the ops loop).
|
|
||||||
|
|
||||||
> **Speaker notes:** The pipeline is the engine. The key insight: dev is autonomous (no HITL gate); qa/prod/dr require human attestation. The confidence signal is the "AI" — it's a 6-input weighted score, not an LLM. The HITL gate is where the human appears, but only for qa/prod/dr, not for dev.
|
|
||||||
|
|
||||||
> **Transition:** "The pipeline produces decisions — here is how every decision is captured and made accountable."
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Slide 7 — The Decision Ledger
|
|
||||||
|
|
||||||
This slide shows the Decision Ledger — every AI decision captured with confidence, alternatives, and outcome.
|
|
||||||
|
|
||||||
- **Architecture:** `outbox_writer.py` extended → SQLite append-only hash-chain table
|
|
||||||
- **`ai.decision.made` events:** decision_id=run_id, chosen_action=band, confidence=score, alternatives=perInput, human_override=HITL block, outcome backfilled from apply.completed
|
|
||||||
- **`attestation.recorded` events:** qa/prod/dr sign-offs (approver, env, concerns, result)
|
|
||||||
- D-121, D-122, D-132. Honors D-083 (no S3 Object Lock/JWS — local hash-chain this milestone)
|
|
||||||
|
|
||||||
**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" that doesn't exist yet.
|
|
||||||
|
|
||||||
> **Benefit:** you now know why 'autonomous' is defensible — every decision is immutable, queryable, and accountable. And you know exactly what 'AI' means here: a confidence-gated policy engine, not a black-box LLM.
|
|
||||||
|
|
||||||
> **Speaker notes:** The D-122 honesty sentence is critical. If the audience walks away thinking Nova has an LLM planner, we've violated the "no fabrication" constraint. The Decision Ledger is the trust substrate (NORTH_STAR Objective #2) — it's the moat. Features can be copied; an immutable, queryable decision history cannot.
|
|
||||||
|
|
||||||
> **Transition:** "Decisions are captured — here is how stage-gate attestation keeps humans in accountability."
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Slide 8 — The 8-Concern Attestation Matrix
|
|
||||||
|
|
||||||
This slide shows the 8-concern attestation matrix — the designed controls that keep humans at stage gates.
|
|
||||||
|
|
||||||
| Concern | Env | Freshness | Type |
|
|
||||||
|---------|-----|-----------|------|
|
|
||||||
| functional_correctness | qa | 24h | operator-supplied |
|
|
||||||
| performance_baseline | qa | 7d | operator-supplied |
|
|
||||||
| security_posture | qa | 24h | operator-supplied |
|
|
||||||
| contract_nfrs | qa/prod/dr | — | offline-testable |
|
|
||||||
| operational_readiness | prod | 30d | operator-supplied |
|
|
||||||
| incident_response | prod | 90d | operator-supplied |
|
|
||||||
| capacity_cost | prod | 30d | operator-supplied |
|
|
||||||
| resilience_dr_drill | prod | 180d | operator-supplied |
|
|
||||||
| resilience_chaos | prod | 90d | operator-supplied |
|
|
||||||
| resilience_backup | prod | 30d | operator-supplied |
|
|
||||||
| dr_region_deploy | dr | 180d | operator-supplied |
|
|
||||||
|
|
||||||
- Offline-testable concerns run for real; operator-supplied concerns accept signed evidence artifacts
|
|
||||||
- Separation-of-duties on prod (the approver can't be the same person who built it)
|
|
||||||
- Grounded in `core/attestation_matrix.py` + `core/hitl_gates.py`
|
|
||||||
|
|
||||||
> **Benefit:** you now know the gate model — autonomy in operations, human in accountability, by design. The 8-concern matrix is what makes "no-humans in ops" safe.
|
|
||||||
|
|
||||||
> **Speaker notes:** The attestation matrix is the human-in-the-loop safeguard. It's not a rubber stamp — it's a structured, freshness-validated, separation-of-duties-enforced gate. This is what Anti-Goal #3 means: "not a system that removes humans from accountability."
|
|
||||||
|
|
||||||
> **Transition:** "You've now seen how Nova works — the pipeline, the Decision Ledger, the attestation gates. But 'how it works' is not 'proof it works.' The next four slides show the measured evidence: capability health, trust metrics, efficiency, and cost — every number grounded in a real file, not a marketing claim."
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Slide 9 — Telemetry Architecture
|
|
||||||
|
|
||||||
This slide shows how Nova instruments itself — the CloudEvents envelope, the cold store, and the PowerBI export.
|
|
||||||
|
|
||||||
```mermaid
|
|
||||||
graph TB
|
|
||||||
A[Platform components] --> B[CloudEvents 1.0 envelope]
|
|
||||||
B --> C[metrics/events.jsonl]
|
|
||||||
B --> D[metrics/decision_ledger.db]
|
|
||||||
B --> E[metrics/runs/]
|
|
||||||
C --> F[Collector]
|
|
||||||
D --> F
|
|
||||||
E --> F
|
|
||||||
F --> G[metrics/nova_metrics.db]
|
|
||||||
G --> H[metrics/powerbi/]
|
|
||||||
H --> I[PowerBI dashboards]
|
|
||||||
```
|
|
||||||
|
|
||||||
- Platform components → CloudEvents 1.0 envelope → `metrics/events.jsonl` + `metrics/runs/` + `metrics/decision_ledger.db` → collector → `metrics/nova_metrics.db` (SQLite cold store) → `metrics/powerbi/` (CSV/JSON views) → PowerBI
|
|
||||||
- D-120 (Nova-native), D-125 (hybrid events/files), D-126 (cold-only)
|
|
||||||
- <span class="badge planned">Planned</span>: Hot-path (live ops dashboard) — D-126
|
|
||||||
|
|
||||||
> **Benefit:** you now know that every metric in this deck is traceable to a real emitted event — the architecture IS the trust substrate. When a CFO asks 'where does this number come from?', the answer is a file path, not a Slack thread.
|
|
||||||
|
|
||||||
> **Speaker notes:** The architecture is deliberately minimal (Nova-native, no Kafka/Prometheus/ClickHouse). The hot path is deferred (D-126) — the cold store is sufficient for batch/historical analysis. The key point: every number in the Proof act is traceable to a file path. This is the "no fabrication" constraint made architectural.
|
|
||||||
|
|
||||||
> **Transition:** "The architecture is sound — here is the measured proof."
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Slide 10 — Capability Health + Confidence Distribution
|
|
||||||
|
|
||||||
This slide shows the grounded proof: capability health and confidence distribution from real runs.
|
|
||||||
|
|
||||||
**Capability Health:** 18 Verified + 4 Skipped (post-D-096 teardown) from `.ciagent/REGRESSION_REPORT.json`
|
|
||||||
|
|
||||||
| Status | Count |
|
|
||||||
|--------|-------|
|
|
||||||
| Verified | 18 |
|
|
||||||
| Skipped | 4 |
|
|
||||||
| Broken | 0 |
|
|
||||||
| Decayed | 0 |
|
|
||||||
|
|
||||||
- The 4 Skipped are live-AWS capabilities (CAP-013..016) — honestly skipped because resources are torn down (D-096), not a failure
|
|
||||||
- Confidence distribution: from `metrics/nova_metrics.db` `fact_confidence` — score histogram, band breakdown (pass/halt)
|
|
||||||
|
|
||||||
> **Benefit:** you now know the platform is verified — 18 capabilities pass, 4 are honestly skipped, 0 broken. The honesty model (Skipped ≠ failure) is what makes the Verified count credible.
|
|
||||||
|
|
||||||
> **Speaker notes:** The 18V+4S number is the single most important proof point. It says "the platform works, and we're honest about what we can't test." The 4 Skipped are live-AWS capabilities — they're skipped because the live AWS resources are torn down (D-096), not because they're broken. When live AWS is re-provisioned, they reactivate.
|
|
||||||
|
|
||||||
> **Transition:** "Capability health is necessary — here is the trust substrate that makes autonomy defensible."
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Slide 11 — Decision Ledger + Attestation Coverage
|
|
||||||
|
|
||||||
This slide shows the trust metrics — Decision Ledger coverage and attestation coverage, both 100%.
|
|
||||||
|
|
||||||
- **Decision Ledger Coverage:** 100% of platform runs emit `ai.decision.made` with outcome backfill (source: `metrics/decision_ledger.db`)
|
|
||||||
- **Attestation Coverage:** 100% of prod/dr promotions attested by a human (source: `hitl_gates.py` + outbox `approver_*` attributes)
|
|
||||||
- **AI Decision Accuracy:** decisions not followed by apply.failed/incident within 5min
|
|
||||||
- The trust-snapshot report (`metrics/TRUST_SNAPSHOT.md`) with chain-integrity verdict
|
|
||||||
- <span class="badge planned">Planned</span>: Tamper-Evident Ledger Checkpoints (D-083)
|
|
||||||
|
|
||||||
> **Benefit:** you now know the trust is provable — not a marketing claim, a queryable record. The Decision Ledger is the moat; features can be copied, an immutable decision history cannot.
|
|
||||||
|
|
||||||
> **Speaker notes:** The trust metrics are the "provably trustworthy" proof. Decision Ledger Coverage = 100% means no AI decision is ever lost. Attestation Coverage = 100% means no prod/dr promotion lands without a human sign-off. The chain-integrity verdict (from the trust snapshot) proves the ledger hasn't been tampered with.
|
|
||||||
|
|
||||||
> **Transition:** "Trust is provable — here is the operational efficiency that makes the ROI real."
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Slide 12 — Zero-Touch Efficiency
|
|
||||||
|
|
||||||
This slide shows the zero-touch efficiency metrics — touchless resolution, human escalation, and MTTR.
|
|
||||||
|
|
||||||
- **Touchless Resolution Rate:** runs without operational HITL block ÷ total (attestation gates excluded)
|
|
||||||
- **Human Escalation Frequency:** operational HITL blocks only (confidence-driven; attestation sign-offs excluded)
|
|
||||||
- **MTTR (platform-run):** apply.failed → successful retry (D-131)
|
|
||||||
|
|
||||||
**Post-Pilot caveat:** these three metrics are computed on N internal runs today; the production-denominator activates when a pilot estate runs (see NORTH_STAR Post-Pilot Targets section).
|
|
||||||
|
|
||||||
> **Benefit:** you now know the zero-touch efficiency is measurable — the pipeline works today on internal runs, and the denominator expands to production estates when a pilot activates.
|
|
||||||
|
|
||||||
> **Speaker notes:** The Post-Pilot caveat is the honesty model. The pipeline is grounded (it works); the denominator is zero (0 consumers). This is not a fabricated "99% touchless" claim — it's "the measurement works, and the numbers fill when a pilot runs."
|
|
||||||
|
|
||||||
> **Transition:** "Efficiency is half the ROI story — here is the cost side."
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Slide 13 — Cost & ROI
|
|
||||||
|
|
||||||
This slide shows the cost estimates and the ROI formula — with honest caveats about the current denominator.
|
|
||||||
|
|
||||||
- **Cost Estimates via Infracost:** pre-apply, grounded (reads plan JSON, offline)
|
|
||||||
- **ROI formula (shown inline):** `Platform ROI = (FTE hours saved × blended rate + cloud savings + avoided downtime) ÷ platform op cost`
|
|
||||||
- **N=0 caveat:** "These derived metrics are computed on N internal runs today; the production-denominator activates post-pilot. The formula is grounded; the production numbers are not yet."
|
|
||||||
- **FTE Hours Saved** (derived), **Platform ROI** (derived formula)
|
|
||||||
- <span class="badge planned">Planned</span>: Live CUR Reconciliation (D-096), Drift Auto-Reversal (D-096)
|
|
||||||
|
|
||||||
> **Benefit:** you now know the ROI formula — and you know it's computed on internal runs today, not fabricated production numbers. The formula is ready; the production denominator activates with a pilot.
|
|
||||||
|
|
||||||
> **Speaker notes:** The ROI formula is shown inline — not hidden in a footnote. The N=0 caveat is stated explicitly. This is the "no fabrication" constraint in action: we show the formula, we show the caveat, we don't pretend the production numbers exist.
|
|
||||||
|
|
||||||
> **Transition:** "The proof is grounded — here is what is honestly deferred."
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Slide 14 — What's Deferred — and Why
|
|
||||||
|
|
||||||
This slide pairs each deferred metric with its blocking decision — honesty about what isn't measured yet.
|
|
||||||
|
|
||||||
**To be clear:** these deferrals are *measurement infrastructure*, not whether the platform runs without humans. The platform IS autonomous in operations. What's deferred is the *evidence pipeline* for certain metrics — not the autonomy itself.
|
|
||||||
|
|
||||||
| # | Deferred Metric | Blocking Decision |
|
|
||||||
|---|----------------|-------------------|
|
|
||||||
| 1 | Live Infrastructure Health | D-096 |
|
|
||||||
| 2 | Live Outbox Write Rate | D-096 |
|
|
||||||
| 3 | Tamper-Evident Ledger Checkpoints | D-083 |
|
|
||||||
| 4 | Onboarding Funnel (granted) | D-113/D-114/D-119 |
|
|
||||||
| 5 | Drift Auto-Reversal | D-096 + no scheduler |
|
|
||||||
| 6 | Live CUR Reconciliation | D-096 |
|
|
||||||
| 7 | SLA / Unplanned Downtime | D-096 |
|
|
||||||
| 8 | Predictive vs Reactive | future emitter |
|
|
||||||
|
|
||||||
From `docs/METRICS_DEFERRED_ROADMAP.md`.
|
|
||||||
|
|
||||||
> **Benefit:** you now know the boundaries — what Nova measures today, and exactly what blocks the rest. The autonomy is real; the measurement gaps are documented.
|
|
||||||
|
|
||||||
> **Speaker notes:** The preempt is critical: these deferrals are measurement infrastructure, not autonomy. The platform runs without humans in operations. What's deferred is the evidence pipeline for live-infra health, drift detection, predictive remediation — not the autonomy itself. Showing this slide to leadership demonstrates honesty, not weakness.
|
|
||||||
|
|
||||||
> **Transition:** "The proof is honest — here is the roadmap from here to the 12–18 month targets."
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Slide 15 — Roadmap to the North Star
|
|
||||||
|
|
||||||
This slide shows the path from v1.17's grounded metrics to the 12–18 month targets — the unblock path for each deferred metric.
|
|
||||||
|
|
||||||
- Each deferred metric → blocking decision → unblock requirement → candidate milestone
|
|
||||||
- The hot-path activation section (post-D-096, Nova-native only, D-120)
|
|
||||||
- Re-evaluation triggers: D-096 lift, D-083 lift, onboarding-grant lift
|
|
||||||
|
|
||||||
From `docs/METRICS_DEFERRED_ROADMAP.md`.
|
|
||||||
|
|
||||||
> **Benefit:** you now know the path — every deferred metric has an unblock requirement and a candidate milestone. Nothing is hand-waved; everything has a plan.
|
|
||||||
|
|
||||||
> **Speaker notes:** The roadmap is the bridge from "honestly deferred" to "here's how we get there." Each deferred metric has a specific unblock requirement and a candidate future milestone. The re-evaluation triggers ensure the metrics layer evolves when the blocking decisions lift.
|
|
||||||
|
|
||||||
> **Transition:** "The roadmap is clear — here is the recap and the ask."
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Slide 16 — Recap + Ask (the "what I told you" deck-level closing)
|
|
||||||
|
|
||||||
This slide recaps the 5 acts and states the ask.
|
|
||||||
|
|
||||||
**Recap:**
|
|
||||||
- **Problem:** the operator is the bottleneck; autonomy in operations, human at stage gates
|
|
||||||
- **Vision:** invisible operations with provable trust (NORTH_STAR)
|
|
||||||
- **How:** pipeline + Decision Ledger + 8-concern attestation matrix
|
|
||||||
- **Proof:** 18V+4S, 100% ledger coverage, 100% attestation, grounded ROI formula
|
|
||||||
- **Roadmap:** deferred metrics have unblock paths
|
|
||||||
|
|
||||||
**The ask:** "The ask is a business decision: approve a pilot estate to activate the production-denominator metrics (Touchless Resolution, Human Escalation, AI Decision Accuracy), and approve the tamper-evident ledger build-out (D-083 lift) to move from local hash-chain to S3 Object Lock + JWS. These two decisions move Nova from 'pipeline-ready' to 'production-proven.'"
|
|
||||||
|
|
||||||
> **Benefit:** you leave with a clear business decision to make — approve a pilot + the ledger build-out — and the confidence that every claim in this deck is grounded, derived, or honestly deferred.
|
|
||||||
|
|
||||||
> **Speaker notes:** The ask is a business decision, not insider language. "Approve a pilot estate" is something a C-suite can decide. "Approve the ledger build-out" is a budget decision. The recap reinforces the 5-act arc — the audience leaves with the structure, not a pile of facts.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Appendix Slide A1 — Metrics Glossary
|
|
||||||
|
|
||||||
This appendix defines every KPI in one line with its grounding badge.
|
|
||||||
|
|
||||||
| KPI | Definition | Status |
|
|
||||||
|-----|-----------|--------|
|
|
||||||
| Touchless Resolution Rate | runs without operational HITL block ÷ total | partial (Post-Pilot) |
|
|
||||||
| Human Escalation Frequency | operational HITL blocks ÷ total | partial (Post-Pilot) |
|
|
||||||
| AI 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 (Infracost) | sum(delta_usd where delta < 0) | partial (CUR 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 |
|
|
||||||
|
|
||||||
> **Benefit:** you now have a reference for every metric mentioned in the deck.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Appendix Slide A2 — Operating Model & Cost
|
|
||||||
|
|
||||||
This appendix shows the real cost figures + the zero-cost steady state.
|
|
||||||
|
|
||||||
- **Cost figures** from `COST.md`: $0.001883 over 8 days, ~$0.007/month, S3-dominated, zero BAU compute
|
|
||||||
- **Zero-cost steady state:** all resources torn down post-v1.11 (D-096); the platform runs offline
|
|
||||||
- References the pre-mortem (`PRE_MORTEM.md`: v1.10 decay root cause + four forward failure modes + structural mitigations)
|
|
||||||
|
|
||||||
> **Benefit:** you now know the operating cost is negligible — and the structural mitigation that prevents decay.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
> **End of deck.** 16 main slides + 2 appendix slides = 18 total.
|
|
||||||
> Both old decks (`how-the-platform-works` + `the-developer-experience`) are retired (D-130).
|
|
||||||
Binary file not shown.
+45
-33
@@ -1,13 +1,12 @@
|
|||||||
# RACI — Who Owns What
|
# RACI — Who Owns What
|
||||||
|
|
||||||
> **Source of truth:** `.ciagent/PROJECT.md` § RACI Matrix (v1.18, REQ-215,
|
> This page is the citizen-developer-facing copy.
|
||||||
> D-139). This page is the citizen-developer-facing copy.
|
|
||||||
|
|
||||||
Nova's delivery lifecycle has three roles. This page clarifies who owns
|
Nova's delivery lifecycle has four roles. This page clarifies who owns
|
||||||
what — so the citizen developer knows what they bring, what the platform
|
what — so the citizen developer knows what they bring, what the platform
|
||||||
provides, and what is co-owned.
|
provides, what quality engineering guards, and what is co-owned with SRE.
|
||||||
|
|
||||||
## The Three Roles
|
## The Four Roles
|
||||||
|
|
||||||
### Citizen Developer (CD)
|
### Citizen Developer (CD)
|
||||||
|
|
||||||
@@ -15,40 +14,49 @@ That's you — the consumer (technical developer L3A or non-technical L3B).
|
|||||||
You are **Responsible** for all **Functional Requirements (FRs)** and
|
You are **Responsible** for all **Functional Requirements (FRs)** and
|
||||||
**User Acceptance Testing (UAT)**. You produce the FRs + UAT via your AI
|
**User Acceptance Testing (UAT)**. You produce the FRs + UAT via your AI
|
||||||
coding agent, an upstream agentic SDLC platform, or any upstream
|
coding agent, an upstream agentic SDLC platform, or any upstream
|
||||||
development platform. **The source does not matter** — all are subject
|
development platform. **The source does not matter** — all are subject to
|
||||||
to the same compliance standards (the submission-readiness gate, the
|
the same compliance standards (the submission-readiness gate, the
|
||||||
contract schema, the policy envelope, the immutable audit stream). Nova
|
contract schema, the policy envelope, the immutable audit stream). Nova
|
||||||
validates the submission, not the author.
|
validates the submission, not the author.
|
||||||
|
|
||||||
### Platform (Nova)
|
### Platform (Nova)
|
||||||
|
|
||||||
Nova is **Responsible** for all **Non-Functional Requirements (NFRs)**,
|
Nova is **Responsible** for all **Non-Functional Requirements (NFRs)**,
|
||||||
**Infrastructure** (cloud resource lifecycle, state, IAM), **QA** (the
|
**Infrastructure** (cloud resource lifecycle, state, IAM), and
|
||||||
platform-side quality checks: policy enforcement, confidence scoring,
|
**Production deployments to cloud** (the apply path, the pipeline, the
|
||||||
schema validation), and **Production deployments to cloud** (the apply
|
release mechanics).
|
||||||
path, the pipeline, the release mechanics).
|
|
||||||
|
|
||||||
### Release Management (RM) — co-owned
|
### Quality Engineering (QE)
|
||||||
|
|
||||||
The release is **co-owned**. The platform performs the QA + SRE
|
Quality Engineering is **Responsible** for the platform-side quality
|
||||||
attestations agentically (it runs the confidence signal, the policy
|
checks: policy enforcement, confidence scoring, schema validation, and
|
||||||
checks, the separation-of-duties). The citizen developer **oversees and
|
the functional/contract/non-functional evidence that feeds attestation.
|
||||||
triggers** the actual release — the human attestation at the stage gate
|
QE owns the **quality** of what the platform produces — the gate
|
||||||
is your authorization. The platform runs the checks; you authorize the
|
evidence, not the gate decision.
|
||||||
promotion. This is the "autonomy in operations, human at stage gates"
|
|
||||||
model.
|
### 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
|
## The Matrix
|
||||||
|
|
||||||
| Work Category | Citizen Developer | Platform | Release Management |
|
| Work Category | Citizen Developer | Platform | Quality Engineering | SRE |
|
||||||
|---|---|---|---|
|
|---|---|---|---|---|
|
||||||
| **Functional Requirements (FRs)** | **R/A** | C | I |
|
| **Functional Requirements (FRs)** | **R/A** | C | I | I |
|
||||||
| **User Acceptance Testing (UAT)** | **R/A** | C | I |
|
| **User Acceptance Testing (UAT)** | **R/A** | C | I | I |
|
||||||
| **Non-Functional Requirements (NFRs)** | I | **R/A** | C |
|
| **Non-Functional Requirements (NFRs)** | I | **R/A** | C | C |
|
||||||
| **Infrastructure (cloud, state, IAM)** | I | **R/A** | C |
|
| **Infrastructure (cloud, state, IAM)** | I | **R/A** | I | C |
|
||||||
| **QA (policy, confidence, schema checks)** | C | **R/A** | I |
|
| **QA (policy, confidence, schema checks)** | C | R | **R/A** | I |
|
||||||
| **Production deployment to cloud** | I | **R/A** | C |
|
| **Production deployment to cloud** | I | **R/A** | C | C |
|
||||||
| **Release attestation (QA + SRE sign-off)** | **A** | R | **R** |
|
| **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
|
**Key:** **R** = Responsible (does the work) · **A** = Accountable (owns
|
||||||
the outcome, sign-off) · **C** = Consulted · **I** = Informed.
|
the outcome, sign-off) · **C** = Consulted · **I** = Informed.
|
||||||
@@ -64,11 +72,15 @@ the outcome, sign-off) · **C** = Consulted · **I** = Informed.
|
|||||||
- The NFRs (security, observability, compliance — baked into the
|
- The NFRs (security, observability, compliance — baked into the
|
||||||
pipeline, not your concern).
|
pipeline, not your concern).
|
||||||
- The infrastructure (cloud resources, state management, IAM scoping).
|
- The infrastructure (cloud resources, state management, IAM scoping).
|
||||||
- The QA (policy enforcement, confidence scoring, schema validation).
|
|
||||||
- The production deployment (the apply path, the pipeline, the release).
|
- The production deployment (the apply path, the pipeline, the release).
|
||||||
|
|
||||||
**You co-own the release:**
|
**Quality Engineering guards:**
|
||||||
- Nova runs the attestations (QA confidence, SRE operational readiness).
|
- 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
|
- You authorize the promotion at the stage gate. No promotion happens
|
||||||
without your recorded attestation.
|
without your recorded attestation.
|
||||||
|
|
||||||
@@ -79,7 +91,7 @@ agentic SDLC platform, or a traditional IDE. Nova does not
|
|||||||
differentiate. All submissions pass through the same gate
|
differentiate. All submissions pass through the same gate
|
||||||
(`schemas/submission-readiness.schema.json`): tags, environment
|
(`schemas/submission-readiness.schema.json`): tags, environment
|
||||||
metadata, policy preconditions, profile markers. The compliance
|
metadata, policy preconditions, profile markers. The compliance
|
||||||
standards are the same regardless of how the code was authored. This
|
standards are the same regardless of how the code was authored. This is
|
||||||
is by design: the audit trail is the same, the policy envelope is the
|
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
|
same, the evidence stream is the same. The source does not matter; the
|
||||||
submission does.
|
submission does.
|
||||||
+7
-3
@@ -1,6 +1,5 @@
|
|||||||
# Scope — Nova is Downstream of PDLC
|
# Scope — Nova is Downstream of PDLC
|
||||||
|
|
||||||
> **Source of truth:** `.ciagent/PROJECT.md` § Scope (v1.18, REQ-216).
|
|
||||||
> This page is the citizen-developer-facing copy.
|
> This page is the citizen-developer-facing copy.
|
||||||
|
|
||||||
## The Boundary
|
## The Boundary
|
||||||
@@ -14,8 +13,13 @@ PDLC includes:
|
|||||||
- Application business logic
|
- Application business logic
|
||||||
- IDE workflows / developer experience
|
- IDE workflows / developer experience
|
||||||
|
|
||||||
Nova never penetrates the PDLC. Nova's domain is **infrastructure +
|
Nova never reaches into the PDLC. Nova's domain is **infrastructure +
|
||||||
delivery only**.
|
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
|
## What Nova Does
|
||||||
|
|
||||||
|
|||||||
+2
-2
@@ -34,14 +34,14 @@
|
|||||||
|
|
||||||
## Atelier Provenance
|
## Atelier Provenance
|
||||||
|
|
||||||
The skills are derived from [Atelier](https://git.cloudinit.dev/coreci/atelier)
|
The skills are derived from [Atelier](https://example.com/atelier)
|
||||||
— a first-principles docs-as-code engineering framework with 8 core
|
— a first-principles docs-as-code engineering framework with 8 core
|
||||||
principles (C1–C8) and 19 domains, each with 10 derived P-rules. The
|
principles (C1–C8) and 19 domains, each with 10 derived P-rules. The
|
||||||
skills distill the citizen-developer-relevant subset of each domain's
|
skills distill the citizen-developer-relevant subset of each domain's
|
||||||
first-principles, link to the agent-checklist triggers, and map to the
|
first-principles, link to the agent-checklist triggers, and map to the
|
||||||
existing BA.A catalog.
|
existing BA.A catalog.
|
||||||
|
|
||||||
Atelier is vendored under `mcp/atelier/vendor/` (pinned tag, D-136) for
|
Atelier is vendored under `mcp/atelier/vendor/` (pinned tag) for
|
||||||
audit reproducibility — an agentic validation result is replayable
|
audit reproducibility — an agentic validation result is replayable
|
||||||
against the exact principles that produced it.
|
against the exact principles that produced it.
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -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 penetrate 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 reach into 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.
|
||||||
|
|||||||
@@ -1,9 +1,8 @@
|
|||||||
# Nova Atelier MCP Server
|
# Nova Atelier MCP Server
|
||||||
|
|
||||||
> **v1.18, REQ-223, REQ-224.** An MCP (Model Context Protocol) server that
|
|
||||||
> exposes Atelier engineering principles to the citizen developer's AI
|
> exposes Atelier engineering principles to the citizen developer's AI
|
||||||
> agent. Plugin-registry architecture (D-140); stdio transport (D-135);
|
> agent. Plugin-registry architecture; stdio transport;
|
||||||
> vendored Atelier (D-136) for audit reproducibility.
|
> vendored Atelier for audit reproducibility.
|
||||||
|
|
||||||
## What This Is
|
## What This Is
|
||||||
|
|
||||||
@@ -22,7 +21,7 @@ observability gaps.
|
|||||||
| `atelier.matrix_lookup(domain)` | Look up the domain→core principle mapping for a given domain. |
|
| `atelier.matrix_lookup(domain)` | Look up the domain→core principle mapping for a given domain. |
|
||||||
| `atelier.validate_against_principles(snippet, domains?)` | Validate a code/diff snippet against the Atelier agent-checklist. Returns pass/fail per check item with the principle citation. |
|
| `atelier.validate_against_principles(snippet, domains?)` | Validate a code/diff snippet against the Atelier agent-checklist. Returns pass/fail per check item with the principle citation. |
|
||||||
|
|
||||||
## Architecture — Plugin Registry (D-140)
|
## Architecture — Plugin Registry
|
||||||
|
|
||||||
```
|
```
|
||||||
mcp/atelier/
|
mcp/atelier/
|
||||||
@@ -31,7 +30,7 @@ mcp/atelier/
|
|||||||
│ ├── __init__.py
|
│ ├── __init__.py
|
||||||
│ ├── principles.py # lookup_principle, list_domains, matrix_lookup
|
│ ├── principles.py # lookup_principle, list_domains, matrix_lookup
|
||||||
│ └── validation.py # validate_against_principles
|
│ └── validation.py # validate_against_principles
|
||||||
├── vendor/ # pinned Atelier snapshot (D-136)
|
├── vendor/ # pinned Atelier snapshot
|
||||||
│ ├── VERSION.md # pinned tag + upgrade instructions
|
│ ├── VERSION.md # pinned tag + upgrade instructions
|
||||||
│ ├── core/first-principles.md
|
│ ├── core/first-principles.md
|
||||||
│ ├── domains/security/first-principles.md
|
│ ├── domains/security/first-principles.md
|
||||||
@@ -68,7 +67,7 @@ s.load_plugins()
|
|||||||
result = s.call_tool("atelier_lookup_principle", {"domain": "security", "principle_id": "P4"})
|
result = s.call_tool("atelier_lookup_principle", {"domain": "security", "principle_id": "P4"})
|
||||||
```
|
```
|
||||||
|
|
||||||
## Vendoring (D-136)
|
## Vendoring
|
||||||
|
|
||||||
Atelier is vendored under `vendor/` at a pinned tag (`v0.3.6`, see
|
Atelier is vendored under `vendor/` at a pinned tag (`v0.3.6`, see
|
||||||
`vendor/VERSION.md`). An agentic validation result is only reproducible if
|
`vendor/VERSION.md`). An agentic validation result is only reproducible if
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
# Nova Metrics Directory
|
# Nova Metrics Directory
|
||||||
|
|
||||||
> v1.17 — Strategic Direction, Leadership Metrics & Unified Story (D-128)
|
|
||||||
|
|
||||||
This directory holds Nova's telemetry/observability artifacts. The
|
This directory holds Nova's telemetry/observability artifacts. The
|
||||||
metrics layer is **Nova-native** (D-120): JSONL event log + SQLite cold
|
metrics layer is **Nova-native** (D-120): JSONL event log + SQLite cold
|
||||||
|
|||||||
@@ -1,7 +1,5 @@
|
|||||||
# Nova PowerBI Dashboard — Import Guide
|
# Nova PowerBI Dashboard — Import Guide
|
||||||
|
|
||||||
> v1.17 — Strategic Direction, Leadership Metrics & Unified Story (REQ-208)
|
|
||||||
> Generated: 2026-08-04
|
|
||||||
|
|
||||||
This guide documents how to import Nova's metrics views into PowerBI
|
This guide documents how to import Nova's metrics views into PowerBI
|
||||||
via the folder connector, and suggests a starter visual model.
|
via the folder connector, and suggests a starter visual model.
|
||||||
|
|||||||
@@ -48,6 +48,11 @@
|
|||||||
"description": "Target group target type (ip or instance).",
|
"description": "Target group target type (ip or instance).",
|
||||||
"required": false,
|
"required": false,
|
||||||
"default": "ip"
|
"default": "ip"
|
||||||
|
},
|
||||||
|
"enabled": {
|
||||||
|
"type": "boolean",
|
||||||
|
"default": true,
|
||||||
|
"description": "Feature flag: enable/disable this module. Set to false to skip resource creation."
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"outputs": {
|
"outputs": {
|
||||||
@@ -85,20 +90,42 @@
|
|||||||
{
|
{
|
||||||
"type": "aws:elbv2:loadbalancer",
|
"type": "aws:elbv2:loadbalancer",
|
||||||
"description": "Application load balancer in the VPC subnets.",
|
"description": "Application load balancer in the VPC subnets.",
|
||||||
"inputs": ["name", "subnets", "security_group", "load_balancer_type"],
|
"inputs": [
|
||||||
"outputs": ["lb_arn"]
|
"name",
|
||||||
|
"subnets",
|
||||||
|
"security_group",
|
||||||
|
"load_balancer_type"
|
||||||
|
],
|
||||||
|
"outputs": [
|
||||||
|
"lb_arn"
|
||||||
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "aws:elbv2:targetgroup",
|
"type": "aws:elbv2:targetgroup",
|
||||||
"description": "Target group for the ECS service tasks.",
|
"description": "Target group for the ECS service tasks.",
|
||||||
"inputs": ["name", "port", "protocol", "vpc_id", "target_type"],
|
"inputs": [
|
||||||
"outputs": ["target_group_arn"]
|
"name",
|
||||||
|
"port",
|
||||||
|
"protocol",
|
||||||
|
"vpc_id",
|
||||||
|
"target_type"
|
||||||
|
],
|
||||||
|
"outputs": [
|
||||||
|
"target_group_arn"
|
||||||
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "aws:elbv2:listener",
|
"type": "aws:elbv2:listener",
|
||||||
"description": "Listener forwarding the LB port to the target group.",
|
"description": "Listener forwarding the LB port to the target group.",
|
||||||
"inputs": ["lb_arn", "port", "protocol", "target_group_arn"],
|
"inputs": [
|
||||||
"outputs": ["listener_arn"]
|
"lb_arn",
|
||||||
|
"port",
|
||||||
|
"protocol",
|
||||||
|
"target_group_arn"
|
||||||
|
],
|
||||||
|
"outputs": [
|
||||||
|
"listener_arn"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
resource "aws_lb" "this" {
|
resource "aws_lb" "this" {
|
||||||
|
count = var.enabled ? 1 : 0
|
||||||
name = var.name
|
name = var.name
|
||||||
load_balancer_type = var.load_balancer_type
|
load_balancer_type = var.load_balancer_type
|
||||||
subnets = local.subnet_list
|
subnets = local.subnet_list
|
||||||
@@ -6,6 +7,7 @@ resource "aws_lb" "this" {
|
|||||||
}
|
}
|
||||||
|
|
||||||
resource "aws_lb_target_group" "this" {
|
resource "aws_lb_target_group" "this" {
|
||||||
|
count = var.enabled ? 1 : 0
|
||||||
name_prefix = "${var.name}-"
|
name_prefix = "${var.name}-"
|
||||||
port = var.port
|
port = var.port
|
||||||
protocol = var.protocol
|
protocol = var.protocol
|
||||||
@@ -18,13 +20,14 @@ resource "aws_lb_target_group" "this" {
|
|||||||
}
|
}
|
||||||
|
|
||||||
resource "aws_lb_listener" "this" {
|
resource "aws_lb_listener" "this" {
|
||||||
load_balancer_arn = aws_lb.this.id
|
count = var.enabled ? 1 : 0
|
||||||
|
load_balancer_arn = aws_lb.this[0].id
|
||||||
port = var.port
|
port = var.port
|
||||||
protocol = var.protocol
|
protocol = var.protocol
|
||||||
|
|
||||||
default_action {
|
default_action {
|
||||||
type = "forward"
|
type = "forward"
|
||||||
target_group_arn = aws_lb_target_group.this.arn
|
target_group_arn = aws_lb_target_group.this[0].arn
|
||||||
}
|
}
|
||||||
|
|
||||||
depends_on = [aws_lb_target_group.this]
|
depends_on = [aws_lb_target_group.this]
|
||||||
|
|||||||
@@ -1,14 +1,14 @@
|
|||||||
output "lb_arn" {
|
output "lb_arn" {
|
||||||
value = aws_lb.this.id
|
value = aws_lb.this[0].id
|
||||||
description = "The load balancer ARN."
|
description = "The load balancer ARN."
|
||||||
}
|
}
|
||||||
|
|
||||||
output "listener_arn" {
|
output "listener_arn" {
|
||||||
value = aws_lb_listener.this.arn
|
value = aws_lb_listener.this[0].arn
|
||||||
description = "The listener ARN."
|
description = "The listener ARN."
|
||||||
}
|
}
|
||||||
|
|
||||||
output "target_group_arn" {
|
output "target_group_arn" {
|
||||||
value = aws_lb_target_group.this.arn
|
value = aws_lb_target_group.this[0].arn
|
||||||
description = "The target group ARN."
|
description = "The target group ARN."
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -50,3 +50,9 @@ variable "vpc_id" {
|
|||||||
description = "VPC ID for the target group (ref to vpc or platform VPC)."
|
description = "VPC ID for the target group (ref to vpc or platform VPC)."
|
||||||
default = null
|
default = null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
variable "enabled" {
|
||||||
|
type = bool
|
||||||
|
description = "Feature flag: enable/disable this module. Set to false to skip resource creation."
|
||||||
|
default = true
|
||||||
|
}
|
||||||
|
|||||||
@@ -43,6 +43,11 @@
|
|||||||
"type": "string",
|
"type": "string",
|
||||||
"description": "AWS region (CloudFront is global but the provider region is used for the OAC).",
|
"description": "AWS region (CloudFront is global but the provider region is used for the OAC).",
|
||||||
"required": true
|
"required": true
|
||||||
|
},
|
||||||
|
"enabled": {
|
||||||
|
"type": "boolean",
|
||||||
|
"default": true,
|
||||||
|
"description": "Feature flag: enable/disable this module. Set to false to skip resource creation."
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"outputs": {
|
"outputs": {
|
||||||
@@ -75,17 +80,37 @@
|
|||||||
{
|
{
|
||||||
"type": "aws:cloudfront:distribution",
|
"type": "aws:cloudfront:distribution",
|
||||||
"description": "CloudFront distribution with S3 origin via OAC.",
|
"description": "CloudFront distribution with S3 origin via OAC.",
|
||||||
"inputs": ["bucket_regional_domain_name", "price_class", "viewer_protocol_policy", "default_ttl", "max_ttl", "waf_web_acl_arn", "oac_id"],
|
"inputs": [
|
||||||
"outputs": ["distribution_arn", "distribution_domain_name"]
|
"bucket_regional_domain_name",
|
||||||
|
"price_class",
|
||||||
|
"viewer_protocol_policy",
|
||||||
|
"default_ttl",
|
||||||
|
"max_ttl",
|
||||||
|
"waf_web_acl_arn",
|
||||||
|
"oac_id"
|
||||||
|
],
|
||||||
|
"outputs": [
|
||||||
|
"distribution_arn",
|
||||||
|
"distribution_domain_name"
|
||||||
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "aws:cloudfront:originaccesscontrol",
|
"type": "aws:cloudfront:originaccesscontrol",
|
||||||
"description": "Origin Access Control for the S3 origin.",
|
"description": "Origin Access Control for the S3 origin.",
|
||||||
"inputs": ["name", "origin_type", "signing_behavior"],
|
"inputs": [
|
||||||
"outputs": ["oac_id"]
|
"name",
|
||||||
|
"origin_type",
|
||||||
|
"signing_behavior"
|
||||||
|
],
|
||||||
|
"outputs": [
|
||||||
|
"oac_id"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"intra_refs": [
|
"intra_refs": [
|
||||||
{"from": "aws:cloudfront:distribution.oac_id", "to": "aws:cloudfront:originaccesscontrol.oac_id"}
|
{
|
||||||
|
"from": "aws:cloudfront:distribution.oac_id",
|
||||||
|
"to": "aws:cloudfront:originaccesscontrol.oac_id"
|
||||||
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
resource "aws_cloudfront_origin_access_control" "this" {
|
resource "aws_cloudfront_origin_access_control" "this" {
|
||||||
|
count = var.enabled ? 1 : 0
|
||||||
name = local.oac_name
|
name = local.oac_name
|
||||||
origin_access_control_origin_type = local.oac_origin_type
|
origin_access_control_origin_type = local.oac_origin_type
|
||||||
signing_behavior = local.oac_signing_behavior
|
signing_behavior = local.oac_signing_behavior
|
||||||
@@ -6,10 +7,11 @@ resource "aws_cloudfront_origin_access_control" "this" {
|
|||||||
}
|
}
|
||||||
|
|
||||||
resource "aws_cloudfront_distribution" "this" {
|
resource "aws_cloudfront_distribution" "this" {
|
||||||
|
count = var.enabled ? 1 : 0
|
||||||
origin {
|
origin {
|
||||||
origin_id = "s3-origin"
|
origin_id = "s3-origin"
|
||||||
domain_name = var.bucket_regional_domain_name
|
domain_name = var.bucket_regional_domain_name
|
||||||
origin_access_control_id = aws_cloudfront_origin_access_control.this.id
|
origin_access_control_id = aws_cloudfront_origin_access_control.this[0].id
|
||||||
s3_origin_config {
|
s3_origin_config {
|
||||||
origin_access_identity = ""
|
origin_access_identity = ""
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,14 +1,14 @@
|
|||||||
output "distribution_arn" {
|
output "distribution_arn" {
|
||||||
value = aws_cloudfront_distribution.this.arn
|
value = aws_cloudfront_distribution.this[0].arn
|
||||||
description = "The CloudFront distribution ARN."
|
description = "The CloudFront distribution ARN."
|
||||||
}
|
}
|
||||||
|
|
||||||
output "distribution_domain_name" {
|
output "distribution_domain_name" {
|
||||||
value = aws_cloudfront_distribution.this.domain_name
|
value = aws_cloudfront_distribution.this[0].domain_name
|
||||||
description = "The CloudFront distribution domain name."
|
description = "The CloudFront distribution domain name."
|
||||||
}
|
}
|
||||||
|
|
||||||
output "oac_id" {
|
output "oac_id" {
|
||||||
value = aws_cloudfront_origin_access_control.this.id
|
value = aws_cloudfront_origin_access_control.this[0].id
|
||||||
description = "The Origin Access Control ID."
|
description = "The Origin Access Control ID."
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -38,3 +38,9 @@ variable "region" {
|
|||||||
description = "AWS region (CloudFront is global but the provider region is used for the OAC)."
|
description = "AWS region (CloudFront is global but the provider region is used for the OAC)."
|
||||||
default = null
|
default = null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
variable "enabled" {
|
||||||
|
type = bool
|
||||||
|
description = "Feature flag: enable/disable this module. Set to false to skip resource creation."
|
||||||
|
default = true
|
||||||
|
}
|
||||||
|
|||||||
@@ -19,6 +19,11 @@
|
|||||||
"type": "string",
|
"type": "string",
|
||||||
"description": "ARN of the CMK for repository encryption; if absent, uses AWS-managed key.",
|
"description": "ARN of the CMK for repository encryption; if absent, uses AWS-managed key.",
|
||||||
"required": false
|
"required": false
|
||||||
|
},
|
||||||
|
"enabled": {
|
||||||
|
"type": "boolean",
|
||||||
|
"default": true,
|
||||||
|
"description": "Feature flag: enable/disable this module. Set to false to skip resource creation."
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"outputs": {
|
"outputs": {
|
||||||
@@ -48,4 +53,4 @@
|
|||||||
"default": true
|
"default": true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ locals {
|
|||||||
}
|
}
|
||||||
|
|
||||||
resource "aws_ecr_repository" "this" {
|
resource "aws_ecr_repository" "this" {
|
||||||
|
count = var.enabled ? 1 : 0
|
||||||
name = var.name
|
name = var.name
|
||||||
image_tag_mutability = "MUTABLE"
|
image_tag_mutability = "MUTABLE"
|
||||||
image_scanning_configuration {
|
image_scanning_configuration {
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
output "repository_url" {
|
output "repository_url" {
|
||||||
value = aws_ecr_repository.this.repository_url
|
value = aws_ecr_repository.this[0].repository_url
|
||||||
description = "The ECR repository URL."
|
description = "The ECR repository URL."
|
||||||
}
|
}
|
||||||
|
|
||||||
output "repository_arn" {
|
output "repository_arn" {
|
||||||
value = aws_ecr_repository.this.arn
|
value = aws_ecr_repository.this[0].arn
|
||||||
description = "The ECR repository ARN."
|
description = "The ECR repository ARN."
|
||||||
}
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user