From 05efb014d61b6155100a099bb14bef494fe116ab Mon Sep 17 00:00:00 2001 From: Jon Chery Date: Wed, 19 Aug 2026 21:58:41 +0000 Subject: [PATCH] =?UTF-8?q?docs(P00):=20clarify=20=E2=80=94=20v1.28=20ambi?= =?UTF-8?q?guities=20resolved=20(6=20Qs=20+=205=20grounding=20gaps,=20D-22?= =?UTF-8?q?6..D-231)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ---ci--- project: acdl phase: 0 milestone: v1.28 status: clarify ---/ci--- --- .ciagent/CHECKPOINT.json | 7 +- .ciagent/CLARIFY.md | 371 ++++++++++++++++++++++++--------------- 2 files changed, 236 insertions(+), 142 deletions(-) diff --git a/.ciagent/CHECKPOINT.json b/.ciagent/CHECKPOINT.json index b25335c..5973b92 100644 --- a/.ciagent/CHECKPOINT.json +++ b/.ciagent/CHECKPOINT.json @@ -1,10 +1,10 @@ { "phase": 0, - "stage": "specify", + "stage": "clarify", "milestone": "v1.28", "phase_role": "pre_execution", "attempts": 0, - "updated_at": "2026-08-19T20:00:00Z", + "updated_at": "2026-08-19T20:10:00Z", "project": "acdl", "projects": ["acdl", "nova-blockchain-exchange"], "active_milestone": "v1.28", @@ -12,5 +12,6 @@ "phase_branch": "phase/00-pre-execution", "tag_line": "v1.27.x", "previous_milestone": {"milestone": "v1.27", "tag": "v1.26.3", "status": "complete"}, - "notes": "v1.28 SPECIFY. Re-mapped from source spec (v1.18 framing) to v1.28. ID allocations: REQ-323..353, CAP-033..038, INV-12..17, D-226..231. kj engine mapped to kyverno-json (D-227). Requirements validated in REQUIREMENTS.md. Next: CLARIFY." + "decisions": ["D-226", "D-227", "D-228", "D-229", "D-230", "D-231"], + "notes": "v1.28 CLARIFY. 6 open Qs + 5 grounding gaps resolved at full autonomy. D-226..D-231 authored (confidence >= 0.80). kj mapped to kyverno-json (D-227). Cognito-drop reframed as greenfield (G3). CAP-033..038, INV-12..17, REQ-323..353 allocated. Next: RESEARCH." } \ No newline at end of file diff --git a/.ciagent/CLARIFY.md b/.ciagent/CLARIFY.md index 3609e65..3609a1f 100644 --- a/.ciagent/CLARIFY.md +++ b/.ciagent/CLARIFY.md @@ -1,183 +1,276 @@ -# CLARIFY — v1.27 PO State Catalog & Ciagent Compression +# CLARIFY — v1.28 CLI Canonicalization + Identity Layer > **Autonomy:** full. Auto-resolution with assumption logging per -> `config.autonomy.level: "full"`. No human escalation unless -> confidence < 0.60. The prior conversation resolved all material -> ambiguities (4 user-answered questions). This file records the -> assumptions for the v1.27 record. +> `config.autonomy.level: "full"`. No human escalation unless confidence +> < 0.60. The user-approved re-mapping plan (v1.18 spec → v1.28) resolved +> the headline discrepancy. This file records the remaining ambiguities +> and the grounding gaps surfaced in pre-flight. --- ## Method -The clarify stage identifies ambiguities in the v1.27 specification -and resolves them at full autonomy. The v1.27 spec is the user-approved -plan from the prior conversation + the STATE.md design locked by 4 -question answers. Each ambiguity gets a decision ID (D-214+; continuing -from the v1.26 decisions D-200..D-213), a resolution, a confidence -score, and a rationale. +The clarify stage identifies ambiguities in the v1.28 specification and +resolves them at full autonomy. The v1.28 spec is the user-provided +"Universal Feature Specification — v1.18 CLI Canonicalization + Identity +Layer," re-mapped to v1.28 (milestone number, tag line, and all +ID namespaces) per the user-approved plan. Each ambiguity gets a +decision ID (D-226+, continuing from v1.27's D-214..D-225), a resolution, +a confidence score, and a rationale. --- ## Prior-conversation resolutions (already locked, restated for the record) -These were resolved by user-answered questions in the conversation that -spawned v1.27. They are load-bearing for v1.27 execution and cited -here so the v1.27 record is self-contained. +These were resolved by the user-approved re-mapping plan in the +conversation that spawned v1.28. They are load-bearing for v1.28 +execution. -### Q-P1 — What should the new PO-reference file catalog? +### Q-P1 — The source spec is titled "v1.18" but v1.18 already shipped. What milestone is this? -**Resolution:** Capability catalog (what the system can do today). -**Confidence:** 1.0 (user-confirmed). **Decision:** D-214. +**Resolution:** Re-map the spec's *content* (CLI Canonicalization + +Identity Layer) to **v1.28**, the next milestone after v1.27 (complete). +Tags run on the **v1.27.x** line (P0 = `v1.27.0`). Milestone branch: +`milestone/v1.28-cli-identity`. +**Confidence:** 1.0 (user-confirmed — "Re-map to v1.28"). **Decision:** n/a (milestone identity, not a D-ID). -### Q-P2 — How should the new file relate to CAPABILITY_INVENTORY.md? +### Q-P2 — The spec's "locked inputs" (D-NEW-26, kj engine, Nova-idp, INV-63/64/65, CAP-025..030, REQ-001..031) don't exist in the repo. How to handle? -**Resolution:** Call it `STATE.md`. PO-owned, ciagent-updated after -milestone implementation. CAPABILITY_INVENTORY.md is archived. -**Confidence:** 1.0 (user-confirmed). **Decision:** D-215. +**Resolution:** Author them fresh in this milestone's CLARIFY/RESEARCH as +**D-226..D-231, INV-12..17, CAP-033..038, REQ-323..353**. The `kj` engine +is mapped to the existing **kyverno-json** engine (INV-4 swappable) — no +new engine is built. CAP/INV/REQ IDs are re-allocated to avoid collisions +with shipped history (CAP-025..032 and INV-1..11 are blockchain/pilot). +**Confidence:** 1.0 (user-confirmed — "Re-map to v1.28"). **Decision:** D-227 (kj→kyverno-json), plus the ID-allocation block in REQUIREMENTS.md. -### Q-P3 — Where should the file live, and who owns it? +### Q-P3 — The spec claims a "Cognito drop." No Cognito exists in the repo. What does NFR-5 mean? -**Resolution:** Owned by the PO, updated by ciagent after the milestone -is implemented with additives. -**Confidence:** 1.0 (user-confirmed). **Decision:** D-216. - -### Q-P4 — How should "additive when new features are implemented" be enforced? - -**Resolution:** On the last phase / milestone ship (the P-final Wave 3 -"milestone ship" step). No regression-gate check in this pass. -**Confidence:** 1.0 (user-confirmed). **Decision:** D-217. - -### Q-P5 — Should the initial STATE.md backfill all shipped capabilities through v1.26? - -**Resolution:** Backfill all shipped capabilities through v1.26 -(compressed one-liners for v1.1–v1.24; full entries for v1.25 + v1.26). -**Confidence:** 1.0 (user-confirmed). **Decision:** D-218. - -### Q-P6 — Should the v1.26 pre-execution artifacts (CLARIFY, GRILL, IDEATE, RESEARCH) be archived? - -**Resolution:** Archive all 4 to `.ciagent/archive/` with `-v1.26` -suffixes. The next milestone's P0 writes fresh versions. Decisions are -already folded into PROJECT.md load-bearing decisions + PLAN.md -binding revisions. -**Confidence:** 1.0 (user-confirmed). **Decision:** D-219. +**Resolution:** NFR-5 (no AWS-managed identity in the path) is a +**greenfield constraint**, not a migration. Nova-idp is built fresh; no +Cognito/IAM Identity Center is *introduced*. The "drop" framing is +aspirational language from the source spec, not a literal removal. +**Confidence:** 1.0. **Decision:** D-226 (recorded below; NFR-5 restated +as a greenfield constraint in INV-15). --- -## Ambiguities + Resolutions (this CLARIFY pass) +## Open questions from the spec's §7 (auto-resolved at full autonomy) -### Q1 — Is v1.27 a feature milestone or an NFR milestone? +### Q1 — Argon2 native dependency in Lambda runtime -**Ambiguity:** v1.27 authors `STATE.md` (a new file/capability for the -PO) and archives 11 files. Does the new-file authoring count as `feat:` -(making this a feature milestone, tags on v1.26.x with progressive -patches) or `docs:`/`chore:` (NFR milestone, same tag behavior but -subject to the NFR purity gate)? +`argon2-cffi` has a C extension that may not build cleanly in the Lambda +Python 3.12 runtime. -**Resolution:** NFR milestone. `STATE.md` is documentation (a catalog of -existing capabilities), not a new platform capability. The archive moves -are `chore:` (file relocation, lossless). No code, no schema, no -platform behavior change. Tags run on the v1.26.x patch line: -`v1.26.0` (P0) → `v1.26.1..v1.26.3` (P1..P3). The final phase's patch -(`v1.26.3`) IS the milestone release. +**Resolution (D-228):** Use `argon2-cffi` with bundled wheels; if the +extension fails to load, fall back to the pure-Python implementation. If +both fail, document the Fargate migration path for the auth Lambda. +CAP-036 covers end-to-end verification. +**Confidence:** 0.85. **Rationale:** Bundled wheels are the standard +workaround for Lambda native deps; the pure-Python fallback is a safe +degradation. Fargate is the escape hatch if Lambda's runtime is +fundamentally incompatible. RESEARCH will validate wheel availability for +Python 3.12 + the Lambda execution environment. +**Impact if wrong:** Auth Lambda migrates to Fargate, adding ~1 week to P2. -**Confidence:** 0.95. **Decision:** D-220. +### Q2 — PAT revocation propagation latency -### Q2 — Where does the consumer-side archive (nova-blockchain-exchange/ROADMAP.md) land? +The 60-second SLO (NFR-4) depends on whether the token-vend Lambda reads +PAT revocation state from DynamoDB on every request (eventually +consistent reads) or via a cached/denylist mechanism. -**Ambiguity:** The platform archive convention is -`.ciagent/archive/-.md`. The consumer subproject -(`nova-blockchain-exchange/`) has no `archive/` subdirectory. Does the -consumer ROADMAP archive at `.ciagent/archive/` (platform-side, mixed) -or `.ciagent/nova-blockchain-exchange/archive/` (consumer-side, new -subdir)? +**Resolution (D-229):** Read-on-every-request with strongly consistent +reads on the PAT hash table. Cost is acceptable given expected request +volume (token vending is not a hot path — it precedes a deploy, not every +request). REV-351 verifies the SLO in CI. +**Confidence:** 0.90. **Rationale:** Strongly consistent DynamoDB reads +have single-digit-ms latency at expected volume; the 60s SLO has >10x +headroom. A cache layer adds invalidation complexity that the SLO does +not require. +**Impact if wrong:** If read latency exceeds 60s under load, introduce a +DynamoDB TTL + cache layer; SLO must be re-verified. -**Resolution:** Consumer-side. Create -`.ciagent/nova-blockchain-exchange/archive/` and relocate to -`ROADMAP-v1.26.md`. This preserves the per-project path convention -(multi-project mode: `.ciagent//` paths). The platform archive -directory is not mixed with consumer archives. +### Q3 — JWKS endpoint: Lambda function URL vs. API Gateway -**Confidence:** 0.92. **Decision:** D-221. +A function URL is simpler and cheaper but lacks throttling, WAF, and +custom domains out of the box. -### Q3 — Does archiving CLARIFY/GRILL/IDEATE/RESEARCH lose the "how v1.26 was specified" traceability? +**Resolution (D-230):** Start with a Lambda function URL behind a custom +domain; rate limiting configured at the DNS/CDN layer. API Gateway +migration deferred to v1.19+ if throttling requirements grow. +**Confidence:** 0.80. **Rationale:** The JWKS endpoint is public-key +only (no secrets); the threat surface is low. Function URL + CDN rate- +limiting covers the v1.28 volume. API Gateway is over-engineering until +traffic patterns are known. +**Impact if wrong:** If throttling becomes a requirement, API Gateway +migration adds ~3-5 days. -**Ambiguity:** The pre-execution artifacts document the v1.26 decision -path. Archiving them moves them out of active context. Is the -traceability preserved? +### Q4 — Mode resolver precedence with invalid `NOVA_CLIENT_MODE` value -**Resolution:** Yes. Three layers preserve it: (1) the archive files -are byte-identical relocations inside `.ciagent/archive/` (reachable by -agents + git history); (2) the decisions D-200..D-213 are folded into -`PROJECT.md` load-bearing decisions (the durable record); (3) git -history at the v1.26 commits preserves the authoritative state. The -active-context reduction is the point — v1.26 is shipped; the next P0 -writes fresh CLARIFY/GRILL/IDEATE/RESEARCH. +What happens if the env var is set to something other than `agent` or +`interactive` (e.g., `NOVA_CLIENT_MODE=auto`)? -**Confidence:** 0.95. **Decision:** D-222. +**Resolution (D-226):** Invalid env var values are ignored, falling +through to credential type. A warning is logged. Behavior is documented +in the `nova-cli` README. This is a sub-clause of the mode-resolution +priority decision. +**Confidence:** 0.90. **Rationale:** Ignoring + warning is the least +surprising behavior for an operator debugging mode issues. Failing hard +would block legitimate workflows that set a stale/typo'd env var. +**Impact if wrong:** Operators debugging mode issues may be confused; +non-blocking. -### Q4 — Should IAM_POLICY.md be archived (it predates v1.26 and is dated v1.11)? +### Q5 — Service-account PAT vs. developer PAT in the same session -**Ambiguity:** `IAM_POLICY.md` is dated v1.11 (2026-07-28). It predates -v1.26 by 5 milestones. The D-207 future key-split (P1+ R-3 in -REVIEW-AUDIT-P05) is pending. Archive or keep? +What if both credential types are available (e.g., a developer explicitly +exports a service-account PAT)? -**Resolution:** Keep active. `IAM_POLICY.md` is a live baseline — -referenced by the regression gate -(`tests/test_iam_policy_baseline.py`), enforced by a managed policy on -account `581513795199`, and the D-207 key-split is a pending future- -hardening item. It is not stale; it is a baseline that grows when -grants change. The v1.11 date reflects the last grant addition, not -staleness. +**Resolution (D-226):** The most recently acquired credential wins. +Documented in `nova auth login` output. The credential type is what +drives mode resolution (INV-14), so the operator sees which mode was +selected and why. +**Confidence:** 0.85. **Rationale:** "Most recent wins" is the simplest +deterministic rule that matches operator mental models of "I just logged +in as X." The audit event records the winning credential type, so the +selection is traceable. +**Impact if wrong:** Mode selection may surprise the operator; non- +blocking, but `nova auth status` must make the active credential explicit. -**Confidence:** 0.90. **Decision:** D-223. +### Q6 — ABAC policy ownership and versioning -### Q5 — Should REGRESSION_REPORT.{json,md} be refreshed as part of v1.27? +`platform/abac/token-vend.policy` is referenced, but who owns changes? +How are policy versions tracked in audit? -**Ambiguity:** Both files are dated 2026-08-01 (v1.10 Phase 52), show -CAP-025 absent, and mark live-aws CAPs "Skipped" (state bucket absent -pre-v1.26 re-bootstrap). They are stale. Should v1.27 refresh them? - -**Resolution:** No. Both files are machine-managed — written by -`core/regression_verify.py:704-705` on every `run_regression.sh` run. -They regenerate on the next regression run. v1.27 is docs/chore only -(no code); touching machine-managed files by hand creates a drift -source. The stale state is honest (the last gate run was v1.10; the -next run regenerates). The STATE.md Domain 7 row "Regression gate" -notes the current CAP range (CAP-001..025). - -**Confidence:** 0.88. **Decision:** D-224. - -### Q6 — Does PROJECT.md get the v1.26 phase-status fix in v1.27 P1 or P2? - -**Ambiguity:** The plan splits work into P1 (author + archive) and P2 -(fix stale + wire). The PROJECT.md phase-status fix (P3/P4/P5 pending -→ complete) is a "fix stale" item. P1 or P2? - -**Resolution:** P2. P1 is the additive authoring + lossless archive -moves. P2 is the corrections to kept files + the ship-discipline wiring. -This keeps P1 a pure-additive, no-edit phase (easier review + audit) and -P2 the correction phase. The PROJECT.md fix is a correction; P2. - -**Confidence:** 0.85. **Decision:** D-225. +**Resolution (D-231):** Policy changes require PR review; the policy +version (git SHA) is recorded in every token-vend audit event. Owner: +Platform Security. The policy file lives in the platform repo at +`platform/abac/token-vend.policy` and is reviewed like any other +production config. +**Confidence:** 0.90. **Rationale:** Git SHA is the natural version +identifier for a repo-resident policy; recording it in the audit event +makes every allow/deny decision reconstructable to the exact policy text. +**Impact if wrong:** Untracked policy changes could lead to unexpected +allow/deny decisions in production, undermining audit defensibility. --- -## Summary +## Grounding gaps surfaced in pre-flight (auto-resolved) -6 prior-conversation resolutions (D-214..D-219, all user-confirmed) -+ 6 new ambiguities (D-220..D-225, all auto-resolved at full autonomy, -confidence ≥ 0.60). 0 escalations. +### G1 — The `kj` engine does not exist; the spec treats it as locked. -**Key decisions:** -- D-220: v1.27 is an NFR milestone (tags on v1.26.x; final patch is the - milestone release). -- D-221: Consumer archives land in `.ciagent/nova-blockchain-exchange/archive/`. -- D-222: Archiving pre-execution artifacts preserves traceability - (archive files + PROJECT.md load-bearing decisions + git history). -- D-223: IAM_POLICY.md stays active (live baseline, test-enforced, - D-207 pending). -- D-224: REGRESSION_REPORT.{json,md} regenerate on next - `run_regression.sh` (machine-managed; v1.27 is docs/chore only). -- D-225: PROJECT.md phase-status fix is P2 (correction phase), not P1 - (additive phase). \ No newline at end of file +**Resolution (D-227):** The token-vend Lambda uses the existing +**kyverno-json** engine (INV-4 swappable) as the ABAC evaluator. The +policy at `platform/abac/token-vend.policy` is a kyverno-json policy. +No new `kj` engine is built in v1.28. If a distinct `kj` engine is +desired later, it is a separate research spike (not this milestone). +**Confidence:** 0.95. **Rationale:** The repo already has a swappable +policy engine (INV-4) implemented as kyverno-json. Building a second +engine to do the same job violates the swappable-engine invariant's +spirit. kyverno-json's `evaluate` semantics cover the spec's ABAC needs +(subject, claims, resource, environment → allow/deny). +**Impact if wrong:** If the user actually wants a new `kj` engine, v1.28 +scope expands significantly (engine design + implementation + migration). +This was flagged as caveat #3 in the approved plan; the recommended path +(kyverno-json) is locked here. + +### G2 — The spec's INV-18..21, INV-34, INV-63/64/65 don't exist. + +**Resolution:** Re-allocated as **INV-12..INV-17** (see REQUIREMENTS.md +§v1.28 Invariants). The 1:1 mapping: +- INV-63 (mode observability) → INV-12 +- INV-64 (mode determinism) → INV-13 +- INV-65 (credential type encodes role) → INV-14 +- INV-18..21 (attestation invariants) → INV-15 (no AWS-managed identity), + INV-16 (password storage), INV-17 (ABAC discipline). The spec's + attestation invariants INV-18..21 are partially covered by existing + invariants (INV-6 immutable audit) + INV-17; the JWS-from-PAT behavior + (REQ-332) is a requirement, not a separate invariant, in this mapping. +- INV-34 (MFA enforcement) → deferred to v1.21+ (out of scope per §2.2); + no INV allocated in v1.28. +**Confidence:** 0.85. **Rationale:** The mapping preserves the spec's +intent without colliding with the repo's INV-1..11. INV-34 (MFA) is +explicitly deferred per the spec's own §2.2 out-of-scope table. +**Impact if wrong:** If the user wants the exact INV-18..21 semantics as +separate invariants, INV-12..17 can be re-numbered; non-blocking. + +### G3 — The spec's CAP-025..030 collide with blockchain/pilot CAPs. + +**Resolution:** Re-allocated as **CAP-033..CAP-038** (see REQUIREMENTS.md +§v1.28 + REQ-352). The 1:1 mapping: +- CAP-025 (CLI subcommand surface) → CAP-033 +- CAP-026 (subcommand delegates to core/) → CAP-034 +- CAP-027 (layer matches wheel) → CAP-035 +- CAP-028 (Nova-idp auth flow) → CAP-036 +- CAP-029 (token-vend signs via KMS) → CAP-037 +- CAP-030 (PAT issuance + revocation) → CAP-038 +**Confidence:** 1.0. **Rationale:** Existing CAP-025..032 are +blockchain/pilot capabilities (STATE.md); re-use would corrupt the +capability registry. The re-allocated IDs are the next available. +**Impact if wrong:** None — this is a numbering decision, not a semantic +one. + +### G4 — The spec's REQ-001..031 collide / don't exist. + +**Resolution:** Re-allocated as **REQ-323..REQ-353** (1:1 with the spec's +REQ-001..031). Full text in REQUIREMENTS.md §v1.28. Max existing REQ = +REQ-322. +**Confidence:** 1.0. **Rationale:** Same as G3 — avoid collision, use +next available range. + +### G5 — `platform/abac/`, `nova/` subcommand dir, `nova-idp-*` Lambdas don't exist. + +**Resolution:** These are **greenfield deliverables** of v1.28 execution +phases, not pre-existing "locked architectures." RESEARCH will design +them; PLAN will sequence them; EXECUTE will build them. The spec's +"Operating Principle 1" (incremental delivery) is honored — v1.28 is +net-new work. +**Confidence:** 1.0. **Rationale:** The spec itself describes these as +new ("introducing Nova-idp"). The mis-framing was in calling them +"locked" — they are locked in *scope*, not in *prior existence*. +**Impact if wrong:** None — this is a framing correction. + +--- + +## Decision ledger (v1.28 — D-226..D-231) + +| ID | Title | Confidence | Load-bearing for | +|----|-------|------------|------------------| +| D-226 | Mode resolution priority + invalid-env + dual-credential | 0.90 | REQ-327, INV-12, INV-13, INV-14 | +| D-227 | ABAC engine = kyverno-json (no `kj` engine built) | 0.95 | REQ-336, REQ-339, INV-17, NFR-9 | +| D-228 | Argon2id in Lambda: bundled wheels + pure-Python fallback + Fargate path | 0.85 | REQ-333, REQ-334, INV-16, NFR-8 | +| D-229 | PAT revocation: strongly-consistent DDB read-on-every-request, 60s SLO | 0.90 | REQ-342, REQ-343, REQ-351, NFR-4 | +| D-230 | JWKS endpoint: Lambda function URL + custom domain + CDN rate-limit | 0.80 | REQ-338, NFR-5 | +| D-231 | ABAC policy ownership: Platform Security, git SHA in audit | 0.90 | REQ-339, NFR-9 | + +--- + +## Assumptions logged (full autonomy, no human escalation) + +1. **CodeArtifact is provisionable** in AWS account `581513795199` (the + pilot account). RESEARCH will confirm IAM permissions + repository + creation. If not, v1.28 falls back to a private PyPI server or a + Gitea-hosted wheel index; the CLI subcommand surface (REQ-324) and + identity layer (REQ-333+) are unaffected. +2. **Python 3.12** is the target runtime for both the CLI wheel and the + Lambda functions (spec §4 REQ-004.3). The repo's current Python + version will be confirmed in RESEARCH; if it differs, the CLI pins + 3.12 and Lambda uses the 3.12 runtime regardless. +3. **KMS asymmetric signing** (RSA-2048 or ECDSA P-256) is available in + the target account. RESEARCH will confirm. If only symmetric KMS is + available, the token-vend Lambda uses symmetric signing + a public-key + publication step (less ideal, but functional); INV-15 is unaffected. +4. **The Forge action** (REQ-326) is the existing `nova cli-action` + pattern, extended to both GitHub and Gitea marketplaces. The repo's + current Forge/Gitea workflow conventions (`.gitea/workflows/`, + `deploy.yml@v1.25`) are the baseline. +5. **MFA/TOTP** code path ships in v1.28 (per spec §2.2) but enforcement + for prod/dr is deferred to v1.21+. This is a doc/test-only path in + v1.28 — no enforcement gate. + +--- + +## CLARIFY complete + +All material ambiguities resolved at full autonomy (6 open questions + +5 grounding gaps → D-226..D-231, confidence ≥ 0.80). No human escalation +triggered (all confidences ≥ 0.60 threshold). REQUIREMENTS.md updated +with the decision ledger + invariants. Next: RESEARCH. \ No newline at end of file