bbfcbcc4d3
---ci--- project: acdl phase: 0 milestone: v1.28 status: grill ---/ci---
489 lines
24 KiB
Markdown
489 lines
24 KiB
Markdown
# 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-final-review-ship`.
|
||
>
|
||
> **Tags:** `v1.27.0` (P0) → `v1.27.1..v1.27.5` (P1..P5) →
|
||
> `v1.27.6` (P6 final = milestone release). 6 execution phases
|
||
> (P5 idp-setup folded into P4 Wave 8 per grill C-2.1).
|
||
|
||
## 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.
|
||
|
||
**Exit criterion:** CAP-033 + CAP-034 + CAP-035 Verified + all REQ-323..328
|
||
tests pass. CodeArtifact provisioned (Wave 0 gate).
|
||
|
||
#### Wave 0 — CodeArtifact provisioning (backend-engineer) [C-3.2/C-8.1]
|
||
- **Task 0.1** (backend-engineer): provision CodeArtifact domain
|
||
(`nova`) + repository (`nova-pypi`) in `581513795199`. Verify
|
||
`codeartifact:*` IAM grant on `nova-spike-runner`. **Binary go/no-go
|
||
gate for Wave 4.** If fail: activate Gitea wheel index fallback
|
||
(CLARIFY assumption #1) and document in PLAN.md.
|
||
|
||
#### 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.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_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.py` —
|
||
`hypothesis` 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;
|
||
JWS-from-PAT key derivation (C-5.2).
|
||
|
||
**Exit criterion:** all REQ-329..331 tests pass + JWS-from-PAT KDF
|
||
specified.
|
||
|
||
#### 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 + JWS KDF (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()`.
|
||
- **Task 2.3** (security-engineer): JWS-from-PAT key derivation
|
||
(C-5.2). HKDF-SHA256(PAT_bytes, salt='nova-local-attestation',
|
||
info='jws-signing-key') → 32-byte symmetric key. The JWS is
|
||
HMAC-SHA256 (symmetric, not asymmetric). The "public key derivable
|
||
from the PAT" AC (REQ-332) is re-interpreted: the *verification key*
|
||
is derived from the PAT via the same KDF (the PAT is the shared
|
||
secret). Document in `docs/developer-guide-auth.md`. Update REQ-332
|
||
AC accordingly.
|
||
|
||
#### 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.
|
||
|
||
**Exit criterion:** CAP-036 Verified (E2E sign-up → sign-in → session
|
||
passes in CI).
|
||
|
||
#### 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 P4 Wave 8 `nova idp setup`).
|
||
PITR enabled on each (REQ-335).
|
||
|
||
#### Wave 2 — Argon2id (security-engineer) [C-1.2/C-7.2]
|
||
- **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**). **Parameters: t=3, m=65536 KiB, p=1** (OWASP-recommended
|
||
minimum). Lambda memory ≥512 MB (m=64 MiB + Python overhead fits).
|
||
Raw passwords never in logs/traces/env/DDB (INV-16, REQ-334).
|
||
- **Task 2.2** (security-engineer): `tests/test_argon2_fail_closed.py`
|
||
— mock `argon2.low_level` import failure → assert auth Lambda
|
||
returns 503 (not a crash, not a weak hash). C-1.2.
|
||
|
||
#### 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, REQ-340, REQ-341) [was P4+P5]
|
||
|
||
**Goal:** `nova-idp-token-vend` Lambda (KMS-signed OIDC, kyverno-json
|
||
ABAC), JWKS endpoint, PAT lifecycle, `nova auth` commands, **and**
|
||
`nova idp setup` (folded from P5 per C-2.1). CAP-037 + CAP-038 target.
|
||
**Highest-risk phase — critical-path.** The kj-binary spike (Wave 1)
|
||
is the single highest-probability schedule slip; Fargate fallback adds
|
||
~1 week (D-227). This is a **double-length phase** (8 waves).
|
||
|
||
**Exit criterion:** CAP-037 + CAP-038 Verified + `nova idp setup
|
||
--check/--apply/--verify` works against a fresh AWS account.
|
||
|
||
#### Wave 1 — kj-binary spike (backend-engineer + security-engineer) [C-8.2]
|
||
- **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` a **pinned
|
||
release** (e.g. `kj@v1.x.y`) + record SHA256 into `layer/kj.sha256`
|
||
— C-8.2, supply-chain safety) 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) [C-5.1]
|
||
- **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}`. **`requested_claims` = list of claim names** (the
|
||
policy asserts the subject is *allowed* to request those claims; the
|
||
values are assigned by the Lambda, not the requestor — C-5.1).
|
||
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) [C-1.1]
|
||
- **Task 3.1** (security-engineer): **verify KMS asymmetric key
|
||
support** before implementation: `aws kms create-key --key-spec
|
||
ECC_NIST_P256 --key-usage SIGN_VERIFY` in the target account
|
||
(C-1.1). If fail: fall back to RSA-2048 (also supported, larger
|
||
tokens) or escalate. Do not discover this mid-Wave.
|
||
- **Task 3.2** (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_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) [C-6.1/C-7.1 ABAC FAIL-CLOSED]
|
||
- **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).
|
||
- **Task 4.3** (security-engineer) 🔴: **ABAC fail-closed.** If
|
||
`KyvernoJsonEngine.is_configured()` returns false or `evaluate()`
|
||
raises, return 403 + audit `token.vend.denied` (reason:
|
||
`abac_eval_failed`). **Never fail open.** This is INV-17's runtime
|
||
enforcement (C-6.1/C-7.1 — the grill's #1 finding). Test:
|
||
`tests/test_abac_fail_closed.py` — mock `kj` absent → assert 403 +
|
||
audit event.
|
||
|
||
#### 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) [C-7.3]
|
||
- **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.
|
||
**Max TTL: ≤24h for developer PATs, ≤1h for service-account PATs**
|
||
(C-6.2 threat model).
|
||
- **Task 6.2** (cli-engineer): `nova/auth/{login,revoke,status}.py` —
|
||
`nova 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). **C-7.3: `~/.nova/credentials.json`
|
||
stores the OIDC token + PAT metadata (jti, exp, type) ONLY — NOT the
|
||
raw PAT.** The raw PAT is entered once at `nova auth login` and not
|
||
persisted (reduces filesystem-compromise blast radius).
|
||
|
||
#### 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.
|
||
|
||
#### Wave 8 — nova idp setup (cli-engineer + backend-engineer) [folded from P5 per C-2.1]
|
||
|
||
**Goal:** `nova idp setup` command with `--check/--apply/--verify`
|
||
modes; CloudFormation template generation + review (REQ-340, REQ-341).
|
||
|
||
- **Task 8.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).
|
||
- **Task 8.2** (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 8.3** (backend-engineer): IAM policy delta computation —
|
||
compares current `nova-spike-runner` grants to required
|
||
`cloudformation:*` + `codeartifact:*` + `kms:*` + `lambda:*` +
|
||
`dynamodb:*` + `ssm:*`.
|
||
|
||
### Phase P5 — docs-integration (REQ-345..REQ-351)
|
||
|
||
**Goal:** Operator guide, developer guide, threat model; E2E
|
||
integration test; property tests; KMS round-trip; PAT revocation SLO.
|
||
|
||
**Exit criterion:** all REQ-345..351 tests pass + docs published +
|
||
threat model reviewed.
|
||
|
||
#### Wave 1 — docs (lead-developer + security-engineer) [C-6.2/C-6.3/C-9.2]
|
||
- **Task 1.1** (lead-developer): `docs/operator-guide-idp.md` (REQ-345)
|
||
— `nova idp setup --check/--apply/--verify`, prerequisite IAM policy,
|
||
CloudFormation review flow. **C-6.3 additions:** KMS key rotation
|
||
procedure (90 days), Lambda layer update procedure, DDB PITR restore
|
||
procedure, emergency PAT revocation (DDB-level, not CLI).
|
||
- **Task 1.2** (lead-developer): `docs/developer-guide-auth.md`
|
||
(REQ-346) — signup, signin, login, mode resolution, TTY vs piped
|
||
stdout behavior, JWS-from-PAT KDF (P2 Wave 2 Task 2.3).
|
||
- **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. **C-6.2 additions:** (a) JWKS unauthenticated endpoint DDoS
|
||
surface + reserved-concurrency mitigation; (b) PAT theft + max TTL
|
||
(≤24h dev, ≤1h service-account); (c) ABAC fail-closed guarantee
|
||
(C-6.1). **C-9.2 addition:** INV-18..21 compression audit — verify
|
||
the spec's attestation invariant semantics are fully captured by
|
||
INV-15/16/17 + REQ-332.
|
||
|
||
#### 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 P6 — final-review-ship (Final Phase)
|
||
|
||
**Goal:** Multi-persona code review across P1..P5; 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/06` →
|
||
`milestone/v1.28-cli-identity` → `main`; tag `v1.27.6` (= 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 signup` → `nova-idp-auth` Lambda → Argon2id hash →
|
||
`nova-users` PutItem → session token.
|
||
2. `nova auth signin` → `nova-idp-auth` → Argon2id verify → session.
|
||
3. `nova auth login` → `nova-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-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).
|
||
|
||
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 (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 setup` succeeds in a fresh AWS account.
|
||
- [ ] Byte-identical Forge action on GitHub + Gitea (REQ-326, NFR-11).
|
||
|
||
---
|
||
|
||
## Plan completeness checklist
|
||
|
||
- [x] Every REQ-323..353 mapped to a phase + wave + task.
|
||
- [x] Every CAP-033..038 mapped to a phase + gate rule.
|
||
- [x] Every INV-12..17 referenced in persona constraints.
|
||
- [x] Every D-226..231 referenced in task rationale.
|
||
- [x] Vertical slices: each phase ships independently (P1 CLI substrate
|
||
is useful before P2 packaging; P2 before P3 auth; etc.).
|
||
- [x] Wave ordering within phases (no wave N+1 depends on wave N work
|
||
in the same phase).
|
||
- [x] Persona assignments per task (4 active personas).
|
||
- [x] MVP/UX CHECK: 3 sections present (User-Facing Surface, Happy Path,
|
||
UX Acceptance Criteria).
|
||
- [x] Highest-risk item flagged (P4 Wave 1 kj-binary spike).
|
||
- [x] Grill conditions applied (3 critical + 16 tracked; see GRILL.md).
|
||
|
||
---
|
||
|
||
## Cost envelope (C-3.1)
|
||
|
||
Monthly estimate for the default (no CloudFront) Nova-idp deployment in
|
||
account `581513795199`:
|
||
|
||
| Resource | Quantity | Pricing | Est. monthly |
|
||
|----------|----------|---------|-------------|
|
||
| DynamoDB (on-demand) | 4 tables | $1.25/1M write, $0.25/1M read | ~$1 (pilot volume) |
|
||
| DynamoDB PITR | 4 tables | $0.20/GB-month | ~$1 (small tables) |
|
||
| KMS asymmetric key | 1 key | $1/key-month + $0.03/10k signs | ~$1 |
|
||
| Lambda invocations | 3 Lambdas | $0.20/1M req + $0.000016/GB-s | ~$2 (low volume) |
|
||
| Lambda layer storage | ~50 MB | $0.02/GB-month | <$1 |
|
||
| CodeArtifact | 1 domain + 1 repo | $1/domain + $1/repo | $2 |
|
||
| SSM Parameter | 1 | $0.05/param (advanced) | <$1 |
|
||
| **Total (default)** | | | **~$9/month** |
|
||
|
||
Optional CloudFront + WAF + ACM (if `--public-jwks-domain`): +~$3/month
|
||
at pilot volume. ACM is free for CloudFront-attached certs.
|
||
|
||
This is a pilot-scale cost envelope. Production scale (100x volume)
|
||
would still be <$50/month. No hidden costs identified. |