docs(P00): grill — v1.28 adversarial review (PROCEED 0.76, 3 critical + 16 tracked conditions applied)

---ci---
project: acdl
phase: 0
milestone: v1.28
status: grill
---/ci---
This commit is contained in:
Jon Chery
2026-08-19 22:10:15 +00:00
parent e1dc59ba79
commit bbfcbcc4d3
4 changed files with 269 additions and 208 deletions
+136 -47
View File
@@ -8,7 +8,11 @@
> `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`.
> `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
@@ -37,7 +41,15 @@ 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).
**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
@@ -103,7 +115,11 @@ with `mode` + `selection_reason`. The CLI is installable and every
**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.
`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
@@ -113,7 +129,7 @@ paths share ≥80% code); `core/env.py:+synthesize_local_env()` for
precedent per RESEARCH §1.2). Verify ≥80% code share (CAP-034 / code
review). Local path via `core/local_emulators.py:LocalLambdaStub`.
#### Wave 2 — local env synthesizer (backend-engineer)
#### 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
@@ -121,6 +137,14 @@ paths share ≥80% code); `core/env.py:+synthesize_local_env()` for
- **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)
@@ -131,18 +155,25 @@ paths share ≥80% code); `core/env.py:+synthesize_local_env()` for
**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 P5 `nova idp setup`). PITR
enabled on each (REQ-335).
in a CloudFormation snippet (reused by P4 Wave 8 `nova idp setup`).
PITR enabled on each (REQ-335).
#### Wave 2 — Argon2id (security-engineer)
#### 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).
Lambda memory ≥512 MB. Raw passwords never in logs/traces/env/DDB
(INV-16, REQ-334).
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 —
@@ -157,46 +188,68 @@ Argon2id hashing + DynamoDB tables. CAP-036 target.
→ sign-in → session round-trip (moto[dynamodb] for local; deployed
for CI). CAP-036 verification.
### Phase P4 — token-vend-pat (REQ-336..REQ-344)
### 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. CAP-037 +
CAP-038 target. **Highest-risk phase** (the `kj` binary in Lambda
layer — RESEARCH §7).
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).
#### Wave 1 — kj-binary spike (backend-engineer + security-engineer)
**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` 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).
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)
#### 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}`. JMESPath checks for role/scope/env/owner. Severity
`critical` = deny on fail.
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)
- **Task 3.1** (security-engineer): KMS key `alias/nova-oidc-signing`
#### 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)
#### 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` —
@@ -204,15 +257,20 @@ layer — RESEARCH §7).
`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)
#### 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).
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`
@@ -220,43 +278,50 @@ layer — RESEARCH §7).
verify with `pyjwt`. `tests/test_pat_revocation.py` (REQ-351,
CAP-038) — issue → vend → revoke → assert 403 within 60s P95.
### Phase P5 — idp-setup (REQ-340, REQ-341)
#### 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.
modes; CloudFormation template generation + review (REQ-340, REQ-341).
#### Wave 1 — CloudFormation template (backend-engineer)
- **Task 1.1** (backend-engineer): `nova/idp/setup.py` (+ backend
- **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).
#### Wave 2 — setup modes (cli-engineer + backend-engineer)
- **Task 2.1** (cli-engineer): `--check` (prerequisites + IAM policy
- **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 2.2** (backend-engineer): IAM policy delta computation —
- **Task 8.3** (backend-engineer): IAM policy delta computation —
compares current `nova-spike-runner` grants to required
`cloudformation:*` + `codeartifact:*` + `kms:*` + `lambda:*` +
`dynamodb:*` + `ssm:*`.
### Phase P6 — docs-integration (REQ-345..REQ-351)
### 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.
#### Wave 1 — docs (lead-developer + security-engineer)
**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.
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.
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.
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) —
@@ -267,9 +332,9 @@ integration test; property tests; KMS round-trip; PAT revocation SLO.
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)
### Phase P6 — final-review-ship (Final Phase)
**Goal:** Multi-persona code review across P1..P6; project-health
**Goal:** Multi-persona code review across P1..P5; project-health
audit; milestone ship to main; CAP-033..038 Verified.
#### Wave 1 — review (lead-developer)
@@ -282,8 +347,8 @@ audit; milestone ship to main; CAP-033..038 Verified.
issues in this phase.
#### Wave 3 — milestone ship (lead-developer)
- **Task 3.1** (lead-developer): `ciagent-ship` — merge `phase/07` →
`milestone/v1.28-cli-identity` → `main`; tag `v1.27.7` (= the v1.28
- **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
@@ -356,7 +421,6 @@ the audit event chain in CI against a deployed Nova-idp.
| 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.
@@ -397,4 +461,29 @@ CAP-033..CAP-038 are Verified; all v1.28 release-gate criteria met.
- [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] 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.