docs(P00): research findings — v1.26 (PoA blockchain, deploy model, DynamoDB gap, personas)
---ci--- project: acdl phase: 0 milestone: v1.26 status: research requirements: [REQ-310..REQ-322] personas: [lead-developer, backend-engineer, data-engineer, policy-engineer, blockchain-engineer] ---/ci---
This commit is contained in:
@@ -1,17 +1,17 @@
|
||||
{
|
||||
"phase": 0,
|
||||
"stage": "clarify",
|
||||
"stage": "research",
|
||||
"milestone": "v1.26",
|
||||
"phase_role": "pre_execution",
|
||||
"attempts": 0,
|
||||
"updated_at": "2026-08-12T21:12:00Z",
|
||||
"updated_at": "2026-08-12T21:13:00Z",
|
||||
"project": "acdl",
|
||||
"projects": ["acdl", "nova-blockchain-exchange"],
|
||||
"active_milestone": "v1.26",
|
||||
"milestone_branch": "milestone/v1.26-pilot-activation",
|
||||
"phase_branch": "phase/00-specify-clarify-research-plan",
|
||||
"tag_line": "v1.25.x",
|
||||
"requirements": ["REQ-310", "REQ-311", "REQ-312", "REQ-313", "REQ-314", "REQ-315", "REQ-316", "REQ-317", "REQ-318", "REQ-319", "REQ-320", "REQ-321"],
|
||||
"requirements": ["REQ-310", "REQ-311", "REQ-312", "REQ-313", "REQ-314", "REQ-315", "REQ-316", "REQ-317", "REQ-318", "REQ-319", "REQ-320", "REQ-321", "REQ-322"],
|
||||
"pre_run": {
|
||||
"flaky_test_fixed": "8c68d68 test(metrics): fix attestation-event test freshness time-bomb",
|
||||
"acdl_to_nova_migration": "f844fea chore(bootstrap): migrate ACDL_* env vars to NOVA_*",
|
||||
|
||||
+151
-114
@@ -1,132 +1,169 @@
|
||||
---
|
||||
project: acdl
|
||||
milestone: v1.25
|
||||
milestone: v1.26
|
||||
generated_at: 2026-08-12
|
||||
generator: lead-developer
|
||||
verification_toolchain:
|
||||
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_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"
|
||||
lint: "ruff check core/policy_engine.py adapters/kyverno-json/ 2>/dev/null || python3 -m py_compile core/policy_engine.py"
|
||||
typecheck: "python3 -m py_compile core/confidence_signal.py core/metrics/outcome_backfill.py adapters/terraform/adapter.py modules/l1/dynamodb/terraform/main.tf"
|
||||
test: "pytest tests/test_adapter.py tests/test_contract_resolver.py tests/test_confidence_signal.py tests/test_outcome_backfill.py tests/test_settlement_finality_policy.py tests/test_pilot_readiness_policy.py tests/test_block.py tests/test_order_book.py tests/test_settlement.py -v"
|
||||
lint: "ruff check core/metrics/outcome_backfill.py adapters/kyverno-json/policies/pilot-readiness/ adapters/kyverno-json/policies/settlement-finality/ 2>/dev/null || python3 -m py_compile core/metrics/outcome_backfill.py"
|
||||
note: |
|
||||
v1.25 is the kyverno-json Unified Policy Engine milestone — a feat
|
||||
v1.26 is the Live Pilot Estate Activation milestone — a feat
|
||||
milestone. Four active personas: lead-developer (coordination +
|
||||
docs + ARCHITECTURE.md §12.7), backend-engineer (core/policy_engine.py
|
||||
protocol + registry + contract_resolver.py wiring + run_platform.sh
|
||||
Step 5 + pipeline tests), policy-engineer (adapters/kyverno-json/
|
||||
engine + policies across all 4 target dirs + meta-policies + policy
|
||||
tests + adapter README + STANDARDS.md policy-authoring section),
|
||||
data-engineer (config.json policy object + schemas/README.md note +
|
||||
capability-inventory JSON fixture for regression policies).
|
||||
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).
|
||||
docs + ARCHITECTURE.md §12.8), backend-engineer (confidence_signal.py
|
||||
escalation reason + outcome_backfill.py + run_platform.sh wiring +
|
||||
env-JSON state_backend reconciliation), data-engineer (DynamoDB L1
|
||||
primitive + metrics cold store outcome backfill), policy-engineer
|
||||
(kyverno-json pilot-readiness + settlement-finality policies), +
|
||||
blockchain-engineer (custom, phase-specific — chain core + order
|
||||
engine + settlement). frontend-engineer is deactivated (no UI).
|
||||
Territory enforcement: warn (the pilot is cross-territory by
|
||||
nature — the consumer repo + the platform repo share the milestone).
|
||||
---
|
||||
|
||||
# ACDL — Persona Roster (v1.25 kyverno-json Unified Policy Engine)
|
||||
# PERSONAS — v1.26 Live Pilot Estate Activation
|
||||
|
||||
> v1.25 roster. Four active personas + one deactivated. This is a feat
|
||||
> milestone: the work is a swappable policy-engine protocol + a new
|
||||
> adapter + policies across 4 Nova artifacts + pipeline wiring + docs.
|
||||
> 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.
|
||||
> Generated by the lead-developer at the end of RESEARCH. Assesses the
|
||||
> project domains, activates/deactivates personas, creates custom
|
||||
> personas for domains beyond the default four, aligns frameworks +
|
||||
> territory + constraints to the actual project structure.
|
||||
|
||||
## Active personas
|
||||
## Active Roster (5)
|
||||
|
||||
### lead-developer
|
||||
- **Domain:** coordination + docs
|
||||
- **Frameworks:** []
|
||||
- **Constraints:** ["pragmatic", "battle-tested defaults", "docs match code", "swap boundary is the moat"]
|
||||
- **Territory:**
|
||||
- `.ciagent/ARCHITECTURE.md` (§12.7 Policy Engine Registry — NEW)
|
||||
- `.ciagent/PROJECT.md` (v1.25 section)
|
||||
- `.ciagent/REQUIREMENTS.md` (v1.25 section)
|
||||
- `.ciagent/ROADMAP.md` (v1.25 section)
|
||||
- `.ciagent/PLAN.md`, `.ciagent/RESEARCH.md`, `.ciagent/CLARIFY.md`,
|
||||
`.ciagent/GRILL.md`, `.ciagent/PERSONAS.md`
|
||||
- `docs/METRICS.md` (swappable engine narrative — REQ-307)
|
||||
- **Reason:** Owns the milestone coordination + the architecture
|
||||
narrative. The swap boundary (PolicyEngine protocol) is the moat per
|
||||
Strategic Objective #2 — the lead-developer owns the boundary
|
||||
description in ARCHITECTURE.md §12.7 and the docs/METRICS.md note.
|
||||
No Python policy code (backend-engineer + policy-engineer territory).
|
||||
No UI (frontend-engineer deactivated).
|
||||
### 1. lead-developer (active)
|
||||
- **active:** true
|
||||
- **phase_specific:** false
|
||||
- **reason:** Coordinates task decomposition + resolves conflicts between
|
||||
engineering personas. Owns the milestone narrative (PROJECT.md,
|
||||
ROADMAP.md, ARCHITECTURE.md §12.8). Final architectural decisions when
|
||||
personas disagree (e.g. where the outcome-backfill emitter lives).
|
||||
- **domain:** project coordination, milestone narrative, cross-persona
|
||||
conflict resolution.
|
||||
- **frameworks:** none (coordination role).
|
||||
- **territory:** `.ciagent/`, `docs/METRICS.md`, `adapters/README.md`,
|
||||
`modules/README.md`, `modules/STANDARDS.md`.
|
||||
- **constraints:** does not write Python/Terraform (delegates to
|
||||
backend/data-engineer); does not author policies (delegates to
|
||||
policy-engineer); does not author chain code (delegates to
|
||||
blockchain-engineer).
|
||||
|
||||
### backend-engineer
|
||||
- **Domain:** backend (Python + bash + pipeline wiring)
|
||||
- **Frameworks:** ["boto3", "terraform"]
|
||||
- **Constraints:** ["api-first", "strict-typing", "engine-agnostic confidence signal", "fail-soft when kj absent"]
|
||||
- **Territory:**
|
||||
- `core/policy_engine.py` (NEW — PolicyEngine Protocol + PolicyEngineRegistry + NullEngine)
|
||||
- `core/contract_resolver.py` (MODIFIED — invoke registry pre/post resolve)
|
||||
- `scripts/run_platform.sh` (MODIFIED — Step 5 kyverno-json parallel pass)
|
||||
- `scripts/install-kyverno-json.sh` (NEW)
|
||||
- `tests/test_policy_engine.py` (NEW — protocol conformance, registry, NullEngine)
|
||||
- `tests/test_run_platform_plan_json_policies.py` (NEW — script-substring assertion)
|
||||
- `.github/workflows/ci.yml` + `.gitea/workflows/ci.yml` (MODIFIED — Go + kj install)
|
||||
- **Reason:** Owns the Python protocol layer + the pipeline wiring. The
|
||||
`PolicyEngine` Protocol + `PolicyEngineRegistry` are Python structural-
|
||||
typing constructs (PEP 544) — backend-engineer's strict-typing
|
||||
constraint. The `contract_resolver.py` wiring + `run_platform.sh`
|
||||
Step 5 are backend territory. Does NOT write kyverno-json policy
|
||||
files (policy-engineer territory) — only the Python that *invokes* the
|
||||
engine. Does NOT modify the confidence signal (it already consumes
|
||||
`list[PolicyCheckResult]` engine-agnostically — PROJECT.md hard-
|
||||
constraint).
|
||||
### 2. backend-engineer (active)
|
||||
- **active:** true
|
||||
- **phase_specific:** false
|
||||
- **reason:** Owns the platform-side Python changes: confidence signal
|
||||
escalation reason (REQ-318), outcome-backfill emitter (REQ-317),
|
||||
env-JSON state_backend wiring (REQ-319), adapter test updates for
|
||||
DynamoDB (REQ-322), regression CAP-025 (REQ-316).
|
||||
- **domain:** core Python (confidence_signal.py, metrics/, adapter.py,
|
||||
regression_verify.py, contract_resolver.py), run_platform.sh wiring.
|
||||
- **frameworks:** Python 3.12, pytest, boto3, SQLite, DynamoDB.
|
||||
- **territory:** `core/confidence_signal.py`, `core/metrics/`,
|
||||
`adapters/terraform/adapter.py`, `core/regression_verify.py`,
|
||||
`core/environments/`, `scripts/run_platform.sh`, `tests/test_adapter.py`,
|
||||
`tests/test_confidence_signal.py`, `tests/test_outcome_backfill.py`,
|
||||
`tests/test_regression_pilot.py`.
|
||||
- **constraints:** does not change `schemas/policy_check_result.schema.json`
|
||||
(v1.25 moat, D-211); does not change `schemas/contract.schema.json`
|
||||
(no schema breaks, D-213); does not author Terraform modules
|
||||
(delegates to data-engineer for DynamoDB); does not author policies
|
||||
(delegates to policy-engineer); does not author chain code (delegates
|
||||
to blockchain-engineer).
|
||||
|
||||
### policy-engineer
|
||||
- **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.
|
||||
### 3. data-engineer (active)
|
||||
- **active:** true
|
||||
- **phase_specific:** false
|
||||
- **reason:** Owns the DynamoDB L1 primitive (REQ-322) — the single
|
||||
platform-side module build-out. Owns the metrics cold store
|
||||
outcome-backfill integration (REQ-317, the `fact_decision.outcome`
|
||||
column + `backfilled_at` timestamp). Owns the env-JSON data updates
|
||||
(REQ-319, `core/environments/*.json` account_id + state_backend.bucket).
|
||||
- **domain:** Terraform modules (`modules/l1/`), schema definitions
|
||||
(`interface.json`), registry (`modules/registry.json`), metrics cold
|
||||
store (`metrics/nova_metrics.db`, `core/metrics/collector.py`).
|
||||
- **frameworks:** Terraform, JSON, SQLite, DynamoDB, boto3.
|
||||
- **territory:** `modules/l1/dynamodb/`, `modules/registry.json`,
|
||||
`modules/README.md`, `core/environments/*.json`,
|
||||
`core/metrics/collector.py`, `tests/test_adapter.py` (DynamoDB
|
||||
emission test).
|
||||
- **constraints:** does not change the adapter (stateless, v1.11);
|
||||
follows the v1.8 NFR defaults (encryption + deletion protection +
|
||||
PITR); follows the module standards (`modules/STANDARDS.md`).
|
||||
|
||||
### data-engineer
|
||||
- **Domain:** data (config schema + structured fixtures)
|
||||
- **Frameworks:** ["jsonschema", "yaml"]
|
||||
- **Constraints:** ["schema-first", "type-safe config", "backward-compatible additions"]
|
||||
- **Territory:**
|
||||
- `.ciagent/config.json` (MODIFIED — new `policy` object: engine + policy_root)
|
||||
- `schemas/policy_check_result.schema.json` (READ-ONLY — no change per D-116)
|
||||
- `schemas/README.md` (MODIFIED — note engine: "kyverno" shared by K8s adapter + kj)
|
||||
- `tests/fixtures/capability_inventory.json` (NEW — clean + drifted inventory fixtures for regression policies)
|
||||
- **Reason:** The `config.json.policy` object is a schema-first addition
|
||||
(new top-level key with `engine` + `policy_root` fields). The
|
||||
capability-inventory JSON fixtures for the regression-gate policies
|
||||
(REQ-304) are structured data — the data-engineer owns the fixture
|
||||
shape. The `policy_check_result.schema.json` is read-only (D-116 — no
|
||||
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).
|
||||
### 4. policy-engineer (active, custom — added in v1.25)
|
||||
- **active:** true
|
||||
- **phase_specific:** false
|
||||
- **reason:** Owns the kyverno-json policy authoring for the pilot:
|
||||
settlement-finality (REQ-315), pilot-readiness (REQ-320). Extends
|
||||
v1.25's policy engine to the securities domain.
|
||||
- **domain:** declarative policies (kyverno-json ValidatingPolicy YAML),
|
||||
JMESPath assertions, policy tests.
|
||||
- **frameworks:** kyverno-json, JMESPath, JSON, pytest.
|
||||
- **territory:** `adapters/kyverno-json/policies/pilot-readiness/`,
|
||||
`adapters/kyverno-json/policies/settlement-finality/`,
|
||||
`tests/test_settlement_finality_policy.py`,
|
||||
`tests/test_pilot_readiness_policy.py`.
|
||||
- **constraints:** policies are declarative (no imperative Python);
|
||||
`is_configured()` guard skips gracefully when `kj` absent; follows
|
||||
the v1.25 policy-authoring standard (`modules/STANDARDS.md` policy
|
||||
section + `adapters/kyverno-json/README.md`).
|
||||
|
||||
## Deactivated personas
|
||||
### 5. blockchain-engineer (active, custom, phase-specific — added in v1.26)
|
||||
- **active:** true
|
||||
- **phase_specific:** true (created for v1.26 P1; removed after P1
|
||||
unless the chain has ongoing work in P2..P4)
|
||||
- **reason:** The pilot introduces a homegrown blockchain — a domain
|
||||
beyond the default four personas. Owns the chain core (block, ledger,
|
||||
validator, REQ-310), the order-matching engine (REQ-311), the
|
||||
settlement service (REQ-312), and the consumer `contract.yaml`
|
||||
(REQ-313) + deploy invocation (REQ-314).
|
||||
- **domain:** blockchain consensus (PoA, single validator), order
|
||||
matching (limit order book, price-time priority), settlement
|
||||
(T+1, finality = block commit), consumer-repo deploy model.
|
||||
- **frameworks:** Python 3.12 (the chain is Python, not Solidity/Go —
|
||||
it's a homegrown ledger, not a smart-contract platform), pytest,
|
||||
YAML (contract.yaml), GitHub Actions / Gitea Actions (deploy.yml
|
||||
invocation).
|
||||
- **territory:** `/root/nova-blockchain-exchange/` (the consumer repo:
|
||||
`chain/`, `engine/`, `settlement/`, `contract.yaml`,
|
||||
`contracts/*.yml`, `.github/workflows/deploy.yml`,
|
||||
`.gitea/workflows/deploy.yml`, `tests/`).
|
||||
- **constraints:** the chain is deterministic (same inputs → same block)
|
||||
— it is automation, not AI (NORTH_STAR Objective #2 tenet); equities
|
||||
only (D-200); single validator PoA (D-201); the consumer deploy MUST
|
||||
go through `deploy.yml@v1.25` (no direct terraform apply); the
|
||||
contract MUST validate against `schemas/contract.schema.json`.
|
||||
|
||||
### frontend-engineer
|
||||
## Deactivated (1)
|
||||
|
||||
### frontend-engineer (inactive)
|
||||
- **active:** false
|
||||
- **Reason:** ACDL has no frontend (no package.json — confirmed in
|
||||
config.json personas.personas[frontend-engineer].reason). v1.25 adds
|
||||
no UI work — the policy engine is backend + policy artifacts only.
|
||||
Deactivated per the v1.15+ convention.
|
||||
- **phase_specific:** false
|
||||
- **reason:** The pilot has no UI — the blockchain exchange is a
|
||||
backend service (matching engine + settlement). The consumer repo
|
||||
has no web/frontend. Reactivated if a future milestone adds a trading
|
||||
dashboard.
|
||||
|
||||
## Phase-Specific Notes
|
||||
|
||||
- **blockchain-engineer** is created for v1.26 P1 (blockchain core +
|
||||
order engine + settlement). If P2..P4 have no chain changes, the
|
||||
persona is removed after P1 (the chain is a stable substrate for the
|
||||
pilot run). If P2 (consumer-contract-and-deploy) requires chain
|
||||
adjustments, the persona stays through P2.
|
||||
- **policy-engineer** is active for P3 (pilot-metrics-and-policies) +
|
||||
may consult on P4 (pilot run policy verification).
|
||||
- **data-engineer** is active for P3 (DynamoDB primitive + outcome
|
||||
backfill + env-JSON) + P4 (regression CAP-025 may touch the registry).
|
||||
|
||||
## Territory Enforcement
|
||||
|
||||
- **Mode:** `warn` (the pilot is cross-territory by nature — the
|
||||
consumer repo + the platform repo share the milestone; the
|
||||
blockchain-engineer works in the consumer repo, backend/data/policy
|
||||
engineers work in the platform repo).
|
||||
- **Cross-territory collisions:** REQ-322 (DynamoDB primitive) is
|
||||
data-engineer territory, but the adapter test update
|
||||
(`tests/test_adapter.py` `EXPECTED_L1_KEYS`) is backend-engineer
|
||||
territory. The lead-developer resolves: data-engineer authors the
|
||||
module + registry; backend-engineer updates the test assertion
|
||||
(the test is backend territory, the module is data territory).
|
||||
+4
-2
@@ -1759,13 +1759,15 @@ the pilot end-to-end.
|
||||
|
||||
### Requirements
|
||||
|
||||
New requirements REQ-310..REQ-321 — see
|
||||
New requirements REQ-310..REQ-322 — see
|
||||
`.ciagent/nova-blockchain-exchange/REQUIREMENTS.md` §v1.26. Summary:
|
||||
blockchain core (REQ-310), order engine (REQ-311), settlement
|
||||
(REQ-312), consumer contract (REQ-313), deploy invocation (REQ-314),
|
||||
settlement-finality policy (REQ-315), pilot regression CAP (REQ-316),
|
||||
outcome backfill (REQ-317), escalation reason (REQ-318), env-JSON
|
||||
wiring (REQ-319), pilot-readiness policy (REQ-320), docs (REQ-321).
|
||||
wiring (REQ-319), pilot-readiness policy (REQ-320), docs (REQ-321),
|
||||
DynamoDB L1 primitive (REQ-322 — the single platform-side module
|
||||
build-out; ECS + S3 already exist).
|
||||
|
||||
### Hard constraints
|
||||
|
||||
|
||||
+192
-380
@@ -1,438 +1,250 @@
|
||||
# Nova — v1.25 Research Findings
|
||||
# Nova — v1.26 Research Findings
|
||||
|
||||
> Phase: research (pre-execution). Milestone: v1.25 (kyverno-json Unified
|
||||
> Policy Engine). Status: research. Researcher: ci-researcher.
|
||||
> Phase: research (pre-execution). Milestone: v1.26 (Live Pilot Estate
|
||||
> Activation). Status: research. Researcher: ci-researcher.
|
||||
> Autonomy: full.
|
||||
|
||||
## 1. Problem domain
|
||||
---
|
||||
|
||||
Nova's compliance/policy posture is fragmented across three engines with
|
||||
three rule languages and three adapter shapes (see PROJECT.md v1.25
|
||||
"Why" for the full diagnosis). The `PolicyCheckResult` schema
|
||||
(`schemas/policy_check_result.schema.json`) is already the engine-agnostic
|
||||
contract that `core/confidence_signal.py` consumes — the *contract* is
|
||||
right; the *orchestration* is fragmented. There is no single declarative
|
||||
place where "what Nova considers compliant" lives. The K8s-only Kyverno
|
||||
adapter (`adapters/kyverno/`) can't help because it only speaks to K8s
|
||||
manifests and the platform emits Terraform (D-053).
|
||||
## 1. Domain — Homegrown PoA Blockchain for Securities Settlement
|
||||
|
||||
`kyverno-json` is the correction: a Kyverno-ecosystem runtime that applies
|
||||
Kyverno policies to **any** JSON/YAML payload. It becomes the **unified
|
||||
orchestrator** of compliance checks, behind a swappable `PolicyEngine`
|
||||
protocol so OPA can replace it one day. Checkov and Wiz remain as
|
||||
raw-finding adapters feeding *into* kyverno-json meta-policies.
|
||||
### 1.1 Why a homegrown chain (not Ethereum/Solana/Hyperledger)
|
||||
|
||||
## 2. kyverno-json — the engine surface
|
||||
The pilot's purpose is to exercise the Nova platform's deploy/policy/
|
||||
attestation gates over a real consumer estate — not to build a
|
||||
production blockchain. A homegrown PoA ledger is the minimal viable
|
||||
chain: append-only blocks, single validator (pilot), SHA-256 hash chain,
|
||||
deterministic block production. It records every order, match, and
|
||||
settlement as transactions; settlement finality = block commit. This
|
||||
is sufficient to demonstrate that Nova's policy engine (kyverno-json)
|
||||
can assert settlement finality declaratively (REQ-315) and that the
|
||||
Decision Ledger captures the apply decision.
|
||||
|
||||
### 2.1 What it is
|
||||
A production chain (Ethereum/Solana/Hyperledger) would be the *consumer
|
||||
app's* choice, not the platform's. The platform is chain-agnostic — it
|
||||
deploys whatever the consumer's `contract.yaml` declares. For the pilot,
|
||||
the homegrown chain is the simplest way to produce a real consumer
|
||||
estate without a heavyweight external dependency.
|
||||
|
||||
[kyverno-json](https://github.com/kyverno/kyverno-json) is a standalone Go
|
||||
binary from the Kyverno project. It is a **separate runtime** from the
|
||||
Kyverno K8s admission controller — same policy lineage, different
|
||||
application target. Where Kyverno (K8s) evaluates `ClusterPolicy`
|
||||
resources against Kubernetes manifests at admission time, kyverno-json
|
||||
evaluates `ValidatingPolicy` resources against **any** JSON or YAML
|
||||
payload file via the CLI (`kj scan`) or a Go library. It is **not** a
|
||||
Python package (no PyPI release); it is installed via
|
||||
`go install github.com/kyverno/kyverno-json/cmd/kj@latest` (D-115) or by
|
||||
downloading a pinned binary from GitHub releases.
|
||||
### 1.2 PoA consensus — single validator (pilot)
|
||||
|
||||
### 2.2 CLI surface (the v1.25 invocation path)
|
||||
Proof-of-Authority with a single validator is the minimal consensus
|
||||
model: the validator proposes + commits blocks. No Byzantine fault
|
||||
tolerance (single validator = no forks). Deterministic block
|
||||
production: same ordered transactions → same block (same hash). This
|
||||
makes the chain auditable (the hash chain is verifiable) and
|
||||
reproducible (a replay produces the same chain). Multi-validator BFT
|
||||
is a future milestone (D-201).
|
||||
|
||||
The v1.25 engine uses the `kj scan` subcommand:
|
||||
### 1.3 T+1 settlement finality
|
||||
|
||||
```
|
||||
kyverno-json scan [flags]
|
||||
Equities settle T+1 (trade date + 1 business day). The pilot's
|
||||
settlement service records matches as transactions on the chain; a
|
||||
settlement is final when its block is committed. The settlement-finality
|
||||
kyverno-json policy (REQ-315) asserts `all_committed: true` before any
|
||||
promotion (qa→prod) — the declarative gate that turns settlement
|
||||
finality into a policy artifact. This is the securities-specific
|
||||
extension of v1.25's policy engine: the same `KyvernoJsonEngine`
|
||||
evaluates a policy over a new payload shape (settlement-service status
|
||||
JSON).
|
||||
|
||||
Flags:
|
||||
--labels strings Labels selectors for policies
|
||||
--output string Output format (text or json) (default "text")
|
||||
--payload string Path to payload (json or yaml file)
|
||||
--policy strings Path to kyverno-json policies
|
||||
--pre-process strings JMESPath expression used to pre process payload
|
||||
```
|
||||
### 1.4 Equities-only scope (D-200)
|
||||
|
||||
The `KyvernoJsonEngine.evaluate()` implementation (REQ-293) invokes:
|
||||
```
|
||||
kj scan --policy <policy_dir> --payload <payload.json> --output json
|
||||
```
|
||||
and parses the JSON `results[]` array. The `--pre-process` flag is
|
||||
available for JMESPath pre-projection (noted for the meta-policy use case
|
||||
where the payload is the merged PCR list and a pre-process expression
|
||||
can index by `ruleId` — recorded as a future optimization, not used in
|
||||
v1.25's initial implementation).
|
||||
Bonds (T+2), derivatives (varying), and options (exercise models) have
|
||||
different settlement models. A pilot should demonstrate the Nova
|
||||
platform's gates over the simplest case (equities T+1) before
|
||||
expanding. "All types of securities" is the product vision; v1.26 is
|
||||
the pilot (equities first). Future milestones add other security types
|
||||
with their settlement models.
|
||||
|
||||
Other subcommands (`kj jp`, `kj serve`, `kj playground`, `kj docs`) are
|
||||
out of scope for v1.25. `kj serve` is the long-running web-app mode
|
||||
(noted as a future consideration for lower-latency evaluation in the
|
||||
Out of Scope section of REQUIREMENTS.md). `kj jp` is the JMESPath REPL —
|
||||
useful for policy authoring/debugging, not invoked by the engine.
|
||||
---
|
||||
|
||||
### 2.3 Policy structure (the `ValidatingPolicy` resource)
|
||||
## 2. Nova Consumer Deploy Model
|
||||
|
||||
kyverno-json policies are Kubernetes-style resources (cluster-scoped)
|
||||
belonging to the `json.kyverno.io` API group, kind `ValidatingPolicy`,
|
||||
version `v1alpha1`:
|
||||
### 2.1 The reusable `deploy.yml@v1.25` workflow
|
||||
|
||||
```yaml
|
||||
apiVersion: json.kyverno.io/v1alpha1
|
||||
kind: ValidatingPolicy
|
||||
metadata:
|
||||
name: <policy-name> # becomes the KJ_<policy-name> ruleId prefix
|
||||
spec:
|
||||
rules:
|
||||
- name: <rule-name>
|
||||
identifier: <jmespath> # optional — path to the unique entry id
|
||||
match: # assertion tree — which payload entries
|
||||
any: # the rule applies to
|
||||
- <assertion>
|
||||
exclude: # optional — exclude matching entries
|
||||
any:
|
||||
- <assertion>
|
||||
context: # optional — named bindings available to
|
||||
- name: <binding> # the rule's assertions ($<binding>)
|
||||
variable: <value>
|
||||
validate:
|
||||
message: "<human-readable>" # optional per-rule message
|
||||
assert:
|
||||
all: # all assertions must hold
|
||||
- check: <assertion-tree>
|
||||
message: "<per-check>"
|
||||
# OR
|
||||
any: # at least one assertion must hold
|
||||
- check: <assertion-tree>
|
||||
```
|
||||
The platform's `.github/workflows/deploy.yml` is a `workflow_call` —
|
||||
a reusable workflow that a consumer repo invokes via
|
||||
`uses: acdl/.github/workflows/deploy.yml@v1.25`. Inputs: `contract`
|
||||
(default `.nova/contract.yml`), `mode` (default `full`; enum
|
||||
`full|plan-only|check-only|decommission`), `environment` (override).
|
||||
The workflow checks out the consumer repo + the platform repo, runs
|
||||
`scripts/run_platform.sh`, and records the apply decision +
|
||||
attestation in the Decision Ledger. Secrets: `NOVA_AWS_*`
|
||||
(account + access key + secret) + `NOVA_LAMBDA_URL` (error reporting).
|
||||
|
||||
Key differences from K8s Kyverno policies:
|
||||
- **Always cluster-scoped** — no `namespace` field.
|
||||
- **No `forEach`, pattern operators, anchors, or wildcards.** Iteration
|
||||
is done via the `~` projection modifier in assertion trees (see §2.4).
|
||||
- **Assertion trees** with JMESPath expressions replace Kyverno's
|
||||
pattern-matching syntax (see §2.4).
|
||||
The pilot consumer (`nova-blockchain-exchange`) invokes this workflow
|
||||
with `mode: full` for `dev` (D-209). The `.gitea/workflows/deploy.yml`
|
||||
mirror is byte-identical (the platform's deploy workflow is
|
||||
forge-agnostic — Gitea + GitHub).
|
||||
|
||||
### 2.4 Assertion trees (the rule language)
|
||||
### 2.2 `run_platform.sh --apply` path (confirmed)
|
||||
|
||||
An `assert` declaration contains an `all` or `any` list. Each entry has a
|
||||
`check` (the assertion tree — a nested JMESPath projection) and an
|
||||
optional `message`. **All comparisons happen in the leaves of the tree.**
|
||||
`scripts/run_platform.sh:431-455` — the `--apply` (or `mode: full`)
|
||||
path runs `terraform apply -auto-approve` after the HITL gate
|
||||
(`:438`). For `dev` (autonomous, no HITL gate), the apply proceeds
|
||||
directly. The apply records the env via `core/env_transition.py record`
|
||||
(`:450`). The full pipeline (no `--apply` flag) continues to Step 7
|
||||
(confidence signal) + Step 8 (outbox write).
|
||||
|
||||
A simple example (assert a pod doesn't use the default service account):
|
||||
```yaml
|
||||
validate:
|
||||
assert:
|
||||
all:
|
||||
- message: "serviceAccountName 'default' is not allowed"
|
||||
check:
|
||||
spec:
|
||||
(serviceAccountName == 'default'): false
|
||||
```
|
||||
**Gap (noted in RESEARCH §4):** the `--apply` path exits before the
|
||||
outbox write (Step 8). The pilot runs the full pipeline (not `--apply`
|
||||
alone), so the outbox write happens. The `run.completed` event lands in
|
||||
the JSONL Decision Ledger (not the DynamoDB outbox) — this is by design
|
||||
(the outbox is the platform-run evidence stream; the Decision Ledger is
|
||||
the cold store for metrics).
|
||||
|
||||
The `(expression)` syntax evaluates a JMESPath expression; the result
|
||||
becomes the current object for descendants; the leaf value is compared
|
||||
to the expected value.
|
||||
### 2.3 Contract schema — multi-module manifest
|
||||
|
||||
**Iteration via the `~` modifier.** The `~` prefix on a key applies
|
||||
descendant assertions to **each element** of an array/map individually
|
||||
(rather than comparing the whole array). Given `foo.bar: [1,2,3]`:
|
||||
```yaml
|
||||
check:
|
||||
foo:
|
||||
~.bar: # iterate each element
|
||||
(@ < `5`): true # assert each element < 5
|
||||
```
|
||||
The `~index_name.bar` form binds the index (array) or key (map) to
|
||||
`$index_name` for use in descendants. This is how v1.25 iterates
|
||||
`resources[]` in the Stack IR policies (REQ-297) and
|
||||
`planned_values.root_module.resources[]` in the plan-JSON policies
|
||||
(REQ-300).
|
||||
`schemas/contract.schema.json:7,24-48` — required fields: `id`,
|
||||
`name`, `environment`, `infrastructure`. The `infrastructure` block is
|
||||
`minProperties: 1` with `patternProperties` accepting any module name
|
||||
key. Multi-module manifest is supported: one contract can declare
|
||||
`infrastructure: { microservice: {...}, dynamodb: {...}, s3: {...} }`.
|
||||
The constraint is the `modules/registry.json` (the module must be
|
||||
registered), not the schema.
|
||||
|
||||
**Explicit bindings** via `->binding_name` allow descendants to refer
|
||||
to a parent node via `$binding_name`. Built-in bindings: `$payload`
|
||||
(the whole input), `$policy`, `$rule`.
|
||||
---
|
||||
|
||||
**Escaping** via `\key\` prevents projection when a payload key collides
|
||||
with the projection syntax. Not needed for Nova payloads (no `(key)`
|
||||
fields), noted for completeness.
|
||||
## 3. Platform Module Readiness (the critical finding)
|
||||
|
||||
### 2.5 Output shape (what `kj scan --output json` produces)
|
||||
### 3.1 The adapter is stateless (v1.11 rewrite)
|
||||
|
||||
The JSON output is a `results[]` array. Each result entry has (at
|
||||
minimum):
|
||||
- `policy`: the policy metadata.name
|
||||
- `rule`: the rule name
|
||||
- `result`: `"pass"` | `"fail"` | `"error"` | `"skip"` (lowercase)
|
||||
- `message`: the assertion message (or engine error message)
|
||||
- `resource`: the matched payload entry (the `identifier` value, or the
|
||||
whole payload when no identifier/match)
|
||||
- `namespace`/`kind`/`name`: K8s-style fields (present but empty for
|
||||
non-K8s payloads — the K8s Kyverno adapter's evidence uses these; the
|
||||
kyverno-json engine's evidence uses `assertion`/`jmespath` instead)
|
||||
- `severity`: not present by default (kyverno-json does not assign
|
||||
severities — the Nova policy author assigns severity via a Nova-
|
||||
specific annotation; see §2.6)
|
||||
`adapters/terraform/adapter.py:1-11` — the adapter is a "STATELESS
|
||||
ASSEMBLER" that owns no module content. There is **no `TYPE_MAP`**,
|
||||
`INPUT_MAP`, or `OUTPUT_MAP` (deleted in the v1.11 stateless rewrite;
|
||||
`modules/STANDARDS.md:212-214` confirms). A new stack type requires a
|
||||
new L1 module (`modules/l1/<name>/` with `interface.json` +
|
||||
`terraform/main.tf` + `README.md` + `instance.json`) + a
|
||||
`modules/registry.json` entry — not an adapter change.
|
||||
|
||||
The `KyvernoJsonEngine._to_pcr()` translator (REQ-293) maps:
|
||||
- `policy` → `ruleId` (prefixed `KJ_<policy_name>` per D-116)
|
||||
- `result` → `result` (`pass`/`fail`/`error` → pass/fail/error;
|
||||
`skip`/`skipped` → skipped)
|
||||
- `message` → `message`
|
||||
- `resource` → `resourceRef` + `evidence.resource`
|
||||
- severity from the policy's `metadata.annotations` (see §2.6)
|
||||
- `engine: "kyverno"` (per D-116 — no new enum value)
|
||||
### 3.2 ECS — ready
|
||||
|
||||
### 2.6 Severity assignment (Nova convention)
|
||||
`modules/l1/ecs-service/terraform/main.tf:1,11` —
|
||||
`aws_ecs_task_definition` + `aws_ecs_service`. `interface.json:5-6` —
|
||||
`type: aws:ecs:task_definition`. `registry.json:29-37` — registered.
|
||||
Tests: `test_adapter.py:164-185,257-360`, `test_contract_resolver.py:61-92`.
|
||||
The `microservice` L2 (`modules/l2/microservice/composition.json`)
|
||||
references 6 L1 children (ecs-cluster, ecr, iam-role, alb, ecs-service,
|
||||
kms-key) — the ECS pattern is fully wired end-to-end.
|
||||
|
||||
kyverno-json does not natively assign severities to results. Nova's
|
||||
confidence signal requires a `severity` per PCR (critical/high/medium/
|
||||
low/info). The v1.25 convention: each Nova policy file declares its
|
||||
severity via a `metadata.annotations` field:
|
||||
### 3.3 S3 — ready
|
||||
|
||||
```yaml
|
||||
metadata:
|
||||
name: forbid-public-ingress
|
||||
annotations:
|
||||
nova.cloudinit.dev/severity: high
|
||||
```
|
||||
`modules/l1/s3/terraform/main.tf:1` — `aws_s3_bucket` (+ versioning +
|
||||
SSE). `interface.json:5-6` — `type: aws:s3:bucket`. `registry.json:2-10`
|
||||
— registered. Tests: `test_adapter.py:56-110,241-257`,
|
||||
`test_contract_resolver.py:36-51,92-130`.
|
||||
|
||||
The `KyvernoJsonEngine._to_pcr()` 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`. This keeps severity in the policy (declarative, version-
|
||||
controlled) rather than in the engine adapter (imperative). The
|
||||
annotation key is `nova.cloudinit.dev/severity` (matches the existing
|
||||
`nova.cloudinit.dev` namespace used in `schemas/tagging-standard.json`).
|
||||
### 3.4 DynamoDB — GAP (REQ-322)
|
||||
|
||||
## 3. The four policy targets (v1.25 scope)
|
||||
**No `modules/l1/dynamodb/` directory, no `registry.json` key, no
|
||||
`interface.json`, no `terraform/`, no tests.** The blockchain exchange's
|
||||
ledger table needs this primitive. REQ-322 authors it: `interface.json`
|
||||
(stack type `aws:dynamodb:table`), `terraform/main.tf`
|
||||
(`aws_dynamodb_table` with PK + optional SK, `PAY_PER_REQUEST` default,
|
||||
encryption + PITR enabled per v1.8 NFR defaults), `README.md`,
|
||||
`instance.json`, + `registry.json` entry. The adapter needs no change
|
||||
(stateless); the contract's `infrastructure.dynamodb` block references
|
||||
this primitive. This is the single platform-side module build-out for
|
||||
the milestone.
|
||||
|
||||
### 3.1 Consumer contract JSON (REQ-295)
|
||||
### 3.5 Stale doc (not a blocker)
|
||||
|
||||
The payload is the parsed contract dict (the raw YAML loaded as JSON).
|
||||
Policies assert the `contract.schema.json` constraints declaratively:
|
||||
`require-id-pattern` (JMESPath regex `^[a-z][a-z0-9-]{2,5}$` over
|
||||
`id`), `require-env-in-enum` (`environment` in `["dev","qa","prod","dr"]`),
|
||||
`require-infrastructure-min-1` (`length(infrastructure) > 0`),
|
||||
`forbid-unknown-fields` (keys subset of the 4 allowed). These 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.
|
||||
`adapters/README.md:49-54` references the deleted `TYPE_MAP`/
|
||||
`INPUT_MAP`/`OUTPUT_MAP` — contradicts `adapter.py:1-11` +
|
||||
`modules/STANDARDS.md:212-214`. REQ-321 (docs) should fix this.
|
||||
|
||||
**Invocation point:** `core/contract_resolver.py` pre-resolve (REQ-296).
|
||||
Early-fail: if a contract policy fails, the resolver still proceeds
|
||||
(the confidence signal decides the gate, consistent with the existing
|
||||
`--soft-fail` Checkov pattern) — but the failing PCRs are in the
|
||||
`policy` input, which lowers the score.
|
||||
---
|
||||
|
||||
### 3.2 Resolved Target Stack IR JSON (REQ-297)
|
||||
## 4. Metric Pipeline Grounding (Post-Pilot targets)
|
||||
|
||||
The payload is the resolved Stack IR dict produced by
|
||||
`core/contract_resolver.py` (the merged module outputs). Policies
|
||||
assert over `resources[]` (the array of resolved resources):
|
||||
`require-tagging-standard` (every resource's `tags` has `nova:owner` +
|
||||
`nova:environment` — ports
|
||||
`adapters/terraform/policy/custom_rules/nova_tagging.py`),
|
||||
`forbid-public-ingress` (no resource has `public_ingress: true` — the
|
||||
v1.0 demo rule, now declarative), `require-encryption-by-default` (every
|
||||
S3/EBS/KMS-aliased resource carries encryption config — ports the v1.8
|
||||
D-encryption-default rule). The `~` modifier iterates `resources[]`.
|
||||
### 4.1 AI Decision Accuracy — outcome backfill (REQ-317)
|
||||
|
||||
**Invocation point:** `core/contract_resolver.py` post-resolve (REQ-298).
|
||||
Additive — the resolver's return values and exceptions are unchanged;
|
||||
the PCRs are appended to the contract-policy PCRs.
|
||||
`core/metrics/decision_ledger.py:210-211` documents the event chain:
|
||||
`confidence.computed → ai.decision.made → attestation.recorded →
|
||||
run.completed/failed`. `collector.py:262` inserts `fact_decision.outcome`
|
||||
as `"pending"` — **there is no outcome-backfill step** wiring
|
||||
`run.completed`/`run.failed` back into `fact_decision.outcome`. The AI
|
||||
Decision Accuracy metric (`trust_snapshot.py:70-85`, `_get_ai_decision_accuracy`)
|
||||
reads `decisions WHERE outcome='succeeded' ÷ total` — so it reads 0%
|
||||
today (all pending). REQ-317 adds `core/metrics/outcome_backfill.py`
|
||||
that reads run-manifest events and updates `fact_decision.outcome` +
|
||||
`fact_decision.backfilled_at`. The PCR schema is unchanged (D-211).
|
||||
|
||||
### 3.3 Terraform plan JSON (REQ-300)
|
||||
### 4.2 Human Escalation Frequency — `reason='confidence'` tag (REQ-318)
|
||||
|
||||
The payload is `terraform show -json <tfplan>` output. Policies assert
|
||||
over `planned_values.root_module.resources[]`:
|
||||
`forbid-plaintext-secrets` (no `aws_db_instance.password` /
|
||||
`aws_iam_user.login_profile.password` in plaintext — ports
|
||||
`CKV_AWS_41/45/46`), `forbid-iam-wildcard` (no `Action: "*"` or
|
||||
`Resource: "*"` in `aws_iam_policy.PolicyDocument` — ports
|
||||
`CKV_AWS_1/40`), `require-kms-reference` (KMS keys referenced by alias,
|
||||
not inline key material — ports `CKV_AWS_7/33`). These are declarative
|
||||
**mirrors** of `checkov_adapter.py:RULE_MAP` — the Checkov rule stays
|
||||
the source of truth for `terraform_plan` scanning; the kyverno-json
|
||||
policy covers the same plan JSON with a different rule language
|
||||
(defense-in-depth against engine drift).
|
||||
`core/confidence_signal.py:184` — a `block` band sets
|
||||
`human_override=True` in the `ai.decision.made` event.
|
||||
`run_platform.sh:636` fails the pipeline on `block`. The Human
|
||||
Escalation Frequency metric (`docs/metrics/human_escalation_frequency.md:11-12`)
|
||||
is defined as `count(runs WHERE hitl_block=1 AND reason='confidence') ÷
|
||||
total runs`. The `reason='confidence'` discriminator is **not currently
|
||||
stored** — `hitl_block` is a boolean from the manifest. REQ-318 adds
|
||||
`escalation_reason: 'confidence'` to the `ai.decision.made` event when
|
||||
`band == 'block'` + persists it into `fact_run` via the collector.
|
||||
|
||||
**Invocation point:** `run_platform.sh` Step 5 (REQ-301). After
|
||||
Checkov/Wiz produce raw PCRs, the script runs `kj scan` over the plan
|
||||
JSON; both PCR lists concatenate into the confidence signal's `policy`
|
||||
input. When `which kj` is false, the script logs and proceeds with the
|
||||
Checkov/Wiz list only.
|
||||
### 4.3 Touchless Resolution Rate — denominator activates post-pilot
|
||||
|
||||
### 3.4 PolicyCheckResult records (meta-policies, REQ-303)
|
||||
`docs/metrics/touchless_resolution_rate.md:12-15` — defined as a SQL
|
||||
query over `fact_run` (`runs WHERE hitl_block=0 ÷ total runs`). The data
|
||||
lands in `fact_run.hitl_block` via `collector.py:216-227`. No dedicated
|
||||
emitter computes the ratio — it's a downstream query. The denominator
|
||||
is 0 today (no consumer runs). The pilot run activates the denominator.
|
||||
|
||||
The payload is the **merged** `list[PolicyCheckResult]` produced by
|
||||
checkov + wiz + the plan-JSON policies. This is the most novel target —
|
||||
kyverno-json policies over the policy results themselves.
|
||||
`block-on-any-critical` asserts no PCR has `severity: "critical"` +
|
||||
`result: "fail"`; if any does, the meta-policy emits a `fail` PCR with
|
||||
`ruleId: "KJ_META_BLOCK_CRITICAL"` and severity `critical`. This is the
|
||||
declarative source of truth for "critical = block" (D-119 — the
|
||||
`confidence_signal.py` `PENALTY["critical"]: None` hard-override stays
|
||||
as defense-in-depth). `tagging-rules-agree` cross-checks the Checkov
|
||||
`NOVA_TAG_NAMING` result against the kyverno-json
|
||||
`KJ_REQUIRE_TAGGING_STANDARD` result by `resourceRef`; divergence emits
|
||||
an `error` PCR (D-118).
|
||||
---
|
||||
|
||||
**Invocation point:** after the three target policies (contract/stack-
|
||||
IR/plan-JSON) produce their PCR lists, the merged list is the payload
|
||||
for the meta-policies. The meta-policy PCRs are appended to the merged
|
||||
list, which is what the confidence signal consumes.
|
||||
## 5. kyverno-json Policy Extensibility
|
||||
|
||||
## 4. The `PolicyEngine` swap boundary
|
||||
`adapters/kyverno-json/kyverno_json_engine.py:74-80` — the engine is
|
||||
**policy-dir agnostic**: it loads whatever subdir the caller passes.
|
||||
Existing subdirs: `contract/`, `stack-ir/`, `plan-json/`, `meta/`,
|
||||
`regression/`. Adding a new subdir (e.g. `pilot-readiness/`,
|
||||
`settlement-finality/`) requires: (1) `mkdir
|
||||
adapters/kyverno-json/policies/<name>/`, (2) drop `ValidatingPolicy`
|
||||
YAML/JSON files, (3) wire a caller. No engine code change needed.
|
||||
Test pattern: one test file per subdir (`tests/test_<name>_policies.py`).
|
||||
|
||||
### 4.1 Protocol shape (REQ-291)
|
||||
The pilot adds two new policy subdirs: `pilot-readiness/`
|
||||
(REQ-320, no-placeholder-account) + `settlement-finality/` (REQ-315,
|
||||
all-matches-committed). Both follow the established pattern.
|
||||
|
||||
A Python `Protocol` (PEP 544 — structural subtyping, no inheritance):
|
||||
```python
|
||||
class PolicyEngine(Protocol):
|
||||
@property
|
||||
def name(self) -> str: ...
|
||||
def is_configured(self) -> bool: ...
|
||||
def evaluate(self, payload: dict | str, policy_dir: Path,
|
||||
contract_id: str) -> list[dict]: ...
|
||||
```
|
||||
`list[dict]` (not `list[PolicyCheckResult]` — there's no dataclass; the
|
||||
schema is enforced via `jsonschema` validation in tests, matching the
|
||||
existing adapter pattern). The registry selects the active engine from
|
||||
`config.json.policy.engine`. A `NullEngine` is the fallback when the
|
||||
`policy` key is absent (emits `SKIPPED` — backward compatibility for
|
||||
tests that don't set the key).
|
||||
---
|
||||
|
||||
### 4.2 The OPA-equivalent surface (future swap)
|
||||
## 6. Env-JSON Wiring Reconciliation (REQ-319)
|
||||
|
||||
OPA (Open Policy Agent) is the most likely future replacement. The
|
||||
mapping:
|
||||
| Nova `PolicyEngine` member | kyverno-json impl | OPA equivalent |
|
||||
|---|---|---|
|
||||
| `name` | `"kyverno-json"` | `"opa"` |
|
||||
| `is_configured()` | `which kj` | `which opa` |
|
||||
| `evaluate(payload, policy_dir, contract_id)` | `kj scan --policy <dir> --payload <json> -o json` | `opa eval -d <dir> -i <json> 'data.nova.<...>'` |
|
||||
| Policy file format | `ValidatingPolicy` (YAML) | Rego (`.rego`) |
|
||||
| Result shape | `results[]` (pass/fail/error/skip) | `result` (set of violations) |
|
||||
| Severity | Nova annotation `nova.cloudinit.dev/severity` | Nova convention (Rego `metadata` or a wrapper) |
|
||||
`core/environments/dev.json:4` — `account_id: "000000000000"` (placeholder).
|
||||
`core/environment_check.py:48-53` warns (non-fatal) when account_id is
|
||||
placeholder + env != dev. `adapters/terraform/adapter.py:116-117` —
|
||||
computes the state bucket as `nova-tfstate-<AWS_ACCOUNT_ID>-us-east-1`
|
||||
from the `AWS_ACCOUNT_ID` env var, **not** from the env JSON's
|
||||
`state_backend.bucket`. This is the wiring gap: the env JSON's
|
||||
`state_backend` field is currently unused by the live apply path.
|
||||
REQ-319 makes the adapter read `env.state_backend.bucket` when present
|
||||
(falling back to the computed name for backwards compat) + updates
|
||||
`dev.json` to the real account `581513795199` + real bucket
|
||||
`nova-tfstate-581513795199-us-east-1`.
|
||||
|
||||
The protocol is minimal (3 members) specifically so the OPA
|
||||
implementation is a known quantity: an `OpaEngine` class that shells to
|
||||
`opa eval`, translates the Rego violation set to PCR dicts, and
|
||||
implements `is_configured()` via `which opa`. The policy *files* would
|
||||
need rewriting (Rego, not ValidatingPolicy) — but the protocol, the
|
||||
registry, the confidence signal, and the PCR schema are all untouched.
|
||||
This is the swap boundary the user asked for ("Implemented as an
|
||||
adapter since we might one day decide to replace it with something else
|
||||
like OPA").
|
||||
---
|
||||
|
||||
### 4.3 Why not a full plugin registry?
|
||||
|
||||
A `setuptools` entry-point plugin registry (like checkov's
|
||||
`--external-checks-dir`) was considered and rejected: Nova has 1 active
|
||||
engine today (kyverno-json) and at most 2 in the foreseeable future
|
||||
(kyverno-json + OPA). A `Protocol` + `dict` registry in
|
||||
`core/policy_engine.py` is the right weight — discoverable, typed,
|
||||
testable, and ~40 lines. An entry-point registry adds packaging
|
||||
complexity (entry-point metadata, version resolution) for no gain at
|
||||
this scale. The `register(name, factory)` method on the registry is
|
||||
the extension point if a future milestone needs runtime plugin
|
||||
discovery.
|
||||
|
||||
## 5. Latency / MTTR impact (G-Q3 anticipation)
|
||||
|
||||
NORTH_STAR.md MTTR target: < 60s p95. `run_platform.sh` Step 5 today
|
||||
runs Checkov over the terraform plan (typically 5-15s for a small
|
||||
stack). Adding `kj scan` over the same plan JSON adds:
|
||||
- Process spawn: ~50ms (Go binary startup)
|
||||
- Policy load: ~20ms (a handful of YAML files)
|
||||
- Assertion evaluation: ~100-500ms (JMESPath over a small plan)
|
||||
- Total: < 1s for a typical Nova stack
|
||||
|
||||
The kyverno-json pass runs **in parallel** with Checkov (REQ-301 — the
|
||||
script launches both and waits on both), so the wall-clock impact is
|
||||
`max(checkov_time, kj_time)` ≈ checkov_time (kj is faster). The
|
||||
contract + stack-IR policies run during resolve (already a fast step).
|
||||
Meta-policies run over the merged list (in-memory, < 10ms). **No
|
||||
measurable MTTR impact** is expected. This will be verified in P3
|
||||
VERIFY with a timing assertion.
|
||||
|
||||
## 6. "Platform functions without AI" tenet (G-Q1 / D-120)
|
||||
|
||||
kyverno-json is deterministic (same policy + payload → same result,
|
||||
every run). It is not an LLM, not a probabilistic model, not a
|
||||
"judgement" engine. The NORTH_STAR.md tenet ("the platform functions
|
||||
without AI — 'AI decisions' are really automated decisions")
|
||||
distinguishes AI (non-reproducible) from automation (reproducible).
|
||||
kyverno-json is the latter. Adding 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 → `SKIPPED` PCR
|
||||
→ confidence signal proceeds).
|
||||
|
||||
## 7. ECS policy catalog overlap (prior art)
|
||||
|
||||
The kyverno-json catalog ships ECS policies that overlap with Nova's
|
||||
L1 modules: `ecs-cluster-enable-logging`, `ecs-cluster-required-
|
||||
container-insights`, `ecs-service-public-ip`, `ecs-service-required-
|
||||
latest-platform-fargate`, `ecs-task-definition-fs-read-only`. These are
|
||||
**reference policies**, not drop-in Nova policies — they target the
|
||||
AWS ECS API shape (`type: aws_ecs_service` etc.), not Nova's Stack IR
|
||||
shape. v1.25 policies target the Nova IR (REQ-297) and the terraform
|
||||
plan JSON (REQ-300), not the raw AWS API. The catalog is useful as
|
||||
prior art for JMESPath patterns over ECS resources — the
|
||||
`ecs-service-public-ip` policy's `contains('$allowed-values',
|
||||
@.assign_public_ip)` pattern informs the Nova `forbid-public-ingress`
|
||||
policy shape. No catalog policies are imported directly in v1.25.
|
||||
|
||||
## 8. Risks & mitigations
|
||||
## 7. Risk Analysis
|
||||
|
||||
| Risk | Likelihood | Impact | Mitigation |
|
||||
|---|---|---|---|
|
||||
| `kj` binary not in CI image | medium | blocks P3+ tests | `is_configured()` guard + `pytest.skip` + `scripts/install-kyverno-json.sh` |
|
||||
| kyverno-json output shape changes across versions | low | breaks `_to_pcr()` | pin `@latest` to a known-good commit in `install-kyverno-json.sh` after P1 smoke; defensive parsing (malformed → `error` PCR, not exception) |
|
||||
| Policy explosion (4 targets × N rules) | medium | maintenance load | wave ordering (PLAN); policies co-located per target dir; meta-policy cross-check keeps the set auditable |
|
||||
| Checkov + kj tagging-rule drift | medium | false `error` PCRs | `tagging-rules-agree` meta-policy emits `error` on divergence (visible, not silent); the Checkov rule stays source of truth for HCL, kj for IR |
|
||||
| OPA swap turns out harder than the protocol implies | low | future milestone rework | RESEARCH §4.2 documents the OPA-equivalent surface; the protocol is the contract, not the implementation |
|
||||
| `--pre-process` needed for meta-policies but undocumented behavior | low | meta-policy bugs | v1.25 meta-policies use plain assertion trees over the PCR list (no pre-process); `--pre-process` noted as a future optimization only |
|
||||
| `NOVA_AWS_*` key lacks a needed IAM permission mid-pilot | Low (bootstrap succeeded → root-equivalent) | High (blocks apply) | D-207; the key has root-equivalent perms (empirically confirmed). |
|
||||
| DynamoDB primitive takes longer than expected (new module) | Medium | Medium | REQ-322 is the single platform-side build-out; the `s3`/`rds` primitives are the template — straightforward. |
|
||||
| Homegrown chain has a correctness bug (hash chain breaks) | Low | High | REQ-310 tests cover chain integrity, hash determinism, genesis, append/verify. |
|
||||
| `deploy.yml@v1.25` ref doesn't resolve (floating tag) | Low | High | The platform's `release.yml` creates + force-moves the `v1.25` + `v1` floating tags on merge to main. The pilot contract uses `@v1.25`. |
|
||||
| Settlement-finality policy false-negatives (blocks a valid promotion) | Medium | Medium | REQ-315 tests cover passing + failing fixtures; the policy is skip-when-kj-absent (graceful). |
|
||||
| D-083 deferral challenged (audit ledger not tamper-evident) | Low | Low | D-204; the SQLite hash-chain + DynamoDB outbox is the pilot's audit record. Tamper-evidence is a future milestone. |
|
||||
|
||||
## 9. Assumptions (logged, full autonomy)
|
||||
---
|
||||
|
||||
- A1: `kj scan --output json` produces a stable `results[]` array shape.
|
||||
Will be verified in P1 smoke test (`_smoke.json` policy + a trivial
|
||||
payload); if the shape differs, `_to_pcr()` is adjusted defensively
|
||||
(malformed → `error` PCR). Confidence: 0.85.
|
||||
- A2: The `nova.cloudinit.dev/severity` annotation convention is
|
||||
read by the engine from the policy YAML (loaded once per evaluate()
|
||||
call). kyverno-json does not validate unknown annotations — they pass
|
||||
through. Confidence: 0.90.
|
||||
- A3: The `~` projection modifier iterates `resources[]` in the Stack
|
||||
IR and `planned_values.root_module.resources[]` in the plan JSON
|
||||
correctly. Verified in P2/P3 tests. Confidence: 0.85.
|
||||
- A4: `go install` works in the CI image (Go toolchain available or
|
||||
installable). If not, the binary-release download path is the
|
||||
documented fallback in `install-kyverno-json.sh`. Confidence: 0.80.
|
||||
- A5: The `NullEngine` fallback (when `policy` key absent in
|
||||
config.json) keeps all existing tests passing — they don't set the
|
||||
key, so they get `NullEngine` → `SKIPPED` PCRs → confidence signal
|
||||
proceeds with `policy` input `[SKIPPED]` → per-input score 1.0
|
||||
(skipped counts as pass in `_per_input_score`). Confidence: 0.95
|
||||
(verified against `confidence_signal.py:84-89`).
|
||||
## 8. Persona Assessment
|
||||
|
||||
## 10. Decisions referenced
|
||||
|
||||
D-115 (install path), D-116 (engine enum reuse), D-117 (adapter
|
||||
signatures unchanged), D-118 (tagging cross-check), D-119 (critical-
|
||||
override defense-in-depth), D-120 (deterministic not AI). See
|
||||
CLARIFY.md for the full resolution text.
|
||||
|
||||
## 11. Architecture updates (deferred to RESEARCH-stage file edits)
|
||||
|
||||
- `.ciagent/ARCHITECTURE.md` gains §12.7 "Policy Engine Registry" with
|
||||
the registry diagram. Deferred to the RESEARCH commit (this file's
|
||||
commit) — the section is authored as part of this research.
|
||||
- `schemas/README.md` notes `engine: "kyverno"` is shared by the K8s
|
||||
adapter and kyverno-json (distinguished by `ruleId` prefix).
|
||||
- `modules/STANDARDS.md` gains a "Policy authoring standard" section
|
||||
(P4, REQ-307).
|
||||
- `docs/METRICS.md` notes the policy engine is swappable (P4, REQ-307).
|
||||
See `PERSONAS.md` (next section, produced by the lead-developer at the
|
||||
end of RESEARCH). The active roster: backend-engineer (blockchain core
|
||||
+ settlement + outcome backfill), data-engineer (DynamoDB primitive +
|
||||
metrics cold store), policy-engineer (kyverno-json policies), +
|
||||
blockchain-engineer (custom, phase-specific — chain consensus, order
|
||||
matching, settlement finality). frontend-engineer is deactivated (no
|
||||
UI in the pilot).
|
||||
+6
-2
@@ -2308,6 +2308,10 @@ Tags: `v1.25.0` (P0) → `v1.25.5` (P5 = milestone release).
|
||||
`nova-tfstate-581513795199-us-east-1`.
|
||||
- REQ-320: `adapters/kyverno-json/policies/pilot-readiness/no-placeholder-account.json`
|
||||
— declarative gate preventing apply against a placeholder account.
|
||||
- REQ-322: `modules/l1/dynamodb/` — new L1 primitive (interface.json +
|
||||
terraform/main.tf + README.md + instance.json + registry.json entry).
|
||||
The single platform-side module build-out (ECS + S3 already exist;
|
||||
the adapter is stateless/registry-driven).
|
||||
|
||||
### Phase P4 — pilot-run-and-docs (planned, tag v1.25.4)
|
||||
- REQ-321: `adapters/README.md` (new consumer row) +
|
||||
@@ -2330,7 +2334,7 @@ Tags: `v1.25.0` (P0) → `v1.25.5` (P5 = milestone release).
|
||||
`milestone/v1.26-pilot-activation` → `main`; tag `v1.25.5` (= the
|
||||
v1.26 release per prev-minor tagging rule); create Gitea release with
|
||||
full milestone summary; delete all milestone branches.
|
||||
- Update `REQUIREMENTS.md` (mark REQ-310..321 complete), `ROADMAP.md`
|
||||
- Update `REQUIREMENTS.md` (mark REQ-310..322 complete), `ROADMAP.md`
|
||||
(mark v1.26 complete), `NORTH_STAR.md` (note Strategic Objectives #1
|
||||
+ #3 — first real consumer estate; Post-Pilot denominators activated).
|
||||
- **Requirements:** REQ-310..321 (12 requirements).
|
||||
- **Requirements:** REQ-310..322 (13 requirements).
|
||||
|
||||
@@ -60,6 +60,8 @@ contract references the Nova platform's deploy workflow
|
||||
(`uses: acdl/.github/workflows/deploy.yml@v1.25`) and declares the
|
||||
blockchain exchange stack (the AWS resources the app needs: ECS for
|
||||
the matching engine, DynamoDB for the ledger, S3 for block storage).
|
||||
The DynamoDB L1 primitive (REQ-322) must land before this contract can
|
||||
declare `dynamodb` — ECS + S3 already exist.
|
||||
|
||||
**Must-haves:**
|
||||
- `contract.yaml` — id, name (`blockchain-exchange`), environment
|
||||
@@ -177,11 +179,43 @@ Update `adapters/README.md` (new consumer row), `docs/METRICS.md` (the
|
||||
- `.ciagent/ARCHITECTURE.md` — §12.8 Pilot Estate.
|
||||
- `.ciagent/nova-blockchain-exchange/README.md` — onboarding guide.
|
||||
|
||||
### REQ-322 — DynamoDB L1 primitive (platform-side)
|
||||
|
||||
The blockchain exchange's ledger table needs a DynamoDB L1 primitive.
|
||||
Research (RESEARCH §3) confirmed the adapter is stateless/registry-
|
||||
driven (no `TYPE_MAP` — deleted in v1.11); a new stack type requires a
|
||||
new L1 module, not an adapter change. The `dynamodb` primitive mirrors
|
||||
the existing `s3` / `rds` primitives: `interface.json` (stack type
|
||||
`aws:dynamodb:table`, inputs `table_name`/`region`/`pk`/`sk`/`billing_mode`,
|
||||
outputs `table_arn`/`table_name`), `terraform/main.tf`
|
||||
(`resource "aws_dynamodb_table" "this"`), `README.md`, `instance.json`,
|
||||
+ a `registry.json` entry. The pilot contract's `infrastructure.dynamodb`
|
||||
block references this primitive. This is the single platform-side
|
||||
module build-out for the milestone (ECS + S3 already exist).
|
||||
|
||||
**Must-haves:**
|
||||
- `modules/l1/dynamodb/interface.json` — stack type
|
||||
`aws:dynamodb:table`, inputs, outputs.
|
||||
- `modules/l1/dynamodb/terraform/main.tf` —
|
||||
`resource "aws_dynamodb_table" "this"` (PK + optional SK,
|
||||
`billing_mode = PAY_PER_REQUEST` default, encryption + point-in-time-
|
||||
recovery enabled per v1.8 NFR defaults).
|
||||
- `modules/l1/dynamodb/README.md` — module doc.
|
||||
- `modules/l1/dynamodb/instance.json` — sample instance.
|
||||
- `modules/registry.json` — `dynamodb` entry (kind `l1`,
|
||||
`terraform_dir: modules/l1/dynamodb/terraform`).
|
||||
- `tests/test_adapter.py` — add `dynamodb` to `EXPECTED_L1_KEYS` +
|
||||
a resolution + emission test.
|
||||
- `modules/README.md` — catalog index updated.
|
||||
|
||||
### Summary
|
||||
|
||||
12 requirements (REQ-310..321). Equities-only pilot; minimal PoA ledger;
|
||||
13 requirements (REQ-310..322). Equities-only pilot; minimal PoA ledger;
|
||||
T+1 settlement; consumer deploy via `deploy.yml@v1.25`; 3 Post-Pilot
|
||||
metrics grounded (outcome backfill + escalation reason + pilot runs);
|
||||
3 kyverno-json policies extending v1.25 (settlement-finality,
|
||||
pilot-readiness, + the existing meta-policies apply); env-JSON wiring
|
||||
reconciled.
|
||||
reconciled; DynamoDB L1 primitive authored (the single platform-side
|
||||
module build-out — the adapter is stateless/registry-driven, so the
|
||||
primitive is a new `modules/l1/dynamodb/` module + registry entry, not
|
||||
an adapter change).
|
||||
Reference in New Issue
Block a user