Files
acdl/.ciagent/PLAN.md
T
Jon Chery e1dc59ba79 docs(P00): create phase plans — v1.28 (7 phases, 31 REQs, 6 CAPs, MVP/UX sections)
---ci---
project: acdl
phase: 0
milestone: v1.28
status: plan
---/ci---
2026-08-19 22:06:38 +00:00

19 KiB
Raw Blame History

PLAN — v1.28 CLI Canonicalization + Identity Layer

Milestone: v1.28 (feature — CLI substrate + Nova-idp identity layer). Tags on the v1.27.x line: v1.27.0 (P0) → v1.27.1..v1.27.6 (P1..P6) → v1.27.7 (P7 final = milestone release). The final phase's patch IS the milestone release. Branch: milestone/v1.28-cli-identity. Phase branches: phase/00-pre-execution, phase/01-cli-substrate, phase/02-lambda-packaging, phase/03-idp-auth, phase/04-token-vend-pat, phase/05-docs-integration, phase/06-capability-gate, phase/07-final-review-ship.

Milestone goal

The Nova CLI is installable from internal PyPI (CodeArtifact); every core/ module is reachable as a nova <subcommand>; the CLI and Lambda functions share a single core/ source tree; and Nova owns its identity layer end-to-end (Nova-idp: nova-idp-auth + nova-idp-token-vend Lambdas, KMS-signed OIDC tokens, kyverno-json ABAC token vending, PAT lifecycle). No AWS-managed identity services in the path (INV-15).

Requirements

31 requirements: REQ-323..REQ-353 (full text in .ciagent/REQUIREMENTS.md §v1.28). 6 capabilities: CAP-033..CAP-038. 6 invariants: INV-12..INV-17. 6 decisions: D-226..D-231 (CLARIFY) + RESEARCH amendments (D-228 fail-closed, D-229 strong-read-on-PK).

Phase breakdown

Phase P1 — cli-substrate (REQ-323..REQ-328)

Goal: CodeArtifact wheel + Lambda layer pipeline; nova/ CLI package with a subcommand per core/ module; nova init; nova cli-action composite action; core/mode_resolver.py; audit emission with mode + selection_reason. The CLI is installable and every core/ module is reachable.

Capability gate target: CAP-033 (surface), CAP-034 (delegation).

Wave 1 — pyproject + entry point (cli-engineer)

  • Task 1.1 (cli-engineer): pyproject.toml — add [project.scripts] nova = "nova.cli:main"; add [tool.setuptools.packages.find] including nova, nova.*, core, core.*, adapters.*; bump requires-python to >=3.12; add argon2-cffi, cryptography, pyjwt, hypothesis to deps/test-deps. Verify pip install -e . produces a nova executable.

Wave 2 — CLI dispatch + subcommands (cli-engineer)

  • Task 2.1 (cli-engineer): nova/__init__.py + nova/cli.py (~80 lines, auto-discovers nova/<module>.py via pkgutil.iter_modules, dispatches, emits cli.invocation audit event stub with INV-12 fields).
  • Task 2.2 (cli-engineer): nova/<module>.py for each core/ module (≤50 lines, add_parser + run delegates to core/). Cover: resolve, decommission, env-transition, env-check, hitl, onboard, outbox, publish-outputs, policy, regression, sod, readiness, attestation-matrix, confidence. Skip internal-only (env, local_emulators, output_publisher if not user-facing).
  • Task 2.3 (cli-engineer): nova/init.py (REQ-325) — scaffolds .nova/, .nova/contract.yml.attestations/, .gitignore (excludes secrets, ~/.nova/credentials.json).

Wave 3 — mode_resolver + audit (cli-engineer)

  • Task 3.1 (cli-engineer): core/mode_resolver.pyresolve_mode(flag, env_var, credential_type, stdin_isatty) per D-226. sys.stdin.isatty() is the TTY check (RESEARCH §11). Invalid env → warn + fall through. Returns (mode, selection_reason).
  • Task 3.2 (cli-engineer): wire mode_resolver into nova/cli.py — resolve mode before dispatch, emit cli.invocation with mode, selection_reason, credential_type, command, args (INV-12, REQ-328).
  • Task 3.3 (cli-engineer): tests/test_mode_resolver.pyhypothesis property tests (REQ-349): deterministic, flag-wins, invalid-env-ignored, no-silent-fallback. Edge cases: TTY + piped stdout, missing credential, conflicting flag/env, invalid env value.

