---ci--- project: acdl phase: 0 milestone: v1.28 status: plan ---/ci---
19 KiB
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]includingnova,nova.*,core,core.*,adapters.*; bumprequires-pythonto>=3.12; addargon2-cffi,cryptography,pyjwt,hypothesisto deps/test-deps. Verifypip install -e .produces anovaexecutable.
Wave 2 — CLI dispatch + subcommands (cli-engineer)
- Task 2.1 (cli-engineer):
nova/__init__.py+nova/cli.py(~80 lines, auto-discoversnova/<module>.pyviapkgutil.iter_modules, dispatches, emitscli.invocationaudit event stub with INV-12 fields). - Task 2.2 (cli-engineer):
nova/<module>.pyfor eachcore/module (≤50 lines,add_parser+rundelegates tocore/). 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_publisherif 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.py—resolve_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_resolverintonova/cli.py— resolve mode before dispatch, emitcli.invocationwithmode,selection_reason,credential_type,command,args(INV-12, REQ-328). - Task 3.3 (cli-engineer):
tests/test_mode_resolver.py—hypothesisproperty 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 → CodeArtifacttwine upload→ build layer (pip install --target layer/python/+argon2-cffi+cryptography+pyjwt) →lambda publish-layer-version→ SSM/nova/layer/nova-cli/versionmapping (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_MODEfrom input. - Task 5.2 (backend-engineer): byte-identical integration test —
CI matrix runs the action on GitHub
ubuntu-latest+ Giteaact_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 --helplists a subcommand for everycore/module)- CAP-034 (AST scan: ≤50 lines, ≤3 defs, all calls resolve to
core., no conditionals beyondif __name__). Wire into CI merge gate.
- CAP-034 (AST scan: ≤50 lines, ≤3 defs, all calls resolve to
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 viacore/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 +--localflag. Mirrorscore/onboarding.py:generate_env_file(). - Task 2.2 (cli-engineer):
nova/apply.py(≤50 lines) —nova apply --localdelegates tocore.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 P5nova 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 viaargon2-cffi(D-228: bundled abi3 wheel; fail-closed onImportError, 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-authLambda handler — sign-up, sign-in, session creation endpoints. Function URL + IAM auth. DynamoDB via lazyboto3.resource(the existing pattern). - Task 3.2 (security-engineer): session token issuance + session
storage in
nova-sessions(TTLexpires_at). Password reset flow innova-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
kjGo binary (~40 MB Linux amd64) runs in the Lambda Python 3.12 runtime on AL2023. Bundle it in thenova-clilayer (wgetintolayer/bin/kj,chmod +x). VerifyKyvernoJsonEngine.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-jsonValidatingPolicy(D-227). Payload:{subject, requested_claims, target_resource, environment, pat_jti, policy_version}. JMESPath checks for role/scope/env/owner. Severitycritical= 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 everytoken.vend.allowed/deniedaudit 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 viakms.sign(SigningAlgorithm="ECDSA_SHA_256")→ DER→raw ECDSA conversion (decode_dss_signature→r.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 URLAuthType: NONE,Cache-Control: max-age=3600.kms.get_public_key→ DER SPKI → JWK viacryptography. 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-patsPutItem (jti, pat_hash, status=active). Only hash stored (REQ-343). Revoked PATs retained. - Task 6.2 (cli-engineer):
nova/auth/{login,revoke,status}.py—nova auth login(session→OIDC token, store in~/.nova/credentials.json0600),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 withpyjwt.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-domainflag).
Wave 2 — setup modes (cli-engineer + backend-engineer)
- Task 2.1 (cli-engineer):
--check(prerequisites + IAM policy delta),--apply(generate →$PAGER→y/N→cloudformation 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-runnergrants to requiredcloudformation:*+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-reviewacross 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— mergephase/07→milestone/v1.28-cli-identity→main; tagv1.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).
- CLI flag:
nova --helplists every subcommand;nova initscaffolds a project;nova auth loginauthenticates;nova apply --localruns locally;nova idp setupdeploys the identity stack. - README quickstart:
docs/developer-guide-auth.md(REQ-346) documents signup → signin → login →nova applyin a quickstart. .featureScenario: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:
nova auth signup→nova-idp-authLambda → Argon2id hash →nova-usersPutItem → session token.nova auth signin→nova-idp-auth→ Argon2id verify → session.nova auth login→nova-idp-token-vend(exchanges session for Nova OIDC token; stores in~/.nova/credentials.json0600).nova initin a project dir →.nova/,.gitignore,.nova/contract.yml.attestations/.nova apply --local --sign-local-review→core.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).
nova --helpexits 0 and lists a subcommand for everycore/module (CAP-033).nova initin an empty dir creates.nova/,.nova/contract.yml.attestations/,.gitignore(secrets excluded).nova auth loginat a TTY resolvesmode=interactive, selection_reason=credential:developer_pat(INV-12, INV-14).nova apply --localproduces a JWS attestation verifiable with the public key derived from the PAT (REQ-332).nova idp setup --checkreports prerequisites + IAM policy delta;--applypresents the CloudFormation template for review before any resource is created (NFR-10);--verifyconfirms the KMS round-trip.- The Forge action (
nova cli-action) runsnova applyinmode=agent, selection_reason=credential:service_account_patwith no TTY dependency (Journey 3, INV-12). - 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 (J1–J4) 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 setupsucceeds 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).