# GRILL — v1.28 CLI Canonicalization + Identity Layer

> Adversarial review of the v1.28 SPECIFY + CLARIFY + RESEARCH + PLAN.
> Griller: ci-griller subagent. Autonomy: full. All 9 axes reviewed;
> every claim verified against the live codebase.

---

## Overall verdict: **PROCEED-WITH-CONDITIONS** · Confidence 0.76

The plan is fundamentally sound — architecture correct, re-mapping
clean (no ID collisions), technical depth accurate (DER→raw, strong-
read revocation, stdin TTY), highest-risk item (kj binary) has a
Fargate fallback. Not unfeasible, not over-scoped beyond an agent-driven
repo's capacity, not security-broken by design.

**3 critical conditions (must-fix before P1) + 16 tracked conditions.**
No escalations (all axes ≥ 0.70 confidence).

---

## Axis verdicts

| Axis | Verdict | Confidence | Critical condition |
|------|---------|-----------|-------------------|
| §1 Feasibility | PROCEED-WITH-CONDITIONS | 0.82 | C-1.1 KMS asym verify; C-1.2 Argon2 fail-closed test |
| §2 Scope | PROCEED-WITH-CONDITIONS | 0.74 | C-2.1 fold P5 into P4; C-2.2 P4 overload |
| §3 Cost | PROCEED-WITH-CONDITIONS | 0.70 | C-3.1 cost estimate; C-3.2 CodeArtifact P1 task |
| §4 Schedule | PROCEED-WITH-CONDITIONS | 0.76 | C-4.1 P4 critical path; C-4.2 per-phase exit |
| §5 Technical Depth | PROCEED-WITH-CONDITIONS | 0.80 | C-5.1 ABAC shape; **C-5.2 JWS KDF** |
| §6 Operational Readiness | PROCEED-WITH-CONDITIONS | 0.72 | **C-6.1 ABAC fail-closed**; C-6.2 threat model; C-6.3 ops guide |
| §7 Security Posture | PROCEED-WITH-CONDITIONS | 0.73 | **C-7.1 ABAC fail-closed**; C-7.2 Argon2 params; C-7.3 cred file |
| §8 Dependency Risk | PROCEED-WITH-CONDITIONS | 0.83 | C-8.1 CodeArtifact P1; C-8.2 pin kj version |
| §9 Re-mapping Integrity | PROCEED-WITH-CONDITIONS | 0.84 | **C-9.1 traceability fix**; C-9.2 INV audit |

---

## Critical conditions (the 3 must-fix-before-P1)

### 🔴 C-6.1 / C-7.1 — ABAC fail-closed
The token-vend Lambda's behavior on `kj` absence/error is unspecified.
Without fail-closed, INV-17 is documentation, not a runtime guarantee —
a `kj` load failure would bypass the ABAC gate (every PAT gets a token).
**Fix applied to PLAN.md P4 Wave 4 Task 4.1:** "If
`KyvernoJsonEngine.is_configured()` returns false or `evaluate()`
raises, return 403 + audit `token.vend.denied` (reason:
`abac_eval_failed`). Never fail open. Test: `tests/test_abac_fail_closed.py`."

### 🔴 C-5.2 — JWS-from-PAT key derivation
REQ-332's AC ("public key derivable from the PAT") is unimplementable
without a specified KDF. A PAT is a JWT, not a keypair.
**Fix applied to PLAN.md P2 Wave 2 Task 2.3 + REQ-332 AC:** the JWS
uses HMAC-SHA256 with a key derived via
`HKDF-SHA256(PAT_bytes, salt='nova-local-attestation', info='jws-signing-key')`
→ 32-byte symmetric key. The "public key derivable" AC is re-interpreted:
the *verification key* is derived from the PAT via the same KDF (the
PAT is the shared secret). This is a symmetric scheme, not asymmetric.

### 🔴 C-9.1 — Traceability drift
REQUIREMENTS.md §v1.28 traceability table mapped 16 REQs to P2;
PLAN.md splits them across P2/P3/P4/P5/P6. **Fix applied to
REQUIREMENTS.md** — traceability table updated to match PLAN.md phase
structure.

---

## Tracked conditions (16 — applied to PLAN.md as amendments)

- **C-1.1** KMS asymmetric key verification before P4 Wave 3 (one
  `aws kms create-key --key-spec ECC_NIST_P256` call).
- **C-1.2** Argon2 fail-closed test in P3 Wave 2 (Lambda returns 503
  on `ImportError`, not a crash or pure-Python hash).
- **C-2.1** Fold P5 (idp-setup) into P4 as P4 Wave 8 → **reduces to 6
  execution phases** (P1..P6, P7 = final). Applied.