Wave 4 — CodeArtifact + layer pipeline (backend-engineer)

  • Task 4.1 (backend-engineer): .gitea/workflows/publish.yml + .github/workflows/publish.yml (byte-identical) — build wheel → CodeArtifact twine upload → build layer (pip install --target layer/python/ + argon2-cffi + cryptography + pyjwt) → lambda publish-layer-version → SSM /nova/layer/nova-cli/version mapping (CAP-035). Fail either → job fails (merge blocked, REQ-323). Pin version to <semver>+<sha7> for idempotent re-runs.

Wave 5 — composite action (cli-engineer + backend-engineer)

  • Task 5.1 (cli-engineer): .github/actions/nova-cli/action.yml — composite action, setup-python@v5 (3.12), CodeArtifact login + pip install nova, nova ${{ inputs.command }}. NOVA_CLIENT_MODE from input.
  • Task 5.2 (backend-engineer): byte-identical integration test — CI matrix runs the action on GitHub ubuntu-latest + Gitea act_runner; assert same stdout/exit code (REQ-326 AC2, NFR-11).

Wave 6 — CAP-033/034 gate (cli-engineer)

  • Task 6.1 (cli-engineer): tests/test_cli_subcommands.py — CAP-033 (nova --help lists a subcommand for every core/ module)
    • CAP-034 (AST scan: ≤50 lines, ≤3 defs, all calls resolve to core., no conditionals beyond if __name__). Wire into CI merge gate.

Phase P2 — lambda-packaging (REQ-329, REQ-330, REQ-331)

Goal: Dual-use core/lambda/contract_ingestor.py (Lambda + CLI paths share ≥80% code); core/env.py:+synthesize_local_env() for nova apply --local; .nova/contract.yml.attestations/ scaffolded.

Wave 1 — dual-use refactor (backend-engineer)

  • Task 1.1 (backend-engineer): refactor core/lambda/contract_ingestor.py — extract the shared logic into importable functions; the Lambda handler + the CLI __main__ block both call them. The __main__ block already exists (the dual-use precedent per RESEARCH §1.2). Verify ≥80% code share (CAP-034 / code review). Local path via core/local_emulators.py:LocalLambdaStub.

Wave 2 — local env synthesizer (backend-engineer)

  • Task 2.1 (backend-engineer): core/env.py:+synthesize_local_env() — produces a local env dict (account_id placeholder, region local, no real AWS) from a contract + --local flag. Mirrors core/onboarding.py:generate_env_file().
  • Task 2.2 (cli-engineer): nova/apply.py (≤50 lines) — nova apply --local delegates to core.env.synthesize_local_env() + core.contract_resolver.resolve().

Wave 3 — attestations dir (cli-engineer)

  • Task 3.1 (cli-engineer): verify nova init (P1 Wave 2 Task 2.3) creates .nova/contract.yml.attestations/ (empty). REQ-331 test.

Phase P3 — idp-auth (REQ-333, REQ-334, REQ-335)

Goal: nova-idp-auth Lambda (sign-up, sign-in, session) with Argon2id hashing + DynamoDB tables. CAP-036 target.

Wave 1 — DynamoDB schema (backend-engineer)

  • Task 1.1 (backend-engineer): define the 4 DynamoDB table schemas (nova-users, nova-sessions, nova-password-resets, nova-pats) in a CloudFormation snippet (reused by P5 nova idp setup). PITR enabled on each (REQ-335).

Wave 2 — Argon2id (security-engineer)

  • Task 2.1 (security-engineer): core/lambda/nova_idp_auth.py — Argon2id password hashing via argon2-cffi (D-228: bundled abi3 wheel; fail-closed on ImportError, 503, no pure-Python fallback). Lambda memory ≥512 MB. Raw passwords never in logs/traces/env/DDB (INV-16, REQ-334).

Wave 3 — auth Lambda (backend-engineer + security-engineer)

  • Task 3.1 (backend-engineer): nova-idp-auth Lambda handler — sign-up, sign-in, session creation endpoints. Function URL + IAM auth. DynamoDB via lazy boto3.resource (the existing pattern).
  • Task 3.2 (security-engineer): session token issuance + session storage in nova-sessions (TTL expires_at). Password reset flow in nova-password-resets (TTL 15m).

