diff --git a/.ciagent/CHECKPOINT.json b/.ciagent/CHECKPOINT.json index 78ca2d4..29faaba 100644 --- a/.ciagent/CHECKPOINT.json +++ b/.ciagent/CHECKPOINT.json @@ -1,10 +1,10 @@ { "phase": 0, - "stage": "clarify", + "stage": "research", "milestone": "v1.30", "phase_role": "pre_execution", "attempts": 0, - "updated_at": "2026-08-20T13:25:00Z", + "updated_at": "2026-08-20T13:35:00Z", "project": "acdl", "projects": ["acdl", "nova-blockchain-exchange"], "active_milestone": "v1.30", @@ -23,5 +23,5 @@ "branches_deleted": true, "releases_created": true }, - "notes": "v1.30 Phase 0 CLARIFY complete. D-241 (discrete artifact, overrides STATE.md intake assumption 3), D-242 (narrow render_pptx.py extension), D-243 (month-only date anchor). STATE.md intake assumption 3 overridden. Spec §7 Q1-Q4 auto-resolved at full autonomy. Next: RESEARCH." + "notes": "v1.30 Phase 0 RESEARCH complete. R1-R8: render pipeline behavior+limits, smoke-test conventions, Marp frontmatter/footer/notes, theme enforcement, python-pptx install (resolved), vision grounding, CAP-024 non-collision, slides.yml non-interference. PERSONAS.md: lead-developer + backend-engineer + ci-doc-writer (phase-specific) + ci-cli-engineer. frontend/data/security deactivated. Next: PLAN." } \ No newline at end of file diff --git a/.ciagent/PERSONAS.md b/.ciagent/PERSONAS.md index eb1f6a0..ba44a6f 100644 --- a/.ciagent/PERSONAS.md +++ b/.ciagent/PERSONAS.md @@ -1,267 +1,111 @@ --- project: acdl -milestone: v1.28 -generated_at: 2026-08-19 -generator: lead-developer -verification_toolchain: - typecheck: "python3 -m py_compile core/mode_resolver.py nova/cli.py 2>&1 | head -5 || true" - test: "pytest tests/test_mode_resolver.py tests/test_cli_subcommands.py -q 2>&1 | tail -15 || true" - lint: "ruff check nova/ core/lambda/nova_idp_*.py 2>/dev/null || true" - note: | - v1.28 is a feature milestone (CLI Canonicalization + Identity Layer). - Four active personas: backend-engineer (Lambda/DynamoDB/KMS/CodeArtifact), - security-engineer (Argon2id/KMS/ABAC/threat model), cli-engineer - (subcommand surface/mode_resolver/argparse/CAP-034), lead-developer - (plan/review/ship/capability gate). frontend-engineer + data-engineer - deactivated (no UI, no data pipelines). The kj-binary-in-Lambda-layer - risk (D-227, RESEARCH §7) is the highest-risk item; P2 spike confirms. ---- - -# Personas — v1.28 CLI Canonicalization + Identity Layer - -## Roster - -### backend-engineer -```yaml -active: true -domain: "Lambda functions, DynamoDB, KMS integration, dual-use packaging, CodeArtifact publish, CloudFormation generation" -frameworks: ["Python 3.12", "boto3", "argparse", "pytest", "moto[dynamodb]", "CloudFormation"] -constraints: ["INV-15", "INV-16", "INV-17", "D-228", "D-229", "D-230", "NFR-5", "NFR-6", "NFR-7", "NFR-8"] -territory: - - "core/lambda/**" - - "core/metrics/**" - - "core/env.py" - - "core/outbox_writer.py" - - "terraform/bootstrap/**" - - ".gitea/workflows/publish.yml" - - ".github/workflows/publish.yml" - - ".github/actions/nova-cli/**" -``` - -### security-engineer -```yaml -active: true -domain: "Argon2id hashing, KMS asymmetric signing (ECDSA P-256 / ES256), ABAC policy, JWKS exposure, PAT lifecycle, threat model, DER→raw ECDSA conversion" -frameworks: ["argon2-cffi", "cryptography", "pyjwt", "kyverno-json", "JMESPath", "KMS Sign/Verify/GetPublicKey"] -constraints: ["INV-15", "INV-16", "INV-17", "NFR-5", "NFR-8", "NFR-9", "D-227", "D-231"] -territory: - - "platform/abac/**" - - "core/policy_engine.py" - - "adapters/kyverno-json/**" - - "core/lambda/nova_idp_auth.py" - - "core/lambda/nova_idp_token_vend.py" - - "core/lambda/nova_idp_jwks.py" - - "docs/threat-model.md" -``` - -### cli-engineer -```yaml -active: true -domain: "CLI subcommand surface, mode_resolver, argparse, [project.scripts] entry-point, CAP-034 AST scan, nova auth/idp subgroups, property tests" -frameworks: ["Python 3.12", "argparse", "setuptools [project.scripts]", "hypothesis", "pkgutil"] -constraints: ["INV-12", "INV-13", "INV-14", "D-226", "NFR-1", "NFR-2", "NFR-3"] -territory: - - "nova/**" - - "core/mode_resolver.py" - - "pyproject.toml" - - "tests/test_mode_resolver.py" - - "tests/test_cli_subcommands.py" -``` - -### lead-developer -```yaml -active: true -domain: "Phase plan, persona roster, review gates, milestone ship, capability gate (CAP-033..038), ROADMAP/STATE/PROJECT wiring" -frameworks: ["git", "Gitea Actions", "semver tagging", ".ciagent/ discipline"] -constraints: ["INV-1..17 (cross-cutting)", "v1.28 hard constraints", "NFR-6", "NFR-11"] -territory: - - ".ciagent/**" - - "PLAN.md" - - "CHECKPOINT.json" - - "STATE.md" - - "REQUIREMENTS.md" - - "ROADMAP.md" -``` - -### frontend-engineer -```yaml -active: false -phase_specific: false -reason: "No UI in v1.28 (CLI + JSON endpoints only). JWKS serves application/json; no HTML/CSS/JS surface." -``` - -### data-engineer -```yaml -active: false -phase_specific: false -reason: "No data pipelines / metrics / PowerBI work in v1.28. The metrics layer is v1.17-complete; v1.28 adds audit events but no new fact/dim tables." -``` - -## Territory overlap notes - -- `core/lambda/contract_ingestor.py` (dual-use refactor, REQ-329) = - backend-engineer territory. `core/lambda/nova_idp_auth.py` + - `nova_idp_token_vend.py` are **co-owned** by backend-engineer (Lambda - plumbing, DynamoDB, function URLs) + security-engineer (crypto, ABAC, - Argon2id logic inside). -- `core/mode_resolver.py` = cli-engineer. `core/policy_engine.py` = - security-engineer (the ABAC evaluation path). -- `nova/idp/setup.py` = cli-engineer (the subcommand + arg parsing) + - backend-engineer (the CloudFormation generation + deploy). -- `nova/auth/*` = cli-engineer (subcommands) + security-engineer (the - token exchange + credential storage logic). - -## Phase-specific personas - -None. All four active personas span the full milestone. The -security-engineer is heaviest in P2 (identity layer) + P3 (threat model); -the cli-engineer is heaviest in P1 (CLI substrate); the backend-engineer -spans P1 (CodeArtifact/layer) + P2 (Lambdas/DynamoDB). - ---- - -# Personas — v1.29 Reposplit + Identity Layer Bring-Live - -```yaml -project: acdl -milestone: v1.29 +milestone: v1.30 generated_at: 2026-08-20 generator: lead-developer verification_toolchain: - typecheck: "python3 -m py_compile nova/idp/setup.py core/lambda/nova_idp_setup.py 2>&1 | head -5 || true" - test: "pytest tests/test_idp_auth.py tests/test_kms_roundtrip.py -q 2>&1 | tail -15 || true" - lint: "ruff check nova/idp/ core/lambda/nova_idp_setup.py 2>/dev/null || true" + typecheck: "python3 -m py_compile scripts/render_pptx.py 2>&1 | head -5 || true" + test: "bash scripts/check_leadership_deck.sh 2>&1 | tail -20; echo \"exit=$?\"" + lint: "python3 -c \"import pptx; print('python-pptx', pptx.__version__)\" 2>&1" note: | - v1.29 is a feature milestone (Reposplit + Identity Layer Bring-Live). - Pure ops/devops focus — Terraform modules are authored out-of-band in - nova-platform-ops; CIAgent in acdel delivers publish.yml, Gitea scrub, - CFN archive + CLI terraform-delegation, operator guide, consumer bump. - Five active personas: backend-engineer (publish.yml ECR image, Lambda - zip, GitHub Releases), security-engineer (kj static build verification, - KMS round-trip tests, ABAC E2E, M1.5 gate), cli-engineer (nova idp - setup --apply terraform delegation, CFN archive), data-engineer - (DynamoDB import references, outbox bootstrap docs), lead-developer - (plan/review/ship, Gitea scrub, decisions, operator guide, milestone - wiring). frontend-engineer deactivated (no UI). -``` + v1.30 is a single-shot presentation artifact milestone (Leadership + Deck). Four active personas: lead-developer (coordination + STATE.md + CAP-042 + PROJECT.md D-241), backend-engineer (render_pptx.py + extension + PPTX render + python-pptx install), ci-doc-writer + (custom, phase-specific — Marp markdown deck authoring), ci-cli- + engineer (custom — smoke-test script). frontend-engineer + + data-engineer + security-engineer deactivated (no UI, no data + pipelines, no runtime security surface — the deck is a static + artifact). The render_pptx.py extension (D-242) is the only code + change; it is a narrow prerequisite, not a REQ-372 deliverable. +--- + +# Personas — v1.30 Single-shot Leadership Deck ## Roster ### lead-developer ```yaml active: true -domain: "Milestone plan, persona roster, Gitea scrub (REQ-367), decisions D-232..240 (REQ-368), operator guide (P4), milestone ship, STATE/ROADMAP/PROJECT wiring, covered-reference REQ tracking" -frameworks: ["git", "Gitea Actions", "GitHub Actions", "semver tagging", ".ciagent/ discipline", "Terraform (reference only)"] -constraints: ["D-232 (forge parity abandoned)", "D-235 (tag-pin handoff)", "D-236 (cutover shape)", "D-238 (KJ-LOCKSTEP)", "OPER-PRIV", "TFM-HITL", "v1.29 hard constraints"] +domain: "Milestone coordination, STATE.md CAP-042, PROJECT.md D-241 record, ship discipline" +frameworks: [] +constraints: ["pragmatic", "battle-tested defaults", "D-241", "D-242", "D-243"] territory: - - ".ciagent/**" - - "PLAN.md" - - "CHECKPOINT.json" - - "STATE.md" - - "REQUIREMENTS.md" - - "ROADMAP.md" - - "PROJECT.md" - - "CLARIFY.md" - - "RESEARCH.md" - - "docs/operator-guide-platform-ops.md" - - ".github/workflows/ci.yml" - - "scripts/sync_workflows.py" - - "pyproject.toml" - - "README.md" + - ".ciagent/STATE.md" + - ".ciagent/PROJECT.md" + - ".ciagent/CHECKPOINT.json" + - ".ciagent/REQUIREMENTS.md" + - ".ciagent/ROADMAP.md" +reason: "Owns the ship-wave records (CAP-042, D-241) and milestone coordination. The deck is a single-shot artifact; the lead-developer ensures the STATE.md/PROJECT.md records are appended correctly at ship." ``` ### backend-engineer ```yaml active: true -domain: "publish.yml ECR container image build (CGO_ENABLED=0 static kj), Lambda zip + layer wheel + Python wheel attach to GitHub Releases, ECR push with tag v1.29.x-kj-, kj-version.txt read, Dockerfile for lambda:3.12-al2023 base" -frameworks: ["Python 3.12", "GitHub Actions", "Docker", "ECR", "Go (CGO_ENABLED=0 build)", "file(1)", "sha256sum"] -constraints: ["KJ-STATIC", "D-239 (ECR tag format)", "D-235 (tag-pin handoff)", "REQ-354 criteria 1-4"] +domain: "scripts/render_pptx.py extension (path arg + custom output + footer textbox), PPTX render, python-pptx install" +frameworks: ["Python 3.11", "python-pptx 1.0.2", "pip"] +constraints: ["D-242", "narrow extension only", "no new renderer", "S&P theme tokens only in source"] territory: - - ".github/workflows/publish.yml" - - "platform/abac/kj-version.txt" - - "core/lambda/nova_idp_token_vend.py" - - "core/lambda/nova_idp_auth.py" - - "core/lambda/nova_idp_jwks.py" - - "tests/test_idp_auth.py" - - "tests/test_kms_roundtrip.py" + - "scripts/render_pptx.py" + - "docs/presentations/nova-leadership-deck.pptx" +reason: "Owns the narrow render_pptx.py extension (D-242) and the PPTX render. Frameworks overridden from fastify/hono (default) to python-pptx (actual project dependency for this milestone). The extension is a non-REQ-372 prerequisite per spec §3.3 Edge 2." ``` -### security-engineer +### ci-doc-writer ```yaml active: true -domain: "kj static-link audit (file(1) asserts statically linked + no shared library), KMS round-trip test against alias/nova-oidc-signing, ABAC E2E (sign-up→sign-in→token-vend→verify, INV-17 fail-closed), M1.5 verification gate tests (8-item spike), KJ-LOCKSTEP digest-equality verification" -frameworks: ["KMS Sign/Verify/GetPublicKey", "kyverno-json", "jose", "file(1)", "readelf", "pytest", "moto[dynamodb]"] -constraints: ["KJ-STATIC", "KJ-LOCKSTEP", "INV-17 (ABAC fail-closed)", "INV-18 (JWKS-EDGE-ONLY)", "ABAC-FAIL-CLOSED", "ARGON", "KF (KMS asymmetric)"] +phase_specific: true +domain: "Marp markdown deck authoring (7 slides, speaker notes, [1] citations, S&P theme)" +frameworks: ["Marp", "Markdown"] +constraints: ["REQ-372.1", "REQ-372.3", "REQ-372.4", "REQ-372.6", "REQ-372.7", "REQ-372.9", "REQ-372.12", "D-241", "D-243"] territory: - - "platform/abac/**" - - "platform/abac/kj-version.txt" - - "adapters/kyverno-json/policies/token-vend.policy" - - "tests/test_kms_roundtrip.py" - - "tests/test_idp_auth.py" - - "tests/test_abac_e2e.py" - - "docs/threat-model.md" + - "docs/presentations/nova-leadership-deck-marp.md" +reason: "Custom persona for presentation authoring. Created for P1 (the deck is the primary deliverable). Removed after P1 ships. The deck is hand-authored against the Slide Content Map in PROJECT.md §v1.30 — NOT a compression (D-241)." ``` -### cli-engineer +### ci-cli-engineer ```yaml active: true -domain: "nova idp setup --apply terraform delegation (REQ-369 AC 2), CFN archive to docs/archive/nova-idp-cfn-v1.28.md (REQ-369 AC 3), which terraform detection + CFN fallback deprecation warning" -frameworks: ["Python 3.12", "argparse", "subprocess", "importlib", "shutil.which"] -constraints: ["REQ-369", "D-235 (tag-pin handoff)"] +domain: "Smoke-test script (bash, runnable on demand, NOT a CI gate)" +frameworks: ["Bash", "grep", "awk", "wc"] +constraints: ["REQ-372.8", "not a CI gate", "exit 0 on pass", "non-zero on fail"] territory: - - "nova/idp/setup.py" - - "core/lambda/nova_idp_setup.py" - - "docs/archive/nova-idp-cfn-v1.28.md" - - "nova/idp/__init__.py" + - "scripts/check_leadership_deck.sh" +reason: "Custom persona for the smoke-test script. Owns the 6 assertions (a–f): file exists, slide count=7, word bands, footer string, S&P colors only, PPTX exists. Pure bash — no python dependency (keeps it runnable without the python-pptx install)." ``` -### data-engineer -```yaml -active: true -phase_specific: false -domain: "DynamoDB table import references (nova-contracts, nova-change-requests, nova-outbox, nova-users, nova-sessions, nova-pats) documented in operator guide, PITR restore procedure, audit outbox bootstrap" -frameworks: ["DynamoDB", "AWS CLI (reference)"] -constraints: ["REQ-361 (import idempotency, covered-reference)", "JWKS-ROTATION"] -territory: - - "docs/operator-guide-platform-ops.md" - - ".ciagent/ARCHITECTURE.md" -reason: | - Re-activated for v1.29: the operator guide (P4) documents DynamoDB PITR - restore, table imports, and the audit outbox bootstrap — data-engineer - owns the data-layer sections of the guide. The Terraform import itself - is out-of-band (nova-platform-ops), but the operator-facing docs are - in-acdl. -``` +## Deactivated ### frontend-engineer ```yaml active: false -phase_specific: false -reason: "No UI in v1.29 (pure ops/devops focus). JWKS serves application/json via CloudFront; no HTML/CSS/JS surface." +reason: "ACDL has no frontend (no package.json); the deck is markdown (ci-doc-writer territory). Already deactivated in config.json personas[3]." ``` -## Territory overlap notes +### data-engineer +```yaml +active: false +reason: "No schema/migration/data-pipeline work in v1.30. The milestone is a single-shot presentation artifact." +``` -- `.github/workflows/publish.yml` (REQ-354) = backend-engineer (ECR - image build, Dockerfile, Lambda zip) + lead-developer (Gitea scrub - removes the `.gitea/workflows/publish.yml` mirror in P2, D-232). -- `nova/idp/setup.py` (REQ-369) = cli-engineer (the `--apply` delegation - + `which terraform` detection) + backend-engineer (the CFN archive - content — the CFN template is backend-engineer territory from v1.28). -- `platform/abac/kj-version.txt` = security-engineer (KJ-STATIC audit - reads + verifies the SHA) + backend-engineer (publish.yml reads the - SHA to embed in the ECR tag). -- `docs/operator-guide-platform-ops.md` (P4) = lead-developer (cutover - gates, cost section, artifact-mirror fallback) + data-engineer (PITR - restore, DynamoDB imports) + security-engineer (KMS rotation, JWKS - reachability, PAT revocation). +### security-engineer +```yaml +active: false +reason: "No runtime security surface in v1.30. The deck is a static artifact; the existing security posture (ABAC, KMS, JWKS) is referenced in slide content, not modified. Security review of the deck content is handled by the verify stage (no secrets, no publish.yml integration)." +``` -## Phase-specific personas +## Phase-specific persona lifecycle -None. All five active personas span the full milestone. The -backend-engineer is heaviest in P1 (publish pipeline); the -lead-developer is heaviest in P2 (Gitea scrub + decisions) + P4 -(operator guide) + P6 (final ship); the cli-engineer is heaviest in P3 -(CFN archive + TF delegation); the security-engineer is heaviest in P1 -(M1.5 gate tests) + P4 (operator guide security sections); the -data-engineer is heaviest in P4 (operator guide data sections). \ No newline at end of file +- **ci-doc-writer**: created for P1, removed after P1 ships. The deck + source is the deliverable; no further presentation authoring in P2 + (final review only). +- All other personas persist through P2 (final review + ship). + +## Territory enforcement + +- **Mode:** `warn` (per `config.json personas.territory_enforcement`). +- **Note:** v1.30 has a small, non-overlapping territory surface. + `lead-developer` owns `.ciagent/STATE.md` + `.ciagent/PROJECT.md`; + `backend-engineer` owns `scripts/render_pptx.py` + the PPTX; + `ci-doc-writer` owns the markdown source; `ci-cli-engineer` owns + the smoke-test script. No territory conflicts expected. \ No newline at end of file diff --git a/.ciagent/RESEARCH.md b/.ciagent/RESEARCH.md index c1060e3..2931da2 100644 --- a/.ciagent/RESEARCH.md +++ b/.ciagent/RESEARCH.md @@ -1,590 +1,305 @@ -# Nova — v1.28 Research Findings +# RESEARCH — v1.30 Single-shot Leadership Deck -> Phase: research (pre-execution). Milestone: v1.28 (CLI Canonicalization -> + Identity Layer). Status: research. Researcher: ci-researcher. -> Autonomy: full. -> -> Research delegated to the ci-researcher subagent (full domain/ecosystem -> research with web citations). This file is the curated summary; the -> full 868-line research document is preserved in git history (the -> subagent's task output). Key findings + recommendations are below. +> **Autonomy:** full. Research findings load-bearing for v1.30 PLAN. +> The research scope is narrow: this is a single-shot presentation +> artifact, not a runtime feature. The research covers (1) the +> existing render pipeline's behavior + limits, (2) the smoke-test +> script conventions, (3) the Marp frontmatter/footer/speaker-notes +> handling, (4) the theme-token enforcement strategy, (5) the +> python-pptx install path in this environment, (6) the vision +> document grounding for `[1]` citations. --- -## §1 — Codebase Inventory (grounding) +## R1 — Existing render pipeline (`scripts/render_pptx.py`) -### 1.1 `core/` modules (the REQ-324 subcommand surface) +**Source:** `scripts/render_pptx.py` (688 lines, REQ-269 v1.23). -19 Python files under `core/` (plus `core/lambda/`, `core/metrics/`). -Two already have `_cli.py` companions (`contract_resolver_cli.py` 40 -lines, `regression_verify_cli.py` 32 lines) — the thin-delegate -precedent for `nova/.py`. **No `nova/` dir, no `bin/`, no -`[project.scripts]` entry exists today.** The CLI is greenfield. +**Behavior:** +- Argv: `render_pptx.py [deck-name]` → reads + `docs/presentations/{deck}-marp.md`, writes + `docs/presentations/{deck}-python.pptx` (lines 677-680). **Does + not accept a full path or non-`-marp.md` filename.** +- Frontmatter: stripped (lines 62-67) — the Marp `footer:`, + `paginate:`, `theme:`, `size:`, `style:` directives are NOT read + by the python-pptx path. They are source-only (smoke test checks + source; the Marp CLI path in `render_slides.sh` reads them, but + that path needs Chromium which is unavailable here). +- Slide splitting: `re.split(r"\n---\s*\n", ...)` after frontmatter + strip (line 69). Exactly 7 `---`-delimited slides required. +- Body parsing (`parse_slide`, lines 360-498): + - HTML comments (``) are **skipped entirely** (lines + 366-379). **Speaker notes are NOT embedded in the PPTX.** + Acceptable for REQ-372.4 (smoke test checks source word counts, + not PPTX-embedded notes). + - Headings `#`/`##` → title (first) or lead (subsequent). + - Bold lead `**...**` (own line, exactly 2 `**`) → `lead` block + (red, bold). + - Blockquotes `>` → `quote` block (grey, italic). + - Unordered list `[-*+]\s+...` → `bullet` (level by indent). + **`*italic*` (no space after `*`) does NOT match** — safe as + plain text. + - Ordered list `\d+\.\s+...` → `ordered`. + - Tables `| ... |` + separator → `table`. + - `→`-prefixed lines → `plain` text (not bullets). Content + preserved. + - `_strip_inline_emphasis` (lines 209-220): `**bold**`, `*italic*`, + `` `code` `` markers are collapsed to plain text in the PPTX. + Content is preserved; emphasis styling is lost (acceptable — the + PPTX is an editable comparison artifact; REQ-372.7 content match + is by visual review). +- Theme: hardcoded S&P constants (lines 37-43): `RED=#D6002A`, + `BLACK=#1B1B1B`, `WHITE=#FFFFFF`, `GREY_HEADER=#F0F0F0`, + `GREY_TEXT=#2E2E2E`, `BODY_TEXT=#1B1B1B`. **Note: `GREY_TEXT=#2E2E2E` + is a 5th color used internally for blockquote/body text.** This is + a renderer-internal color, NOT a source hex color — REQ-372.6 + scopes to "color values extracted from the source markdown (Marp + directives + inline overrides)", so `#2E2E2E` in the renderer does + not violate REQ-372.6. The smoke test checks the *source* file for + hex colors. +- Footer: **NOT rendered.** No footer textbox is added by the + existing renderer. **D-242 extension required** to add a + right-aligned footer textbox on every slide. +- Title slide: `render_title_slide` (line 501) — black bg, red top + bar, white title. Triggered when `idx==0` + (`title_is_h1` or + `is_title_class`). The leadership deck's slide 1 uses a bold lead + (`**The friction...**`) as the first line — this is an H1 (`# The + friction...`) in the source, so slide 1 renders as a title slide + (black bg). **Decision for PLAN:** author slide 1 with `#` H1 + title (title slide, black bg, red bar — strong opener) OR author + as `##` H2 (content slide, white bg). The Slide Content Map shows + slide 1 with a bold title + italic subtitle + arrows + italic + closing — a content-rich slide. **Recommend: `##` H2 title for all + 7 slides → all render as content slides (white bg, red title bar) + for visual consistency.** Slide 1 as a black-bg title slide would + hide the `→` arrows in white-on-black, which is fine but differs + from the map's framing. The map doesn't specify background; visual + review accepts either. **Final call in PLAN:** all `##` content + slides for consistency + readability of the 3-pattern frame. -### 1.2 Existing Lambda pattern (`core/lambda/contract_ingestor.py`) - -521 lines. Function URL + IAM auth (D-051). DynamoDB via lazy -module-global `boto3.resource`. Secrets Manager for tokens. Schema -validation in-Lambda. **`__main__` block already does CLI dispatch** -(`--check-readiness` → `core.submission_readiness.cli_main`) — this is -the dual-use precedent for REQ-329. Local testing via -`core/local_emulators.py:LocalLambdaStub`. - -### 1.3 `core/env.py` — getter, not synthesizer - -31 lines. `get_env(name, default)` reads `NOVA_` from `os.environ`. -**REQ-330 needs a NEW `synthesize_local_env()` function** added here. -The closest existing pattern is `core/onboarding.py:generate_env_file()`. - -### 1.4 `PolicyEngine` Protocol + `KyvernoJsonEngine` (the ABAC substrate) - -`core/policy_engine.py`: `PolicyEngine` Protocol with `evaluate(payload, -policy_dir, contract_id) -> list[dict]`. `KyvernoJsonEngine` shells to -`kj scan --policy --payload --output json`. Policy shape = -`ValidatingPolicy` (`apiVersion: json.kyverno.io/v1alpha1`) with -`spec.rules[].assert.all[].check` using JMESPath. Severity from -`metadata.annotations["nova.cloudinit.dev/severity"]`. **The payload -can be ANY JSON** — not just contracts (the v1.25 design point). This -is what makes kyverno-json usable for ABAC token vending (D-227). - -### 1.5 `pyproject.toml` state - -name `nova`, version `1.14.0`, requires-python `>=3.10` (spec wants -3.12 — bump needed for REQ-326). setuptools build backend. No -`[project.scripts]`, no `[tool.setuptools.packages.find]` — both needed. -Deps: `boto3`, `jsonschema`, `pyyaml`. No `argon2-cffi`, `cryptography`, -`pyjwt`, `click`/`typer` — **argparse-only** is the repo convention. - -### 1.6 Forge conventions - -`.github/workflows/` + `.gitea/workflows/` kept byte-identical. Python -3.12 already pinned via `actions/setup-python@v5`. No composite action -exists yet — `nova cli-action` (REQ-326) is greenfield. - -### 1.7 IAM baseline (load-bearing for REQ-340) - -`.ciagent/IAM_POLICY.md` + `terraform/bootstrap/spike_runner_policy.json`. -The `nova-spike-runner` principal already has KMS (incl. `CreateKey`, -`Sign`, `GetPublicKey`), Lambda (incl. `PublishLayerVersion`), DynamoDB -grants. **New grants needed:** `cloudformation:*` (for `nova idp setup ---apply`) + `codeartifact:*` (for the wheel publish pipeline). Flagged -for P1/P2. +**Gaps for v1.30 (D-242 extension):** +1. Accept explicit source `.md` path + `--output` filename. +2. Add right-aligned footer textbox on every slide with exact string + `Nova Platform - Infrastructure & Operations`. --- -## §2 — CodeArtifact + Lambda Layer Pipeline (REQ-323) +## R2 — Smoke-test script conventions -**Recommendation:** single CI job on merge to `main` affecting -`core/**`/`adapters/**`/`nova/**`/`pyproject.toml`. Build wheel -(`python -m build --wheel`) → `twine upload` to CodeArtifact → build -layer (`pip install --target layer/python/ dist/nova-*.whl argon2-cffi -cryptography pyjwt`) → `aws lambda publish-layer-version` → record -version mapping in SSM `/nova/layer/nova-cli/version` (CAP-035). If -either publish fails, the job fails (merge blocked, REQ-323 AC). +**Source:** `scripts/check_north_star_diff.sh` (REQ-204), other +`scripts/check_*.sh` / `scripts/run_*.sh`. -**Atomicity:** wheel publish is idempotent (pin version to -`+`); layer publish retries on failure. CAP-035 reads the -SSM parameter to verify layer-version ↔ wheel-version match. +**Conventions:** +- Shebang `#!/usr/bin/env bash` +- Header comment with purpose + Usage + Returns +- `set -euo pipefail` +- Exit 0 on pass, non-zero (1) on fail +- `echo "WARN: ..."` / `echo "ERROR: ..."` to stderr +- Runnable from repo root: `bash scripts/check_*.sh` -**Risks:** CodeArtifact not yet provisioned in `581513795199` (CLARIFY -assumption #1); `codeartifact:*` grant missing. Fallback: Gitea-hosted -wheel index. Layer `--compatible-architectures`: build x86_64 only for -v1.28 (aarch64 only if Graviton Lambda needed). +**v1.30 smoke test (`scripts/check_leadership_deck.sh`) assertions +(REQ-372.8 a–f):** +- (a) `docs/presentations/nova-leadership-deck-marp.md` exists +- (b) slide count = 7 (count `---` separators on own line, excluding + frontmatter) +- (c) per-slide speaker-note word counts in band (extract HTML + comments per slide; slides 1/2/4/6: 150–300; 3/5: 250–400; 7: + 200–300) +- (d) footer string `Nova Platform - Infrastructure & Operations` + present in source (frontmatter `footer:` directive) +- (e) only S&P hex colors `#D6002A`, `#1B1B1B`, `#FFFFFF`, `#F0F0F0` + in source (grep for `#[0-9A-Fa-f]{6}` and diff against the allow- + list) +- (f) `docs/presentations/nova-leadership-deck.pptx` exists (hard + fail per Q-M4) + +**Implementation approach:** pure bash + `grep`/`awk`/`wc`. No +python dependency for the smoke test (keeps it runnable on demand +without the python-pptx install). Slide count: count lines matching +`^---\s*$` after the frontmatter, +1. Speaker notes: per slide, +extract content between ``, strip HTML comment markers, +`wc -w`. Color scan: `grep -oiE '#[0-9A-Fa-f]{6}'` on the source, +sort -u, compare to allow-list. --- -## §3 — CLI Subcommand Architecture (REQ-324) +## R3 — Marp frontmatter / footer / speaker-notes handling -**Recommendation:** three-layer. `nova/__init__.py` (marker) → -`nova/cli.py` (~80 lines, auto-discovers `nova/.py` via -`pkgutil.iter_modules`, dispatches, emits `cli.invocation` audit event) -→ `nova/.py` (≤50 lines each, exports `add_parser(subparsers)` -+ `run(args) -> int`, delegates to `core/`). Entry point: -`[project.scripts] nova = "nova.cli:main"`. **argparse-only** (no -click/typer — repo convention). +**Source:** `docs/presentations/nova-autonomous-cloud-delivery-marp.md` +(lines 1-27), Marp CLI v4.5.0 (available via npx). -**CAP-034 AST scan:** ≤50 lines; ≤3 function defs; every `ast.Call` -resolves to a `core.` import; no conditionals beyond `if __name__`. +**Existing deck frontmatter:** +```yaml +marp: true +theme: default +paginate: true +size: 16x9 +footer: 'Nova — The Autonomous Cloud Delivery Platform' +style: | + section { ... color: #1B1B1B; ... } + h1 { color: #D6002A; ... } + ... +``` -**Subcommand groups:** `nova auth`, `nova idp`, `nova metrics` = -nested subparsers (same pattern, one level deeper). +**v1.30 leadership deck frontmatter (per cover note + spec):** +```yaml +marp: true +theme: default +footer: "Nova Platform - Infrastructure & Operations" +paginate: false +size: 16x9 +style: | + section { font-family: "Akkurat Pro", "Helvetica Neue", "Arial", sans-serif; font-size: 22px; color: #1B1B1B; padding: 48px 56px 40px; overflow: auto; } + h1 { color: #D6002A; font-size: 34px; margin-bottom: 0.3em; } + h2 { color: #D6002A; font-size: 26px; margin-bottom: 0.2em; } + blockquote { border-left: 4px solid #D6002A; color: #1B1B1B; font-size: 20px; padding-left: 12px; } + strong { color: #D6002A; } + ... +``` -**setuptools:** add `[tool.setuptools.packages.find]` including `nova`, -`nova.*`, `core`, `core.*`, `adapters.*`. +**Key differences from the existing deck:** +- `paginate: false` (existing: `true`) — per cover note. +- `footer: "Nova Platform - Infrastructure & Operations"` (existing: + different string) — per cover note + REQ-372.5. +- The `style:` block uses only the 4 S&P tokens. The existing deck's + `style:` uses `#2E2E2E` for blockquote color — **this must be + changed to `#1B1B1B`** in the leadership deck's `style:` block to + satisfy REQ-372.6 (only 4 hex colors in source). The renderer's + internal `GREY_TEXT=#2E2E2E` is not in the source, so it doesn't + violate REQ-372.6 — but the *source* `style:` block must not + contain `#2E2E2E`. + +**Speaker notes:** HTML comments `` within the slide +body, before the next `---`. The Marp CLI renders these as speaker +notes in the HTML/PPTX; the python-pptx path skips them. The smoke +test extracts them from the *source* for word-count checking. --- -## §4 — Argon2id in Lambda Python 3.12 (REQ-334, D-228) +## R4 — Theme-token enforcement strategy -**Findings:** `argon2-cffi-bindings` v25.1.0 ships `cp39-abi3` -manylinux x86_64 + aarch64 wheels — **ABI-stable, compatible with -Python 3.9..3.13**. Lambda Python 3.12 runs Amazon Linux 2023 (glibc -2.34 ≥ 2.28 required). **The abi3 manylinux wheel loads cleanly.** -Confidence: 0.92. +**REQ-372.6:** only `#D6002A`, `#1B1B1B`, `#FFFFFF`, `#F0F0F0` as hex +colors in the source. -**D-228 AMENDMENT:** the "pure-Python fallback" clause is **weaker than -stated** — there is no maintained pure-Python Argon2 implementation. A -pure-Python crypto fallback is a **liability** (weaker hashing, -violates INV-16's spirit). Revised recommendation: -1. **Primary:** bundled manylinux abi3 wheel in the `nova-cli` Lambda - layer. Works. Confidence 0.92. -2. **Fallback:** detect `ImportError` at Lambda cold-start → **fail - closed** (503, refuse sign-ups). The Lambda health check reports - C-extension status. **Do NOT ship a pure-Python fallback.** -3. **Escape hatch:** Fargate (~1 week, per CLARIFY Q1). - -Lambda memory ≥ 512 MB (Argon2id memory_cost ~20 MB + overhead). +**Enforcement:** +1. **Source `style:` block:** use only the 4 tokens. Replace the + existing deck's `#2E2E2E` (blockquote color) with `#1B1B1B`. +2. **No inline `color:` overrides** in slide bodies — the slides use + no inline HTML/color spans. +3. **Smoke test (8e):** `grep -oiE '#[0-9A-Fa-f]{6}'` on the source, + `sort -u`, compare to the 4-token allow-list. Any other hex color + → fail. --- -## §5 — KMS Asymmetric Signing for OIDC Tokens (REQ-337) +## R5 — python-pptx install path (this environment) -**Recommendation: key spec = `ECC_NIST_P256`, alg = `ECDSA_SHA_256` -(JWS `ES256`).** RSA-2048 is larger + slower; P-256 is RFC 7518's -recommended JWT alg. Signature size 64 bytes (vs RSA 256). JWKS -compactness matters (fetched often). +**Environment:** Debian/Ubuntu, Python 3.11.2, no system pip, no +root, no `python3-venv`/`python3-pip` packages, no `ensurepip`. -**The #1 gotcha:** KMS returns DER-encoded ECDSA signatures; **JWS -requires raw r‖s concatenation** (RFC 7515 §3.1.3). The token-vend -Lambda converts via `cryptography.hazmat.primitives.asymmetric.utils. -decode_dss_signature` → `r.to_bytes(32) + s.to_bytes(32)`. ~5 lines. -Flagged for the threat model (REQ-347) + KMS round-trip test (REQ-350). +**Resolved install path:** +1. `curl -sS https://bootstrap.pypa.io/get-pip.py -o /home/opencode/tmp/get-pip.py` +2. `python3 get-pip.py --user --break-system-packages` +3. `/home/opencode/.local/bin/pip install --user --break-system-packages "python-pptx>=0.6.23"` +4. `pip install --user --break-system-packages "pytest>=8.0"` (for + verify stage) -**Flow:** validate PAT → ABAC eval → build JWT header/payload → -`kms.sign(Message=signing_input, MessageType="RAW", SigningAlgorithm= -"ECDSA_SHA_256")` → DER→raw → JWT. `kid` = KMS key alias. +**Result:** python-pptx 1.0.2 + pytest 9.1.1 installed to user-site. +`python3 -c "import pptx"` succeeds. No Chromium needed (python-pptx +is the render path, not Marp CLI PPTX). -**Verification:** use `pyjwt` (`jwt.decode` handles JWK→key natively); -`cryptography` only for SPKI→JWK in the JWKS Lambda. - -**Rotation:** manual, 90 days (matches D-069 CMK cadence). New key + -re-point alias + JWKS serves both `kid`s during overlap. +**Confirmed in RESEARCH execution:** all commands ran successfully +in this session. --- -## §6 — JWKS Endpoint (REQ-338, D-230) +## R6 — Vision document grounding for `[1]` citations -**D-230 confirmed.** Lambda function URL (`AuthType: NONE` — JWKS is -public-key only) + reserved concurrency 10 (max 100 RPS, JWKS is -cached client-side). `Cache-Control: max-age=3600`. Separate tiny -`nova-idp-jwks` Lambda (separation of concerns). +**Source:** `docs/vision.md` (the spec's `acdl-vision.md` / `[1]` +reference). -**Custom domain + WAF = OPTIONAL** via `--public-jwks-domain ` -flag on `nova idp setup`. Without it, raw function URL (acceptable for -v1.28 pilot). With it: CloudFront + ACM + WAF rate-based rule (>100 -req/5min per IP) + Route53 ALIAS. Adds ~8 CloudFormation resources. +**Key tenets for slide grounding:** +- **§1 The Friction** (slide 1): "Software delivery scales with the + coordination surface around it, not the engineering inside it." + Grounds the three-pattern problem frame + binding-constraint + claim. +- **§3 Core Tenets** (slides 3, 5, 7): + - "The Delivery Lifecycle is a Sovereign Boundary" — grounds + slide 3's Sovereign boundary tenet + slide 5's boundary + discipline + slide 7's "Nova stays in its lane." + - "Lower Environments are Autonomous; Higher Environments are + Attested" — grounds slide 3's Lower autonomous · higher attested + tenet + slide 4's HITL discipline. + - "Infrastructure is Consumed, Not Maintained" — grounds slide 5's + "VM, bare-metal, OS lifecycles" exclusion. +- **§4 Domain Boundaries** (slides 2, 5, 6): "The platform begins + where the artifact is compiled and ends where it runs in + production." "Out of scope: Application business logic, IDE + workflows, product backlog management, sprint planning, compute + requiring node-level or OS-level management." Grounds slide 5's + in-lane/out-of-lane split + slide 6's "Nova absorbs no IDE, no + editor, no sprint tool, no agent harness." -**Defer API Gateway** (D-230) — $3.50/M + complexity for no benefit at -v1.28 volume. +**Citation convention:** `[1]` in speaker notes, resolving to +`docs/vision.md`. The spec §citation-references confirms `[1]` → +`acdl-vision.md` (vision document, source [1]). --- -## §7 — kyverno-json ABAC Policy (REQ-339, D-227) +## R7 — CAP-024 regression policy (collision check) -**D-227 confirmed.** Policy at `platform/abac/token-vend.policy` = -`ValidatingPolicy` with JMESPath checks against a payload of -`{subject, requested_claims, target_resource, environment, pat_jti, -policy_version}`. Decision logic: any `fail` PCR with severity -`critical` → deny (403 + audit); all pass → allow → KMS sign. +**Source:** `adapters/kyverno-json/policies/regression/cap-024-deck-structure.json` ++ `tests/test_regression_policies.py`. -**`policy_version` (D-231):** git SHA of the policy file, baked into -the Lambda layer, recorded in every `token.vend.allowed/denied` audit -event. - -**BIGGEST PACKAGING RISK:** the token-vend Lambda needs the `kj` Go -binary (~40 MB) on PATH. Bundle it in the `nova-cli` Lambda layer -(`wget` the Linux amd64 release into `layer/bin/kj`). `KyvernoJsonEngine -.is_configured()` checks `which kj` → `/opt/bin/kj` (layer mount). P2 -spike confirms it runs in AL2023 Lambda. Fallback: Fargate. Confidence -0.75 — needs the spike. +**Finding:** CAP-024 validates the citizen-developer deck's 4-beat +arc (Problem/Solution/Proof/Roadmap+Ask) against fixture files +(`clean.json`/`drifted.json` in `tests/fixtures/`), NOT against the +actual deck markdown files. The leadership deck +(`nova-leadership-deck-marp.md`) does NOT pass through this policy. +No collision risk. The leadership deck's 7-slide structure is a +different artifact (CAP-042, not CAP-024). --- -## §8 — PAT Lifecycle (REQ-342, REQ-343, REQ-344) +## R8 — `slides.yml` CI (non-interference check) -**PAT = signed JWT** (KMS-signed, `typ: "developer_pat"` distinguishes -from `nova_oidc_token` per INV-14). Claims: `iss, sub, typ, jti, iat, -exp, roles, owner`. +**Source:** `workflows-src/slides.yml`. -**`nova-pats` DynamoDB table** (4th table): PK=`jti`, GSI1=`sub` (list -PATs for user), GSI2=`pat_hash` (lookup by hash). Only the hash stored -(not raw PAT). Revoked PATs retained for audit. - -**Revocation (D-229 CLARIFIED):** GSIs don't support strongly-consistent -reads. The token-vend Lambda extracts `jti` from the PAT JWT (decode -without verifying — signature verified separately) → -`GetItem(PK=jti, ConsistentRead=True)` on the main table. Satisfies the -60s SLO. Confidence 0.90. - -**CLI:** `nova auth login` (session→OIDC token, store locally), -`nova auth revoke --pat `, `nova auth status` (active credential, -mode, selection_reason). Local file `~/.nova/credentials.json` (0600, -never to stdout, in `.gitignore`). "Most recent wins" (D-226 Q5) = -`active_credential_jti` field. +**Finding:** The CI workflow triggers on `docs/presentations/**` +path changes, but `scripts/render_slides.sh` defaults to +`DECK="nova-autonomous-cloud-delivery"` and only renders that one +deck. Adding `nova-leadership-deck-marp.md` to +`docs/presentations/` will trigger the CI, but it will only re- +render the citizen-developer deck (no-op if that deck is unchanged). +The leadership deck is NOT rendered by CI (per spec: no CI gate, no +`publish.yml` integration). **No interference.** The bot commit from +CI (if any) will be a no-op re-render of the unchanged citizen- +developer deck. --- -## §9 — `nova idp setup` CloudFormation (REQ-340, REQ-341) - -**Template (raw dict → JSON, no troposphere dep):** 2-3 Lambdas, 4 -DynamoDB tables (`nova-users`, `nova-sessions`, `nova-password-resets`, -`nova-pats`), KMS key `alias/nova-oidc-signing` (ECC_NIST_P256), -function URLs, IAM roles, optional CloudFront/WAF/ACM. - -**`--check`:** validates prerequisites (AWS creds, CFN perms, KMS perms, -layer exists via CAP-035). Prints required IAM policy delta. -**`--apply`:** generate → print to temp file + resource summary → -`$PAGER` → `Apply? [y/N]` → `cloudformation deploy --capabilities -CAPABILITY_IAM`. NFR-10 satisfied by the explicit prompt. -**`--dry-run`:** resource list only, no write. -**`--verify`:** runs the KMS round-trip test (REQ-350). - -**New IAM grants needed:** `cloudformation:*`, `iam:CreateRole`/`PassRole`, -`lambda:CreateFunction`/`CreateFunctionUrlConfig`, -`dynamodb:CreateTable`, `kms:CreateKey`/`CreateAlias`, `ssm:PutParameter`. - ---- - -## §10 — GitHub + Gitea Marketplace Composite Action (REQ-326) - -**Single `action.yml`** at `.github/actions/nova-cli/action.yml`, -referenced by both GitHub + Gitea via `uses: continuous-intelligence/ -acdl/.github/actions/nova-cli@v1.28`. Composite action: `setup-python@v5` -(python 3.12) → CodeArtifact login + `pip install nova` → `nova -${{ inputs.command }}`. `NOVA_CLIENT_MODE` env from input. - -**Byte-identical test (REQ-326 AC2):** CI matrix runs the action on -GitHub `ubuntu-latest` + Gitea `act_runner` with same inputs; assert -same stdout/exit code. - -**Risk:** Gitea `actions/checkout`/`setup-python` may need Gitea -mirrors (`https://gitea.com/actions/...`). P1 test on the actual Gitea -instance. Confidence 0.70. - ---- - -## §11 — `mode_resolver` Priority (REQ-327, D-226) - -**TTY detection: check `sys.stdin.isatty()`** (NOT stdout). Edge 3 -(`nova apply | tee log.txt`): stdout piped, stdin is TTY → user is -present → `interactive` (correct). `sys.stdout.isatty()` would -misresolve to `agent`. **`stdin` answers "is a human at a terminal?"** - -**Credential type detection:** read `~/.nova/credentials.json` → -`active_credential_jti`'s `type` (`developer_pat`/`nova_oidc_token`). -Both + TTY → `interactive`; + no TTY → `agent` (INV-14). - -**Property tests (REQ-349):** `hypothesis` with strategies for -flag/env/cred/tty. Properties: deterministic (INV-13), flag-wins, -invalid-env-ignored, no-silent-fallback (every resolution has a -non-empty `selection_reason`). - -**`mode_resolver.py` lives in `core/`** (not `nova/`) so Lambdas could -import it, but **it's CLI-only** — the token-vend Lambda doesn't resolve -modes. - ---- - -## §12 — Persona Assessment - -See `.ciagent/PERSONAS.md` for the full YAML roster. Summary: -- **Deactivate** frontend-engineer (no UI) + data-engineer (no data - pipelines in v1.28). -- **Activate** backend-engineer (Lambda/DynamoDB/KMS/CodeArtifact) + - lead-developer (plan/review/ship). -- **Add** security-engineer (Argon2id/KMS/ABAC/threat model) + - cli-engineer (subcommand surface/mode_resolver/argparse/CAP-034). - ---- - -## §13 — Architecture Sketch (ARCHITECTURE.md §12.10) - -See `.ciagent/ARCHITECTURE.md` §12.10 (appended this stage). New -greenfield files: `nova/` CLI package, `platform/abac/token-vend.policy`, -`core/mode_resolver.py`, `core/env.py:+synthesize_local_env()`, -`core/lambda/nova_idp_{auth,token_vend,jwks}.py`, `tests/test_*`, -`docs/{operator-guide-idp,developer-guide-auth,threat-model}.md`. - ---- - -## Decisions re-validated / amended - -| Decision | Status | Change | -|---|---|---| -| D-226 | re-validated + refined | `sys.stdin.isatty()` is the TTY check (not stdout) | -| D-227 | re-validated | `kj` Go binary bundled in Lambda layer — packaging risk flagged | -| D-228 | **amended** | Pure-Python fallback → fail-closed + Fargate (pure-Python crypto is a liability) | -| D-229 | re-validated + clarified | Strong read on main table PK (`jti`), not GSI (GSIs don't support strong reads) | -| D-230 | re-validated | CloudFront/WAF/ACM made optional via `--public-jwks-domain` flag | -| D-231 | re-validated | `policy_version` (git SHA) in the ABAC payload | - -**New recommendations for PLAN/GRILL to formalize (no D-ID yet):** -- KMS key spec = `ECC_NIST_P256`, alg `ES256`; DER→raw ECDSA conversion required. -- `nova-cli` Lambda layer bundles the `kj` Go binary (~40 MB). -- `nova-pats` = 4th DynamoDB table; PK=`jti`, GSI1=`sub`, GSI2=`pat_hash`. -- `sys.stdin.isatty()` is the TTY heuristic. -- `[project.scripts] nova = "nova.cli:main"`; argparse-only. -- `cloudformation:*` + `codeartifact:*` = new IAM baseline grants (P1/P2). - ---- - -## RESEARCH complete - -All 11 research questions answered with cited findings + concrete -recommendations + risks. D-228 amended (fail-closed, not pure-Python -fallback). The `kj` binary packaging is the highest-risk item (P2 -spike). Next: PLAN. - ---- - -# Nova — v1.29 Research Findings - -> Phase: research (pre-execution). Milestone: v1.29 (Reposplit + Identity -> Layer Bring-Live). Status: research. Researcher: ci-researcher. -> Autonomy: full. -> -> Research delegated to the ci-researcher subagent (10 topics — Terraform -> import idempotency, `data.aws_ecr_image` digest resolution, -> `lifecycle.precondition`, CloudFront OAC for Lambda Function URL, WAF -> on CloudFront, ACM DNS validation + Route53 alias, `kj` Go binary -> static build, ECR tag format, codebase inspection, Gitea Actions HITL). -> This file is the curated summary. Key findings + recommendations below. - ---- - -## §1 — Terraform `import` idempotency (REQ-361) - -- `terraform import ` reads an existing cloud resource into - state without modifying it; the resource must have a matching - `resource` block in config. -- Re-importing an address already in state fails with **`Error: Resource - already managed by Terraform`** (non-zero exit). The CI import step - must treat this specific error as idempotent success (grep the - message, not just exit code) — this is the IMPORT-IDEMPOTENT contract. -- `importable-resources.tf` is a convention (not built-in): a dedicated - file listing resource addresses imported from the live account (S3 - state bucket, DynamoDB tables, IAM OIDC role, KMS keys) so the import - surface is enumerable + reviewable. -- Drift detection: `terraform plan -detailed-exitcode` (exit 2 = drift) - fails the apply; the state bucket is bootstrapped manually then - imported (never created by Terraform — avoids bootstrapping the - bootstrapper, Q1/§7.1, D-235). - -**Recommendation:** `nova-platform-ops` maintains an -`importable-resources.tf` map; CI import treats "already managed" as -idempotent success; `plan -detailed-exitcode` asserts zero drift. - -## §2 — `data.aws_ecr_image` digest resolution (REQ-355, REQ-371) - -- `data "aws_ecr_image" "kj_image" { repository_name = …; image_tag = … }` - resolves the tag to an **immutable `sha256:` digest** via - `image_digest`. -- ECR tags are mutable by default (a re-push moves a tag → different - digest). KJ-LOCKSTEP pins on `image_digest`, never the tag. -- `image_uri` = `${data.aws_ecr_repository.kj.repository_url}@${data.aws_ecr_image.kj_image.image_digest}` - — pinning by `@digest`, not `:tag`. Both Lambda and Fargate reference - the same data source → same digest by construction. -- `data.aws_ecr_image` reads at plan time; if the tag doesn't exist - (engineering hasn't published), the data source fails the plan (Q7 - fail-closed). - -**Recommendation:** Both image-bearing resources reference a single -`data.aws_ecr_image.kj_image`; `image_uri` = `repo@digest`; LOCKSTEP is -true by construction + the precondition (§3) is a verification. - -## §3 — `lifecycle.precondition` — the KJ-LOCKSTEP mechanism (REQ-371) - -- **Version correction (D-240):** preconditions introduced in - **Terraform v1.2.0 (May 2022)**, NOT v1.4+ as the spec implies. The - ops repo `required_version = ">= 1.2.0"` suffices. -- Syntax: `precondition` block inside `lifecycle { … }` for resources. - Evaluated **before** the resource action (during planning); a failing - precondition aborts the **plan** with the custom `error_message`. -- `error_message` is a string expression — can interpolate values: - `error_message = "KJ-LOCKSTEP: Fargate='${aws_ecs_task_definition.kj.image}' canonical='${data.aws_ecr_image.kj_image.image_digest}'"`. -- Asserting two attributes resolve to the same value: - ```hcl - lifecycle { - precondition { - condition = self.image_uri == "${data.aws_ecr_repository.kj.repository_url}@${data.aws_ecr_image.kj_image.image_digest}" - error_message = "KJ-LOCKSTEP: Lambda image does not match the resolved ECR digest" - } - } - ``` - -**Pitfalls:** precondition blocks cannot reference `count`/`for_each` -unexpanded resources; both resources must depend on the same data source -(explicit `depends_on` if `image_uri` is computed indirectly). - -**Recommendation:** Add `lifecycle { precondition { … } }` to **both** -the Lambda and Fargate task; set `required_version = ">= 1.2.0"`. - -## §4 — CloudFront OAC pinning to Lambda Function URL (D-233, REQ-364) - -- **Critical:** CloudFront OAC for a Lambda Function URL origin requires - `AuthType: AWS_IAM` on the Function URL (NOT `AuthType: NONE`). With - `AWS_IAM`, direct access returns 403 unless SigV4-signed; CloudFront + - OAC signs requests on the viewer's behalf → CloudFront 200, direct 403 - (INV-18 JWKS-EDGE-ONLY). -- OAC resource: `OriginAccessControlOriginType = "lambda"`, - `SigningBehavior = "always"`, `SigningProtocol = "sigv4"`. Attach via - `OriginAccessControlId` on the origin block; HTTPS only. -- Resource-based permission: `aws lambda add-permission --action - lambda:InvokeFunctionUrl --principal cloudfront.amazonaws.com - --source-arn ` — binds the Function URL to the - specific distribution. -- OAC replaces the deprecated S3-origin OAI; for Lambda origins, OAC is - the only signing mechanism. - -**Pitfall:** if `AuthType: NONE` is left on the Function URL, OAC signing -is ignored and the URL stays public — the 403 guarantee evaporates. - -**Recommendation:** JWKS Function URL `authorization_type = "AWS_IAM"`, -`lambda`-type OAC (`SigningBehavior: always`), `lambda:InvokeFunctionUrl` -permission scoped to the distribution ARN. - -## §5 — WAF WebACL rate-limit + AWS Managed Rules on CloudFront (REQ-365) - -- Rate-based rule: `RateBasedStatement` with `Limit: 3000`, - `AggregateKeyType: "IP"`, `EvaluationWindowSec: 300` (5-min window; - accepted values 60/120/300/600). WAF checks ~every 10s. -- AWS Managed Rules Common Rule Set = managed rule group - `AWSManagedRulesCommonRuleSet` (vendor `AWS`), attached as a separate - priority from the rate rule. -- CloudFront WebACLs **must** be created in `us-east-1` with - `Scope = "CLOUDFRONT"` (regional WebACLs cannot associate with - CloudFront). -- CloudWatch metrics: per-rule `VisibilityConfig.CloudWatchMetricsEnabled - = true`; S3 access logs via `aws_cloudfront_distribution.logging_config`. - -**Recommendation:** WebACL in `us-east-1` `Scope=CLOUDFRONT`; rate rule -(3000/5min/IP) + Common Rule Set; associate to JWKS distribution; -CloudWatch metrics + S3 access logs. - -## §6 — ACM cert DNS validation + Route53 alias (REQ-366) - -- ACM DNS validation: `aws_acm_certificate` with - `validation_method = "DNS"`; create `aws_route53_record` for each - `domain_validation_options` CNAME; `aws_acm_certificate_validation` - waits on `ISSUED`. For CloudFront, the cert **must** be in - `us-east-1`. -- Route53 alias: `type = "A"`, `alias { name = - aws_cloudfront_distribution.jwks.domain_name; zone_id = - aws_cloudfront_distribution.jwks.hosted_zone_id; - evaluate_target_health = false }`. -- `route53_record_not_resolvable` failure mode: the alias doesn't - resolve until CloudFront `status = Deployed` AND ACM cert `ISSUED`. If - the validation CNAME is mis-created or Route53 is not authoritative, - the CNAME never validates → cert stays `PENDING_VALIDATION` → alias - NXDOMAIN. - -**Recommendation:** ACM cert in `us-east-1` DNS validation; validation -CNAMEs in the authoritative Route53 zone; `aws_acm_certificate_validation` -gates on `ISSUED`; Route53 A-alias to the distribution. Operator guide -documents the `route53_record_not_resolvable` → check-cert-status -debugging path. - -## §7 — `kj` Go binary static build for AL2023 Lambda (KJ-STATIC, REQ-354, REQ-363) - -- Build: `CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -ldflags="-s - -w" -o kj ./…`. `CGO_ENABLED=0` is load-bearing — no cgo, no dynamic - libc link. -- `file(1)` must report `ELF 64-bit LSB executable, x86-64, statically - linked` + absence of `shared library`/`interpreter`. Secondary: - `readelf -d kj` shows no `NEEDED` entries. -- Base image `public.ecr.aws/lambda/python:3.12-al2023`; copy binary to - `/opt/kj/kj` `chmod 0555` owned by `sbx_user:1051` (Lambda sandbox - user, uid/gid 1051 in AL2023). `0555` + immutable-owned prevents - runtime tampering. -- Lambda handler invokes `subprocess.run(['/opt/kj/kj', 'apply', …], - capture_output=True, check=True)` — `kj` is a substrate binary, not a - library; the Python handler is a thin shim. kyverno-json (INV-4) is - separate + unaffected. - -**Pitfall:** `CGO_ENABLED=1` (default on systems with gcc) produces a -dynamically-linked binary; AL2023 glibc mismatch → runtime -`GLIBC_X not found`. `CGO_ENABLED=0` eliminates this. - -**Recommendation:** `publish.yml` P1 builds with `CGO_ENABLED=0 -GOOS=linux GOARCH=amd64`, asserts `file` reports `statically linked` + -no `shared library` (fail build otherwise), copies to `/opt/kj/kj` -`chmod 0555`, handler calls `subprocess.run(['/opt/kj/kj', 'apply', …])`. - -## §8 — ECR image tag format (REQ-354 AC 3) — SPEC CORRECTION (D-239) - -- **ECR image tags do NOT allow `+`.** The ECR tag regex is - `^[a-zA-Z0-9]+(?:[._-][a-zA-Z0-9]+)*$` — permitted chars - `[a-zA-Z0-9._-]` only; `+` is rejected by `PutImage`/`BatchGetImage` - with `InvalidParameterException`. -- The spec's tag format `v1.29.x+kj-` is **invalid** as written. - Correct format: **`v1.29.x-kj-`** (replace `+` with `-`). -- The digest is the immutable trust surface regardless of the tag string - — a re-tag is detectable only via digest mismatch. The tag is a human - hint, not a security boundary. - -**Decision D-239 (spec correction):** REQ-354 AC 3 tag format corrected -to `v1.29.x-kj-`. Confidence 0.95. Applied to REQUIREMENTS.md -§v1.29 REQ-354 AC (3). - -## §9 — Codebase inspection (actual file paths) - -| Target | Path | Summary | -|---|---|---| -| `publish.yml` | `.github/workflows/publish.yml` (165 lines) + `.gitea/workflows/publish.yml` mirror | Currently publishes wheel + Lambda layer on `push: branches: [main]` (NOT tag-triggered). P1 must change trigger to `on: push: tags: ['v1.29.*']` + attach Lambda zip + ECR image to GitHub Releases. | -| `nova/idp/setup.py` CFN | `nova/idp/setup.py` (40 lines, thin CLI dispatcher) + `core/lambda/nova_idp_setup.py` (actual CFN logic, importlib-loaded because `lambda` is reserved) | REQ-369 archives to `docs/archive/nova-idp-cfn-v1.28.md`; `--apply` delegates to `terraform apply`. | -| `platform/abac/kj-version.txt` | `platform/abac/kj-version.txt` (2 lines: `v0.0.3` + SHA `4ebb9a19...`) | Already pins `kj` v0.0.3 + source SHA from v1.28 P4. P1 reads this SHA to embed in the ECR tag + verify the build. | -| `.gitea/` scrub targets | `.gitea/workflows/` (7 files) + `scripts/sync_workflows.py` (line 26: `GITEA_DIR`), `scripts/sync_to_nova.sh`, `scripts/rotate_spike_key.sh`, `terraform/bootstrap/`, ~100 `.ciagent/` doc matches | REQ-367 P2 removes `.gitea/`, scrubs `gitea` from `.github/` `docs/` `pyproject.toml` `README.md` `.ciagent/`, asserts `forge_parity_disabled` in CI (D-232). `sync_workflows.py` is the central removal target. | -| Consumer `deploy.yml` | NOT in `acdl/.github/workflows/deploy.yml` (that's the platform reusable workflow). Consumer's deploy.yml is in the `nova-blockchain-exchange` project — documented at `.ciagent/nova-blockchain-exchange/REQUIREMENTS.md` (REQ-314) + `.ciagent/nova-blockchain-exchange/README.md`. | P5 bumps consumer's `uses:` ref `@v1.25` → `@v1.29` in both `.github/workflows/deploy.yml` + `.gitea/workflows/deploy.yml` (consumer's `.gitea/` is out of scope for REQ-367 — that scrub is `acdl/acdl` only) + smoke test. | - -## §10 — Gitea Actions HITL approval (REQ-357, TFM-HITL) - -- Gitea Actions has **no Environments API** with required reviewers. The - approval signal is `gitea.actor` (triggering user) + - `gitea.triggering_actor` (may differ on re-run — the re-dispatcher). -- PR author: `${{ gitea.event.pull_request.user.login }}`. INV-3 check: - `${{ gitea.triggering_actor }} != ${{ gitea.event.pull_request.user.login }}` - (use `triggering_actor` for re-run safety). -- Gitea scoped-workflows (v1.27+) supports **required workflows** that - gate PR merges via status checks — but this gates *merge*, not *apply*. -- The `workflow_dispatch` approve-input pattern (D-042) is the mechanism: - plan runs automatically on PR; apply is a separate `workflow_dispatch` - with `approve_apply` input; the apply job asserts INV-3 + fails closed. -- **Codebase precedent:** `core/hitl_gates.py` + `core/separation_of_duties.py` - (D-042) — `hitl_gates.attest(env, approver)` reads - `GITHUB_ACTOR`/`FORGE_ACTOR`, writes to DynamoDB outbox; - `separation_of_duties.check` compares approvers. This is the production - pattern to extend for `nova-platform-ops` `terraform apply`. - -**Pitfalls:** scoped-workflow required-check enforcement needs branch -protection on `main`; a re-run changes `gitea.actor` to the re-dispatcher -— use `gitea.triggering_actor` for the effective approver. - -**Recommendation:** `nova-platform-ops` uses `workflow_dispatch` -approve-input pattern (extending `hitl_gates.py`/`separation_of_duties.py`); -plan auto-runs on PR, apply is `workflow_dispatch` with `approve_apply`; -apply job asserts `${{ gitea.triggering_actor }} != ${{ gitea.event.pull_request.user.login }}`; -branch protection on `main` + required scoped-workflow status check. - ---- - -## New decisions for the decision ledger (research-derived) - -| D-ID | Title | Confidence | Source | -|---|---|---|---| -| **D-239** | ECR tag format `v1.29.x+kj-` invalid (`+` not in ECR tag regex) → corrected to `v1.29.x-kj-` | 0.95 | §8 ECR API PutImage character class | -| **D-240** | `lifecycle.precondition` introduced in Terraform v1.2.0 (not v1.4+); ops repo `required_version = ">= 1.2.0"` suffices | 0.98 | §3 Terraform v1.2.0 CHANGELOG | - -Both are spec-vs-reality corrections logged at full autonomy (confidence -≥ 0.60 threshold). D-239 is applied to REQUIREMENTS.md §v1.29 REQ-354 -AC (3). D-240 is documented in the operator guide (P4) for the -`nova-platform-ops` `required_version` floor. - ---- - -## RESEARCH complete - -All 10 research questions answered with cited findings + concrete -recommendations + risks. Two spec corrections (D-239 ECR tag, D-240 -Terraform precondition floor). The highest-risk item is the M1.5 -verification gate (Q7 carry-forward — `kj` static build + 3 consecutive -rebuilds in `nova-platform-ops` CI). Next: PLAN. \ No newline at end of file +## Persona assessment (lead-developer) + +**Active personas for v1.30:** +- **lead-developer** (coordination) — owns STATE.md CAP-042, PROJECT.md + D-241, milestone coordination. Territory: `.ciagent/STATE.md`, + `.ciagent/PROJECT.md`. +- **backend-engineer** (backend) — owns `scripts/render_pptx.py` + extension + PPTX render + python-pptx install. Territory: + `scripts/render_pptx.py`, `docs/presentations/nova-leadership-deck.pptx`. + Framework override: python-pptx (not fastify/hono — the default + frameworks don't match this project's Python stack). +- **ci-doc-writer** (custom, phase-specific) — owns the Marp markdown + deck source. Territory: + `docs/presentations/nova-leadership-deck-marp.md`. Created for this + phase (presentation authoring); removed after P1. +- **ci-cli-engineer** (custom) — owns the smoke-test script. + Territory: `scripts/check_leadership_deck.sh`. + +**Deactivated personas:** +- **frontend-engineer** — already `active: false` in config (no UI). + Confirmed. +- **data-engineer** — no schema/migration work in this milestone. + Deactivate for v1.30. + +**Territory enforcement:** `warn` (per config). \ No newline at end of file