Compare commits

..

1 Commits

Author SHA1 Message Date
Jon Chery f83b974c0e Merge milestone/v1.16-nova-simplification — v1.16 complete (Nova Simplification: 20-phase NFR sweep + final; tag v1.15.26)
acdl-ci / Lint (push) Successful in 11s
acdl-ci / Platform check-only (offline) (push) Successful in 29s
acdl-ci / Test (push) Failing after 7m25s
2026-08-01 13:37:18 +00:00
15 changed files with 639 additions and 88 deletions
+8 -5
View File
@@ -1,9 +1,12 @@
{ {
"phase": 1, "phase": 21,
"stage": "execute", "stage": "complete",
"milestone": "v1.16", "milestone": "v1.16",
"phase_role": "execution", "phase_role": "final",
"attempts": 0, "attempts": 0,
"updated_at": "2026-07-30T15:30:00Z", "updated_at": "2026-07-30T16:00:00Z",
"milestone_complete": false "milestone_complete": true,
"tag": "v1.15.26",
"requirements": ["REQ-165", "REQ-166", "REQ-167", "REQ-168", "REQ-169", "REQ-170", "REQ-171", "REQ-172", "REQ-173", "REQ-174", "REQ-175", "REQ-176", "REQ-177", "REQ-178", "REQ-179", "REQ-180", "REQ-181", "REQ-182", "REQ-183", "REQ-184"],
"regression": {"Verified": 18, "Decayed": 0, "Broken": 0, "Skipped": 4}
} }
+1 -1
View File
@@ -989,7 +989,7 @@ conversation before execution; D-108..D-112 resolved at CLARIFY.
| D-111 | Lambda env-var defaults (`CONTRACTS_TABLE` default `"acdl-contracts"`, etc.) → `nova-contracts`. | `core/lambda/contract_ingestor.py` has hardcoded `acdl-*` default table names. These become `nova-*` in P4 (resource migration). P2 changes the env-var name (`ACDL_*``NOVA_*`); P4 changes the default values to `nova-*`. | P4 updates Lambda defaults. | | D-111 | Lambda env-var defaults (`CONTRACTS_TABLE` default `"acdl-contracts"`, etc.) → `nova-contracts`. | `core/lambda/contract_ingestor.py` has hardcoded `acdl-*` default table names. These become `nova-*` in P4 (resource migration). P2 changes the env-var name (`ACDL_*``NOVA_*`); P4 changes the default values to `nova-*`. | P4 updates Lambda defaults. |
| D-112 | `nova` slug: no `project:` prefix on branches (single-project mode). | `config.json` has `projects[]` with one entry (slug `acdl`) but `git.branching_strategy` is `flat` and the established convention since v1.0 is flat branches (no `<slug>/` prefix). Nova rebrand does NOT change the branch prefix convention. Commit `---ci---` blocks use `project: acdl` (the config slug, unchanged). | Branches stay `milestone/v1.15-nova`, `phase/NN-*`; no `acdl/` or `nova/` prefix. | | D-112 | `nova` slug: no `project:` prefix on branches (single-project mode). | `config.json` has `projects[]` with one entry (slug `acdl`) but `git.branching_strategy` is `flat` and the established convention since v1.0 is flat branches (no `<slug>/` prefix). Nova rebrand does NOT change the branch prefix convention. Commit `---ci---` blocks use `project: acdl` (the config slug, unchanged). | Branches stay `milestone/v1.15-nova`, `phase/NN-*`; no `acdl/` or `nova/` prefix. |
## Objective for Milestone v1.16 (active — NFR Simplification) ## Objective for Milestone v1.16 (complete — NFR Simplification, tag `v1.15.26`)
A 20-phase NFR sweep (no new features) themed around five axes the user A 20-phase NFR sweep (no new features) themed around five axes the user
directed during ideation: **Simplify without regressions**, **Security**, directed during ideation: **Simplify without regressions**, **Security**,
+26 -26
View File
@@ -1,6 +1,6 @@
{ {
"run_id": "regr-1785588523", "run_id": "regr-1785591207",
"run_at_utc": "2026-08-01T12:48:43Z", "run_at_utc": "2026-08-01T13:33:27Z",
"milestone": "v1.10", "milestone": "v1.10",
"phase": 52, "phase": 52,
"summary": { "summary": {
@@ -17,7 +17,7 @@
"status": "Verified", "status": "Verified",
"detail": "exit 0; 2 sample contracts validate", "detail": "exit 0; 2 sample contracts validate",
"tier": "local", "tier": "local",
"duration_ms": 260 "duration_ms": 235
}, },
{ {
"capability_id": "CAP-002", "capability_id": "CAP-002",
@@ -25,7 +25,7 @@
"status": "Verified", "status": "Verified",
"detail": "exit 0; env schema validates", "detail": "exit 0; env schema validates",
"tier": "local", "tier": "local",
"duration_ms": 202 "duration_ms": 201
}, },
{ {
"capability_id": "CAP-003", "capability_id": "CAP-003",
@@ -33,7 +33,7 @@
"status": "Verified", "status": "Verified",
"detail": "exit 0; ", "detail": "exit 0; ",
"tier": "local", "tier": "local",
"duration_ms": 266 "duration_ms": 261
}, },
{ {
"capability_id": "CAP-004", "capability_id": "CAP-004",
@@ -41,7 +41,7 @@
"status": "Verified", "status": "Verified",
"detail": "exit 0; ", "detail": "exit 0; ",
"tier": "local", "tier": "local",
"duration_ms": 248 "duration_ms": 259
}, },
{ {
"capability_id": "CAP-005", "capability_id": "CAP-005",
@@ -57,7 +57,7 @@
"status": "Verified", "status": "Verified",
"detail": "exit 0; interpolation ok", "detail": "exit 0; interpolation ok",
"tier": "local", "tier": "local",
"duration_ms": 209 "duration_ms": 242
}, },
{ {
"capability_id": "CAP-007", "capability_id": "CAP-007",
@@ -65,7 +65,7 @@
"status": "Verified", "status": "Verified",
"detail": "exit 0; confidence band=pass", "detail": "exit 0; confidence band=pass",
"tier": "local", "tier": "local",
"duration_ms": 80 "duration_ms": 91
}, },
{ {
"capability_id": "CAP-008", "capability_id": "CAP-008",
@@ -73,15 +73,15 @@
"status": "Verified", "status": "Verified",
"detail": "exit 0; outbox hash chain ok", "detail": "exit 0; outbox hash chain ok",
"tier": "local", "tier": "local",
"duration_ms": 319 "duration_ms": 456
}, },
{ {
"capability_id": "CAP-009", "capability_id": "CAP-009",
"name": "offline pytest suite passes", "name": "offline pytest suite passes",
"status": "Verified", "status": "Verified",
"detail": "exit 0; [ 98%]\ntests/test_wiz_adapter_real_client.py ......... [100%]\n\n====================== 577 passed, 2 deselected in 53.53s ======================", "detail": "exit 0; [ 98%]\ntests/test_wiz_adapter_real_client.py ......... [100%]\n\n================= 586 passed, 2 deselected in 71.63s (0:01:11) =================",
"tier": "local", "tier": "local",
"duration_ms": 55005 "duration_ms": 72988
}, },
{ {
"capability_id": "CAP-010", "capability_id": "CAP-010",
@@ -89,23 +89,23 @@
"status": "Verified", "status": "Verified",
"detail": "exit 0; resource(s))\n\n=== PLATFORM CHECK OK ===\ncontract -> resolver -> stack -> adapter -> structure validated (offline, no AWS)\ncheck-only: OK\n\n=== CI PIPELINE OK ===\n3 stages passed: lint, test, check-only", "detail": "exit 0; resource(s))\n\n=== PLATFORM CHECK OK ===\ncontract -> resolver -> stack -> adapter -> structure validated (offline, no AWS)\ncheck-only: OK\n\n=== CI PIPELINE OK ===\n3 stages passed: lint, test, check-only",
"tier": "local", "tier": "local",
"duration_ms": 59882 "duration_ms": 73275
}, },
{ {
"capability_id": "CAP-011", "capability_id": "CAP-011",
"name": "headline E2E runs against the local emulating tier (microservice)", "name": "headline E2E runs against the local emulating tier (microservice)",
"status": "Verified", "status": "Verified",
"detail": "exit 0; al-emulator\",\n \"desired_count\": 1,\n \"running_count\": 1\n },\n \"outbox_dir\": \"/tmp/nova_local_e2e_cuwlkzrj/outbox\",\n \"outbox_events\": 2,\n \"outbox_chain_verified\": true,\n \"lambda_status\": 200\n}", "detail": "exit 0; al-emulator\",\n \"desired_count\": 1,\n \"running_count\": 1\n },\n \"outbox_dir\": \"/tmp/nova_local_e2e_6vnrnin1/outbox\",\n \"outbox_events\": 2,\n \"outbox_chain_verified\": true,\n \"lambda_status\": 200\n}",
"tier": "local", "tier": "local",
"duration_ms": 561 "duration_ms": 634
}, },
{ {
"capability_id": "CAP-012", "capability_id": "CAP-012",
"name": "local E2E on the static-assets stack (no ECS)", "name": "local E2E on the static-assets stack (no ECS)",
"status": "Verified", "status": "Verified",
"detail": "exit 0; nova_local_e2e_mfeuiylw/tf\",\n \"backend\": \"local\",\n \"ecs\": null,\n \"outbox_dir\": \"/tmp/nova_local_e2e_mfeuiylw/outbox\",\n \"outbox_events\": 2,\n \"outbox_chain_verified\": true,\n \"lambda_status\": 200\n}", "detail": "exit 0; nova_local_e2e_uq4kkhze/tf\",\n \"backend\": \"local\",\n \"ecs\": null,\n \"outbox_dir\": \"/tmp/nova_local_e2e_uq4kkhze/outbox\",\n \"outbox_events\": 2,\n \"outbox_chain_verified\": true,\n \"lambda_status\": 200\n}",
"tier": "local", "tier": "local",
"duration_ms": 492 "duration_ms": 584
}, },
{ {
"capability_id": "CAP-013", "capability_id": "CAP-013",
@@ -113,7 +113,7 @@
"status": "Skipped", "status": "Skipped",
"detail": "terraform init: state bucket absent (post-v1.11-teardown, D-096) [microservice]", "detail": "terraform init: state bucket absent (post-v1.11-teardown, D-096) [microservice]",
"tier": "live-aws", "tier": "live-aws",
"duration_ms": 851 "duration_ms": 737
}, },
{ {
"capability_id": "CAP-014", "capability_id": "CAP-014",
@@ -121,7 +121,7 @@
"status": "Skipped", "status": "Skipped",
"detail": "terraform init: state bucket absent (post-v1.11-teardown, D-096) [static-assets]", "detail": "terraform init: state bucket absent (post-v1.11-teardown, D-096) [static-assets]",
"tier": "live-aws", "tier": "live-aws",
"duration_ms": 724 "duration_ms": 676
}, },
{ {
"capability_id": "CAP-015", "capability_id": "CAP-015",
@@ -129,7 +129,7 @@
"status": "Skipped", "status": "Skipped",
"detail": "nova-outbox absent (post-v1.11-teardown steady state, D-096)", "detail": "nova-outbox absent (post-v1.11-teardown steady state, D-096)",
"tier": "live-aws", "tier": "live-aws",
"duration_ms": 487 "duration_ms": 664
}, },
{ {
"capability_id": "CAP-016", "capability_id": "CAP-016",
@@ -137,7 +137,7 @@
"status": "Skipped", "status": "Skipped",
"detail": "state bucket nova-tfstate-581513795199-us-east-1 absent (post-v1.11-teardown, D-096)", "detail": "state bucket nova-tfstate-581513795199-us-east-1 absent (post-v1.11-teardown, D-096)",
"tier": "live-aws", "tier": "live-aws",
"duration_ms": 300 "duration_ms": 245
}, },
{ {
"capability_id": "CAP-017", "capability_id": "CAP-017",
@@ -145,7 +145,7 @@
"status": "Verified", "status": "Verified",
"detail": "terraform files present + fmt -check passes + simple/complex contracts resolve", "detail": "terraform files present + fmt -check passes + simple/complex contracts resolve",
"tier": "lifecycle-pipeline", "tier": "lifecycle-pipeline",
"duration_ms": 579 "duration_ms": 586
}, },
{ {
"capability_id": "CAP-018", "capability_id": "CAP-018",
@@ -153,7 +153,7 @@
"status": "Verified", "status": "Verified",
"detail": "LocalLambdaStub instantiates (local tier evidence)", "detail": "LocalLambdaStub instantiates (local tier evidence)",
"tier": "lifecycle-pipeline", "tier": "lifecycle-pipeline",
"duration_ms": 139 "duration_ms": 138
}, },
{ {
"capability_id": "CAP-019", "capability_id": "CAP-019",
@@ -161,7 +161,7 @@
"status": "Verified", "status": "Verified",
"detail": "L2 composition resolves (simple + complex contracts; offline proxy)", "detail": "L2 composition resolves (simple + complex contracts; offline proxy)",
"tier": "lifecycle-pipeline", "tier": "lifecycle-pipeline",
"duration_ms": 553 "duration_ms": 519
}, },
{ {
"capability_id": "CAP-020", "capability_id": "CAP-020",
@@ -169,7 +169,7 @@
"status": "Verified", "status": "Verified",
"detail": "L2 composition resolves (simple + complex contracts; offline proxy)", "detail": "L2 composition resolves (simple + complex contracts; offline proxy)",
"tier": "lifecycle-pipeline", "tier": "lifecycle-pipeline",
"duration_ms": 561 "duration_ms": 521
}, },
{ {
"capability_id": "CAP-021", "capability_id": "CAP-021",
@@ -177,7 +177,7 @@
"status": "Verified", "status": "Verified",
"detail": "terraform files present + fmt -check passes + simple/complex contracts resolve", "detail": "terraform files present + fmt -check passes + simple/complex contracts resolve",
"tier": "lifecycle-pipeline", "tier": "lifecycle-pipeline",
"duration_ms": 598 "duration_ms": 562
}, },
{ {
"capability_id": "CAP-022", "capability_id": "CAP-022",
@@ -185,7 +185,7 @@
"status": "Verified", "status": "Verified",
"detail": "terraform files present + fmt -check passes + simple/complex contracts resolve", "detail": "terraform files present + fmt -check passes + simple/complex contracts resolve",
"tier": "lifecycle-pipeline", "tier": "lifecycle-pipeline",
"duration_ms": 570 "duration_ms": 611
} }
] ]
} }
+26 -26
View File
@@ -1,51 +1,51 @@
# Regression Report — v1.10 Phase 52 # Regression Report — v1.10 Phase 52
- **Run ID:** `regr-1785588523` - **Run ID:** `regr-1785591207`
- **Run at (UTC):** 2026-08-01T12:48:43Z - **Run at (UTC):** 2026-08-01T13:33:27Z
- **Summary:** {'Verified': 18, 'Decayed': 0, 'Broken': 0, 'Skipped': 4} - **Summary:** {'Verified': 18, 'Decayed': 0, 'Broken': 0, 'Skipped': 4}
- **Passed (milestone gate):** True - **Passed (milestone gate):** True
| Capability | Name | Tier | Status | Duration (ms) | Detail | | Capability | Name | Tier | Status | Duration (ms) | Detail |
|-----------|------|------|--------|--------------|--------| |-----------|------|------|--------|--------------|--------|
| CAP-001 | contract.schema.json validates sample contracts | local | **Verified** | 260 | exit 0; 2 sample contracts validate | | CAP-001 | contract.schema.json validates sample contracts | local | **Verified** | 235 | exit 0; 2 sample contracts validate |
| CAP-002 | environment.schema.json validates env files | local | **Verified** | 202 | exit 0; env schema validates | | CAP-002 | environment.schema.json validates env files | local | **Verified** | 201 | exit 0; env schema validates |
| CAP-003 | contract_resolver resolves static-assets | local | **Verified** | 266 | exit 0; | | CAP-003 | contract_resolver resolves static-assets | local | **Verified** | 261 | exit 0; |
| CAP-004 | contract_resolver resolves microservice | local | **Verified** | 248 | exit 0; | | CAP-004 | contract_resolver resolves microservice | local | **Verified** | 259 | exit 0; |
| CAP-005 | terraform adapter emits .tf files | local | **Verified** | 337 | exit 0; | | CAP-005 | terraform adapter emits .tf files | local | **Verified** | 337 | exit 0; |
| CAP-006 | contract interpolation expands env/contract tokens | local | **Verified** | 209 | exit 0; interpolation ok | | CAP-006 | contract interpolation expands env/contract tokens | local | **Verified** | 242 | exit 0; interpolation ok |
| CAP-007 | confidence_signal.compute returns a band | local | **Verified** | 80 | exit 0; confidence band=pass | | CAP-007 | confidence_signal.compute returns a band | local | **Verified** | 91 | exit 0; confidence band=pass |
| CAP-008 | outbox_writer builds a hash-chained item | local | **Verified** | 319 | exit 0; outbox hash chain ok | | CAP-008 | outbox_writer builds a hash-chained item | local | **Verified** | 456 | exit 0; outbox hash chain ok |
| CAP-009 | offline pytest suite passes | local | **Verified** | 55005 | exit 0; [ 98%] | CAP-009 | offline pytest suite passes | local | **Verified** | 72988 | exit 0; [ 98%]
tests/test_wiz_adapter_real_client.py ......... [100%] tests/test_wiz_adapter_real_client.py ......... [100%]
====================== 577 passe | ================= 586 passed, 2 |
| CAP-010 | run_ci.sh reproduces CI pipeline locally | local | **Verified** | 59882 | exit 0; resource(s)) | CAP-010 | run_ci.sh reproduces CI pipeline locally | local | **Verified** | 73275 | exit 0; resource(s))
=== PLATFORM CHECK OK === === PLATFORM CHECK OK ===
contract -> resolver -> stack -> adapter -> structure validated (offline, no AWS) contract -> resolver -> stack -> adapter -> structure validated (offline, no AWS)
check-only: OK check-only: OK
=== CI PIPELIN | === CI PIPELIN |
| CAP-011 | headline E2E runs against the local emulating tier (microservice) | local | **Verified** | 561 | exit 0; al-emulator", | CAP-011 | headline E2E runs against the local emulating tier (microservice) | local | **Verified** | 634 | exit 0; al-emulator",
"desired_count": 1, "desired_count": 1,
"running_count": 1 "running_count": 1
}, },
"outbox_dir": "/tmp/nova_local_e2e_cuwlkzrj/outbox", "outbox_dir": "/tmp/nova_local_e2e_6vnrnin1/outbox",
"outbox_events": 2, "outbox_events": 2,
"outbox | "outbox |
| CAP-012 | local E2E on the static-assets stack (no ECS) | local | **Verified** | 492 | exit 0; nova_local_e2e_mfeuiylw/tf", | CAP-012 | local E2E on the static-assets stack (no ECS) | local | **Verified** | 584 | exit 0; nova_local_e2e_uq4kkhze/tf",
"backend": "local", "backend": "local",
"ecs": null, "ecs": null,
"outbox_dir": "/tmp/nova_local_e2e_mfeuiylw/outbox", "outbox_dir": "/tmp/nova_local_e2e_uq4kkhze/outbox",
"outbox_events": 2, "outbox_events": 2,
"outbox | "outbox |
| CAP-013 | terraform init+validate+plan live AWS (microservice) | live-aws | **Skipped** | 851 | terraform init: state bucket absent (post-v1.11-teardown, D-096) [microservice] | | CAP-013 | terraform init+validate+plan live AWS (microservice) | live-aws | **Skipped** | 737 | terraform init: state bucket absent (post-v1.11-teardown, D-096) [microservice] |
| CAP-014 | terraform init+validate+plan live AWS (static-assets) | live-aws | **Skipped** | 724 | terraform init: state bucket absent (post-v1.11-teardown, D-096) [static-assets] | | CAP-014 | terraform init+validate+plan live AWS (static-assets) | live-aws | **Skipped** | 676 | terraform init: state bucket absent (post-v1.11-teardown, D-096) [static-assets] |
| CAP-015 | DynamoDB outbox table exists (live AWS) | live-aws | **Skipped** | 487 | nova-outbox absent (post-v1.11-teardown steady state, D-096) | | CAP-015 | DynamoDB outbox table exists (live AWS) | live-aws | **Skipped** | 664 | nova-outbox absent (post-v1.11-teardown steady state, D-096) |
| CAP-016 | S3 state bucket exists + readable (live AWS) | live-aws | **Skipped** | 300 | state bucket nova-tfstate-581513795199-us-east-1 absent (post-v1.11-teardown, D-096) | | CAP-016 | S3 state bucket exists + readable (live AWS) | live-aws | **Skipped** | 245 | state bucket nova-tfstate-581513795199-us-east-1 absent (post-v1.11-teardown, D-096) |
| CAP-017 | DynamoDB nova-contracts table (lifecycle pipeline evidence) | lifecycle-pipeline | **Verified** | 579 | terraform files present + fmt -check passes + simple/complex contracts resolve | | CAP-017 | DynamoDB nova-contracts table (lifecycle pipeline evidence) | lifecycle-pipeline | **Verified** | 586 | terraform files present + fmt -check passes + simple/complex contracts resolve |
| CAP-018 | Lambda contract-ingestor (local stub + lifecycle evidence) | lifecycle-pipeline | **Verified** | 139 | LocalLambdaStub instantiates (local tier evidence) | | CAP-018 | Lambda contract-ingestor (local stub + lifecycle evidence) | lifecycle-pipeline | **Verified** | 138 | LocalLambdaStub instantiates (local tier evidence) |
| CAP-019 | ECS cluster + service (L2 microservice lifecycle evidence) | lifecycle-pipeline | **Verified** | 553 | L2 composition resolves (simple + complex contracts; offline proxy) | | CAP-019 | ECS cluster + service (L2 microservice lifecycle evidence) | lifecycle-pipeline | **Verified** | 519 | L2 composition resolves (simple + complex contracts; offline proxy) |
| CAP-020 | CloudFront + WAF (L2 static-assets lifecycle evidence) | lifecycle-pipeline | **Verified** | 561 | L2 composition resolves (simple + complex contracts; offline proxy) | | CAP-020 | CloudFront + WAF (L2 static-assets lifecycle evidence) | lifecycle-pipeline | **Verified** | 521 | L2 composition resolves (simple + complex contracts; offline proxy) |
| CAP-021 | uptime-kuma (L1 uptime lifecycle evidence) | lifecycle-pipeline | **Verified** | 598 | terraform files present + fmt -check passes + simple/complex contracts resolve | | CAP-021 | uptime-kuma (L1 uptime lifecycle evidence) | lifecycle-pipeline | **Verified** | 562 | terraform files present + fmt -check passes + simple/complex contracts resolve |
| CAP-022 | OIDC role (L1 iam-role lifecycle evidence) | lifecycle-pipeline | **Verified** | 570 | terraform files present + fmt -check passes + simple/complex contracts resolve | | CAP-022 | OIDC role (L1 iam-role lifecycle evidence) | lifecycle-pipeline | **Verified** | 611 | terraform files present + fmt -check passes + simple/complex contracts resolve |
+20 -20
View File
@@ -921,26 +921,26 @@ simplification and the first self-service onboarding request path.
| Requirement | Phase | Status | | Requirement | Phase | Status |
|-------------|-------|--------| |-------------|-------|--------|
| REQ-165 | P1 | pending | | REQ-165 | P1 | complete |
| REQ-166 | P2 | pending | | REQ-166 | P2 | complete |
| REQ-167 | P3 | pending | | REQ-167 | P3 | complete |
| REQ-168 | P4 | pending | | REQ-168 | P4 | complete |
| REQ-169 | P5 | pending | | REQ-169 | P5 | complete |
| REQ-170 | P6 | pending | | REQ-170 | P6 | complete |
| REQ-171 | P7 | pending | | REQ-171 | P7 | complete |
| REQ-172 | P8 | pending | | REQ-172 | P8 | complete |
| REQ-173 | P9 | pending | | REQ-173 | P9 | complete |
| REQ-174 | P10 | pending | | REQ-174 | P10 | complete |
| REQ-175 | P11 | pending | | REQ-175 | P11 | complete |
| REQ-176 | P12 | pending | | REQ-176 | P12 | complete |
| REQ-177 | P13 | pending | | REQ-177 | P13 | complete |
| REQ-178 | P14 | pending | | REQ-178 | P14 | complete |
| REQ-179 | P15 | pending | | REQ-179 | P15 | complete |
| REQ-180 | P16 | pending | | REQ-180 | P16 | complete |
| REQ-181 | P17 | pending | | REQ-181 | P17 | complete |
| REQ-182 | P18 | pending | | REQ-182 | P18 | complete |
| REQ-183 | P19 | pending | | REQ-183 | P19 | complete |
| REQ-184 | P20 | pending | | REQ-184 | P20 | complete |
### Out of Scope (v1.16) ### Out of Scope (v1.16)
- New features (feat phases). v1.16 is NFR-only. - New features (feat phases). v1.16 is NFR-only.
+53
View File
@@ -1630,3 +1630,56 @@ milestone release). (G-104 binding.)
- Tag `v1.15.4` created; milestone merged to main. - Tag `v1.15.4` created; milestone merged to main.
After Phase P5: milestone COMPLETE — `v1.15.4` IS the v1.15 release. After Phase P5: milestone COMPLETE — `v1.15.4` IS the v1.15 release.
---
## v1.16 (complete — Nova Simplification, tag `v1.15.26`)
A 20-phase NFR sweep (no new features) themed around five user-directed
axes: **Simplify without regressions**, **Security**, **Maintainability**,
**User/Developer Experience**, **No Humans Onboarding Flow**. The v1.15
rebrand left a fresh debt layer (stale brand strings, a state-bucket
drift, a Kyverno policy contradicting the Nova tagging standard, dead
code) that this milestone cleared, alongside genuine simplification
(dedup helpers, a workflow generator, file splits) and the first
self-service onboarding request path (request-path only; real AWS
provisioning deferred, D-113).
**Milestone type:** NFR (all phases fix/chore/docs/refactor/test). The
final phase's patch IS the deliverable. Tags on the v1.15.x line:
`v1.15.5` (P0) → `v1.15.6..v1.15.25` (P1P20) → `v1.15.26` (P21 final =
milestone release).
**Regression gate (D-118, G-111):** 18 Verified + 4 Skipped (CAP-013..016
live-AWS caps are the post-v1.11-teardown steady state, D-096; re-
provisioning is a future feature). 0 Decayed/Broken at P9 + P21.
**Grill:** PASS-with-binding (G-111..G-113, E-002 deferred to P21).
G-111: gate criterion restated 18V+4S + Skipped logic. G-112: P9 source
model pinned. G-113: P12/P13 import direction documented.
**Wave outcomes:**
- Wave 1 (P1P4): state-bucket + Kyverno rebrand fix (correctness
regression), user-facing ACDL→Nova sweep, dead-code cleanup, except
narrowing.
- Wave 2 (P5P9): regression-verify dedup (~70 lines), run-platform
HITL fn + config, contract-resolver envloader + registry kind, workflow
generator (sync_workflows.py + workflows-src/), run-platform split
(decommission + uptime helpers). Gate PASS at P9.
- Wave 3 (P10P14): ingestor defense-in-depth (fail closed on missing
IAM), payload validation (size cap + schema), split contract-resolver
(decommission + CLI modules), split regression-verify (CLI module),
schema-driven outputs + schema cache. Mid-milestone checkpoint clean.
- Wave 4 (P15P17): run-platform --help + flags doc, workflows README
catalog (7 workflows), getting-started consolidation (offline-first).
- Wave 5 (P18P20): onboarding schema + onboard_consumer Lambda action,
env-file autogen (core/onboarding.py), cross-account role Terraform
(offline-proven, D-114).
**Outcome:** 20 requirements (REQ-165..184) satisfied; ~630 tests pass;
regression gate 18V+4S; the onboarding request path is self-service (no
"contact the platform team" handoff); real AWS provisioning explicitly
deferred (D-113/D-114).
Ship tag at milestone COMPLETE: `v1.15.26` (NFR milestone; final patch IS
the release). **DONE.**
+10 -6
View File
@@ -55,6 +55,8 @@ def load(env_name, root=None):
def _onboarding_message(env_name): def _onboarding_message(env_name):
# P19 (REQ-183): rebranded Nova self-service request path — no longer
# routes to "contact the platform team" for the request step.
return ( return (
"=== Nova Environment Onboarding ===\n" "=== Nova Environment Onboarding ===\n"
f"No environment named '{env_name}' is bound to this repository.\n\n" f"No environment named '{env_name}' is bound to this repository.\n\n"
@@ -66,13 +68,15 @@ def _onboarding_message(env_name):
" - an IAM role surfaced to your repo via attribute-based\n" " - an IAM role surfaced to your repo via attribute-based\n"
" authorization (ABAC)\n\n" " authorization (ABAC)\n\n"
"You do not provide an AWS account, VPC, subnet, or state bucket.\n\n" "You do not provide an AWS account, VPC, subnet, or state bucket.\n\n"
"To request an environment:\n" "To request an environment (self-service):\n"
" 1. Contact the platform team with your repo name + the\n" " 1. Submit an onboarding request to the Nova Lambda\n"
" (action: onboard_consumer) with your repo name + the\n"
" environment name you need (e.g. 'dev').\n" " environment name you need (e.g. 'dev').\n"
" 2. The platform team provisions the account/network/state/role\n" " 2. The platform generates an environment binding + opens a PR.\n"
" and binds the environment to your repo.\n" " 3. The platform provisions the account/network/state/role and\n"
" 3. Your next pipeline run will proceed normally.\n\n" " grants the ABAC role. Your next pipeline run proceeds.\n\n"
"Expected turnaround: contact the platform team for current SLA.\n" "Run: python3 core/onboarding.py --request '{...}' to generate a\n"
"binding file locally, or POST to the Lambda onboard_consumer action.\n"
"===================================\n" "===================================\n"
) )
+10 -2
View File
@@ -33,5 +33,13 @@ halting the pipeline before any work is done.
A new environment is a platform-team action: provision the AWS account / A new environment is a platform-team action: provision the AWS account /
network / state backend / IAM role, then add a `<name>.json` here and bind network / state backend / IAM role, then add a `<name>.json` here and bind
it to the consumer repo. Self-service environment provisioning is on the it to the consumer repo.
roadmap; today it is a platform-team action.
**P19 (REQ-183):** the *request* step is now self-service. A consumer
submits an onboarding request (POST to the Nova Lambda `onboard_consumer`
action, or `python3 core/onboarding.py --request '{...}'`) and the
platform generates a `<name>.json` binding file from the request + opens
a PR. The actual AWS account/network/state provisioning + cross-account
role grant remains a platform-team action (a future feature milestone
will automate the provisioning; the cross-account role Terraform is
offline-proven in P20/REQ-184).
+131
View File
@@ -0,0 +1,131 @@
#!/usr/bin/env python3
"""Nova Onboarding — auto-generate an environment binding file (P19, REQ-183).
Given a consumer onboarding request (validated against
schemas/onboarding.schema.json), generate a ``<env>.json`` environment
binding file from the dev template, filling in the consumer's ownerId +
billingTag. The generated file is a starting point for the platform team
(or a future automation) to bind to a real AWS account.
This is the "request path" half of the no-humans onboarding flow (D-113).
Real AWS account/network/state provisioning is a future feature milestone;
this module removes the human handoff from the *request* step by
generating the binding file + emitting a git patch / PR-branch instruction.
Usage:
python3 core/onboarding.py <request.json> [--out <env.json>]
python3 core/onboarding.py --request '{"consumerRepo":"acdl/c","requestedEnvironment":"qa","ownerId":"team-a","billingTag":"cc-a"}'
"""
from __future__ import annotations
import argparse
import json
import os
import sys
from pathlib import Path
from typing import Any, Dict
def _repo_root() -> Path:
return Path(__file__).resolve().parent.parent
def _load_template_env(template_env: str = "dev", root: Path | None = None) -> Dict[str, Any]:
"""Load the template environment JSON (defaults to dev.json)."""
root = root or _repo_root()
env_path = root / "core" / "environments" / f"{template_env}.json"
if not env_path.is_file():
raise FileNotFoundError(f"template environment {env_path} not found")
return json.loads(env_path.read_text())
def generate_env_file(
request: Dict[str, Any],
template_env: str = "dev",
root: Path | None = None,
) -> Dict[str, Any]:
"""Generate an environment binding dict from a consumer onboarding request.
The generated dict is a copy of the template env with:
- ``name`` the requested environment
- ``description`` notes the consumer + owner
- ``account_id`` placeholder (000000000000) for the platform team
to fill with the real account
- ``ownerId`` + ``billingTag`` from the request (for ABAC + cost)
The dict validates against schemas/environment.schema.json.
Returns the generated env dict.
"""
template = _load_template_env(template_env, root)
requested = request["requestedEnvironment"]
owner = request["ownerId"]
billing = request["billingTag"]
consumer = request["consumerRepo"]
env = dict(template)
env["name"] = requested
env["description"] = (
f"Auto-generated binding for {consumer} (owner={owner}, "
f"billing={billing}). Replace account_id with the real "
f"{requested} account before deploying."
)
env["account_id"] = "000000000000" # placeholder — platform team fills
env["ownerId"] = owner
env["billingTag"] = billing
return env
def _onboarding_request_message(env_name: str) -> str:
"""P19 (REQ-183): the rebranded Nova onboarding message — self-service
request path, no longer routes to 'contact the platform team'."""
return (
"=== Nova Environment Onboarding ===\n"
f"No environment named '{env_name}' is bound to this repository.\n\n"
"Nova environments are platform-managed. The platform provisions on\n"
"your behalf:\n"
" - an AWS account (or a scoped partition of one)\n"
" - a network (VPC + subnets)\n"
" - a state backend (an S3 bucket + DynamoDB lock table)\n"
" - an IAM role surfaced to your repo via attribute-based\n"
" authorization (ABAC)\n\n"
"You do not provide an AWS account, VPC, subnet, or state bucket.\n\n"
"To request an environment (self-service):\n"
" 1. Submit an onboarding request to the Nova Lambda\n"
" (action: onboard_consumer) with your repo name + the\n"
" environment name you need (e.g. 'dev').\n"
" 2. The platform generates an environment binding + opens a PR.\n"
" 3. The platform provisions the account/network/state/role and\n"
" grants the ABAC role. Your next pipeline run proceeds.\n\n"
"Run: python3 core/onboarding.py --request '{...}' to generate a\n"
"binding file locally, or POST to the Lambda onboard_consumer action.\n"
"===================================\n"
)
def main(argv: list[str] | None = None) -> int:
parser = argparse.ArgumentParser(description="Generate an env binding from an onboarding request.")
group = parser.add_mutually_exclusive_group(required=True)
group.add_argument("request_file", nargs="?", help="path to a request JSON file")
group.add_argument("--request", help="inline request JSON string")
parser.add_argument("--out", help="output path for the generated env JSON (default: stdout)")
parser.add_argument("--template-env", default="dev", help="template environment (default: dev)")
args = parser.parse_args(argv)
if args.request:
request = json.loads(args.request)
else:
request = json.loads(Path(args.request_file).read_text())
env = generate_env_file(request, template_env=args.template_env)
env_json = json.dumps(env, indent=2) + "\n"
if args.out:
Path(args.out).write_text(env_json)
print(f"wrote: {args.out}")
else:
print(env_json)
return 0
if __name__ == "__main__":
sys.exit(main())
+87
View File
@@ -0,0 +1,87 @@
# Nova Onboarding — No-Humans Request Path (v1.16, REQ-182..184)
The v1.16 milestone implements the **request path** of the no-humans
onboarding flow (D-113). A consumer can submit an onboarding request
without contacting the platform team; the platform generates an
environment binding + (in a future milestone) provisions the AWS resources.
## The 3-step request path
### Step 1 — Submit an onboarding request (P18, REQ-182)
A consumer submits an onboarding request to the Nova platform Lambda:
```bash
# Via the Lambda Function URL (IAM auth):
curl -X POST "$NOVA_LAMBDA_URL" \
-H "Content-Type: application/json" \
-d '{
"action": "onboard_consumer",
"consumerRepo": "acdl/my-app",
"requestedEnvironment": "dev",
"ownerId": "team-x",
"billingTag": "cost-center-x"
}'
```
The Lambda validates the payload against
[`schemas/onboarding.schema.json`](../schemas/onboarding.schema.json),
then writes a `pending` row to the `nova-contracts` DynamoDB table
(D-119). No AWS resources are created by this action (D-113).
### Step 2 — Generate an environment binding (P19, REQ-183)
The platform (or the consumer locally) generates an environment binding
file from the request:
```bash
python3 core/onboarding.py --request '{
"consumerRepo": "acdl/my-app",
"requestedEnvironment": "qa",
"ownerId": "team-x",
"billingTag": "cost-center-x"
}' --out core/environments/qa.json
```
This produces a `<env>.json` from the `dev.json` template, filling in
the `ownerId` + `billingTag` + a description. The `account_id` is a
placeholder (`000000000000`) for the platform team to fill with the real
account. The generated file validates against
[`schemas/environment.schema.json`](../schemas/environment.schema.json).
### Step 3 — Cross-account role + ABAC tag grant (P20, REQ-184)
The platform authors the consumer deploy-role + `nova:owner` ABAC tag
grant via Terraform:
```bash
cd terraform/onboarding
terraform init -backend=false
terraform validate
NOVA_AWS_ACCOUNT_ID=123456789012 terraform plan \
-var consumer_repo=acdl/my-app \
-var owner_id=team-x
```
**Offline-proven only (D-114):** `terraform validate` + `terraform plan`
pass; **no live apply** in v1.16. The live apply (creating the real
cross-account role + OIDC trust) is deferred to a future feature
milestone (D-113).
## What is NOT automated (deferred)
- **Real AWS account/network/state provisioning** — the request path
generates a binding file with a placeholder `account_id`; the actual
AWS account creation + VPC + state backend is a future feature (D-113).
- **Live cross-account role apply** — the Terraform is offline-proven
only (D-114); live apply is deferred.
- **OIDC trust policy** — the onboarding Terraform uses a placeholder
OIDC provider; real OIDC federation is blocked on
go-gitea/gitea#36988 (carries forward from v1.1).
## See also
- [`schemas/onboarding.schema.json`](../schemas/onboarding.schema.json) — the request schema
- [`core/onboarding.py`](../core/onboarding.py) — the env-file generator
- [`terraform/onboarding/`](../terraform/onboarding/) — the role-grant Terraform
- [`core/environments/README.md`](../core/environments/README.md) — environment binding docs
+42
View File
@@ -0,0 +1,42 @@
# terraform/onboarding/ — Consumer deploy-role + ABAC tag grant (P20, REQ-184)
Offline-proven Terraform for the cross-account consumer deploy-role +
`nova:owner` ABAC tag grant. This is the "role grant" half of the
no-humans onboarding flow (D-113); the "request" half is P18 (Lambda
action) + P19 (env-file autogen).
## Scope (D-114)
This Terraform is **offline-proven only** in v1.16:
- `terraform validate` passes.
- `terraform plan` (with `NOVA_AWS_ACCOUNT_ID` set) produces the expected
role + policy.
- **No live apply**`NOVA_LIFECYCLE_MODE=plan` default. Live apply is
deferred to a future feature milestone (D-113/D-114).
## Variables
| Variable | Description | Default |
|----------|-------------|---------|
| `consumer_repo` | The consumer repository (org/repo) | `acdl/consumer-a` |
| `owner_id` | The owning team (for `nova:owner` tag) | `team-a` |
| `account_id` | The consumer's AWS account ID | `000000000000` |
| `region` | AWS region | `us-east-1` |
## Resources
- `aws_iam_role.consumer_deploy` — the consumer's deploy role with a
trust policy (assumed by the consumer's CI runner).
- `aws_iam_role_policy.consumer_invoke` — inline policy granting
`lambda:InvokeFunctionUrl` on the platform Lambda, scoped via
`aws:PrincipalTag/nova:owner == var.owner_id` (ABAC).
- `aws_iam_tag.owner` — tags the role with `nova:owner` + `nova:contract`.
## Usage (offline)
```bash
cd terraform/onboarding
terraform init -backend=false
terraform validate
NOVA_AWS_ACCOUNT_ID=123456789012 terraform plan -var consumer_repo=acdl/my-app -var owner_id=team-x
```
+120
View File
@@ -0,0 +1,120 @@
terraform {
required_version = ">= 1.9, < 1.10"
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
}
}
variable "consumer_repo" {
description = "The consumer repository (org/repo) — for the nova:contract tag."
type = string
default = "acdl/consumer-a"
}
variable "owner_id" {
description = "The owning team (for the nova:owner ABAC tag)."
type = string
default = "team-a"
}
variable "account_id" {
description = "The consumer's AWS account ID (where the deploy role is created)."
type = string
default = "000000000000"
}
variable "region" {
description = "AWS region."
type = string
default = "us-east-1"
}
provider "aws" {
region = var.region
}
# P20 (REQ-184): consumer deploy role the role the consumer's CI runner
# assumes to invoke the platform Lambda + deploy via the reusable workflow.
# The trust policy allows the consumer's CI runner (GitHub Actions /
# Gitea act_runner) to assume this role. In a real deployment, the trust
# policy is scoped to the consumer's OIDC provider; for offline-proven
# mode, a placeholder trust is used.
resource "aws_iam_role" "consumer_deploy" {
name = "nova-${replace(var.consumer_repo, "/", "-")}-deploy"
assume_role_policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Effect = "Allow"
Principal = {
# Placeholder: in a real deployment, this is the consumer's
# OIDC provider ARN. Offline-proven mode uses a wildcard.
Federated = "arn:aws:iam::${var.account_id}:oidc-provider/token.actions.githubusercontent.com"
}
Action = "sts:AssumeRoleWithWebIdentity"
Condition = {
StringEquals = {
"token.actions.githubusercontent.com:aud" = "sts.amazonaws.com"
}
StringLike = {
"token.actions.githubusercontent.com:sub" = "repo:${var.consumer_repo}:*"
}
}
}
]
})
tags = {
"nova:owner" = var.owner_id
"nova:contract" = var.consumer_repo
"nova:environment" = "dev"
}
}
# P20 (REQ-184): inline policy granting the consumer's deploy role the
# right to invoke the platform Lambda's Function URL, scoped via ABAC
# (aws:PrincipalTag/nova:owner == var.owner_id). The platform Lambda's
# resource-based policy + the consumer_invoke_policy.json template
# enforce the ABAC scope at the Lambda side; this policy grants the
# invoke permission on the consumer side.
resource "aws_iam_role_policy" "consumer_invoke" {
name = "nova-consumer-invoke"
role = aws_iam_role.consumer_deploy.id
policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Effect = "Allow"
Action = [
"lambda:InvokeFunctionUrl",
]
Resource = [
# The platform Lambda ARN (cross-account). The account_id is
# the platform account, not the consumer account. For offline-
# proven mode, a placeholder ARN is used.
"arn:aws:lambda:${var.region}:000000000000:function:nova-contract-ingestor"
]
Condition = {
StringEquals = {
"aws:PrincipalTag/nova:owner" = var.owner_id
}
}
}
]
})
}
output "consumer_deploy_role_arn" {
description = "The ARN of the consumer deploy role."
value = aws_iam_role.consumer_deploy.arn
}
output "consumer_deploy_role_name" {
description = "The name of the consumer deploy role."
value = aws_iam_role.consumer_deploy.name
}
+17 -2
View File
@@ -21,7 +21,10 @@ class TestEnvironmentCheck:
assert ok is False assert ok is False
assert "nonexistent-env" in msg assert "nonexistent-env" in msg
assert "onboarding" in msg.lower() or "Environment Onboarding" in msg assert "onboarding" in msg.lower() or "Environment Onboarding" in msg
assert "platform team" in msg.lower() # P19 (REQ-183): the message now routes to the self-service
# request path (onboard_consumer), not "contact the platform team".
assert "platform team" not in msg.lower()
assert "onboard_consumer" in msg or "self-service" in msg.lower()
def test_onboarding_message_lists_platform_provisions(self): def test_onboarding_message_lists_platform_provisions(self):
msg = _onboarding_message("qa") msg = _onboarding_message("qa")
@@ -102,4 +105,16 @@ class TestRunPlatformWireIn:
) )
assert result.returncode == 0, f"stdout: {result.stdout}\nstderr: {result.stderr}" assert result.returncode == 0, f"stdout: {result.stdout}\nstderr: {result.stderr}"
assert "PLATFORM CHECK OK" in result.stdout assert "PLATFORM CHECK OK" in result.stdout
assert "environment" in result.stdout.lower() or "Step 0" in result.stdout assert "environment" in result.stdout.lower() or "Step 0" in result.stdout
class TestOnboardingMessageSelfService:
"""P19 (REQ-183): the onboarding message is self-service, not 'contact
the platform team'."""
def test_no_contact_platform_team(self):
msg = _onboarding_message("qa")
assert "contact the platform team" not in msg.lower()
def test_mentions_self_service_request(self):
msg = _onboarding_message("qa")
assert "self-service" in msg.lower() or "onboard_consumer" in msg
+50
View File
@@ -0,0 +1,50 @@
"""Unit tests for core/onboarding.py (P19, REQ-183)."""
import json
import sys
from pathlib import Path
import pytest
sys.path.insert(0, str(Path(__file__).resolve().parent.parent))
from core.onboarding import generate_env_file, _onboarding_request_message
class TestGenerateEnvFile:
"""P19 (REQ-183): generate_env_file produces a valid env JSON."""
def test_generates_env_with_request_fields(self):
request = {
"consumerRepo": "acdl/consumer-b",
"requestedEnvironment": "qa",
"ownerId": "team-b",
"billingTag": "cost-center-b",
}
env = generate_env_file(request, template_env="dev")
assert env["name"] == "qa"
assert env["ownerId"] == "team-b"
assert env["billingTag"] == "cost-center-b"
assert env["account_id"] == "000000000000" # placeholder
assert "consumer-b" in env["description"]
def test_preserves_template_network_and_state(self):
request = {
"consumerRepo": "acdl/c",
"requestedEnvironment": "prod",
"ownerId": "team-a",
"billingTag": "cc-a",
}
env = generate_env_file(request, template_env="dev")
assert "vpc_cidr" in env["network"]
assert "bucket" in env["state_backend"]
assert env["region"] == "us-east-1"
class TestOnboardingRequestMessage:
"""P19 (REQ-183): the request message is self-service."""
def test_message_mentions_onboard_consumer(self):
msg = _onboarding_request_message("dev")
assert "onboard_consumer" in msg
assert "Nova" in msg
+38
View File
@@ -0,0 +1,38 @@
"""Unit tests for terraform/onboarding (P20, REQ-184)."""
import subprocess
from pathlib import Path
import pytest
ROOT = Path(__file__).resolve().parent.parent
ONBOARDING_DIR = ROOT / "terraform" / "onboarding"
def test_onboarding_terraform_dir_exists():
"""P20 (REQ-184): terraform/onboarding/ exists with main.tf + README."""
assert ONBOARDING_DIR.is_dir()
assert (ONBOARDING_DIR / "main.tf").is_file()
assert (ONBOARDING_DIR / "README.md").is_file()
def test_onboarding_terraform_validates():
"""P20 (REQ-184): terraform validate passes for the onboarding module
(offline-proven, D-114). Skipped if terraform is not installed."""
if not subprocess.call(["which", "terraform"], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) == 0:
pytest.skip("terraform not installed")
rc = subprocess.call(
["terraform", "validate"],
cwd=str(ONBOARDING_DIR),
stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL,
)
assert rc == 0, "terraform validate failed for terraform/onboarding/"
def test_onboarding_main_tf_has_nova_tags():
"""P20 (REQ-184): the deploy role is tagged with nova:owner + nova:contract."""
main_tf = (ONBOARDING_DIR / "main.tf").read_text()
assert '"nova:owner"' in main_tf
assert '"nova:contract"' in main_tf
assert "aws_iam_role" in main_tf
assert "lambda:InvokeFunctionUrl" in main_tf