Wave 4 — CAP-036 E2E (backend-engineer)

  • Task 4.1 (backend-engineer): tests/test_idp_auth.py — sign-up → sign-in → session round-trip (moto[dynamodb] for local; deployed for CI). CAP-036 verification.

Phase P4 — token-vend-pat (REQ-336..REQ-344)

Goal: nova-idp-token-vend Lambda (KMS-signed OIDC, kyverno-json ABAC), JWKS endpoint, PAT lifecycle, nova auth commands. CAP-037 + CAP-038 target. Highest-risk phase (the kj binary in Lambda layer — RESEARCH §7).

Wave 1 — kj-binary spike (backend-engineer + security-engineer)

  • Task 1.1 (backend-engineer): confirm the kj Go binary (~40 MB Linux amd64) runs in the Lambda Python 3.12 runtime on AL2023. Bundle it in the nova-cli layer (wget into layer/bin/kj, chmod +x). Verify KyvernoJsonEngine.is_configured() finds /opt/bin/kj. If this fails: fall back to Fargate for the token-vend Lambda (D-227 risk, RESEARCH §7). Escalate to user only if both fail (full autonomy: log assumption + proceed with Fargate).

Wave 2 — ABAC policy (security-engineer)

  • Task 2.1 (security-engineer): platform/abac/token-vend.policy — kyverno-json ValidatingPolicy (D-227). Payload: {subject, requested_claims, target_resource, environment, pat_jti, policy_version}. JMESPath checks for role/scope/env/owner. Severity critical = deny on fail.
  • Task 2.2 (security-engineer): policy_version = git SHA of the policy file, baked into the Lambda layer (D-231). Recorded in every token.vend.allowed/denied audit event.

Wave 3 — KMS signing (security-engineer)

  • Task 3.1 (security-engineer): KMS key alias/nova-oidc-signing (ECC_NIST_P256, SIGN_VERIFY). Token-vend Lambda signs via kms.sign(SigningAlgorithm="ECDSA_SHA_256") → DER→raw ECDSA conversion (decode_dss_signaturer.to_bytes(32) + s.to_bytes(32), RESEARCH §5). JWT header {"alg":"ES256","typ":"JWT","kid":"..."}.

Wave 4 — token-vend Lambda (backend-engineer + security-engineer)

  • Task 4.1 (backend-engineer): core/lambda/nova_idp_token_vend.py — accepts PAT/session, validates revocation (nova-pats.GetItem(jti, ConsistentRead=True) — D-229), evaluates ABAC (Wave 2), signs (Wave 3), returns OIDC JWT. Audit at every step.
  • Task 4.2 (security-engineer): token claims sub, aud, iss, exp, iat, jti, roles (REQ-336).

Wave 5 — JWKS endpoint (backend-engineer)

  • Task 5.1 (backend-engineer): core/lambda/nova_idp_jwks.py — function URL AuthType: NONE, Cache-Control: max-age=3600. kms.get_public_key → DER SPKI → JWK via cryptography. Returns {"keys":[...]}. Custom domain + WAF = optional (D-230).

Wave 6 — PAT lifecycle (security-engineer + cli-engineer)

  • Task 6.1 (security-engineer): PAT issuance — signed JWT (typ: "developer_pat", INV-14), nova-pats PutItem (jti, pat_hash, status=active). Only hash stored (REQ-343). Revoked PATs retained.
  • Task 6.2 (cli-engineer): nova/auth/{login,revoke,status}.pynova auth login (session→OIDC token, store in ~/.nova/credentials.json 0600), nova auth revoke --pat <jti>, nova auth status (active credential, mode, selection_reason). All emit audit events (REQ-344).

Wave 7 — CAP-037/038 (security-engineer)

  • Task 7.1 (security-engineer): tests/test_kms_roundtrip.py (REQ-350, CAP-037) — sign test JWT via token-vend, fetch JWKS, verify with pyjwt. tests/test_pat_revocation.py (REQ-351, CAP-038) — issue → vend → revoke → assert 403 within 60s P95.

Phase P5 — idp-setup (REQ-340, REQ-341)

Goal: nova idp setup command with --check/--apply/--verify modes; CloudFormation template generation + review.

