From c629809d75068f4208eda858cbdcdd48b8a4504d Mon Sep 17 00:00:00 2001 From: Jon Chery Date: Wed, 19 Aug 2026 22:05:24 +0000 Subject: [PATCH] =?UTF-8?q?docs(P00):=20research=20findings=20=E2=80=94=20?= =?UTF-8?q?v1.28=20CLI=20+=20identity=20layer=20(11=20Qs,=20D-228=20amende?= =?UTF-8?q?d)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ---ci--- project: acdl phase: 0 milestone: v1.28 status: research ---/ci--- --- .ciagent/ARCHITECTURE.md | 82 ++++++- .ciagent/CHECKPOINT.json | 7 +- .ciagent/PERSONAS.md | 158 +++++++++----- .ciagent/RESEARCH.md | 448 ++++++++++++++++++++++++++++----------- 4 files changed, 505 insertions(+), 190 deletions(-) diff --git a/.ciagent/ARCHITECTURE.md b/.ciagent/ARCHITECTURE.md index 88facf4..a7236e2 100644 --- a/.ciagent/ARCHITECTURE.md +++ b/.ciagent/ARCHITECTURE.md @@ -576,4 +576,84 @@ key only after the new one propagates to the consumer's Actions secret store, verified by a post-PUT GET; on upload/verify failure the old key is left Active and the run exits non-zero. The synced workflow file is forge-agnostic (REQ-230): forge base URL / owner / consumer repo come from -repository secrets (`NOVA_FORGE_*`, `NOVA_CONSUMER_REPO`), not literals. \ No newline at end of file +repository secrets (`NOVA_FORGE_*`, `NOVA_CONSUMER_REPO`), not literals. + +### §12.10 — Nova-idp Identity Layer (v1.28, current) + +Nova owns its identity layer end-to-end. Two (optionally three) Lambda +functions + four DynamoDB tables + one KMS asymmetric signing key + one +kyverno-json ABAC policy. **No Cognito, no IAM Identity Center (INV-15).** +The `nova-cli` Lambda layer carries the Nova wheel + `argon2-cffi` + +`cryptography` + `pyjwt` + the `kj` Go binary, making the same code +importable in both the CLI and the Lambda (REQ-329 dual-use, NFR-7). + +**Components:** + +- `nova-idp-auth` Lambda — sign-up, sign-in, session creation. Argon2id + password hashing (D-228: bundled abi3 wheel; fail-closed on + `ImportError`, no pure-Python fallback). DynamoDB: `nova-users` + (PK `user_id`, Argon2id `password_hash`), `nova-sessions` (PK + `session_id`, TTL `expires_at`), `nova-password-resets` (PK + `reset_token`, TTL 15m). Function URL with IAM auth. +- `nova-idp-token-vend` Lambda — accepts a PAT (or session token), + validates revocation (`nova-pats.GetItem(jti, ConsistentRead=True)` — + D-229, 60s SLO), evaluates the kyverno-json ABAC policy at + `platform/abac/token-vend.policy` (D-227, INV-17), KMS-signs an + ECDSA P-256 JWT (`ES256`), converts DER→raw ECDSA signature (RFC 7515 + §3.1.3), returns the OIDC token. The `policy_version` (git SHA, + D-231) is recorded in every `token.vend.allowed/denied` audit event. +- `nova-idp-jwks` Lambda (optional, separation of concerns) — function + URL with `AuthType: NONE` (public key only), `Cache-Control: max-age=3600`. + `kms.get_public_key` → DER SPKI → JWK via `cryptography`. Custom + domain + WAF via CloudFront is OPTIONAL (`--public-jwks-domain` flag + on `nova idp setup`, D-230). +- `nova-pats` DynamoDB table — PK `jti`, GSI1 `sub` (list PATs for + user), GSI2 `pat_hash` (lookup by hash). Only the hash stored (not + raw PAT, REQ-343). Revoked PATs retained for audit. + +**CLI surface (`nova` package, greenfield):** + +- Entry point: `[project.scripts] nova = "nova.cli:main"` (argparse-only, + no click/typer — repo convention). `nova/cli.py` auto-discovers + `nova/.py` subcommands via `pkgutil.iter_modules`, dispatches, + emits the `cli.invocation` audit event (INV-12) with `mode`, + `selection_reason`, `credential_type`, `command`, `args`. +- Each `nova/.py` is ≤50 lines, delegates to `core/` (CAP-034 + AST scan). Subgroups: `nova auth login/revoke/status`, `nova idp + setup --check/--apply/--verify`, `nova init`, `nova apply --local`. +- `core/mode_resolver.py` — flag → env (`NOVA_CLIENT_MODE`) → credential + type → `sys.stdin.isatty()` (D-226). CLI-only; Lambdas don't resolve + modes. Property-tested with `hypothesis` (REQ-349). +- `core/env.py:+synthesize_local_env()` — synthesizes a local env dict + from a contract + `--local` flag (REQ-330). No cloud provisioning. + +**Packaging (NFR-6, CAP-035):** + +- CI publishes a wheel to CodeArtifact AND a Lambda layer with identical + version strings on every merge affecting `core/`/`adapters/`/`nova/`. + Version mapping recorded in SSM `/nova/layer/nova-cli/version`. + If either publish fails, the merge is blocked (REQ-323). +- `nova cli-action` composite action at + `.github/actions/nova-cli/action.yml`, referenced by both GitHub + + Gitea (`uses: continuous-intelligence/acdl/.github/actions/nova-cli@v1.28`). + Python 3.12 pinned. Byte-identical behavior verified by CI matrix + (REQ-326, NFR-11). + +**Data flows:** + +1. Sign-up → `nova-idp-auth` → Argon2id → `nova-users` PutItem → session + → `nova-sessions` PutItem → return session token. +2. Token vend (hot path) → `nova-idp-token-vend` → `nova-pats` strong + read (revocation) → kyverno-json ABAC eval → if allow → KMS sign → + DER→raw → return OIDC JWT. Audit at every step. +3. JWKS fetch → `nova-idp-jwks` → `kms.get_public_key` → DER→JWK → + `{"keys":[...]}`. Cached 1h at CloudFront (if custom domain) / client. +4. PAT revoke → `nova auth revoke --pat ` → `nova-pats.UpdateItem( + status=revoked)` → audit. Strong read on next vend → 403 (within 60s). + +**`nova idp setup` (REQ-340, NFR-10):** generates a CloudFormation +template (raw dict → JSON, no troposphere dep), presents for review +(`$PAGER` + resource summary), requires explicit `y/N` approval before +`cloudformation deploy --capabilities CAPABILITY_IAM`. `--check` reports +prerequisites + IAM policy delta; `--verify` runs the KMS round-trip +test. New IAM grants required: `cloudformation:*`, `codeartifact:*`. \ No newline at end of file diff --git a/.ciagent/CHECKPOINT.json b/.ciagent/CHECKPOINT.json index 5973b92..0c8f0fb 100644 --- a/.ciagent/CHECKPOINT.json +++ b/.ciagent/CHECKPOINT.json @@ -1,10 +1,10 @@ { "phase": 0, - "stage": "clarify", + "stage": "research", "milestone": "v1.28", "phase_role": "pre_execution", "attempts": 0, - "updated_at": "2026-08-19T20:10:00Z", + "updated_at": "2026-08-19T20:25:00Z", "project": "acdl", "projects": ["acdl", "nova-blockchain-exchange"], "active_milestone": "v1.28", @@ -13,5 +13,6 @@ "tag_line": "v1.27.x", "previous_milestone": {"milestone": "v1.27", "tag": "v1.26.3", "status": "complete"}, "decisions": ["D-226", "D-227", "D-228", "D-229", "D-230", "D-231"], - "notes": "v1.28 CLARIFY. 6 open Qs + 5 grounding gaps resolved at full autonomy. D-226..D-231 authored (confidence >= 0.80). kj mapped to kyverno-json (D-227). Cognito-drop reframed as greenfield (G3). CAP-033..038, INV-12..17, REQ-323..353 allocated. Next: RESEARCH." + "personas": ["backend-engineer", "security-engineer", "cli-engineer", "lead-developer"], + "notes": "v1.28 RESEARCH complete. 11 questions answered via ci-researcher subagent. D-228 amended (fail-closed, not pure-Python fallback). kj-binary-in-Lambda-layer is highest risk (P2 spike). KMS = ECC_NIST_P256 / ES256 with DER->raw conversion. nova-pats = 4th DDB table. mode_resolver uses sys.stdin.isatty(). PERSONAS.md + ARCHITECTURE.md §12.10 authored. Next: PLAN." } \ No newline at end of file diff --git a/.ciagent/PERSONAS.md b/.ciagent/PERSONAS.md index c75a899..eb61501 100644 --- a/.ciagent/PERSONAS.md +++ b/.ciagent/PERSONAS.md @@ -1,77 +1,119 @@ --- project: acdl -milestone: v1.27 +milestone: v1.28 generated_at: 2026-08-19 generator: lead-developer verification_toolchain: - typecheck: "python3 -m py_compile core/confidence_signal.py 2>&1 | head -5 || true" - test: "bash scripts/run_regression.sh 2>&1 | tail -10 || true" - lint: "ruff check .ciagent/STATE.md 2>/dev/null || true" + 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.27 is an NFR milestone (PO State Catalog & Ciagent Compression) — - a docs/chore milestone. Single active persona: lead-developer owns - the milestone narrative (STATE.md authoring, PROJECT/ROADMAP fixes, - archive moves, PLAN/NORTH_STAR wiring, final review + audit). No - code, no schema, no policy authoring. The pre-existing - core/confidence_signal.py LSP diagnostic is out of scope (not - touched by v1.27). Territory enforcement: warn. + 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.27 PO State Catalog & Ciagent Compression +# Personas — v1.28 CLI Canonicalization + Identity Layer -> Generated by the lead-developer at the end of RESEARCH. Assesses the -> project domains, activates/deactivates personas, aligns frameworks + -> territory + constraints to the actual project structure. +## Roster -## Active Roster (1) +### 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/**" +``` -### 1. lead-developer (active) -- **active:** true -- **phase_specific:** false -- **reason:** Owns the full v1.27 milestone narrative: STATE.md - authoring (PO-facing capability catalog, 36 entries across 10 - domains + 11 invariants), archive moves (11 files to - `.ciagent/archive/` + 1 to consumer archive), PROJECT.md + ROADMAP.md - phase-status corrections, archive/README.md contents update, - PLAN.md + ROADMAP.md + NORTH_STAR.md ship-discipline wiring, final - review + audit. -- **domain:** `.ciagent/` docs (STATE.md, PROJECT.md, ROADMAP.md, - PLAN.md, NORTH_STAR.md, archive/README.md), consumer - `.ciagent/nova-blockchain-exchange/` (PROJECT.md pointer, - archive/ROADMAP-v1.26.md). -- **frameworks:** markdown, JSON (CHECKPOINT.json, config.json). -- **territory:** `.ciagent/`, `docs/`. -- **constraints:** no code changes (NFR milestone, D-220); no schema - changes; archive moves are lossless (byte-identical relocation, git - history preserves authoritative state); STATE.md is additive only. +### 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" +``` -## Deactivated (5) +### 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" +``` -### backend-engineer (inactive) -- **active:** false -- **reason:** No code changes in v1.27. The pre-existing - `core/confidence_signal.py` LSP diagnostic is out of scope (not - touched by v1.27). +### 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" +``` -### data-engineer (inactive) -- **active:** false -- **reason:** No schema, migration, or ORM changes. +### 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." +``` -### policy-engineer (inactive) -- **active:** false -- **reason:** No policy authoring. STATE.md Domain 3 catalogues - existing v1.25 + v1.26 policies (descriptive, not authoring). +### 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." +``` -### frontend-engineer (inactive) -- **active:** false -- **reason:** No UI. Deactivated since v1.26 (PERSONAS.md:141). +## Territory overlap notes -### blockchain-engineer (inactive) -- **active:** false -- **reason:** No chain code. The v1.26 pilot is shipped; v1.27 is - platform-side docs/chore only. +- `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). -## Territory Enforcement +## Phase-specific personas -- **Mode:** `warn` (the milestone is `.ciagent/`-only; the lead- - developer owns all writes; no cross-territory collisions expected). \ No newline at end of file +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). \ No newline at end of file diff --git a/.ciagent/RESEARCH.md b/.ciagent/RESEARCH.md index 8257a56..98b86e6 100644 --- a/.ciagent/RESEARCH.md +++ b/.ciagent/RESEARCH.md @@ -1,144 +1,336 @@ -# Nova — v1.27 Research Findings +# Nova — v1.28 Research Findings -> Phase: research (pre-execution). Milestone: v1.27 (PO State Catalog & -> Ciagent Compression). Status: research. Researcher: ci-researcher. +> Phase: research (pre-execution). Milestone: v1.28 (CLI Canonicalization +> + Identity Layer). Status: research. Researcher: ci-researcher. > Autonomy: full. - -v1.27 is an NFR milestone (docs/chore only, no code, no schema). There -is no new domain to research. The research is a codebase-grounded -inventory of (a) the files to archive + their staleness evidence, and -(b) the sources backing the STATE.md capability backfill. This file -records the inventory for the v1.27 record; the active authoring used -these sources directly. +> +> 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. --- -## 1. Files to archive (staleness inventory) +## §1 — Codebase Inventory (grounding) -### 1.1 Pre-execution artifacts (v1.26 — shipped, decisions folded) +### 1.1 `core/` modules (the REQ-324 subcommand surface) -| File | Lines | Staleness evidence | Decisions folded into | -|---|---|---|---| -| `CLARIFY.md` | 225 | v1.26 milestone shipped (`v1.25.5`); decisions D-200..D-213 | `PROJECT.md` load-bearing decisions | -| `GRILL.md` | 225 | v1.26 grill verdict PROCEED 0.84; binding revisions applied | `PLAN.md` revisions (G-Q4 REQ-322→P2 W0; G-Q6 enforcement deferred; G-Q9 key-split future) | -| `IDEATE.md` | 193 | all 7 accepted ideas → REQ-315..322 (shipped) | `REQUIREMENTS.md` v1.26 traceability | -| `RESEARCH.md` | 250 | v1.26 domain research (blockchain, deploy, modules, metrics) | `ARCHITECTURE.md` §12.8; shipped REQs | +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. -All four are pre-execution artifacts for a shipped milestone. The next -P0 writes fresh versions. Per D-219 (user-confirmed) + D-222: archive -all four with `-v1.26` suffixes. +### 1.2 Existing Lambda pattern (`core/lambda/contract_ingestor.py`) -### 1.2 Phase verifications + review (v1.26 — shipped, PASS) +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`. -| File | Lines | Staleness evidence | +### 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. + +--- + +## §2 — CodeArtifact + Lambda Layer Pipeline (REQ-323) + +**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). + +**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. + +**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). + +--- + +## §3 — CLI Subcommand Architecture (REQ-324) + +**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). + +**CAP-034 AST scan:** ≤50 lines; ≤3 function defs; every `ast.Call` +resolves to a `core.` import; no conditionals beyond `if __name__`. + +**Subcommand groups:** `nova auth`, `nova idp`, `nova metrics` = +nested subparsers (same pattern, one level deeper). + +**setuptools:** add `[tool.setuptools.packages.find]` including `nova`, +`nova.*`, `core`, `core.*`, `adapters.*`. + +--- + +## §4 — Argon2id in Lambda Python 3.12 (REQ-334, D-228) + +**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. + +**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). + +--- + +## §5 — KMS Asymmetric Signing for OIDC Tokens (REQ-337) + +**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). + +**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). + +**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. + +**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. + +--- + +## §6 — JWKS Endpoint (REQ-338, D-230) + +**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). + +**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. + +**Defer API Gateway** (D-230) — $3.50/M + complexity for no benefit at +v1.28 volume. + +--- + +## §7 — kyverno-json ABAC Policy (REQ-339, D-227) + +**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. + +**`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. + +--- + +## §8 — PAT Lifecycle (REQ-342, REQ-343, REQ-344) + +**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`. + +**`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. + +--- + +## §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 | |---|---|---| -| `VERIFY-P03.md` | 39 | v1.26 P3 verification — PASS; shipped `v1.25.3` | -| `VERIFY-P04.md` | 31 | v1.26 P4 verification — PASS; shipped `v1.25.4` | -| `REVIEW-AUDIT-P05.md` | 218 | v1.26 P5 final review + audit — PROCEED; shipped `v1.25.5`; 0 P0 remain; audit CLEAN | -| `P4-PILOT-RUN-EVIDENCE.md` | 46 | v1.26 live apply evidence (`blkex-pilot-apply-v0.2`); summarized in `nova-blockchain-exchange/README.md` §5 + REVIEW-AUDIT-P05 §2.2 | +| 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 | -### 1.3 Durable references (superseded or stale) - -| File | Lines | Staleness evidence | Superseded by | -|---|---|---|---| -| `CAPABILITY_INVENTORY.md` | 120 | dated 2026-07-27; framed as "v1.1→v1.8 re-verification sweep"; predates v1.26 pilot (CAP-025 absent; blockchain capabilities absent) | `STATE.md` (this milestone) | -| `AUTONOMY_THESIS.md` | 65 | "Last refined: v1.21"; thesis fully folded into `NORTH_STAR.md` Vision (lines 17–22) + Anti-Goals #2 | `NORTH_STAR.md` | -| `COST.md` | 106 | dated 2026-07-29; framed "v1.0 → v1.14"; predates v1.26 live pilot (ECS + ALB + DynamoDB + S3 costs not reflected) | A future cost milestone writes a fresh report; `STATE.md` Domain 7 notes cost tracking as a capability | - -### 1.4 Consumer-side (nova-blockchain-exchange) - -| File | Lines | Staleness evidence | -|---|---|---| -| `nova-blockchain-exchange/ROADMAP.md` | 57 | marks P3/P4/P5 as "planned" but v1.26 shipped (`v1.25.5`); phase narrative preserved in platform `ROADMAP.md` v1.26 section | - -Per D-221: consumer archives land in -`.ciagent/nova-blockchain-exchange/archive/ROADMAP-v1.26.md`. +**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). --- -## 2. Files to keep active (no-edit or fix-only) +## RESEARCH complete -### 2.1 No-edit (live code paths or durable) - -| File | Why keep active | -|---|---| -| `CHECKPOINT.json` | Authoritative resume state — never archive | -| `config.json` | Operational config — never archive | -| `REGRESSION_REPORT.json` | Written by `core/regression_verify.py:705`; read by `core/metrics/collector.py:27` + `trust_snapshot.py:21` + metrics views (D-224: regenerates on next `run_regression.sh`) | -| `REGRESSION_REPORT.md` | Written by `core/regression_verify.py:704`; read by `scripts/run_regression.sh` (D-224) | -| `PERSONAS.md` | Regenerated at each milestone P0 by the lead-developer; not stale until then | -| `IAM_POLICY.md` | Live baseline, test-enforced (`tests/test_iam_policy_baseline.py`); D-207 future key-split pending (D-223) | -| `PLAN.md` | Active phase plan; reset to next milestone at next P0 | -| `ARCHITECTURE.md` | Durable target architecture (§1–§12 + §12.7 + §12.8 + §12.9) | -| `NORTH_STAR.md` | PO strategy; loaded every ci-run via `config.strategic_direction_file` | -| `nova-blockchain-exchange/PROJECT.md` | Consumer project charter; D-200..D-205 load-bearing | -| `nova-blockchain-exchange/REQUIREMENTS.md` | REQ-310..322 spec intent (shipped but spec stays for reference) | -| `nova-blockchain-exchange/README.md` | Consumer onboarding guide; still accurate (deploy workflow, secrets, contract shape, verification) | - -### 2.2 Fix-only (corrections to stale-but-kept files) - -| File | Fix | -|---|---| -| `PROJECT.md` | v1.26 phase-status block (lines 424–431): P3/P4/P5 "pending" → "complete" with shipped tags `v1.25.3/4/5`; add STATE.md pointer (D-225: P2 phase) | -| `ROADMAP.md` | v1.26 P3/P4/P5 sections (lines 238, 261, 272) "planned" → "complete" with shipped tags; v1.26 Overview line "active" → "complete"; add STATE.md to P5 ship-update list (D-225: P2 phase) | -| `archive/README.md` | Add the 11 new archived files to the contents tables (P2 phase) | - ---- - -## 3. STATE.md capability backfill sources - -The STATE.md backfill (36 capabilities across 10 domains) was sourced -from: - -| Source | Used for | -|---|---| -| `core/regression_verify.py` (lines 129–768) | CAP-001..CAP-025 IDs, names, tiers, evidence pointers | -| `.ciagent/CAPABILITY_INVENTORY.md` (pre-archive) | CAP-001..022 descriptions, defect notes, evidence | -| `modules/registry.json` | L1/L2 module catalog (13 L1 + 2 L2 entries) | -| `.ciagent/REQUIREMENTS.md` v1.25 traceability | REQ-291..309 → policy-engine capabilities | -| `.ciagent/nova-blockchain-exchange/REQUIREMENTS.md` + `.ciagent/REQUIREMENTS.md` v1.26 traceability | REQ-310..322 → pilot capabilities | -| `.ciagent/CHECKPOINT.json` | shipped tags `v1.25.0..v1.25.5` | -| `git log --all --oneline` | file paths for v1.26 shipped features | -| `.ciagent/PROJECT.md` load-bearing decisions | INV-1..INV-11 invariants | -| `docs/submission-readiness.md` + `schemas/contract.schema.json` | INV-1 contract surface | - ---- - -## 4. Persona assessment - -v1.27 is a docs/chore milestone. The active roster: - -- **lead-developer** (active): owns the milestone narrative (STATE.md - authoring, PROJECT/ROADMAP fixes, archive README, PLAN/NORTH_STAR - wiring, this RESEARCH, CLARIFY, PLAN, final review + audit). Territory: - `.ciagent/`, `docs/`. -- **backend-engineer** (active, limited): no code changes in v1.27. - Consulted on the `core/confidence_signal.py` LSP diagnostic (pre- - existing, not touched by v1.27). No territory writes. -- **data-engineer** (inactive): no schema/migration/ORM changes. -- **policy-engineer** (inactive): no policy authoring. -- **frontend-engineer** (inactive): no UI. -- **blockchain-engineer** (inactive): no chain code. - -Territory enforcement: warn. The milestone is `.ciagent/`-only; the -lead-developer owns all writes. - ---- - -## 5. Risk analysis - -| Risk | Likelihood | Impact | Mitigation | -|---|---|---|---| -| Archive move breaks a relative path reference in an active file | Low | Medium | `grep` for the archived filenames across `.ciagent/` + `docs/` before commit; fix any dangling references in P2 | -| STATE.md capability row is inaccurate (wrong shipped tag / wrong file path) | Medium | Low | The backfill sources are the authoritative registries (regression_verify.py, registry.json, CHECKPOINT.json, git log); citations are direct | -| PROJECT.md phase-status fix conflicts with a future v1.26-era commit | Low | Low | v1.26 is shipped (main has the milestone merge); no v1.26-era commits will arrive | -| REGRESSION_REPORT stale state is mistaken for v1.27 scope | Low | Low | D-224 records the decision; STATE.md Domain 7 notes the current CAP range | - ---- - -## 6. Verdict - -v1.27 is feasible, scoped, and the sources are grounded. No new domain, -no new code, no schema breaks. The archive moves are lossless (git -history + archive directory both preserve bytes). The STATE.md -backfill is sourced from authoritative registries. Proceed to PLAN. \ No newline at end of file +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. \ No newline at end of file