- **C-2.2** P4 is a double-length phase; acknowledged in P4 header.
- **C-3.1** Cost envelope subsection added to PLAN.md.
- **C-3.2 / C-8.1** CodeArtifact provisioning = P1 Wave 0 task with
  binary go/no-go gate; Gitea wheel index fallback documented.
- **C-4.1** P4 flagged as critical-path phase (kj spike = highest-
  probability schedule slip; Fargate = +1 week).
- **C-4.2** Per-phase exit criteria added to PLAN.md.
- **C-5.1** `requested_claims` = list of claim names (the policy
  asserts the subject is *allowed* to request those claims).
- **C-6.2** Threat model (REQ-347) adds: JWKS DDoS surface, PAT theft
  + max TTL (≤24h dev, ≤1h service-account), ABAC fail-closed,
  INV-18..21 compression audit.
- **C-6.3** Operator guide (REQ-345) adds: KMS rotation, layer update,
  PITR restore, emergency PAT revocation.
- **C-7.2** Argon2id parameters: t=3, m=65536 KiB, p=1 (OWASP min).
- **C-7.3** `~/.nova/credentials.json` stores OIDC token + PAT metadata
  (jti, exp, type), NOT the raw PAT.
- **C-8.2** `kj` pinned to a specific release + SHA256 recorded.
- **C-9.2** Threat model includes INV-18..21 compression audit
  (verify spec's attestation invariant semantics are captured by
  INV-15/16/17 + REQ-332).

---

## Escalations

None. All 9 axes resolved at confidence ≥ 0.70. No human escalation
required (full autonomy).

---

## Grill complete

The plan proceeds with the 3 critical fixes and 16 tracked conditions
applied to PLAN.md + REQUIREMENTS.md. The binding decisions above are
the authoritative grill record. Next: MVP/UX CHECK → SHIP phase 0.

---

# GRILL — v1.29 Reposplit + Identity Layer Bring-Live

> Adversarial red-team review of the v1.29 SPECIFY + CLARIFY +
> RESEARCH + PLAN. Griller: CIAgent griller (red-team persona).
> Autonomy: full. All 9 review axes grilled; every claim verified
> against the live codebase (`publish.yml`, `kj-version.txt`,
> `nova/idp/setup.py`, existing v1.28 test files).
> Date: 2026-08-20.

---

## Overall verdict: **PROCEED-WITH-CONDITIONS** · Confidence 0.72

The plan is architecturally sound and the in-acdl scope is well-bounded.
The scope split (Terraform out-of-band in `nova-platform-ops`, acdl
authors publish/scrub/archive/guide/consumer-bump) is the correct
boundary per Vision §4. The technical depth is accurate (D-239 ECR tag
correction, D-240 Terraform precondition floor, CloudFront OAC pitfall,
ECR tag mutability → pin-by-digest). The cost envelope is realistic.