Wave 1 — CloudFormation template (backend-engineer)

  • Task 1.1 (backend-engineer): nova/idp/setup.py (+ backend helper) — generates the Nova-idp CloudFormation template (raw dict → JSON): 2-3 Lambdas, 4 DDB tables, KMS key, function URLs, IAM roles, optional CloudFront/WAF/ACM (--public-jwks-domain flag).

Wave 2 — setup modes (cli-engineer + backend-engineer)

  • Task 2.1 (cli-engineer): --check (prerequisites + IAM policy delta), --apply (generate → $PAGERy/Ncloudformation deploy --capabilities CAPABILITY_IAM, NFR-10), --dry-run (resource list only), --verify (KMS round-trip, delegates to REQ-350 test).
  • Task 2.2 (backend-engineer): IAM policy delta computation — compares current nova-spike-runner grants to required cloudformation:* + codeartifact:* + kms:* + lambda:* + dynamodb:* + ssm:*.

Phase P6 — docs-integration (REQ-345..REQ-351)

Goal: Operator guide, developer guide, threat model; E2E integration test; property tests; KMS round-trip; PAT revocation SLO.

Wave 1 — docs (lead-developer + security-engineer)

  • Task 1.1 (lead-developer): docs/operator-guide-idp.md (REQ-345) — nova idp setup --check/--apply/--verify, prerequisite IAM policy, CloudFormation review flow.
  • Task 1.2 (lead-developer): docs/developer-guide-auth.md (REQ-346) — signup, signin, login, mode resolution, TTY vs piped stdout behavior.
  • Task 1.3 (security-engineer): docs/threat-model.md (REQ-347) — Argon2id storage, KMS signing, JWKS exposure, PAT revocation SLO, ABAC token vending, no-AWS-managed-identity (INV-15), DER→raw ECDSA gotcha.

Wave 2 — integration tests (backend-engineer + security-engineer)

  • Task 2.1 (backend-engineer): tests/test_e2e_idp.py (REQ-348) — sign-up → sign-in → token-vend → apply → audit. Verifiable audit chain. Runs in CI against deployed Nova-idp.
  • Task 2.2 (security-engineer): verify REQ-349 (mode_resolver property tests, P1 Wave 3 Task 3.3) + REQ-350 (KMS round-trip, P4 Wave 7 Task 7.1) + REQ-351 (PAT revocation SLO, P4 Wave 7 Task 7.1) pass in CI.

Phase P7 — final-review-ship (Final Phase)

Goal: Multi-persona code review across P1..P6; project-health audit; milestone ship to main; CAP-033..038 Verified.

Wave 1 — review (lead-developer)

  • Task 1.1 (lead-developer): ciagent-review across all phases. Auto-fix P0; flag P1+ for post-hoc. If P1+ found, fix in this phase.

Wave 2 — audit (lead-developer)

  • Task 2.1 (lead-developer): ciagent-audit — reconstruction test (git log ↔ .ciagent/), file/branch/commit discipline. Fix critical issues in this phase.

Wave 3 — milestone ship (lead-developer)

  • Task 3.1 (lead-developer): ciagent-ship — merge phase/07milestone/v1.28-cli-identitymain; tag v1.27.7 (= the v1.28 release); Gitea release with full milestone summary; delete all milestone branches. Update REQUIREMENTS.md (mark REQ-323..353 complete), ROADMAP.md (mark v1.28 complete), STATE.md (append CAP-033..038 + INV-12..17), NORTH_STAR.md.

User-Facing Surface

MVP/UX CHECK §1 (REQ-MVP-UX-001).

  1. CLI flag: nova --help lists every subcommand; nova init scaffolds a project; nova auth login authenticates; nova apply --local runs locally; nova idp setup deploys the identity stack.
  2. README quickstart: docs/developer-guide-auth.md (REQ-346) documents signup → signin → login → nova apply in a quickstart.
  3. .feature Scenario: tests/test_e2e_idp.py (REQ-348) is the E2E happy path (sign-up → sign-in → token-vend → apply → audit).

Happy Path

MVP/UX CHECK §2 (REQ-MVP-UX-001). End-to-end scenario written BEFORE execute.

