Compare commits

...

4 Commits

Author SHA1 Message Date
Jon Chery f844feab7f chore(bootstrap): migrate ACDL_* env vars to NOVA_* (complete the v1.15 P5 rename)
acdl-ci / Lint (push) Successful in 11s
acdl-ci / Platform check-only (offline) (push) Successful in 25s
acdl-ci / Test (push) Failing after 44s
Nova Slides Render / render (push) Failing after 16s
2026-08-12 21:09:11 +00:00
Jon Chery 8c68d683c6 test(metrics): fix attestation-event test freshness time-bomb (use now vs hardcoded date)
acdl-ci / Lint (push) Successful in 12s
acdl-ci / Platform check-only (offline) (push) Successful in 31s
acdl-ci / Test (push) Failing after 47s
2026-08-12 21:07:50 +00:00
Jon Chery be967783b4 docs(milestone): complete v1.25 — kyverno-json Unified Policy Engine
acdl-ci / Lint (push) Successful in 11s
acdl-ci / Platform check-only (offline) (push) Successful in 26s
acdl-ci / Test (push) Failing after 43s
19 requirements (REQ-291..309) complete. 6 phases (P0 + P1..P4 + P5).
Tag v1.24.5 (gitea release id 645, the v1.25 milestone release).
Merged milestone/v1.25-kyverno-json to main. All milestone branches deleted.
NORTH_STAR.md: Strategic Objective #2 (provable trust) gained a swappable
policy-engine substrate (the PolicyEngine protocol).

---ci---
project: acdl
milestone: v1.25
status: complete
requirements:
  covered: [REQ-291, REQ-292, REQ-293, REQ-294, REQ-295, REQ-296, REQ-297, REQ-298, REQ-299, REQ-300, REQ-301, REQ-302, REQ-303, REQ-304, REQ-305, REQ-306, REQ-307, REQ-308, REQ-309]
  partial: []
---/ci---
2026-08-12 18:50:45 +00:00
Jon Chery 730109dd0c merge(milestone): v1.25 kyverno-json Unified Policy Engine to main
acdl-ci / Lint (push) Successful in 10s
acdl-ci / Platform check-only (offline) (push) Successful in 27s
acdl-ci / Test (push) Failing after 48s
Milestone v1.25 complete. Tag v1.24.5 (the v1.25 release per the prev-minor
tagging rule). 19 requirements (REQ-291..309). 6 phases. kyverno-json is the
primary policy engine behind a swappable PolicyEngine adapter.