**However**, the covered-reference pattern — as currently structured —
is a **deferred-trust assertion** for 14 of 17 requirements. The plan
ships REQ-355..366 + 371 as "complete" on the strength of a markdown
pointer (the operator guide's cutover-gate section) to CI in a repo
that does not yet exist and has no CIAgent presence. The M1.5
verification gate, the one surface acdl genuinely owns, can be
authored-but-never-run-green and the milestone still ships. Four
critical fixes convert "documented" into "evidenced-by-operator-
attestation-in-the-guide-which-acdl-audits-at-P6."

**4 critical fixes (must apply before EXECUTE) + 6 tracked conditions.**
No escalations (all axes resolved at confidence ≥ 0.60; the user
confirmed the binding verdict on the covered-reference pattern).

---

## Axis verdicts

| Axis | Verdict | Confidence | Forcing finding |
|------|---------|-----------|----------------|
| §1 Feasibility | PROCEED-WITH-CONDITIONS | 0.70 | KJ-SOURCE: `kj` v0.0.3 source repo unverified by RESEARCH (CF-1) |
| §2 Scope | PROCEED-WITH-CONDITIONS | 0.74 | Covered-reference = deferred-trust for 14/17 REQs (G-1 + CF-2) |
| §3 Cost | PROCEED | 0.82 | $30-40/month realistic at pilot volume; no hidden budget shock |
| §4 Requirements coverage | PROCEED-WITH-CONDITIONS | 0.76 | All REQs mapped; covered-reference verification surface weak (CF-2) |
| §5 Technical risks | PROCEED-WITH-CONDITIONS | 0.72 | KJ-STATIC mitigation sound; KJ-LOCKSTEP by-construction good; M1.5 gate not enforced (CF-1) |
| §6 Testability | REJECT-AS-WRITTEN → PROCEED-WITH-CONDITIONS | 0.66 | "Verified via cutover gates in operator guide" is a punt absent CF-1/CF-2/CF-3/CF-4 |
| §7 Security | PROCEED-WITH-CONDITIONS | 0.68 | INV-18 (AuthType=AWS_IAM), TFM-HITL, IAM-NARROW unverifiable from acdl (CF-2) |
| §8 Timeline/sequencing | PROCEED | 0.80 | P1→P2 ordering safe (acdl-local scrub); P5 smoke hedges (CF-3) |
| §9 Adversarial | PROCEED-WITH-CONDITIONS | 0.70 | Dominant silent-failure = M1.5 never runs green (CF-1 addresses) |

---

## Critical fixes (must apply before EXECUTE)

### 🔴 CF-1 — M1.5 green is a HARD P6 milestone-ship gate; spike extended

**Finding:** P1 authors the M1.5 gate tests (Wave 3) but P1's exit
criterion explicitly marks the live KMS round-trip as "covered-
reference, runs in nova-platform-ops CI." P6 ships the milestone with
no requirement that M1.5 ever ran green. The dominant silent-failure
path (user-confirmed): M1.5 never runs green → 14 REQs ship "complete"
on paper while Nova-idp is not live.

**Fix (binding):**
1. P6 Wave 2 (`ciagent-ship`) MUST NOT ship `v1.28.6` until the operator
   guide (`docs/operator-guide-platform-ops.md`) contains an
   operator-attested "M1.5 Verification Gate Result" row recording:
   (a) the 8-item spike all-green on **3 consecutive rebuilds** in
   `nova-platform-ops` CI; (b) the rebuild run IDs / commit SHAs; (c)
   the operator attestor identity. The P6 audit step (Wave 1) verifies
   this row exists + is non-empty. Absent the row → P6 blocks → escalate.
2. The M1.5 8-item spike (PLAN Happy Path §3.3 Edge 5) is EXTENDED from
   8 to **12 items** by adding:
   - **Item 9 (JWKS-EDGE-ONLY):** direct JWKS Function URL GET (bypassing
     CloudFront) returns **403**; via-CloudFront GET returns 200. Proves
     `AuthType: AWS_IAM` + OAC pinning (INV-18). Without this, the
     `AuthType: NONE` pitfall (RESEARCH §4) is undetected.
   - **Item 10 (IAM-NARROW):** `aws iam get-role-policy` on the OIDC
     role asserts no `Action: "*"` and no `Resource: "*"` (REQ-360).
   - **Item 11 (TFM-HITL):** a `terraform apply` `workflow_dispatch`
     triggered by the PR author is **rejected** (exit non-zero,
     `gitea.triggering_actor == PR author`); a dispatch by a distinct
     user proceeds (REQ-357, RESEARCH §10).
   - **Item 12 (rollback drill):** revert `nova_platform_version` pin →
     `terraform apply` → assert the prior ECR digest runs (proves D-236
     rollback; guards against ECR tag mutability, RESEARCH §2).

**Binding decision G-2.1:** the covered-reference pattern is accepted
as a verification surface **only** with CF-1 applied. M1.5 green
(evidenced by operator attestation in the guide) is the ship gate.

### 🔴 CF-2 — Covered-reference REQs gated by operator-attested evidence rows

**Finding:** 14 of 17 REQs (355..366, 371) are "verified via cutover
gates in the operator guide" (CLARIFY G4). This is a deferred-trust
assertion: if `nova-platform-ops` is never built, or builds the wrong
thing, or its CI silently passes, the REQs ship "complete" on the
strength of a markdown pointer. The user confirmed this is a
deferred-trust assertion, not a verification.

**Fix (binding):** The operator guide (P4 Wave 1 Task 1.1) "Cutover
Gates" section MUST list each covered-reference REQ with:
(a) the gate entry (M1/M1.5/M2); (b) the verification command; (c) a
placeholder "Result" column. The P6 audit step (Wave 1) verifies that
every covered-reference REQ has a non-empty, green "Result" entry
(operator-attested). A REQ with an empty or red Result → P6 blocks.
This converts "documented" to "evidenced-by-operator-attestation-
audited-by-acdl-at-P6."

**Binding decision G-1:** the covered-reference pattern is **accepted
as a verification surface** with CF-1 + CF-2 applied. Without them, it
is a punt and the grill would REJECT.

### 🔴 CF-3 — P5 smoke test must run against a real v1.29.x tag (no hedge)

**Finding:** P5 bumps the consumer deploy.yml `@v1.25` → `@v1.29` and
runs a smoke test "against the v1.29 publish artifacts." But
`publish.yml` triggers on `v1.29.*` tags (P1 Wave 0), and the milestone
release tag is `v1.28.6`. P5 Wave 1 Task 1.2 hedges: "If the v1.29
publish artifacts are not yet available... mark as covered-reference:
requires v1.29.0 tag." This hedge lets P5 ship green without the
smoke test ever running against real artifacts — a second silent-
failure path.

**Fix (binding):**
1. P1 Wave 4 (regression + ship) MUST push a `v1.29.0` tag (or the
   first `v1.29.x` tag) as part of P1 ship, triggering `publish.yml`
   and producing the v1.29 artifacts. Document this in PLAN P1.
2. P5 Wave 1 Task 1.2's hedge clause is REMOVED. The P5 smoke test
   MUST run against the published v1.29.x artifacts. If the artifacts
   are absent (P1 failed to publish), P5 fails closed — no hedge to
   "covered-reference."
3. The milestone release tag remains `v1.28.6` (the v1.28.x line per
   the tagging convention); the `v1.29.0` artifact tag is a P1
   intermediate tag, not the release. This resolves the tag-semantics
   ambiguity the grill surfaced.

### 🔴 CF-4 — kj v0.0.3 source-fetch path confirmed before P1 Wave 1

**Finding:** P1 Wave 1 Task 1.1b says "fetches the `kj` Go source at
the pinned SHA" citing "RESEARCH §7 — source repo confirmed in P1
RESEARCH." RESEARCH §7 confirms the build command (`CGO_ENABLED=0`)
but is **silent on the source repository**. Assumption ledger item #1
says "RESEARCH will confirm the source repository + build commands"
— RESEARCH did NOT confirm the source repo. `kj-version.txt` pins
`v0.0.3` + SHA `4ebb9a19...` but the grill cannot determine whether
this is a source commit SHA or a binary digest, or what repo it lives
in. P1 Wave 1 is built on an open assumption.

**Fix (binding):** Before P1 Wave 1 starts (P1 Wave 0 or a new Wave
0.5), the backend-engineer MUST confirm: (a) the `kj` source repo URL
+ the commit at SHA `4ebb9a19...`; (b) `go build` reproduces a binary
whose SHA-256 matches the recorded one (or the SHA is a source commit,
in which case the build is the verification); (c) the fetched source
compiles `CGO_ENABLED=0` to a statically-linked binary (KJ-STATIC). If
the source is not fetchable at the pinned SHA → P1 fails closed →
escalate (this is a spec dependency, not a CIAgent ambiguity per
assumption #1). Document the confirmed repo URL + commit in
`platform/abac/kj-version.txt` (add a third line: the source repo URL).

---

## Tracked conditions (apply during execution)

- **TC-1 (KJ-STATIC audit, P1 Wave 1 Task 1.2):** `file(1)` asserts
  `statically linked` + `readelf -d` asserts no `NEEDED` entries, as a
  CI gate. Already in PLAN; tracked for enforcement.
- **TC-2 (KJ-LOCKSTEP by construction, covered-reference):** both
  image-bearing resources reference a single `data.aws_ecr_image.kj_image`;
  `image_uri = repo@digest`. Verified via CF-1 item 12 (rollback drill)
  + CF-2 (operator-attested result row for REQ-371).
- **TC-3 (CloudFront OAC pitfall, P4 operator guide):** the guide MUST
  document the `AuthType: NONE` → OAC-ignored pitfall (RESEARCH §4) as
  a callout. CF-1 item 9 mechanically verifies it. Already in PLAN P4
  Wave 0 Task 0.3b; tracked.
- **TC-4 (ECR tag format, P1 Wave 1 Task 1.1f):** assert tag matches
  `^[a-zA-Z0-9._-]+$` before push (D-239). Already in PLAN; tracked.
- **TC-5 (import idempotency, covered-reference REQ-361):** CI import
  treats "Resource already managed by Terraform" as idempotent success
  (grep the message, not just exit code). Documented in RESEARCH §1;
  tracked for the ops repo (operator-attested via CF-2).
- **TC-6 (Fargate sunset discipline, P4 operator guide):** D-237 —
  ≥30 consecutive days green + architecture review before deletion.
  Already in PLAN P4 Wave 0 Task 0.3f; tracked.

---

## Binding decisions (this grill session)

| ID | Decision | Rationale | Confidence |
|----|----------|-----------|-----------|
| **G-1** | The covered-reference pattern is accepted as a verification surface, but ONLY with CF-1 (M1.5 green = hard P6 gate + spike extended to 12 items) + CF-2 (operator-attested result rows for every covered-reference REQ, audited at P6). Without these, it is a deferred-trust assertion (punt) and the grill would REJECT. | User-confirmed: covered-reference is a deferred-trust assertion; M1.5 must be a hard gate; TFM-HITL/IAM-NARROW/JWKS-EDGE-ONLY are unverifiable from acdl absent the extended spike. | 0.78 |
| **G-2.1** | M1.5 green (3 consecutive rebuilds of the 12-item spike) is a binding P6 milestone-ship gate, evidenced by an operator-attested row in the operator guide. The P6 audit verifies the row exists + is green. | Dominant silent-failure path = M1.5 never runs green → 14 REQs false-"complete." User-confirmed. | 0.85 |
| **G-2.2** | The M1.5 spike is extended 8 → 12 items, adding: JWKS-EDGE-ONLY direct-URL-403 check, IAM-NARROW no-wildcard assertion, TFM-HITL self-approval-rejection check, rollback drill. | INV-18, REQ-360, REQ-357 are otherwise unverifiable from acdl. Rollback is untested (D-236). | 0.80 |
| **G-3** | P1 MUST push a `v1.29.0` (or first `v1.29.x`) intermediate tag at P1 ship to produce publish artifacts; P5's "covered-reference: requires v1.29.0 tag" hedge is REMOVED; the smoke test must run against real artifacts or P5 fails closed. | P5's hedge is a second silent-failure path. User-confirmed. | 0.82 |
| **G-4** | The `kj` v0.0.3 source-fetch path (repo URL + commit at SHA `4ebb9a19...`) must be confirmed before P1 Wave 1; the confirmed repo URL is recorded as a third line in `platform/abac/kj-version.txt`. If unfetchable → P1 fails closed → escalate. | RESEARCH §7 is silent on the source repo; P1 Wave 1 is built on an open assumption. User-confirmed. | 0.80 |
| **G-5** | The covered-reference REQs (355..366, 371) are NOT marked "complete" at P6 unless their operator-guide cutover-gate row is non-empty + green (CF-2). An empty/red row blocks the milestone ship. | Converts "documented" → "evidenced-by-operator-attestation-audited-by-acdl." | 0.78 |

---

## Escalations

None. All 9 axes resolved at confidence ≥ 0.66. The user confirmed the
binding verdict (G-1: accepted with 4 conditions). No human escalation
required (full autonomy). The kj source-fetch (CF-4) has a fail-closed
path: if RESEARCH's open assumption is wrong, P1 fails closed and
escalates at that point — but the grill does not pre-escalate a
spec dependency the plan already flags.

---

## Evidence verified against the live codebase

- `.github/workflows/publish.yml` line 47-55: trigger is
  `push: branches: [main]` (P1 Wave 0 changes to `tags: ['v1.29.*']` —
  matches PLAN).
- `.gitea/workflows/publish.yml` exists (P2 removes it — matches PLAN).
- `platform/abac/kj-version.txt`: 2 lines (`v0.0.3` + SHA
  `4ebb9a19...`) — matches PLAN; RESEARCH §7 silent on source repo
  (CF-4).
- `nova/idp/setup.py`: 50 lines, `--check/--apply/--verify/--dry-run`
  (P3 adds terraform delegation — matches PLAN).
- `core/lambda/nova_idp_setup.py` exists (P3 archives its CFN — matches).
- `tests/test_idp_auth.py` + `tests/test_kms_roundtrip.py` EXIST (from
  v1.28); `tests/test_abac_e2e.py` does NOT exist (P1 Wave 3 authors it
  — matches PLAN).
- `pyproject.toml` version = `1.14.0` (P2 bumps to `1.29.0` — matches
  PLAN; note: v1.28 did not bump it, a v1.28 carry-over the grill
  flags as minor but does not block on).

---

## Grill complete

The v1.29 plan proceeds with **4 critical fixes** (CF-1 M1.5 hard gate
+ spike extension; CF-2 operator-attested result rows; CF-3 P5 live
smoke no-hedge; CF-4 kj source confirmation) and **6 tracked
conditions**. The covered-reference pattern is accepted as a
verification surface **only** because CF-1 + CF-2 convert
"documented" into "evidenced-by-operator-attestation-audited-by-acdl-
at-P6." Without those fixes, the grill would REJECT: 14 of 17 REQs
would ship "complete" on the strength of a markdown pointer to a
nonexistent repo's CI.

Next: apply the 4 critical fixes to PLAN.md + REQUIREMENTS.md, then
MVP/UX CHECK → SHIP phase 0.