---ci--- project: acdl phase: 0 milestone: v1.29 status: complete ---/ci---
53 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-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) in581513795199. Verifycodeartifact:*IAM grant onnova-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]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;
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 viacore/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 +--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(). - 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 8nova 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 viaargon2-cffi(D-228: bundled abi3 wheel; fail-closed onImportError→ 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— mockargon2.low_levelimport 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-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, 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
kjGo binary (~40 MB Linux amd64) runs in the Lambda Python 3.12 runtime on AL2023. Bundle it in thenova-clilayer (wgeta pinned release (e.g.kj@v1.x.y) + record SHA256 intolayer/kj.sha256— C-8.2, supply-chain safety) intolayer/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) [C-5.1]
- 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}.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. 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) [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_VERIFYin 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 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) [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 orevaluate()raises, return 403 + audittoken.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— mockkjabsent → assert 403 + audit event.
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) [C-7.3]
- 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. 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.json0600),nova auth revoke --pat <jti>,nova auth status(active credential, mode, selection_reason). All emit audit events (REQ-344). C-7.3:~/.nova/credentials.jsonstores the OIDC token + PAT metadata (jti, exp, type) ONLY — NOT the raw PAT. The raw PAT is entered once atnova auth loginand 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 withpyjwt.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-domainflag). - 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-runnergrants to requiredcloudformation:*+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-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/06→milestone/v1.28-cli-identity→main; tagv1.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).
- 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).
- 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.
PLAN — v1.29 Reposplit + Identity Layer Bring-Live
Milestone: v1.29 (feature — reposplit + identity layer bring-live). Tags on the v1.28.x line:
v1.28.0(P0) →v1.28.1..v1.28.5(P1..P5) →v1.28.6(P6 final = milestone release). The final phase's patch IS the milestone release. Branch:milestone/v1.29-reposplit-identity. Phase branches:phase/00-pre-execution(complete),phase/01-publish-pipeline,phase/02-gitea-scrub-decisions,phase/03-cfn-archive-tf-delegation,phase/04-operator-guide-reference-tracking,nova-blockchain-exchange/phase/05-consumer-deploy-bump(cross-project),phase/06-final-review-ship.Scope split (CLARIFY-grounded): Terraform modules authored out-of-band in
nova-platform-ops. CIAgent inacdlauthors only the acdl-side REQs (354, 367, 368, 369, REQ-OPS-GUIDE, REQ-CONSUMER-BUMP). Covered-reference REQs (355-366, 371) verified via cutover gates documented in the operator guide (P4).
Milestone goal
v1.29 makes platform operations a Terraform-controlled discipline that
lives outside the engineering repo, with a narrow-IAM kj substrate
shared by the primary runtime and its defensive fallback. acdl/acdl
standardizes on GitHub (Gitea scrub); Nova-idp is brought live in
account 581513795199 (code complete since v1.28, unverified in-account
at Phase 0); kj has exactly one identity (one ECR image digest) shared
by both substrates (KJ-LOCKSTEP, REQ-371).
Requirements
17 requirements: REQ-354..REQ-369 + REQ-371 + REQ-363b + REQ-OPS-GUIDE
- REQ-CONSUMER-BUMP (full text in
.ciagent/REQUIREMENTS.md§v1.29). 1 invariant: INV-18 (JWKS-EDGE-ONLY). 10 NFR constraints: KJ-STATIC, KJ-LOCKSTEP, KJ-WARMUP-HEALTH, OPER-PRIV, IAM-NARROW, DRIFT-DETECT, IMPORT-IDEMPOTENT, TFM-HITL, JWKS-SLO, JWKS-ROTATION. 9 decisions: D-232..D-238 (CLARIFY) + D-239/D-240 (RESEARCH spec corrections).
Phase breakdown
Phase P1 — publish-pipeline (REQ-354)
Goal: publish.yml attaches Lambda zip + layer wheel + Python wheel
- ECR container image (static
kj,CGO_ENABLED=0, tagv1.29.x-kj-<sha>) to GitHub Release for each tag, with matching SHA-256 in the body. The M1.5 verification gate tests (test_idp_auth,test_kms_roundtrip, ABAC E2E) are authored.
Exit criterion: REQ-354 criteria 1-4 pass; KJ-STATIC audit (file(1)
asserts statically linked) runs in CI; ECR image pushed with tag
v1.29.x-kj-<sha> (D-239); GitHub Release body lists image URI + digest
alongside wheel + layer + Lambda zip; M1.5 gate tests exist + pass in
moto-DDB (live KMS round-trip is covered-reference, runs in
nova-platform-ops CI).
Branch: phase/01-publish-pipeline. Tag: v1.28.1.
Wave 0 — publish.yml trigger model (backend-engineer)
- Task 0.1 (backend-engineer): change
.github/workflows/publish.ymltrigger frompush: branches: [main]topush: tags: ['v1.29.*']. Preserve the existing wheel + Lambda layer publish steps (REQ-323/ CAP-035). Add the Lambda zip packaging step (nova-lambda-token-vend-v1.29.x.zip). Verify the trigger fires ongit tag v1.29.0 && git push --tags.
Wave 1 — kj source confirmation + static build + ECR image (backend-engineer, security-engineer)
- Task 1.0 (backend-engineer): kj source-fetch confirmation
(grill CF-4/G-4 — binary go/no-go gate before Wave 1). Confirm the
kjGo source repo URL + commit at SHA4ebb9a19...(read fromplatform/abac/kj-version.txt). Record the repo URL as a 3rd line inplatform/abac/kj-version.txt. If unfetchable → P1 fails closed → escalate (this is a spec dependency, not a CIAgent ambiguity). The source repo is thekyverno-json/kjGo binary project (distinct from the kyverno-json Python engine adapter inadapters/kyverno- json/). - Task 1.1 (backend-engineer): add a
build-kj-imagejob topublish.ymlthat: (a) readsplatform/abac/kj-version.txt(v0.0.3 + SHA4ebb9a19...); (b) fetches thekjGo source at the pinned SHA (RESEARCH §7 — source repo confirmed in P1 RESEARCH); (c) builds withCGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -ldflags="-s -w" -o kj ./…; (d) runsfile kjand asserts output containsstatically linkedAND does NOT containshared library(KJ-STATIC — fail build otherwise); (e) builds the container image frompublic.ecr.aws/lambda/python:3.12-al2023, copyingkjto/opt/kj/kjwithchmod 0555owned bysbx_user:1051; (f) pushes the image to ECR with tagv1.29.x-kj-<kj-source-sha>(D-239 — assert tag matches^[a-zA-Z0-9._-]+$before push); (g) records the image URI + digest for the GitHub Release body. - Task 1.2 (security-engineer): add a KJ-STATIC audit step that
runs
file(1)+readelf -d kj(assert noNEEDEDentries) as a CI gate. If either fails, the publish job fails closed. This is the mechanical enforcement of KJ-STATIC (not just a human review).
Wave 2 — GitHub Release body + SHA-256 (backend-engineer)
- Task 2.1 (backend-engineer): extend the
publish.ymlrelease step to attach: (a)nova-lambda-token-vend-v1.29.x.zip; (b)nova-cli-layer-v1.29.x.zip; (c)nova-1.29.x-py3-none-any.whl; (d) the ECR image URI + digest. Compute SHA-256 for each artifact + list in the release body. Verify REQ-354 criteria 1, 2, 4 (artifacts appear, independent per tag, image URI + digest listed).
Wave 3 — M1.5 verification gate tests (security-engineer)
- Task 3.1 (security-engineer): author
tests/test_idp_auth.py— sign-up → sign-in → session flow against moto-DDB (covers Edge 5 item 5). Skip live-KMS assertions (covered-reference — runs in nova-platform-ops CI). - Task 3.2 (security-engineer): author
tests/test_kms_roundtrip.py— sign/verify round-trip againstalias/nova-oidc-signing. Mark as@pytest.mark.live_aws(skipped in acdl CI; runs in nova-platform-ops CI against the live key, REQ-362). - Task 3.3 (security-engineer): author
tests/test_abac_e2e.py— known PAT → ABAC-allowed action → signed OIDC token →joseverification → green; known PAT + ABAC-denied action → 403 with deny reason logged (INV-17 fail-closed, Edge 5 item 7). Uses moto-DDB + mock KMS.
Wave 4 — regression + ship (lead-developer)
- Task 4.1 (lead-developer): run full test suite; verify 1000+
tests still pass (no regressions from publish.yml changes). Verify
CAP-001..038 regression gate green. Push a
v1.29.0intermediate tag at P1 ship (grill CF-3/G-3) to triggerpublish.yml+ produce the v1.29 artifacts (Lambda zip + layer wheel + Python wheel + ECR image). The milestone release tag remainsv1.28.6; thev1.29.0artifact tag is a P1 intermediate to produce publish artifacts for P5's smoke test. Ship P1 →v1.28.1.
Phase P2 — gitea-scrub-decisions (REQ-367, REQ-368)
Goal: Hard scrub of all Gitea references in acdl/acdl; .gitea/
removed; forge_parity_disabled CI assertion; pyproject → 1.29.0;
decisions D-232..238 recorded in PROJECT.md + CLARIFY (already done in
P0; this phase adds the CI assertion + the actual file scrub).
Exit criterion: grep -rni gitea .github/ docs/ pyproject.toml README.md .ciagent/ returns zero matches outside the spec archive
section; find .gitea returns nothing; CI forge_parity_disabled
assertion passes; pyproject.toml version = 1.29.0.
Branch: phase/02-gitea-scrub-decisions. Tag: v1.28.2.
Wave 0 — pyproject bump (lead-developer)
- Task 0.1 (lead-developer): bump
pyproject.tomlversion →1.29.0(spec §7.2). Verifynova --versionreports1.29.0.
Wave 1 — .gitea/ removal (lead-developer)
- Task 1.1 (lead-developer):
rm -rf .gitea/(7 workflow files + README.md, RESEARCH §9d). Removescripts/sync_workflows.py(the byte-identical-forges generator — central removal target, D-232). Remove Gitea references fromscripts/sync_to_nova.sh(line 201:--exclude=/.gitea) +scripts/rotate_spike_key.sh(Gitea API secret upload). Scrubterraform/bootstrap/Gitea OIDC references (the OIDC role for act_runner moves to nova-platform-ops; the bootstrap here becomes archived reference).
Wave 2 — Gitea reference scrub (lead-developer)
- Task 2.1 (lead-developer):
grep -rni gitea .github/ docs/ pyproject.toml README.md .ciagent/— scrub all matches outside the spec archive section (.ciagent/REQUIREMENTS.md§v1.29 + CLARIFY §v1.29- RESEARCH §v1.29 retain "Gitea" as historical/reference text; these
are the "spec archive section" exemption per REQ-367 AC 1). Update
.github/workflows/ci.ymlto remove any Gitea-specific steps.
- RESEARCH §v1.29 retain "Gitea" as historical/reference text; these
are the "spec archive section" exemption per REQ-367 AC 1). Update
Wave 3 — forge_parity_disabled CI assertion (lead-developer)
- Task 3.1 (lead-developer): add a CI step to
.github/workflows/ci.ymlthat assertsforge_parity_disabled— the step runstest ! -d .gitea/and! grep -rqi gitea .github/workflows/and exits 0 on success, non-zero withforge_parity_disabledmessage on failure (REQ-367 AC 3, D-232). This is the deliberate CI failure that documents the abandoned parity.
Wave 4 — decisions verification + ship (lead-developer)
- Task 4.1 (lead-developer): verify D-232..238 + D-239/240 are
present in PROJECT.md + CLARIFY.md + REQUIREMENTS.md (REQ-368 AC 1-2,
already authored in P0; this task is a verification, not re-authoring).
Run full test suite; ship P2 →
v1.28.2.
Phase P3 — cfn-archive-tf-delegation (REQ-369)
Goal: Archive the CFN template in nova/idp/setup.py +
core/lambda/nova_idp_setup.py to docs/archive/nova-idp-cfn-v1.28.md
(read-only reference); nova idp setup --apply delegates to terraform apply (the CLI detects terraform via which terraform; if absent,
falls back to the CFN path with a deprecation warning).
Exit criterion: docs/archive/nova-idp-cfn-v1.28.md exists +
contains the CFN template as read-only reference; nova idp setup --apply invokes terraform apply when terraform is on PATH (tested
with a mock terraform binary); the CFN path emits a deprecation warning
when terraform is absent.
Branch: phase/03-cfn-archive-tf-delegation. Tag: v1.28.3.
Wave 0 — CFN archive (cli-engineer, backend-engineer)
- Task 0.1 (backend-engineer): extract the CFN template from
core/lambda/nova_idp_setup.py+ write it todocs/archive/nova-idp-cfn-v1.28.mdas a fenced code block with a read-only header ("Archived at v1.29.0 — the active path isterraform applyinnova-platform-ops. Deletion is a follow-up after Terraform parity is verified."). - Task 0.2 (cli-engineer): mark the CFN generation code path in
core/lambda/nova_idp_setup.pyas deprecated (add aDeprecationWarningwhen the CFN path is invoked + a docstring pointing to the archive + the terraform delegation path).
Wave 1 — terraform delegation (cli-engineer)
- Task 1.1 (cli-engineer): modify
nova/idp/setup.py--applyto detect terraform viashutil.which("terraform"). If terraform is on PATH: delegate tosubprocess.run(["terraform", "apply", "-auto-approve"])in thenova-platform-opscheckout (the operator runs this from the ops repo root). If terraform is absent: fall back to the CFN path with aDeprecationWarning("CFN path is archived; install terraform or use nova-platform-ops. See docs/archive/nova-idp-cfn-v1.28.md."). - Task 1.2 (cli-engineer): add
nova idp setup --verifydelegation toterraform plan(samewhich terraformdetection). The verify path runsterraform plan+ reports the diff.
Wave 2 — tests (cli-engineer)
- Task 2.1 (cli-engineer): author
tests/test_idp_setup_tf_delegation.py— test the--applypath with a mock terraform binary on PATH (assertsubprocess.runcalled with["terraform", "apply", "-auto-approve"]); test the fallback path with terraform absent (assertDeprecationWarningraised + CFN path invoked); test--verifydelegates toterraform plan.
Wave 3 — ship (lead-developer)
- Task 3.1 (lead-developer): run full test suite; ship P3 →
v1.28.3.
Phase P4 — operator-guide-reference-tracking (REQ-OPS-GUIDE)
Goal: docs/operator-guide-platform-ops.md covering KMS rotation,
JWKS reachability via CloudFront edge, PITR restore, PAT revocation,
edge configuration, Fargate standby health, cost section, artifact-
mirror fallback, and the M1/M1.5/M2 cutover gates as release-gate
entries for the covered-reference REQs. ARCHITECTURE.md §12.9. STATE.md
v1.29 CAPs + invariants. REQUIREMENTS.md covered-reference markers.
Exit criterion: operator guide exists + covers all sections per REQ-OPS-GUIDE AC; ARCHITECTURE.md §12.9 added; STATE.md updated with v1.29 rows; covered-reference REQs in REQUIREMENTS.md marked with their cutover gate.
Branch: phase/04-operator-guide-reference-tracking. Tag:
v1.28.4.
Wave 0 — operator guide (lead-developer, data-engineer, security-engineer)
- Task 0.1 (lead-developer): author
docs/operator-guide-platform-ops.mdsections: (a) Overview + the reposplit rationale (Vision §4); (b) Day-0 cutover procedure (M1 steps from spec §3.2 Journey 2); (c) M1.5 verification gate (8-item spike, 3 consecutive rebuilds); (d) M2 operational handoff loop (tag-pin bump → plan → HITL approval → apply); (e) M2a Fargate activation (conditional on M1.5 failure); (f) Rollback procedure (D-236 — revertnova_platform_versionpin); (g) cost section (WAF ~$5-10/month + Fargate ~$15-20/month, REQ-363b AC 4); (h) artifact- mirror fallback (operator-local mirror by SHA-256 when Gitea act_runner cannot reach GitHub Releases, Edge 6). - Task 0.2 (data-engineer): author the operator guide data
sections: (a) DynamoDB PITR restore procedure (per-table); (b)
DynamoDB import addresses (nova-contracts, nova-change-requests,
nova-outbox, nova-users, nova-sessions, nova-pats — the
importable-resources.tfmap, REQ-361 covered-reference); (c) audit outbox bootstrap; (d) JWKS-ROTATION (24-hour overlap window on key rotation). - Task 0.3 (security-engineer): author the operator guide security
sections: (a) KMS rotation (90-day cadence,
alias/nova-oidc- signing,ECC_NIST_P256, D-234); (b) JWKS reachability via CloudFront edge (OAC pinning,AuthType: AWS_IAM, direct Function URL → 403, INV-18); (c) PAT revocation (60s SLO, D-229); (d) edge configuration (CloudFront + WAF + ACM + Route53 — REQ-364/365/366 covered-reference); (e) Fargate standby health checks (GET /healthevery 10s,KJ-WARMUP-HEALTH, 3 consecutive probe failures → alert + token-vend fails closed, REQ-363b AC 2); (f) Fargate sunset discipline (D-237 — ≥30 consecutive days green before deletion + architecture review); (g) IAM scope (IAM-NARROW, REQ-360 covered- reference — noAction: "*"orResource: "*"); (h) theroute53_record_not_resolvabledebugging path (ACM cert status check).
Wave 1 — covered-reference cutover gates (lead-developer)
- Task 1.1 (lead-developer): add a "Cutover Gates" section to the
operator guide listing each covered-reference REQ (355, 356, 357,
358, 359, 360, 361, 362, 363, 363b, 364, 365, 366, 371) with its
gate entry (M1/M1.5/M2) + the verification command + a "Result"
column (grill CF-2/G-5). P6 audit verifies every covered-reference
REQ has a non-empty, green Result. Empty/red → P6 blocks. The Result
column is populated by the operator attestation (the operator runs
the verification command in
nova-platform-opsCI + records the outcome). This is the acdl-side evidence surface for covered- reference REQs. - Task 1.2 (lead-developer): update REQUIREMENTS.md §v1.29 traceability
table — mark each covered-reference REQ with its cutover gate in the
Status column (e.g.,
planned (M1 gate: nova-platform-ops)).
Wave 2 — ARCHITECTURE.md + STATE.md (lead-developer)
- Task 2.1 (lead-developer): add ARCHITECTURE.md §12.9 (Platform
Ops Reposplit) — the domain boundary (engineering ends at the
compiled artifact; operations begins at the live platform under
guardrails), the
kjsubstrate (one ECR image digest, KJ-LOCKSTEP), the covered-reference REQ tracking pattern, the operator guide pointer. - Task 2.2 (lead-developer): update STATE.md — append v1.29 capability rows (CAP-039: platform-ops-reposplit, CAP-040: kj-substrate-lockstep, CAP-041: jwks-edge-only) + bump invariants (INV-18 JWKS-EDGE-ONLY + the 10 NFR constraints). Bump "Last milestone ship" to v1.29 (pending).
Wave 3 — ship (lead-developer)
- Task 3.1 (lead-developer): run full test suite; ship P4 →
v1.28.4.
Phase P5 — consumer-deploy-bump (REQ-CONSUMER-BUMP, cross-project)
Goal: Bump nova-blockchain-exchange deploy.yml @v1.25 → @v1.29
in both .github/workflows/deploy.yml + .gitea/workflows/deploy.yml
- smoke test (sign-up → sign-in → token-vend → apply → audit against v1.29 publish artifacts).
Exit criterion: both deploy.yml files reference @v1.29; smoke
test passes (the chain completes against v1.29 publish artifacts).
Branch: nova-blockchain-exchange/phase/05-consumer-deploy-bump
(cross-project, multi-project branch naming per branch-strategy.md).
Tag: v1.28.5.
Wave 0 — deploy.yml bump (lead-developer)
- Task 0.1 (lead-developer): in the
nova-blockchain-exchangeproject, update.github/workflows/deploy.yml+.gitea/workflows/ deploy.ymluses:ref fromacdl/.github/workflows/deploy.yml@v1.25→@v1.29(RESEARCH §9e — the consumer's.gitea/is out of scope for the acdl REQ-367 scrub; the consumer may keep its Gitea mirror or follow suit — this is a consumer-repo decision, not an acdl one).
Wave 1 — smoke test (lead-developer, security-engineer)
- Task 1.1 (security-engineer): author
nova-blockchain-exchange/tests/test_v1.29_smoke.py— sign-up → sign-in → token-vend → apply → audit chain against the v1.29 publish artifacts (the consumer's contract →deploy.yml@v1.29mode=full → apply → attest → record against581513795199). Uses the existing CAP-025 round-trip assertion (v1.26). - Task 1.2 (lead-developer): run the smoke test; verify the chain completes against the real v1.29.0 publish artifacts (produced by P1's intermediate tag, grill CF-3/G-3). No hedge — the smoke test MUST run against the published v1.29.x artifacts or P5 fails closed. If the artifacts are not available (P1 did not push the intermediate tag), P5 blocks until P1 re-ships.
Wave 2 — ship (lead-developer)
- Task 2.1 (lead-developer): ship P5 →
v1.28.5. The consumer project ships independently (merge to the consumer's main, not acdl's milestone branch).
Phase P6 — final-review-ship (Final Phase)
Goal: Multi-persona code review across P1..P5; audit (reconstruction
test, branch hygiene, commit discipline, file discipline); milestone
ship (merge phase/06 → milestone/v1.29-reposplit-identity → main;
tag v1.28.6 = the v1.29 release; Gitea release; delete all milestone
branches); mark all v1.29 REQs complete in REQUIREMENTS.md + ROADMAP.md.
Exit criterion: review P0 issues auto-fixed, P1+ flagged; audit
PASS; milestone merged to main; tag v1.28.6 created; Gitea release
published; milestone branches deleted; REQUIREMENTS.md + ROADMAP.md
marked complete.
Branch: phase/06-final-review-ship. Tag: v1.28.6 =
milestone release.
Wave 0 — review (lead-developer)
- Task 0.1 (lead-developer): delegate to
ciagent-review— multi-persona review (lead-developer, backend-engineer, security- engineer, data-engineer, cli-engineer) across P1..P5. Auto-apply P0 fixes; flag P1+ for post-hoc review. If P1+ issues found: fix them in this phase.
Wave 1 — audit (lead-developer)
- Task 1.1 (lead-developer): delegate to
ciagent-audit— reconstruction test (git log ↔.ciagent/), branch hygiene, commit discipline, file discipline. If critical issues found: fix them in this phase.
Wave 2 — milestone ship (lead-developer)
- Task 2.1 (lead-developer): delegate to
ciagent-ship— mergephase/06→milestone/v1.29-reposplit-identity→main; tagv1.28.6; Gitea release with full milestone summary; delete all milestone branches (phase/00..06 + milestone/v1.29-reposplit- identity).
Wave 3 — milestone completion (lead-developer)
- Task 3.1 (lead-developer): update REQUIREMENTS.md (all v1.29 REQs
→ complete), ROADMAP.md (v1.29 → complete), NORTH_STAR.md (note
Strategic Objective — platform operations as a Terraform-controlled
discipline), STATE.md (bump "Last milestone ship" to v1.29, tag
v1.28.6). Commitdocs(milestone): complete v1.29-reposplit- identity.
User-Facing Surface
- CLI flag:
nova idp setup --applynow delegates toterraform apply(REQ-369 AC 2) — the operator runs this from thenova-platform-opscheckout.nova idp setup --verifydelegates toterraform plan. - GitHub Release artifacts page: each
v1.29.xtag's GitHub Release page lists the Lambda zip + layer wheel + Python wheel + ECR image URI/digest with SHA-256 (REQ-354) — this is the engineering- to-ops handoff surface (D-235 tag-pin handoff). - Operator guide:
docs/operator-guide-platform-ops.md— the operator-facing runbook covering KMS rotation, JWKS reachability, PITR restore, PAT revocation, edge config, Fargate standby, cost, artifact-mirror fallback, and the M1/M1.5/M2 cutover gates. - CI assertion:
forge_parity_disabled— the deliberate CI failure documenting the abandoned byte-identical-forges parity (D-232, REQ-367 AC 3).
Happy Path
M1.5 verification gate (spec §3.3 Edge 5, 12-item spike — written BEFORE execute, extended per grill CF-1):
kjv0.0.3 (pinned SHA inplatform/abac/kj-version.txt) compiles withCGO_ENABLED=0 GOOS=linux GOARCH=amd64.- Resulting binary reports
file kj → ELF 64-bit LSB executable, x86-64, statically linked, no shared library(KJ-STATIC). - Container image built from
public.ecr.aws/lambda/python:3.12-al2023with the binary copied to/opt/kj/kj,chmod 0555, owned bysbx_user:1051. - Lambda runtime
python3.12executesnova_idp_token_vend.handler; the handler invokessubprocess.run(['/opt/kj/kj', 'apply', ...])and parses stdout JSON. tests/test_idp_auth.pypasses against the live image in moto-DDB.tests/test_kms_roundtrip.pypasses against the live KMS key (REQ-362 path — covered-reference, runs in nova-platform-ops CI).- End-to-end: known PAT → known ABAC-allowed action → signed OIDC
token →
joseverification → green. Known PAT + ABAC-denied action → 403 with deny reason logged (INV-17). - Image URI is recorded in Terraform state and in the operator guide.
- (grill CF-1) Direct JWKS Function URL → 403 / via-CloudFront →
200 (INV-18, JWKS-EDGE-ONLY —
AuthType: AWS_IAMverified, not prose). - (grill CF-1) IAM-NARROW: no
Action: "*"orResource: "*"in the Gitea OIDC role effective permissions (REQ-360). - (grill CF-1) TFM-HITL: self-approval rejected —
gitea.triggering_actor == pull_request.user.login→ apply fails closed (REQ-357, INV-3). - (grill CF-1) Rollback drill — revert
nova_platform_versionpin → prior digest runs (D-236 cutover shape + rollback procedure).
If items 1-7 fail three consecutive rebuilds, M2a activates REQ-363b (Fargate toggle) with the same image — no warmup hit because the standby is always running the same digest.
HARD P6 SHIP GATE (grill CF-1/G-2.1): P6 must not ship v1.28.6
until the operator guide contains an operator-attested "M1.5
Verification Gate Result" row (3 consecutive green rebuilds, run
IDs/SHAs, attestor identity). P6 audit verifies the row exists. The
M1.5 gate is verified in nova-platform-ops CI (out-of-band); the
operator attestation in the guide is the acdl-side evidence surface.
UX Acceptance Criteria
- M1 acceptance gate (spec §2.3):
terraform applyfrommainbrings the live AWS account to a state where Nova-idp identity tables exist, JWT-issuing paths are wired but not yet consuming container images, JWKS infrastructure is in place, WAF + OAC pinning the CloudFront edge;acdl/acdl v1.29.0ships with zero.gitea/references and zero platform-infra files; D-232..238 recorded in PROJECT.md/CLARIFY. - M1.5 acceptance gate: items 1-12 of the Edge 5 spike all green
on three consecutive rebuilds; image digest resolvable via
data.aws_ecr_image.kj_image; sign/verify round-trip passes; ABAC fail-closed path verified against live policy; JWKS-EDGE-ONLY verified (item 9); IAM-NARROW verified (item 10); TFM-HITL self-approval rejected (item 11); rollback drill passes (item 12). HARD P6 ship gate — operator-attested "M1.5 Verification Gate Result" row in the operator guide (grill CF-1/G-2.1). - M2 acceptance gate: Bumping
local.nova_platform_versionin a PR and merging it results interraform applyupdating bothaws_lambda_function.nova_idp_token_vend.image_uriandaws_ecs_task_definition.kj.container_definitions[0].imageto the same digest (KJ-LOCKSTEP, REQ-371), with zero diff on KMS, DDB, IAM, edge.
Test evidence required for v1.29 release
- Code coverage ≥ 80% on new modules (the acdl-side files:
publish.ymlchanges,nova/idp/setup.pyterraform delegation,docs/operator-guide-platform-ops.mdis docs — no coverage requirement; the M1.5 gate tests). - CI/CD pipeline GREEN for
acdl/acdl(thenova-platform-opspipeline is out-of-band). - M1.5 verification gate green: items 1-12 of §3.3 Edge 5 spike pass on three consecutive rebuilds (covered-reference — verified in nova-platform-ops CI; acdl authors the tests in P1; operator attests in the guide, P4; P6 audit verifies the attestation row, grill CF-1/G-2.1).
- Covered-reference REQs (355-366, 371) have non-empty, green Result in the operator guide "Cutover Gates" section (grill CF-2/G-5 — P6 audit verifies).
lifecycle.preconditionenforced on both image-bearing resources (REQ-371 mechanical proof — covered-reference in nova-platform-ops).- Live KMS sign/verify round-trip verified in account
581513795199(covered-reference). - Live ABAC sign/verify round-trip verified against the production policy (covered-reference).
- Pilot consumer (
nova-blockchain-exchange) smoke test green: sign-up → sign-in → token-vend → apply → audit chain (P5). - All existing capabilities (CAP-001..038) still pass the regression gate.
- Drift-detection baseline:
terraform planexit 0 against live AWS state, captured at cutover (covered-reference). kjstandby Fargate task healthREADYbefore M1 cutover (covered-reference, KJ-WARMUP-HEALTH).- Fargate standby sunset discipline documented in operator-guide (D-237, P4).
forge_parity_disabledCI assertion passes (P2, REQ-367 AC 3).grep -rni gitea .github/ docs/ pyproject.toml README.md .ciagent/returns zero matches outside the spec archive section (P2, REQ-367 AC 1).
Plan completeness checklist
- Every REQ mapped to a phase + wave + task.
- Covered-reference REQs identified + their verification surface documented (operator guide P4, cutover gates).
- Decisions D-232..240 referenced in the plan.
- Invariants + NFR constraints referenced (KJ-STATIC, KJ-LOCKSTEP, INV-18, etc.).
- Personas assigned to every task (lead-developer, backend-engineer, security-engineer, cli-engineer, data-engineer).
- User-Facing Surface section (3 surfaces named).
- Happy Path section (M1.5 8-item spike, written before execute).
- UX Acceptance Criteria section (M1, M1.5, M2 gates).
- Test evidence checklist.
- Phase boundaries + tags (v1.28.0 → v1.28.6).
- Cross-project phase (P5, nova-blockchain-exchange) identified.
Cost envelope (v1.29)
Monthly estimate for the nova-platform-ops live platform (documented
in the operator guide, P4):
| Resource | Quantity | Est. monthly |
|---|---|---|
| WAF WebACL (CloudFront-scoped) | 1 | ~$5-10/month (+ per-request) |
| Fargate standby (0.25 vCPU, 512 MB) | 1 task | ~$15-20/month (REQ-363b AC 4) |
| KMS asymmetric key | 1 | ~$1/month |
| DynamoDB (on-demand, 7 tables) | 7 | ~$2/month (pilot volume) |
| DynamoDB PITR | 7 tables | ~$2/month |
| Lambda invocations (3 Lambdas) | 3 | ~$2/month |
| ECR image storage | ~100 MB | <$1/month |
| S3 state bucket + access logs | 1 | <$1/month |
| CloudFront + ACM + Route53 | 1 distribution | ~$1/month (ACM free) |
| Total | ~$30-40/month |
This is the pilot-scale ops cost envelope. The Fargate standby (~$15-20/month) is the largest line item + is explicitly documented in the operator guide (REQ-363b AC 4) with the D-237 sunset discipline (≥30 consecutive days green before deletion + architecture review).