Journey 2 — Dev authenticates and deploys locally:

  1. nova auth signupnova-idp-auth Lambda → Argon2id hash → nova-users PutItem → session token.
  2. nova auth signinnova-idp-auth → Argon2id verify → session.
  3. nova auth loginnova-idp-token-vend (exchanges session for Nova OIDC token; stores in ~/.nova/credentials.json 0600).
  4. nova init in a project dir → .nova/, .gitignore, .nova/contract.yml.attestations/.
  5. nova apply --local --sign-local-reviewcore.env.synthesize_local_env()core.contract_resolver.resolve() → JWS attestation signed with a key derived from the PAT → local ledger entry.

The E2E test (tests/test_e2e_idp.py, REQ-348) verifies this chain + the audit event chain in CI against a deployed Nova-idp.

UX Acceptance Criteria

MVP/UX CHECK §3 (REQ-MVP-UX-001).

  1. nova --help exits 0 and lists a subcommand for every core/ module (CAP-033).
  2. nova init in an empty dir creates .nova/, .nova/contract.yml.attestations/, .gitignore (secrets excluded).
  3. nova auth login at a TTY resolves mode=interactive, selection_reason=credential:developer_pat (INV-12, INV-14).
  4. nova apply --local produces a JWS attestation verifiable with the public key derived from the PAT (REQ-332).
  5. nova idp setup --check reports prerequisites + IAM policy delta; --apply presents the CloudFormation template for review before any resource is created (NFR-10); --verify confirms the KMS round-trip.
  6. The Forge action (nova cli-action) runs nova apply in mode=agent, selection_reason=credential:service_account_pat with no TTY dependency (Journey 3, INV-12).
  7. PAT revocation takes effect within 60s P95 (NFR-4, CAP-038).

Capability gate (CAP-033..CAP-038)

CAP Name Phase Gate rule
CAP-033 CLI subcommand surface exists P1 nova --help lists a subcommand for every core/ module
CAP-034 Subcommand delegates to core/ P1 Every nova/<module>.py ≤50 lines, no business logic, AST scan
CAP-035 Layer matches wheel P1 Lambda layer ARN version matches nova-cli wheel version (SSM mapping)
CAP-036 Nova-idp auth flow works P3 E2E test (sign-up → sign-in → session) passes in CI
CAP-037 Token-vend signs via KMS P4 KMS round-trip test (REQ-350) passes in CI
CAP-038 PAT issuance + revocation P4 Issue → vend → revoke → 403 within 60s P95 (REQ-351) passes in CI

Release gate (§6 of the spec): CAP-001..CAP-032 remain Verified; CAP-033..CAP-038 are Verified; all v1.28 release-gate criteria met.


Test evidence required for v1.28 release

  • Code coverage ≥ 80% on new modules (mode_resolver.py, nova-idp-auth, nova-idp-token-vend, PAT lifecycle).
  • CI/CD pipeline GREEN: wheel + Lambda layer publish on every merge (REQ-323, CAP-035).
  • QA sign-off: all four happy-path journeys (J1J4) pass integration tests in CI.
  • Security/compliance review: threat model published, Argon2id verified, ABAC policy reviewed.
  • Capability gate GREEN: CAP-001..032 remain Verified; CAP-033..038 Verified.
  • Mode resolver property tests pass (all four priority levels + edge cases; REQ-349).
  • KMS round-trip test passes against deployed JWKS (REQ-350).
  • PAT revocation SLO verified: ≤60s P95 in CI (REQ-351, NFR-4).
  • Operator + developer guides published.
  • nova idp setup succeeds in a fresh AWS account.
  • Byte-identical Forge action on GitHub + Gitea (REQ-326, NFR-11).

Plan completeness checklist

  • Every REQ-323..353 mapped to a phase + wave + task.
  • Every CAP-033..038 mapped to a phase + gate rule.
  • Every INV-12..17 referenced in persona constraints.
  • Every D-226..231 referenced in task rationale.
  • Vertical slices: each phase ships independently (P1 CLI substrate is useful before P2 packaging; P2 before P3 auth; etc.).
  • Wave ordering within phases (no wave N+1 depends on wave N work in the same phase).
  • Persona assignments per task (4 active personas).
  • MVP/UX CHECK: 3 sections present (User-Facing Surface, Happy Path, UX Acceptance Criteria).
  • Highest-risk item flagged (P4 Wave 1 kj-binary spike).