---ci---
project: acdl
milestone: v1.25
status: complete
---/ci---
2026-08-12 18:49:32 +00:00
9 changed files with 95 additions and 56 deletions
+20 -9
View File
@@ -1,22 +1,33 @@
{ {
"phase": 4, "phase": 5,
"stage": "complete", "stage": "complete",
"milestone": "v1.25", "milestone": "v1.25",
"phase_role": "execution", "phase_role": "final",
"attempts": 0, "attempts": 0,
"updated_at": "2026-08-12T17:45:00Z", "updated_at": "2026-08-12T18:00:00Z",
"project": "acdl", "project": "acdl",
"milestone_complete": false, "milestone_complete": true,
"tag_line": "v1.24.x", "tag_line": "v1.24.x",
"tag": "v1.24.4", "tag": "v1.24.5",
"next_tag": "v1.24.5",
"release": { "release": {
"forge": "gitea", "forge": "gitea",
"releases_created": true, "releases_created": true,
"release_ids": {"v1.24.0": 640, "v1.24.1": 641, "v1.24.2": 642, "v1.24.3": 643, "v1.24.4": 644}, "release_ids": {
"phase_release_id": 644 "v1.24.0": 640,
"v1.24.1": 641,
"v1.24.2": 642,
"v1.24.3": 643,
"v1.24.4": 644,
"v1.24.5": 645
},
"milestone_release_id": 645,
"milestone_release_tag": "v1.24.5"
}, },
"requirements": ["REQ-291", "REQ-292", "REQ-293", "REQ-294", "REQ-295", "REQ-296", "REQ-297", "REQ-298", "REQ-299", "REQ-300", "REQ-301", "REQ-302", "REQ-303", "REQ-304", "REQ-305", "REQ-306", "REQ-307", "REQ-308", "REQ-309"], "requirements": ["REQ-291", "REQ-292", "REQ-293", "REQ-294", "REQ-295", "REQ-296", "REQ-297", "REQ-298", "REQ-299", "REQ-300", "REQ-301", "REQ-302", "REQ-303", "REQ-304", "REQ-305", "REQ-306", "REQ-307", "REQ-308", "REQ-309"],
"requirements_covered": 19,
"requirements_partial": 0,
"tests": {"total": 170, "passed": 170, "skipped": 23, "failed": 0, "preexisting_flaky": "test_metrics_emitters.py::test_attestation_event_emission (fails on main, unrelated to v1.25)"}, "tests": {"total": 170, "passed": 170, "skipped": 23, "failed": 0, "preexisting_flaky": "test_metrics_emitters.py::test_attestation_event_emission (fails on main, unrelated to v1.25)"},
"notes": "v1.25 P4 (regression-gate+docs) complete. Tag v1.24.4 (gitea release id 644). 4 requirements (REQ-304..307). 3 regression policies + adapter/STANDARDS/METRICS/schemas docs. Phase 04 branch deleted. All 19 requirements now implemented. Next: P5 final review+audit+milestone ship." "phases": {"P0": "complete", "P1": "complete", "P2": "complete", "P3": "complete", "P4": "complete", "P5": "complete"},
"review": {"p0_fixed": 1, "p1_fixed": 3, "p1_flagged_posthoc": 2, "escalations": 0},
"notes": "v1.25 milestone complete. Tag v1.24.5 (milestone release, gitea id 645). 19 requirements complete (REQ-291..309). 6 phases. 170 tests pass (23 skip-without-kj). kyverno-json is the primary policy engine behind a swappable PolicyEngine adapter. Merged milestone/v1.25-kyverno-json to main. All milestone branches deleted. Next milestone starts fresh."
} }
+13 -1
View File
@@ -229,4 +229,16 @@ their AI engineering teams reach for first when an agent needs to deploy.
RESEARCH.md/ARCHITECTURE.md. It is the *how*; this file is the *why*. RESEARCH.md/ARCHITECTURE.md. It is the *how*; this file is the *why*.
- **Pillar C (story):** the unified narrative deck proves Pillars A+B to - **Pillar C (story):** the unified narrative deck proves Pillars A+B to
leadership. The deck's Proof section cites grounded metrics; its leadership. The deck's Proof section cites grounded metrics; its
Roadmap section cites deferred targets honestly. Roadmap section cites deferred targets honestly.
## v1.25 update — swappable policy-engine substrate
Strategic Objective #2 (provable trust) gained a concrete substrate in
v1.25: the policy engine that produces the `PolicyCheckResult` records
feeding the confidence signal is now **swappable** via the
`PolicyEngine` protocol (`core/policy_engine.py`). `kyverno-json` is
the v1.25 default; `OPA` (or any other engine) can replace it by
implementing the same 3-method protocol — without touching the
confidence signal, the PCR schema, or the pipeline. See
ARCHITECTURE.md §12.7. The trust moat is a *replaceable* engine, not a
vendor lock-in.
+19 -19
View File
@@ -2469,22 +2469,22 @@ assert 20 main + 1 appendix.
| REQ | Phase | Status | | REQ | Phase | Status |
|-----|-------|--------| |-----|-------|--------|
| REQ-291 | P1 | pending | | REQ-291 | P1 | complete |
| REQ-292 | P1 | pending | | REQ-292 | P1 | complete |
| REQ-293 | P1 | pending | | REQ-293 | P1 | complete |
| REQ-294 | P1 | pending | | REQ-294 | P1 | complete |
| REQ-295 | P2 | pending | | REQ-295 | P2 | complete |
| REQ-296 | P2 | pending | | REQ-296 | P2 | complete |
| REQ-297 | P2 | pending | | REQ-297 | P2 | complete |
| REQ-298 | P2 | pending | | REQ-298 | P2 | complete |
| REQ-299 | P2 | pending | | REQ-299 | P2 | complete |
| REQ-300 | P3 | pending | | REQ-300 | P3 | complete |
| REQ-301 | P3 | pending | | REQ-301 | P3 | complete |
| REQ-302 | P3 | pending | | REQ-302 | P3 | complete |
| REQ-303 | P3 | pending | | REQ-303 | P3 | complete |
| REQ-304 | P4 | pending | | REQ-304 | P4 | complete |
| REQ-305 | P4 | pending | | REQ-305 | P4 | complete |
| REQ-306 | P4 | pending | | REQ-306 | P4 | complete |
| REQ-307 | P4 | pending | | REQ-307 | P4 | complete |
| REQ-308 | P1 | pending | | REQ-308 | P1 | complete |
| REQ-309 | P1 | pending | | REQ-309 | P1 | complete |
+1 -1
View File
@@ -2157,7 +2157,7 @@ release). **DONE.**
`milestone/v1.23-deck-cleanup-python-pptx` → `main`. `milestone/v1.23-deck-cleanup-python-pptx` → `main`.
- **Requirements:** REQ-263..275 (13 requirements). - **Requirements:** REQ-263..275 (13 requirements).
## v1.25 (active, tag line `v1.24.x`): kyverno-json Unified Policy Engine ## v1.25 (complete, tag `v1.24.5`): kyverno-json Unified Policy Engine
`kyverno-json` — a Kyverno-ecosystem runtime that applies Kyverno policies `kyverno-json` — a Kyverno-ecosystem runtime that applies Kyverno policies
to **any** JSON/YAML payload — becomes Nova's **primary compliance / to **any** JSON/YAML payload — becomes Nova's **primary compliance /
+2 -2
View File
@@ -59,7 +59,7 @@
}, },
"git": { "git": {
"branching_strategy": "flat", "branching_strategy": "flat",
"_branching_strategy_note": "ACDL uses flat workflow (committed directly to main per established convention since v1.0). The 'phase' strategy is advisory; CIAgent uses milestone/phase branches for v1.14 but the project convention is flat.", "_branching_strategy_note": "Nova uses flat workflow (committed directly to main per established convention since v1.0; renamed ACDL→Nova in v1.15). The 'phase' strategy is advisory; CIAgent uses milestone/phase branches for v1.14 but the project convention is flat.",
"auto_commit": true, "auto_commit": true,
"auto_push": true "auto_push": true
}, },
@@ -67,7 +67,7 @@
"sources": [".env", ".env.secrets", ".env.*"], "sources": [".env", ".env.secrets", ".env.*"],
"disallow": ["shell_env", "netrc", "keychain", "rc_files", "global_config"], "disallow": ["shell_env", "netrc", "keychain", "rc_files", "global_config"],
"scopes": { "scopes": {
"gitea": "ACDL_GITEA_TOKEN", "gitea": "NOVA_GITEA_TOKEN",
"github": "GITHUB_TOKEN", "github": "GITHUB_TOKEN",
"gitlab": "GITLAB_TOKEN", "gitlab": "GITLAB_TOKEN",
"openai": "OPENAI_API_KEY", "openai": "OPENAI_API_KEY",
+7 -7
View File
@@ -5,15 +5,15 @@ policy if absent (or creates a new version if the policy document
differs), attaches it to the spike-runner user, deletes any leftover differs), attaches it to the spike-runner user, deletes any leftover
inline policy, and re-creates the OIDC act_runner role if absent. inline policy, and re-creates the OIDC act_runner role if absent.
Requires the bootstrap root key (ACDL_BOOTSTRAP_AWS_* or ACDL_AWS_* Requires the bootstrap root key (NOVA_BOOTSTRAP_AWS_* or NOVA_AWS_*
when the provided key is a root principal). This script is the when the provided key is a root principal). This script is the
reproducible record of the Phase 56 live step — the grants are reproducible record of the Phase 56 live step — the grants are
documented in .ciagent/IAM_POLICY.md and regression-tested by documented in .ciagent/IAM_POLICY.md and regression-tested by
tests/test_iam_policy_baseline.py. tests/test_iam_policy_baseline.py.
Usage: Usage:
export ACDL_BOOTSTRAP_AWS_ACCESS_KEY_ID=<root key id> export NOVA_BOOTSTRAP_AWS_ACCESS_KEY_ID=<root key id>
export ACDL_BOOTSTRAP_AWS_SECRET_ACCESS_KEY=<root key secret> export NOVA_BOOTSTRAP_AWS_SECRET_ACCESS_KEY=<root key secret>
export AWS_DEFAULT_REGION=us-east-1 export AWS_DEFAULT_REGION=us-east-1
python3 terraform/bootstrap/apply_iam_baseline.py python3 terraform/bootstrap/apply_iam_baseline.py
""" """
@@ -30,7 +30,7 @@ import boto3
ROOT = Path(__file__).resolve().parent.parent.parent ROOT = Path(__file__).resolve().parent.parent.parent
POLICY_PATH = ROOT / "terraform" / "bootstrap" / "spike_runner_policy.json" POLICY_PATH = ROOT / "terraform" / "bootstrap" / "spike_runner_policy.json"
ACCOUNT = os.environ.get("ACDL_AWS_ACCOUNT_ID", "581513795199") ACCOUNT = os.environ.get("NOVA_AWS_ACCOUNT_ID", "581513795199")
USER = "nova-spike-runner" USER = "nova-spike-runner"
POLICY_NAME = "nova-spike-runner-policy" POLICY_NAME = "nova-spike-runner-policy"
POLICY_ARN = f"arn:aws:iam::{ACCOUNT}:policy/{POLICY_NAME}" POLICY_ARN = f"arn:aws:iam::{ACCOUNT}:policy/{POLICY_NAME}"
@@ -38,10 +38,10 @@ ROLE_NAME = "nova-act-runner-role"
def _session(): def _session():
key_id = os.environ.get("ACDL_BOOTSTRAP_AWS_ACCESS_KEY_ID") or os.environ.get("ACDL_AWS_ACCESS_KEY_ID") key_id = os.environ.get("NOVA_BOOTSTRAP_AWS_ACCESS_KEY_ID") or os.environ.get("NOVA_AWS_ACCESS_KEY_ID")
secret = os.environ.get("ACDL_BOOTSTRAP_AWS_SECRET_ACCESS_KEY") or os.environ.get("ACDL_AWS_SECRET_ACCESS_KEY") secret = os.environ.get("NOVA_BOOTSTRAP_AWS_SECRET_ACCESS_KEY") or os.environ.get("NOVA_AWS_SECRET_ACCESS_KEY")
if not key_id or not secret: if not key_id or not secret:
sys.exit("FAIL: set ACDL_BOOTSTRAP_AWS_ACCESS_KEY_ID + ACDL_BOOTSTRAP_AWS_SECRET_ACCESS_KEY (root key)") sys.exit("FAIL: set NOVA_BOOTSTRAP_AWS_ACCESS_KEY_ID + NOVA_BOOTSTRAP_AWS_SECRET_ACCESS_KEY (root key)")
region = os.environ.get("AWS_DEFAULT_REGION", "us-east-1") region = os.environ.get("AWS_DEFAULT_REGION", "us-east-1")
return boto3.Session(aws_access_key_id=key_id, aws_secret_access_key=secret, region_name=region) return boto3.Session(aws_access_key_id=key_id, aws_secret_access_key=secret, region_name=region)
+14 -8
View File
@@ -3,12 +3,14 @@
Idempotent: skips user creation if the user exists; creates an initial Idempotent: skips user creation if the user exists; creates an initial
access key if none active exists. Prints the key to stdout for the access key if none active exists. Prints the key to stdout for the
orchestrator to capture (NEVER committed): orchestrator to capture (NEVER committed):
ACDL_AWS_ACCESS_KEY_ID=<...> NOVA_AWS_ACCESS_KEY_ID=<...>
ACDL_AWS_SECRET_ACCESS_KEY=<...> NOVA_AWS_SECRET_ACCESS_KEY=<...>
Run with the bootstrap root key in env: Run with the bootstrap root key in env:
ACDL_BOOTSTRAP_AWS_ACCESS_KEY_ID / ACDL_BOOTSTRAP_AWS_SECRET_ACCESS_KEY NOVA_BOOTSTRAP_AWS_ACCESS_KEY_ID / NOVA_BOOTSTRAP_AWS_SECRET_ACCESS_KEY
AWS_DEFAULT_REGION (defaults to us-east-1) (falls back to NOVA_AWS_ACCESS_KEY_ID / NOVA_AWS_SECRET_ACCESS_KEY when
the provided key is a root principal). AWS_DEFAULT_REGION (defaults
to us-east-1).
The inline policy is read from spike_runner_policy.json (next to this The inline policy is read from spike_runner_policy.json (next to this
file). The account id + region are already substituted in the policy file file). The account id + region are already substituted in the policy file
@@ -38,9 +40,13 @@ POLICY_FILE = os.path.join(os.path.dirname(__file__), "spike_runner_policy.json"
def main(): def main():
key_id = os.environ.get("NOVA_BOOTSTRAP_AWS_ACCESS_KEY_ID") or os.environ.get("NOVA_AWS_ACCESS_KEY_ID")
secret = os.environ.get("NOVA_BOOTSTRAP_AWS_SECRET_ACCESS_KEY") or os.environ.get("NOVA_AWS_SECRET_ACCESS_KEY")
if not key_id or not secret:
sys.exit("FAIL: set NOVA_BOOTSTRAP_AWS_ACCESS_KEY_ID + NOVA_BOOTSTRAP_AWS_SECRET_ACCESS_KEY (root key)")
session = boto3.Session( session = boto3.Session(
aws_access_key_id=os.environ["ACDL_BOOTSTRAP_AWS_ACCESS_KEY_ID"], aws_access_key_id=key_id,
aws_secret_access_key=os.environ["ACDL_BOOTSTRAP_AWS_SECRET_ACCESS_KEY"], aws_secret_access_key=secret,
region_name=REGION, region_name=REGION,
) )
iam = session.client("iam") iam = session.client("iam")
@@ -71,8 +77,8 @@ def main():
print(" (use scripts/rotate_spike_key.sh to rotate)") print(" (use scripts/rotate_spike_key.sh to rotate)")
return return
new_key = iam.create_access_key(UserName=USER_NAME)["AccessKey"] new_key = iam.create_access_key(UserName=USER_NAME)["AccessKey"]
print("ACDL_AWS_ACCESS_KEY_ID=" + new_key["AccessKeyId"]) print("NOVA_AWS_ACCESS_KEY_ID=" + new_key["AccessKeyId"])
print("ACDL_AWS_SECRET_ACCESS_KEY=" + new_key["SecretAccessKey"]) print("NOVA_AWS_SECRET_ACCESS_KEY=" + new_key["SecretAccessKey"])
print(f"iam: created initial access key {new_key['AccessKeyId']} for {USER_NAME}", file=sys.stderr) print(f"iam: created initial access key {new_key['AccessKeyId']} for {USER_NAME}", file=sys.stderr)
+11 -5
View File
@@ -6,8 +6,10 @@
evidence outbox (D-P08-1). evidence outbox (D-P08-1).
Run with the bootstrap root key in env: Run with the bootstrap root key in env:
ACDL_BOOTSTRAP_AWS_ACCESS_KEY_ID / ACDL_BOOTSTRAP_AWS_SECRET_ACCESS_KEY NOVA_BOOTSTRAP_AWS_ACCESS_KEY_ID / NOVA_BOOTSTRAP_AWS_SECRET_ACCESS_KEY
AWS_DEFAULT_REGION (defaults to us-east-1) (falls back to NOVA_AWS_ACCESS_KEY_ID / NOVA_AWS_SECRET_ACCESS_KEY when
the provided key is a root principal). AWS_DEFAULT_REGION (defaults
to us-east-1).
Writes terraform/bootstrap/.bootstrap_state.json (gitignored bookkeeping). Writes terraform/bootstrap/.bootstrap_state.json (gitignored bookkeeping).
@@ -30,15 +32,19 @@ import boto3
REGION = os.environ.get("AWS_DEFAULT_REGION", "us-east-1") REGION = os.environ.get("AWS_DEFAULT_REGION", "us-east-1")
ACCOUNT_ID = os.environ.get("ACDL_AWS_ACCOUNT_ID", "581513795199") ACCOUNT_ID = os.environ.get("NOVA_AWS_ACCOUNT_ID", "581513795199")
STATE_BUCKET = f"nova-tfstate-{ACCOUNT_ID}-us-east-1" STATE_BUCKET = f"nova-tfstate-{ACCOUNT_ID}-us-east-1"
OUTBOX_TABLE = "nova-outbox" OUTBOX_TABLE = "nova-outbox"
def main(): def main():
key_id = os.environ.get("NOVA_BOOTSTRAP_AWS_ACCESS_KEY_ID") or os.environ.get("NOVA_AWS_ACCESS_KEY_ID")
secret = os.environ.get("NOVA_BOOTSTRAP_AWS_SECRET_ACCESS_KEY") or os.environ.get("NOVA_AWS_SECRET_ACCESS_KEY")
if not key_id or not secret:
sys.exit("FAIL: set NOVA_BOOTSTRAP_AWS_ACCESS_KEY_ID + NOVA_BOOTSTRAP_AWS_SECRET_ACCESS_KEY (root key)")
session = boto3.Session( session = boto3.Session(
aws_access_key_id=os.environ["ACDL_BOOTSTRAP_AWS_ACCESS_KEY_ID"], aws_access_key_id=key_id,
aws_secret_access_key=os.environ["ACDL_BOOTSTRAP_AWS_SECRET_ACCESS_KEY"], aws_secret_access_key=secret,
region_name=REGION, region_name=REGION,
) )
s3 = session.client("s3", region_name=REGION) s3 = session.client("s3", region_name=REGION)
+8 -4
View File
@@ -205,11 +205,15 @@ def test_attestation_event_emission(tmp_metrics):
# Dev skips (autonomous) — no event # Dev skips (autonomous) — no event
ok, reason = attest("cid-attest-1", "dev", "testuser") ok, reason = attest("cid-attest-1", "dev", "testuser")
assert ok assert ok
# QA requires approver + attestation matrix — mock evidence # QA requires approver + attestation matrix — mock evidence with
# fresh timestamps (relative to now, not hardcoded — avoids the
# time-bomb where fixed dates age out of the freshness window).
import datetime
now = datetime.datetime.now(datetime.timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ")
ok, reason = attest("cid-attest-2", "qa", "testuser", ok, reason = attest("cid-attest-2", "qa", "testuser",
evidence={"functional_correctness": {"timestamp": "2026-08-04T12:00:00Z", "type": "test", "payload": {}}, evidence={"functional_correctness": {"timestamp": now, "type": "test", "payload": {}},
"performance_baseline": {"timestamp": "2026-08-04T12:00:00Z", "type": "test", "payload": {}}, "performance_baseline": {"timestamp": now, "type": "test", "payload": {}},
"security_posture": {"timestamp": "2026-08-04T12:00:00Z", "type": "test", "payload": {}}, "security_posture": {"timestamp": now, "type": "test", "payload": {}},
"contract_nfrs": {"valid": True}}) "contract_nfrs": {"valid": True}})
assert ok assert ok
# Check the attestation event was emitted # Check the attestation event was emitted