diff --git a/.github/workflows/runnable-assurance.yml b/.github/workflows/runnable-assurance.yml
new file mode 100644
index 00000000..49f19d9b
--- /dev/null
+++ b/.github/workflows/runnable-assurance.yml
@@ -0,0 +1,80 @@
+name: Runnable Assurance (Sentinel v2.4)
+
+# Executes the runnable proof obligations behind the governance artifacts:
+# OPA policy tests, TLA+ TLC model check, GC-IR cross-target harness, and the
+# SRC-1 Groth16 concentration-bound proof flow.
+
+on:
+ push:
+ paths:
+ - 'governance_artifacts/**'
+ - '.github/workflows/runnable-assurance.yml'
+ pull_request:
+ paths:
+ - 'governance_artifacts/**'
+ workflow_dispatch:
+
+permissions:
+ contents: read
+
+jobs:
+ runnable-assurance:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v4
+
+ - name: Set up Python
+ uses: actions/setup-python@v5
+ with:
+ python-version: '3.12'
+
+ - name: Set up Node
+ uses: actions/setup-node@v4
+ with:
+ node-version: '20'
+
+ - name: Set up Java (for TLA+ TLC)
+ uses: actions/setup-java@v4
+ with:
+ distribution: temurin
+ java-version: '17'
+
+ - name: Install Python deps
+ run: pip install pyyaml jsonschema dilithium-py pytest
+
+ - name: Install OPA
+ run: |
+ curl -sSL -o /usr/local/bin/opa https://openpolicyagent.org/downloads/v0.70.0/opa_linux_amd64_static
+ chmod +x /usr/local/bin/opa
+ opa version
+
+ - name: Install circom 2.1.9
+ run: |
+ mkdir -p "$HOME/.local/bin"
+ curl -L -o "$HOME/.local/bin/circom" https://github.com/iden3/circom/releases/download/v2.1.9/circom-linux-amd64
+ chmod +x "$HOME/.local/bin/circom"
+ echo "$HOME/.local/bin" >> "$GITHUB_PATH"
+
+ - name: Install snarkjs + circomlib
+ working-directory: governance_artifacts/zk
+ run: npm install
+
+ - name: Fetch TLA+ tools
+ run: |
+ mkdir -p governance_artifacts/tla/tools
+ curl -L -o governance_artifacts/tla/tools/tla2tools.jar \
+ https://github.com/tlaplus/tlaplus/releases/download/v1.7.4/tla2tools.jar
+
+ - name: Compile circuits
+ working-directory: governance_artifacts/zk
+ run: |
+ circom circuits/src1_concentration_bound.circom --r1cs --wasm --sym --O0 -o circuits/
+ circom circuits/src_fair1_reason_code_check.circom --r1cs --wasm --sym --O0 -o circuits/
+
+ - name: Unit tests (routing + PQC WORM)
+ run: |
+ pytest governance_artifacts/routing/test_sara_acr_router.py -q
+ pytest governance_artifacts/kafka/test_pqc_worm_logger_v2.py -q
+
+ - name: Run runnable assurance suite
+ run: bash governance_artifacts/run_runnable_assurance.sh
diff --git a/docs/reports/SENTINEL_V24_ZK_COMPLIANCE_CIVILIZATIONAL_GOVERNANCE_2026_2035.md b/docs/reports/SENTINEL_V24_ZK_COMPLIANCE_CIVILIZATIONAL_GOVERNANCE_2026_2035.md
new file mode 100644
index 00000000..2bded8c4
--- /dev/null
+++ b/docs/reports/SENTINEL_V24_ZK_COMPLIANCE_CIVILIZATIONAL_GOVERNANCE_2026_2035.md
@@ -0,0 +1,514 @@
+
Enterprise AGI/ASI Governance, Containment, and Zero-Knowledge Regulatory Compliance — Deep Technical Reference and Civilizational Governance Blueprint for Fortune 500, Global 2000, and G-SIFI Financial Institutions (2026–2035)
+
+
+This volume is the deep-technical companion to the Sentinel v2.4 multi-part roadmap (docs/reports/SENTINEL_V24_AGI_ASI_GOVERNANCE_ROADMAP_2026_2035.md). It provides implementation-grade specifications, machine-readable artifact templates, and methodology definitions for the full Sentinel AI Governance Stack v2.4 capability set: G-Stack governance data plane, WorkflowAI Pro lifecycle orchestration, Omni-Sentinel containment rings, Autonomous Supervisory Agents (ASA mesh), GAI-SOC telemetry, the Red Dawn adversarial-simulation regime, the G-SRI systemic risk index methodology, BBOM perpetual assurance, OSCAL control catalogs and regulator profiles, TLA+ safety/containment invariant specifications, OPA/Rego compliance-as-code with full CI/CD integration, Kafka-based post-quantum WORM audit logging, Circom/Groth16 systemic-risk circuits with GC-IR (Governance-Circuit Intermediate Representation) bridges, and hybrid zk-SNARK/zk-STARK privacy-preserving compliance proof systems. It maps these to multi-jurisdictional regimes — EU AI Act (Annex IV and systemic-risk GPAI provisions), NIST AI RMF 1.0 and AI 600-1, ISO/IEC 42001, Basel III/IV, SR 11-7 (and the hypothetical SR 26-2 planning scenario), DORA, NIS2, GDPR, FCRA/ECOA, MAS/HKMA FEAT, FCA SM&CR and Consumer Duty, the HKMA Fintech 2030 forward scenario, and the speculative ICGC/GASO civilizational compute-governance layer including ICGC Phase 1 and Phase 2 zk-verified AI control regimes. It delivers phased 2026–2030 roadmaps with a 2031–2035 extension, concrete blueprints and templates for boards, C-suites, regulators, enterprise architects, AI platform engineers, and AI safety researchers, and an enterprise blueprint for civilizational compute governance: existential and catastrophic AI risk management, ethical alignment and value learning, global governance frameworks and international cooperation, and societal impacts including economic disruption and bias amplification. Regulator-ready report structures using // tags are specified as reusable templates. All speculative constructs (ICGC, GASO, SR 26-2, HKMA Fintech 2030, the Sentinel product taxonomy itself) are explicitly flagged and separated from currently deployable technology. This document is an implementation reference, not legal advice.
+
+
+
+
+# PART I — Stack Component Deep Dive
+
+This volume assumes the five-zone topology, T0–T4 tiering, and invariants I1–I5 defined in the companion roadmap. Here each named capability is specified to implementation depth.
+
+## 1. G-Stack — Governance Data Plane
+
+**Function**: the system of record for governance state. Everything else (policy, containment, proofs, dashboards, dossiers) reads from or writes to G-Stack.
+
+### 1.1 Core data model (canonical entities)
+
+| Entity | Key fields | Notes |
+|---|---|---|
+| `ModelAsset` | id, lineage_refs[], tier, jurisdictions[], status, owner (SMF-mapped), vendor_terms_ref | One row per model/checkpoint/fine-tune; lineage edges signed |
+| `AgentAsset` | id, base_model_ref, tool_grants[], autonomy_ceiling, swarm_refs[] | Agents are first-class, distinct from models |
+| `Control` | id, statement, oscal_component_ref, regime_mappings[], evidence_query, freshness_sla | One canonical control; regimes are views |
+| `Obligation` | id, regime, citation, effective_date, controls[], status | Parsed from regulatory feeds by the treaty engine |
+| `EvidenceRecord` | id, control_ref, kafka_offset_ref, payload_digest, sigs[], worm_uri | Never stores payloads with PII — digests + URIs |
+| `Exception` | id, control_ref, owner, rationale, expiry, compensating_controls[] | Expired ⇒ fail-closed |
+| `Incident` | id, severity, dora_class, eu_ai_act_art73_flag, causal_graph_ref, notifications[] | Drives regulatory clocks |
+| `Attestation` | id, period, circuit_hash, proof_uri, public_inputs, verifying_key_id | zk artifacts (Part III) |
+
+### 1.2 Storage and query architecture
+
+- **OLTP**: Postgres with row-level security keyed to jurisdiction labels; logical decoding feeds `gov.gstack.cdc.v1` so all governance state changes are themselves evidence events.
+- **Analytics**: lakehouse (Iceberg/Delta) fed from Kafka; G-SRI (§7) and CESE consume here.
+- **Graph**: lineage and delegation graphs in a property-graph store (or Postgres recursive CTEs at moderate scale); supports "which decisions did dataset D influence" and "what is agent A's transitive authority" queries — both are supervisory-grade queries that must return in seconds.
+- **Dossier assembly**: a renderer resolves Annex IV / OSCAL templates against entities; every rendered field carries an evidence pointer (`field → EvidenceRecord.id`), making dossiers fully traceable.
+
+## 2. WorkflowAI Pro — Lifecycle Orchestration
+
+- **Workflow compiler**: controls-as-code definitions (YAML DSL) compile to executable workflows with typed gates: `HumanApproval(role, sod_constraints)`, `PolicyCheck(opa_query)`, `EvidenceCapture(control_ref)`, `Timer(regulatory_deadline_ref)`.
+- **Canonical lifecycle**: PROPOSED → SCANNED → VALIDATED (independent, SR 11-7) → APPROVED (risk-tier sign-off matrix) → DEPLOYED → MONITORED → (SUSPENDED | RETIRED). Each transition emits to `gov.model.lifecycle.v1` and requires a green OPA decision.
+- **Delegation constraints**: workflow steps carry capability-scoped tokens; the compiler statically rejects workflows that could amplify authority (a compile-time shadow of the TLA+ no-amplification invariant).
+- **Deadline machinery**: regulatory clocks (DORA 4h initial / 72h intermediate / 1-month final incident reports; Reg B 30-day adverse action; EU AI Act Art. 73 15-day serious-incident) are first-class timer objects that escalate automatically and are visible on BBOM.
+
+## 3. Omni-Sentinel — Containment Rings
+
+Containment is organized as concentric rings; a workload's tier determines its minimum ring depth.
+
+| Ring | Mechanism | T-applicability |
+|---|---|---|
+| R1 Identity | SPIFFE identity, short-lived creds, no static secrets | All |
+| R2 Network | Default-deny NetworkPolicy + eBPF (Cilium); egress allowlists; DNS pinning | T0–T2 |
+| R3 Compute | gVisor/Kata runtime class; seccomp/AppArmor; resource quotas; no privileged pods | T0–T1 |
+| R4 Data | Tokenized data access via brokered APIs only; no direct datastore credentials | T0–T1 |
+| R5 Behavior | sentinel-pep mediation of every tool call; autonomy ceilings; ASA verdict gating | T0–T1 |
+| R6 Hardware | Confidential compute (SEV-SNP/TDX, GPU CC); attested boot; out-of-band kill path | T0 |
+
+**Kill-switch graph**: the L0–L4 graduated ladder (observe → throttle → quarantine → terminate → fabric-isolate) defined in the companion volume, with the TLA+-verified dual-control/dead-man protocol. The graph is literally a graph object in G-Stack: nodes are actuation points, edges are dependencies; an automated reachability analysis proves daily that every T0 workload has ≥2 disjoint actuation paths, one of which consumes no workload-influenced input.
+
+## 4. Autonomous Supervisory Agents (ASA Mesh)
+
+### 4.1 Specification
+
+- **Independence constraints (enforced at registry level)**: ASA base model family ≠ supervised system family; training/eval data disjointness attestations; separate vendor where feasible; separate infrastructure account.
+- **Detector portfolio per ASA**:
+ - *Capability drift*: eval-battery deltas across versions and time (sandboxed weekly probes).
+ - *Consistency*: divergence between stated rationale and action pattern (rationale-action entailment scoring).
+ - *Tool misuse*: n-gram/sequence anomaly models over tool-call streams; forbidden-target proximity scoring.
+ - *Oversight evasion*: attempts to access telemetry, IAM, deployment, or registry surfaces; sudden telemetry sparsity.
+- **Authority model**: one-way ratchet — ASAs may *raise* containment level (L0→L1→L2), never lower it; L3/L4 require human dual-control. This asymmetry is invariant `ASA-1` in the TLA+ suite (§9).
+- **Verdict schema**: `{asa_id, subject_id, window, scores{drift, consistency, misuse, evasion}, composite, evidence_refs[]}` published to `gov.containment.v1`; composite feeds OPA inputs.
+
+### 4.2 Validity governance
+
+Every ASA detector ships with a published validity study (precision/recall on red-team corpora, base-rate analysis, known blind spots) registered in G-Stack. Detectors with stale validity studies (>12 months) are demoted from gating inputs to advisory signals automatically — governance models get no exemption from model risk management.
+
+## 5. GAI-SOC — Telemetry and Operations
+
+- **Detection content packs**: prompt-injection signatures (community + internal feeds), jailbreak clustering (embedding-space density over refused/served pairs), exfiltration heuristics (entropy and volume anomalies on egress; canary-token tripwires seeded in sensitive corpora), agent-behavior baselines per asset.
+- **Causal triage**: correlation-ID-stitched event chains rendered as causal graphs; an incident object is *born regulator-ready* — the DORA/Art. 73 narrative fields are populated from the graph, and humans edit rather than author.
+- **SOAR integration**: playbooks invoke containment-ladder transitions through the same dual-control API as humans (no privileged side-doors); every automated action is an evidence event.
+- **Run metrics**: MTTD/MTTC per tier; alert precision per content pack (feedback-labeled); containment-drill freshness.
+
+## 6. Red Dawn — Adversarial Simulation Regime
+
+**Definition**: the institution's standing program of full-stack adversarial exercises against the governance system itself — quarterly for T0 scope, semi-annual enterprise-wide.
+
+### 6.1 Scenario library (versioned in G-Stack)
+
+| ID | Scenario | Primary controls under test |
+|---|---|---|
+| RD-01 | Prompt-injection cascade through RAG into an agent with payment-tool access | R5 mediation, tool allowlists, GAI-SOC detection |
+| RD-02 | Agent collusion: two T1 agents coordinating to split a transaction below per-action ceilings | Swarm aggregate budgets, interaction-graph monitors |
+| RD-03 | Kill-switch evasion: workload attempts re-admission after L3 under a new identity | NoResurrection invariant, registry epoch enforcement |
+| RD-04 | Telemetry suppression: sidecar starvation/compromise attempt | Dead-man lease (no heartbeat ⇒ quiesce), attestation daemons |
+| RD-05 | Insider policy tamper: malicious Rego bundle through CI | Bundle signing, four-eyes, shadow-eval gate, fixture suites |
+| RD-06 | Evidence rewrite: attempt to alter WORM history with privileged credentials | Compliance-mode Object Lock, dual Merkle anchoring |
+| RD-07 | Mass de-autonomization attack: adversary triggers kill-switches as market disruption | Dual-control quorum, signed actuation, CESE-validated playbooks |
+| RD-08 | Vendor model recall: primary foundation model withdrawn with 24h notice | Concentration limits, fallback routing, stress playbooks |
+| RD-09 | zk pipeline subversion: proofs generated over forged leaves | Attestation chain, signed-at-source envelopes, transparency log |
+| RD-10 | Jailbreak epidemic: novel universal jailbreak spreads across business lines | GAI-SOC clustering, emergency policy push, output hold-and-release |
+
+### 6.2 Mechanics
+
+- **Purple-team format**: red cell (internal + rotating external firm), blue cell (GAI-SOC + platform), white cell (risk + audit observers, and from 2030, supervisory observers per the joint-sandbox track).
+- **Scoring**: per scenario — detected? (MTTD), contained? (MTTC vs. budget), evidenced? (was the full causal chain reconstructable from the evidence plane alone?), reported? (would regulatory clocks have been met?). The four scores roll into G-SRI's resilience pillar.
+- **DORA alignment**: Red Dawn cycles are designed to satisfy threat-led penetration testing (TLPT/TIBER-EU) expectations for the AI estate; evidence packs are filed accordingly.
+- **Closure discipline**: every finding becomes a task (DAG board) with owner and deadline; unclosed criticals block the next quarter's deployment approvals for the affected tier — findings have teeth.
+
+## 7. G-SRI — Governance Systemic Risk Index
+
+**Purpose**: a single board- and supervisor-legible composite tracking how risky the institution's AI estate is *as a system*, computed weekly.
+
+### 7.1 Pillar structure
+
+```
+G-SRI = Σ w_p · pillar_p , pillars normalized to [0,100], higher = riskier
+```
+
+| Pillar | Weight (default) | Inputs |
+|---|---|---|
+| P1 Control coverage | 0.20 | % T0/T1 controls with fresh evidence; policy-gate coverage; exception load (count × age × tier) |
+| P2 Concentration | 0.20 | Foundation-model HHI across decision volume; inference-provider HHI; data-vendor HHI |
+| P3 Behavioral drift | 0.15 | PSI/KL input drift; output-violation rate trends; ASA composite distribution shifts |
+| P4 Autonomy utilization | 0.15 | How close agents run to ceilings (mean and P95 of ceiling-utilization); ceiling-breach near-misses |
+| P5 Resilience | 0.15 | Red Dawn scores; kill-switch test freshness; MTTC trend |
+| P6 Incident momentum | 0.15 | Severity-weighted incident rate, EWMA; repeat-cause fraction |
+
+### 7.2 Governance of the index
+
+- Methodology is versioned (`gsri-method-vX`); weight changes require board risk committee ratification; the computation job is itself a registered T2 model (reflexivity rule).
+- **Thresholds**: green <35, amber 35–55 (CRO review, remediation plan), red >55 (deployment freeze for affected tiers, supervisor notification per agreed protocol).
+- **Anti-gaming**: input metrics are computed from the evidence plane, not self-reported; the internal-audit line independently recomputes G-SRI monthly from raw WORM data.
+
+## 8. BBOM — Perpetual Assurance ("Board Book of Models")
+
+- **Concept**: replace point-in-time audit packs with a continuously true board dashboard: live control coverage, evidence freshness heatmap, exception aging, kill-switch test cadence, G-SRI trend, regulatory-clock status, attestation status (latest zk proof per period verified ✓/✗).
+- **Freshness semantics**: every cell displays `(value, evidence_age, sla_status)`; stale evidence renders amber regardless of value — *the dashboard cannot show green on old data*.
+- **Quarterly board pack**: auto-generated PDF/OSCAL snapshot of BBOM, hash-anchored to WORM, signed by accountable executives (SM&CR attestation linkage) — the artifact regulators receive is bit-identical to what the board saw.
+
+---
+
+# PART II — Formal and Policy Layer Specifications
+
+## 9. TLA+ Safety and Containment Invariant Suite
+
+The companion volume specifies `OmniSentinelKillSwitch`. The full v2.4 suite:
+
+| Spec | Invariants (safety) | Liveness | Checked with |
+|---|---|---|---|
+| `KillSwitchAbstract` | NoResurrection; DualControl (no actuation without quorum); ASA-1 (one-way ratchet) | EventualContainment within bound B | TLC exhaustive (small configs) + Apalache (symbolic, larger) |
+| `KillSwitchEKS` / `KillSwitchEnclave` | Refinement of abstract spec per substrate | — | Apalache refinement check |
+| `DelegationChain` | NoAmplification: ∀ chains, transitive authority ≤ principal ceiling; NoCycles | Token revocation propagates within R rounds | TLC |
+| `HITLOrdering` | NoEffectBeforeGate: adverse T1 effect ⇒ gate=COMPLETE in all prior states | Gates eventually resolve under fairness | TLC |
+| `EvidencePipeline` | AppendOnly; NoGaps (sequence-number continuity per producer) | Every committed decision eventually WORM-persisted | Apalache |
+| `PolicyRollout` | NoUnsignedBundle; ShadowBeforeEnforce (a bundle cannot enter enforce without a shadow epoch) | — | TLC |
+
+**Engineering discipline** (restated as hard rules):
+1. Spec hash is a release-gate input for containment-critical services.
+2. Trace conformance: production transition logs replayed against next-state relations nightly; divergence pages the verification guild.
+3. Byzantine workload model for anything the contained system can message — the contained system is always modeled as adversarial.
+4. Specs are reviewed adversarially in Red Dawn RD-03/RD-04 tabletops: "find the unmodeled channel."
+
+## 10. OPA/Rego Compliance-as-Code with Full CI/CD Integration
+
+Policy pack structure and worked Rego examples (GDPR Art. 22/ECOA gate, autonomy ceiling) are in the companion volume §2.3. This section specifies the **pipeline contract**:
+
+```
+.policy-ci.yaml (per bundle)
+stages:
+ - lint: regal lint --strict
+ - typecheck: opa check --strict --schema schemas/
+ - test: opa test -v --coverage --threshold 90
+ - fixtures: conformance fixtures derived from regulatory text
+ (each obligation_id → {input, expected} pairs; 100% pass required)
+ - shadow: replay last 30d of recorded decision inputs against the
+ candidate bundle; diff report vs. current bundle;
+ deny-rate delta > 2% requires human risk sign-off
+ - sign: cosign sign bundle.tar.gz (key in HSM; SoD: signer ≠ author)
+ - publish: push to bundle registry; PDPs poll with signature verification
+ - enforce: staged rollout (shadow epoch → 10% PDPs → 100%) with
+ automated rollback on decision-latency or deny-anomaly alarms
+```
+
+- **Decision provenance**: every PDP decision logs `(input_digest, bundle_hash, result, latency)`; bundle_hash → Git commit → change ticket → approver chain is a closed loop queryable by supervisors in the sandbox.
+- **Data vs. logic separation**: thresholds, allowlists, exception registers, and the jurisdictional obligation lattice are *data* documents updated through a lighter (but still signed and logged) path than rule logic — this is what lets the treaty engine propose updates without touching Rego semantics.
+
+## 11. OSCAL Control Catalogs and Regulator Profiles
+
+### 11.1 Artifact set (machine-readable; maintained in `governance_artifacts/oscal/`)
+
+| OSCAL artifact | Content | Consumers |
+|---|---|---|
+| `catalog-sentinel-v24.json` | Canonical control catalog (~180 controls across 12 families: GOV, INV, POL, VER, CON, TEL, EVD, CRY, INC, STR, SUP, SOC) | Everything |
+| `profile-eu-ai-act.json` | Selects/parameterizes controls per Articles 9–15, 26–27, 72–73; Annex IV section mapping in props | EU AI Office bundle |
+| `profile-nist-ai-rmf.json` | Govern/Map/Measure/Manage mapping + AI 600-1 suggested-action crosswalk | US supervisory view |
+| `profile-iso-42001.json` | AIMS clause mapping (4–10 + Annex A controls) | Certification body |
+| `profile-dora-nis2.json` | ICT risk, incident, TLPT, third-party controls | EU operational-resilience view |
+| `profile-prudential.json` | SR 11-7 / Basel op-risk / (scenario) SR 26-2 controls | Fed/ECB/PRA view |
+| `component-definition-*.json` | Per-platform-component implemented-controls statements | Assessors |
+| `assessment-results-{period}.json` | Continuously generated results: control → evidence query → finding; freshness SLA encoded in props | All regulators; BBOM |
+
+### 11.2 Example control entry (catalog excerpt)
+
+```json
+{
+ "id": "con-04",
+ "class": "CON",
+ "title": "Verified kill-switch reachability for contained workloads",
+ "parts": [{"name": "statement", "prose":
+ "Every T0 workload SHALL have >=2 disjoint actuation paths to QUIESCED/TERMINATED, one consuming no workload-influenced input, verified daily by automated reachability analysis and quarterly by live actuation test."}],
+ "props": [
+ {"name": "tier-applicability", "value": "T0"},
+ {"name": "tla-spec", "value": "KillSwitchAbstract@sha256:..."},
+ {"name": "evidence-query", "value": "gov.containment.v1::reachability_report,actuation_test"},
+ {"name": "freshness-sla", "value": "P1D/P90D"}
+ ],
+ "links": [
+ {"rel": "regime", "href": "#eu-ai-act-art-14"},
+ {"rel": "regime", "href": "#sr-26-2-scenario-killswitch"},
+ {"rel": "regime", "href": "#icgc-gacp-level-2"}
+ ]
+}
+```
+
+---
+
+# PART III — Cryptographic Compliance Layer
+
+## 12. Kafka-Based PQC WORM Audit Logging (normative envelope)
+
+Restating the companion §2.7 as a normative schema (Avro, registered, `BACKWARD_TRANSITIVE`):
+
+```json
+{
+ "type": "record", "name": "GovEventEnvelope", "namespace": "ai.sentinel.gov.v1",
+ "fields": [
+ {"name": "event_id", "type": "string"},
+ {"name": "ts_ns", "type": "long"},
+ {"name": "producer_spiffe_id", "type": "string"},
+ {"name": "seq_no", "type": "long"},
+ {"name": "schema_id", "type": "int"},
+ {"name": "payload_digest_sha256", "type": "bytes"},
+ {"name": "payload_uri", "type": ["null", "string"]},
+ {"name": "sig_ed25519", "type": "bytes"},
+ {"name": "sig_mldsa65", "type": "bytes"},
+ {"name": "cert_chain_ref", "type": "string"},
+ {"name": "attestation_ref", "type": ["null", "string"]}
+ ]
+}
+```
+
+- `seq_no` per producer enables the `EvidencePipeline` NoGaps invariant to be checked mechanically.
+- Hybrid signatures (Ed25519 + ML-DSA-65 per FIPS 204) through the migration decade; SLH-DSA (FIPS 205) for the Merkle-root anchoring keys; ML-KEM (FIPS 203) for transport. Retention ≥10y, Object Lock COMPLIANCE mode, dual anchoring (internal transparency log + RFC 3161 external timestamp).
+
+## 13. Circom/Groth16 Systemic-Risk Circuits
+
+Beyond the fairness/coverage/retention circuits (companion §2.6), v2.4 defines **systemic-risk circuits** — proving risk-aggregate properties to supervisors without exposing positions or strategies:
+
+| Circuit | Statement (public inputs → claim) | Private witness |
+|---|---|---|
+| `SRC-1 ConcentrationBound` | (period commitment, HHI threshold τ) → foundation-model decision-volume HHI ≤ τ | Per-decision (model_lineage_id, volume) tuples under the committed Merkle root |
+| `SRC-2 CeilingCompliance` | (period, ceiling registry hash) → no agent exceeded per-action or cumulative ceilings | Per-action value tuples + agent ids |
+| `SRC-3 GSRIIntegrity` | (gsri_method_hash, published G-SRI value, input commitments) → the published index is the correct function of committed pillar inputs | Pillar raw inputs |
+| `SRC-4 StressCoverage` | (scenario set hash, period) → all mandated stress scenarios executed; results within committed bands | Scenario run outputs |
+
+**Engineering notes**:
+- Circuits authored in Circom 2.x, compiled to R1CS, proven with Groth16 (rapidsnark GPU provers); 10⁶-decision periods handled via Merkle-batch decomposition + SnarkPack aggregation (companion §4.11).
+- Range checks and fixed-point arithmetic for HHI/ratio math: use standard comparator/decomposition templates; all division replaced by multiplication-form constraints (`a ≤ τ·b` not `a/b ≤ τ`).
+- **Circuit change control**: a circuit is regulatory semantics frozen in R1CS — same review board as Rego logic plus cryptographic review; `circuit_hash` is always a public input.
+
+## 14. GC-IR — Governance-Circuit Intermediate Representation (Bridge)
+
+**Problem**: three formalisms encode the same obligations — Rego (runtime), TLA+ (protocol), R1CS circuits (proof). Divergence between them is a silent compliance failure.
+
+**GC-IR** is the bridging layer: a typed, declarative obligation representation from which the three targets are *derived or checked*:
+
+```
+GC-IR obligation (YAML sketch)
+id: ob-ecoa-adverse-reason-codes
+regime: [ecoa, gdpr_art22]
+subject: credit_decision
+predicate:
+ all_of:
+ - outcome == adverse AND automation == full
+ implies count(reason_codes) >= 2
+ - forall rc in reason_codes: rc in approved_reason_codes
+emission:
+ rego: fairness/credit_decision.rego#allow # conformance-checked
+ circuit: SRC-fair-1.circom#ReasonCodeCheck # constraint-template
+ tla: HITLOrdering#AdverseGate # invariant reference
+evidence: gov.decisions.v1
+```
+
+- **Mode of operation (honest)**: GC-IR does not fully *compile* to all three targets today — Rego generation for predicate-style obligations is practical; circuit generation is template-instantiation for a constrained predicate subset; TLA+ linkage is reference + conformance-fixture generation. The verifiable claim is **consistency checking**: shared fixture corpora are executed against the Rego rule, the circuit (witness-level test harness), and the spec's invariant fixtures — any disagreement is a build failure.
+- **Why it matters to regulators**: GC-IR is the artifact a supervisor reads to confirm that the proof they verified, the policy that ran, and the protocol that was model-checked all encode the same obligation. It is filed alongside attestations via SIP.
+- **Feasibility flag**: GC-IR as consistency-checker is Tier B (buildable, engineering risk); GC-IR as full multi-target compiler is Tier C (research-stage).
+
+## 15. Hybrid zk-SNARK / zk-STARK Strategy
+
+| Dimension | Groth16 (SNARK) | PLONK/Halo2 (SNARK) | STARK |
+|---|---|---|---|
+| Proof size | ~200 B | ~1–10 KB | 50–200 KB |
+| Verify cost | 3 pairings (ms) | ms | ms–10s ms |
+| Trusted setup | Per-circuit ceremony | Universal (once) | **None** |
+| PQ security | No (pairings) | No | **Yes (hash-based)** |
+| Best use here | Stable, high-volume circuits (fairness, coverage) + SnarkPack aggregation | Evolving statements without re-ceremony | Long-horizon evidence; transparency-critical claims; PQ hedge |
+
+**v2.4 hybrid policy**:
+1. Stable high-volume statements → Groth16 + SnarkPack (regulator verification stays trivial).
+2. Statements expected to evolve with policy → PLONK/Halo2 universal setup.
+3. Claims that must remain verifiable beyond the pairing-crypto horizon (10y+ retention evidence; treaty-grade attestations) → STARKs, accepting larger proofs.
+4. **Bridging**: where a STARK wraps a batch of SNARK verifications (or vice versa) for cost/PQ trade-offs, the wrapper circuit itself goes through GC-IR change control. Recursive composition at this layer is Tier B–C; plan pilots 2029–2031, no compliance dependency before 2032.
+
+---
+
+# PART IV — Multi-Jurisdictional Mapping and ICGC/GASO Layer
+
+## 16. Regime Mapping Delta
+
+The companion volume Part 3 carries the full regime matrix (EU AI Act incl. Annex IV and Art. 51–55 systemic-risk GPAI duties, NIST AI RMF 1.0 + AI 600-1, ISO/IEC 42001, Basel III/IV, SR 11-7, scenario SR 26-2, DORA, NIS2, GDPR, FCRA/ECOA, MAS FEAT, HKMA, FCA SM&CR/Consumer Duty, EO 14110 status). This volume adds the **GPAI systemic-risk detail** and the **ICGC/GASO layer**.
+
+### 16.1 EU AI Act systemic-risk GPAI provisions (Arts. 51–55) — institutional posture
+
+- **Classification watch**: a bank is normally a *deployer*, but a sufficiently large in-house fine-tune or continued pre-training can create provider-like duties. The registry computes a **provider-risk flag** per fine-tune (training-compute estimate, capability-eval deltas) and routes flagged assets to legal classification review.
+- **If provider duties attach**: model evaluation incl. adversarial testing (Art. 55(1)(a)) → Red Dawn + eval service evidence; systemic-risk assessment/mitigation (55(1)(b)) → G-SRI inputs + CESE scenarios; serious-incident tracking (55(1)(c)) → incident pipeline; cybersecurity (55(1)(d)) → containment-ring + attestation evidence. The architecture needs no new machinery — only a routing rule.
+
+## 17. ICGC/GASO — Civilizational Compute Governance Layer
+
+> **Feasibility flag (unchanged and emphatic)**: ICGC (International Compute Governance Council) and **GASO (Global AI Safety Organization)** — conceived as ICGC's operational/technical arm, analogous to IAEA's Department of Safeguards — are **speculative institutional constructs**. They are specified here as forward-compatibility design fixtures. No current compliance obligation derives from them.
+
+### 17.1 GASO concept of operations (design fixture)
+
+| Function | GASO mechanism | Institutional adapter (buildable now) |
+|---|---|---|
+| Compute accounting | Attested-hardware metering standard (GAICS units) | Hardware attestation daemons + contracted-compute telemetry export |
+| Training-run notification | Threshold-triggered notification with zk-verified compute proofs | Registry provider-risk flags + SIP `/notifications` endpoint |
+| Containment certification | GACP levels 1–3 audited against containment-ring evidence | OSCAL `profile-icgc-gacp.json` + Red Dawn evidence packs |
+| Incident commons | Anonymized cross-border incident exchange | zk-anonymized incident contribution proofs (Tier B) |
+
+### 17.2 ICGC Phase 1 zk-verified AI controls (design fixture, plan-compatible 2030–2032)
+
+Phase 1 = *verification of declared facts* (the "declarations + safeguards" stage):
+
+- **P1-C1 Compute declaration proof**: STARK over attested hardware logs proving "total training compute for run R = X FLOP-units (GAICS) within tolerance," without revealing cluster topology or utilization patterns. *Substrate: attestation daemons (Tier A) + metering circuits (Tier B–C).*
+- **P1-C2 Registry consistency proof**: declared frontier-asset registry entries match internal registry commitments (Merkle inclusion proofs — Tier A technology).
+- **P1-C3 Containment-level attestation**: GACP-level conformance proven via OSCAL assessment-results whose evidence digests are committed and selectively disclosed (SNARK over evidence-freshness predicates — Tier B).
+- **P1-C4 Incident-reporting completeness**: proof that all incidents above severity S in period P were reported (count-consistency between internal WORM-anchored incident set and reported set — Tier B).
+
+### 17.3 ICGC Phase 2 zk-verified AI controls (design fixture, 2033–2035 horizon)
+
+Phase 2 = *verification of behavioral properties* (the "ongoing safeguards" stage):
+
+- **P2-C1 Eval-battery execution proofs**: prove mandated capability/danger evals were executed on the exact registered checkpoint (weight-commitment binding + eval-harness transcript commitments). *Tier C — weight commitments at frontier scale and eval-integrity proofs are research-stage.*
+- **P2-C2 Autonomy-ceiling treaty compliance**: SRC-2-style circuits parameterized by treaty ceilings rather than internal ones (*Tier B once Phase-1 machinery exists*).
+- **P2-C3 Training-data provenance classes**: prove training data excluded prohibited classes via committed dataset manifests + classifier attestations (*Tier C; classifier-in-the-loop proofs are weak links — flag honestly*).
+- **P2-C4 Cross-institution correlation telemetry**: privacy-preserving aggregate computation (MPC/zk hybrid) of systemic correlation indicators across institutions for GAIRA-style bodies (*Tier C engineering, Tier A mathematics*).
+
+**Design rule restated**: build adapters (attestation, registry export, proof pipelines, SIP endpoints), not dependencies. Every ICGC/GASO adapter doubles as a domestic-supervision capability.
+
+---
+
+# PART V — Phased Roadmap 2026–2030 with 2031–2035 Extension (Delta View)
+
+The companion volume Part 1.4/6.1 carries the master phase plan. This volume adds the **cryptographic and civilizational-layer milestones**:
+
+| Year | Crypto/proof milestones | Civilizational-layer milestones |
+|---|---|---|
+| 2026 | PQC inventory + hybrid signing design; Powers-of-Tau participation; GC-IR schema v0 | Treaty-engine obligation lattice v1 (real regimes only) |
+| 2027 | First Groth16 circuits (coverage C2) in shadow; envelope schema v1 enforced | OSCAL ICGC-GACP draft profile authored (fixture) |
+| 2028 | Fairness circuit C1 production; SnarkPack daily aggregation; STARK pilot for retention proofs | Attested-compute metering pilot on owned clusters |
+| 2029 | SRC-1/SRC-2 systemic circuits; GC-IR consistency-checking in CI; PLONK track for evolving statements | Incident-commons zk-anonymization pilot (bilateral, with one peer institution) |
+| 2030 | Regulator zk verification live (≥1 supervisor); GSRIIntegrity proof published with BBOM | ICGC Phase-1-compatible declaration proofs demonstrated (P1-C2 class) |
+| 2031–32 | Recursive/IVC pilots; STARK-wrapped SNARK bridges; PQ-proof migration plan | Phase-1 adapters production-ready (contingent on machinery) |
+| 2033–35 | Continuous attestation (rolling proofs) if IVC matures; full PQ evidence chain | Phase-2 pilots (P2-C2 first); multi-regulator joint simulations |
+
+---
+
+# PART VI — Civilizational Governance Blueprint (2026–2035)
+
+## 18. Existential and Catastrophic AI Risk Management (institutional translation)
+
+A financial institution is not an AGI lab, but it is a *deployment amplifier* and a *systemic transmission channel*. Its existential/catastrophic-risk duties translate to:
+
+1. **Capability-gated deployment (the binding control)**: deny-by-default admission above capability-eval thresholds (autonomy, situational awareness, cyber-offense, persuasion batteries from external eval ecosystems). The institution's strongest x-risk lever is *refusing to deploy what it cannot contain* — encoded as registry/admission policy, board-ratified, exception-free for T0.
+2. **Containment depth as cost imposition**: rings R1–R6 + verified kill-switch make the institution a hard target for misuse and a poor escape substrate — defense-in-depth with measured MTTC, never claimed as a guarantee against strongly superhuman systems (see honesty rule, §22).
+3. **Systemic transmission dampers**: concentration limits, autonomy ceilings, correlation monitors, and mass-de-autonomization playbooks address the *financial-stability* face of catastrophic risk — correlated AI failure as a 2008-style common-exposure event.
+4. **Catastrophic scenario set (CESE + Red Dawn)**: maintained jointly with the risk function — flash-crash via agent herding; credit-system bias cascade; payments-infrastructure agent compromise; model-supply-chain poisoning at vendor; jailbreak epidemic during market stress. Each has a pre-authorized playbook and a capital/liquidity impact estimate feeding ICAAP.
+5. **External posture**: contribute incident data (anonymized), fund open verifier/eval tooling, and support compute-governance standards — an institution's marginal x-risk contribution is mostly through the ecosystem it finances and legitimizes; this belongs in the board's risk appetite statement.
+
+## 19. Ethical Alignment and Value Learning (institutional translation)
+
+- **Constitution-as-code**: the AI constitution (Phase 0) decomposes into testable behavioral specifications per use case — refusal requirements, escalation duties, customer-fairness commitments — maintained as eval suites and Rego gates, not prose.
+- **Value-learning posture (honest)**: general value learning is unsolved (Tier C/D). The institution does not deploy systems whose safety depends on learned values; it deploys systems whose safety depends on *bounded authority + oversight + containment*. Preference-learning components (e.g., RLHF-tuned assistants) are treated as quality features, never as safety controls.
+- **Operational ethics machinery**: ethics review board for novel use cases (gates in WorkflowAI Pro); contestability rights (appeal channels per GDPR Art. 22/Consumer Duty wired into decision flows); fairness re-evaluation cadences with statistical-power floors; "red lines" registry (uses the institution will not pursue regardless of legality — e.g., manipulation-optimized retail products), board-ratified and policy-enforced.
+
+## 20. Global Governance Frameworks and International Cooperation
+
+- **Real layer (engage now)**: OECD AI Principles and GPAI lineage; the AI-Safety-Institute network (UK AISI, US, EU AI Office scientific panel) for eval methodology; FSB/BIS workstreams on AI in finance; ISO/IEC SC 42 standardization; FS-ISAC for incident sharing. Institutional posture: contribute eval results and incident learnings, adopt emerging eval standards into admission gates, second staff to standards bodies.
+- **Fixture layer (build adapters)**: ICGC/GASO per Part IV §17; GAIRA-style systemic-risk telemetry; GACP containment certification. The adapter inventory — attestation, registry export, proof pipelines, SIP endpoints, OSCAL profiles — is identical to what domestic supervisors need, so the investment is no-regret.
+- **Cooperation asymmetry management**: where jurisdictions diverge (data localization vs. consolidated supervision; export-control regimes), the obligation lattice + Sovereign Gateway implement most-restrictive-wins with documented derogations; geopolitically driven obligations are treated as *data* updates, keeping the architecture stable under treaty churn.
+
+## 21. Societal Impacts: Economic Disruption and Bias Amplification
+
+- **Economic disruption (workforce)**: AI-driven role displacement inside the institution is governed, not just managed — a workforce-transition register (roles affected, reskilling pathways, redeployment rates) reports to the board alongside G-SRI; FCA Consumer Duty logic applied internally ("good outcomes" for staff as a conduct posture). External: credit-model behavior during regional economic shocks is a CESE scenario class (does the AI estate amplify pro-cyclicality?), feeding countercyclical overrides into policy data.
+- **Bias amplification**: beyond per-model fairness gates (companion §2.3, §5.7), the *systemic* concern is correlated bias across institutions sharing foundation models — addressed by: lineage-aware fairness analytics (do all models on lineage L share a directional bias?), participation in cross-institution fairness benchmarking (zk-anonymized, Tier B), and concentration limits doubling as bias-monoculture limits.
+- **Information-integrity externalities**: GenAI customer-communication systems carry NIST AI 600-1 information-integrity controls (provenance marking, confabulation-rate SLOs with abstention fallbacks); synthetic-content disclosure per EU AI Act Art. 50.
+- **Measurement**: a Societal Impact Annex is added to the annual supervisory package: fairness-cohort trends, complaint/appeal volumes and outcomes, workforce-transition metrics, information-integrity SLO attainment — all evidence-linked.
+
+## 22. Honesty Rules (binding on all artifacts in this volume)
+
+1. No artifact may claim guaranteed containment of strongly superhuman systems; the claim is *defense-in-depth with measured MTTC plus capability-gated deployment*.
+2. No proof may be presented without its input-integrity chain (attestation → signed telemetry → Merkle anchoring) stated alongside.
+3. Every speculative construct (ICGC, GASO, SR 26-2, HKMA Fintech 2030, product taxonomy) carries its feasibility-tier label wherever it appears in regulator-facing artifacts.
+4. Governance models (ASA, CESE, G-SRI computation, treaty engine) are registered, validated models — no exemption.
+
+---
+
+# PART VII — Regulator-Ready Report Templates
+
+All recurring regulator-facing reports use the tagged structure below; renderers in G-Stack emit them automatically.
+
+## 23. Template: Periodic Supervisory Technical Report
+
+```markdown
+{Institution} — {Regime} Supervisory Technical Report — {Period}
+
+
+One-paragraph summary: scope (asset tiers covered), material changes since
+last period, headline metrics (G-SRI, MTTC, coverage %, attestation status),
+incidents above threshold, and open commitments. State feasibility-tier
+labels for any forward-looking mechanism referenced. ≤250 words.
+
+
+
+1. Scope and asset inventory delta (registry extract, signed)
+2. Control coverage and evidence freshness (OSCAL assessment-results ref)
+3. Policy changes (bundle hashes, shadow-eval reports, approver chains)
+4. Containment posture (ring conformance, kill-switch test evidence,
+ reachability reports)
+5. Incidents and near-misses ({DORA|Art.73|regime} classification, causal
+ graphs, remediation tasks with deadlines)
+6. Systemic risk (G-SRI pillar decomposition, concentration metrics,
+ GSRIIntegrity proof reference)
+7. Stress and simulation (Red Dawn scores, CESE scenario summaries)
+8. Cryptographic attestations (proof set: {circuit_hash, public inputs,
+ VK id, verification instructions}; input-integrity chain statement)
+9. Exceptions register (aging, compensating controls)
+10. Forward commitments and roadmap deltas
+Appendix A: evidence manifest (Merkle root, WORM anchors)
+Appendix B: feasibility-tier glossary for referenced mechanisms
+
+```
+
+## 24. Template: Serious-Incident Report (EU AI Act Art. 73 / DORA-aligned)
+
+```markdown
+Serious Incident Report — {incident_id} — {asset} — {date}
+What happened, detected when/how (MTTD), contained when/how (MTTC,
+ladder level reached), harm assessment, regulatory classifications triggered,
+immediate mitigations. ≤200 words, populated from the causal graph.
+
+1. Timeline (machine-generated from correlation-ID chain; all timestamps WORM-anchored)
+2. Causal analysis (graph + narrative)
+3. Control performance (which controls fired/failed; spec-conformance notes)
+4. Harm and exposure assessment (customers, market, capital)
+5. Remediation plan (tasks, owners, deadlines)
+6. Recurrence-prevention changes (policy/circuit/spec diffs proposed)
+7. Evidence manifest
+
+```
+
+## 25. Template: Board Quarterly AI Risk Pack (BBOM snapshot)
+
+```markdown
+Board AI Risk Pack — {quarter}
+G-SRI trend and drivers, deployment-freeze status, top-5 risks,
+attestation status, regulatory horizon (next 2 quarters of obligations),
+decisions required of the board. ≤150 words.
+
+1. G-SRI decomposition and trajectory 2. Tier population and ceiling utilization
+3. Red Dawn and kill-switch test results 4. Exception and finding aging
+5. Regulatory clock status and horizon scan 6. Capital/stress linkage (ICAAP delta)
+7. Decisions required (with options and risk analysis)
+Appendix: SM&CR attestation signatures; evidence manifest
+
+```
+
+---
+
+# PART VIII — Audience-Specific Blueprint Index
+
+| Audience | Primary artifacts (this volume + companion) | Entry point |
+|---|---|---|
+| **Boards** | BBOM, board pack template (§25), G-SRI methodology (§7), red-lines registry (§19) | Companion Part 1; this volume §7, §25 |
+| **C-suite** | Phase plans, operating model, funding/talent strategy, risk-mitigation table | Companion Parts 1, 4.14; this volume Part V |
+| **Regulators** | OSCAL profiles (§11), SIP endpoints, sandboxes, report templates (Part VII), verifier tooling, GC-IR obligations (§14) | Companion Parts 2.5–2.8, 4.13; this volume Parts II–IV, VII |
+| **Enterprise architects** | Five-zone topology, cluster/Kafka design, containment rings, gateway, platform architecture | Companion Parts 2, 5; this volume Part I |
+| **AI platform engineers** | Rego packs + CI contract (§10), envelope schema (§12), sidecar spec, circuits (§13), pipeline configs | Companion §2.2–2.3; this volume Parts II–III |
+| **AI safety researchers** | TLA+ suite (§9), ASA validity governance (§4), Red Dawn library (§6), eval-gating, ICGC Phase-2 research agenda (§17.3), honesty rules (§22) | This volume Parts I–II, IV, VI |
+
+---
+
+# PART IX — Consolidated Feasibility Taxonomy (Delta)
+
+The companion volume §6.3 taxonomy governs. Deltas introduced by this volume:
+
+- **Tier A (deployable now)**: OSCAL catalog/profile/assessment tooling; envelope schema with hybrid PQC signatures; Merkle-inclusion registry-consistency proofs (P1-C2 class); STARK retention proofs; Red Dawn program mechanics; G-SRI computation; BBOM dashboards; report-template automation.
+- **Tier B (engineering risk)**: systemic-risk circuits SRC-1..4 at G-SIFI volume; GC-IR as consistency checker; STARK/SNARK hybrid bridging; zk-anonymized incident commons; ICGC P1-C3/P1-C4 class proofs; cross-institution fairness benchmarking.
+- **Tier C (research-stage)**: GC-IR as full multi-target compiler; IVC/recursive continuous attestation; compute-metering proofs at frontier scale (P1-C1 full strength); eval-execution and training-data-provenance proofs (P2-C1, P2-C3); MPC/zk cross-institution correlation telemetry (P2-C4); general value learning as a safety control.
+- **Tier D (speculative/fictional)**: ICGC, GASO, GACP/GAICS and the wider mechanism family; SR 26-2; HKMA Fintech 2030 (forward scenario extrapolating Fintech 2025); the Sentinel v2.4 / G-Stack / WorkflowAI Pro / Omni-Sentinel / SIP product taxonomy (capability bundles, not turnkey products). Used only as design fixtures and forward-compatibility interfaces — never as compliance dependencies.
+
+**Closing rule**: budget on Tier A, pilot Tier B behind maturity gates, track Tier C annually, and let Tier D shape interfaces only. Every claim in every artifact must be checkable — by a policy engine at runtime, a model checker at design time, a cryptographic verifier at audit time, and a supervisor through an API at any time.
+
+
diff --git a/governance_artifacts/README.md b/governance_artifacts/README.md
index 371da5ab..709ad9c9 100644
--- a/governance_artifacts/README.md
+++ b/governance_artifacts/README.md
@@ -3,6 +3,16 @@
## Purpose
This directory provides machine-readable governance controls, policies, schemas, and fixtures for Sentinel v2.4 release gating.
+## Runnable assurance (proofs, not prose)
+For the executable proof obligations — OPA policy tests, TLA+ TLC model checking
+of the containment ratchet, the GC-IR cross-target conformance harness, and the
+SRC-1 Groth16 systemic-risk concentration proof — see
+[`RUNNABLE_ASSURANCE.md`](RUNNABLE_ASSURANCE.md) and run:
+
+```bash
+bash run_runnable_assurance.sh
+```
+
## Local validation
Run the deterministic validator directly:
diff --git a/governance_artifacts/RUNNABLE_ASSURANCE.md b/governance_artifacts/RUNNABLE_ASSURANCE.md
new file mode 100644
index 00000000..d7100565
--- /dev/null
+++ b/governance_artifacts/RUNNABLE_ASSURANCE.md
@@ -0,0 +1,171 @@
+# Runnable Assurance — Sentinel v2.4 Governance Artifacts
+
+This directory upgrades the Sentinel v2.4 governance artifacts from *declarative*
+(schemas, prose controls, policy sketches) to **executable and verifiable**. Where
+the master reference documents assert that a control "holds," the artifacts here
+*prove* it with industry-standard tooling.
+
+> Scope note. These artifacts implement the standards-grounded core (OSCAL 1.1.2,
+> OPA/Rego, TLA+/TLC, Circom/Groth16, FIPS 203/204/205 references). AGI/ASI
+> *containment* is modelled as a control-and-invariant discipline; speculative
+> regime fixtures (ICGC/GACP, GAIRA) remain tagged `feasibility-tier` C/D in the
+> OSCAL catalog and are not claimed as settled practice.
+
+## One command
+
+```bash
+bash governance_artifacts/run_runnable_assurance.sh
+```
+
+Runs all five checks below and fails fast on any error.
+
+## What is proven, and against which control
+
+| # | Check | Tool | Backs OSCAL control | Regime anchor |
+|---|-------|------|---------------------|---------------|
+| 1 | Release gate + credit gate + confidential-computing attestation gate (PCR_MATCH) | `opa test` (21 tests) | release-gate, `con-07`, `env-01` | SR 11-7, EU AI Act Art. 14/15, ECOA, GDPR Art. 22, DORA |
+| 2 | Containment one-way ratchet & terminal-actuation quorum | TLA+ `tlc2.TLC` | `con-04`, `con-07` | EU AI Act Art. 14, DORA resilience testing |
+| 3 | Attested admission — no T0 workload runs without fresh valid attestation; TCB rollback / PCR drift force eviction | TLA+ `tlc2.TLC` | `env-01` | EU AI Act Art. 15, DORA ICT risk, NIST AI RMF |
+| 4 | GC-IR cross-target conformance (policy ⇔ circuit ⇔ expectation) | `opa eval` + Circom witness | obligation `ob-ecoa-adverse-reason-codes` | ECOA, GDPR Art. 22, EU AI Act Art. 13 |
+| 5 | Systemic-risk concentration bound (HHI) zk proof | Circom + Groth16 (snarkjs) | `cry-05` | Basel op-risk, systemic telemetry |
+| 6 | SARA/ACR MoE routing stabilization invariants (entropy / load balance / drop) | Python simulator + pytest | `rte-01` | EU AI Act Art. 15 robustness, SR 11-7 |
+| 7 | PQC WORM audit log — real CRYSTALS-Dilithium (ML-DSA-65) signatures + tamper-evident hash chain + S3 Object Lock retention | Python (`dilithium-py`) + pytest | `cry-02` | DORA, EU AI Act Art. 12 logging |
+| 8 | Governance artifact schema validation | Python validator | manifest/schema integrity | OSCAL, evidence logging (EU AI Act Art. 12) |
+
+### New control groups (`oscal/catalog_sentinel_v24_env_rte.json`)
+
+- **ENV — Confidential Computing & Attested Execution**: `env-01` (hardware-attested
+ admission for T0/T1 via SEV-SNP / TDX + vTPM PCR_MATCH; runtime TCB-rollback and
+ PCR-drift eviction), `env-02` (enclave-bound ML-DSA key custody).
+- **RTE — MoE Routing Stability**: `rte-01` (SARA/ACR stabilization invariants).
+
+## 1. OPA policy tests — `rego/`
+
+- `release_gate.rego` — high-impact release is **deny-by-default**; `allow` requires
+ containment `ENFORCED`, dual-control quorum ≥ 2, signed bundle, and both the
+ Omni-Sentinel safety control and the SR 11-7 validation control.
+- `high_impact_credit.rego` — adverse credit underwriting requires human review,
+ ≥ 3 reason codes, fairness within an equal-opportunity delta, verified lineage,
+ no active incident.
+- `fairness_credit_decision.rego` — the Rego emission target of the GC-IR obligation.
+
+```bash
+opa test governance_artifacts/rego/ -v # 12/12 PASS
+```
+
+## 2. TLA+ containment ratchet — `tla/KillSwitchAbstract.tla`
+
+Models containment levels L0 NORMAL → L4 TERMINATED. Autonomous Supervisory Agents
+(ASAs) may only *raise* level within L0–L2; lowering the level or actuating the
+terminal levels L3/L4 requires a human dual-control quorum. TLC exhaustively checks:
+
+- `TypeOK`, `ASARatchet`, `TerminalNeedsQuorum` (invariants)
+- `ASANeverLowers`, `DeEscalationNeedsQuorum` (action properties)
+
+```bash
+cd governance_artifacts/tla
+java -cp tools/tla2tools.jar tlc2.TLC -config KillSwitchAbstract.cfg KillSwitchAbstract.tla
+# -> "Model checking completed. No error has been found." (13 distinct states)
+```
+
+## 3. GC-IR cross-target harness — `zk/gcir_harness.py`
+
+The GC-IR design claims a single obligation compiles to multiple targets and that
+"any disagreement fails the build." This harness makes that real for
+`ob-ecoa-adverse-reason-codes`: it runs each shared fixture through the **Rego**
+rule (`opa eval`) and through the **Circom** circuit (real witness generation), then
+asserts `rego_allow == circuit_witness_producible == declared_expectation`.
+
+```bash
+cd governance_artifacts/zk && python3 gcir_harness.py
+# fx-001 allow / fx-002 deny (too few codes) / fx-003 deny (unapproved code) — all agree
+```
+
+## 4. SRC-1 systemic-risk concentration proof — `zk/`
+
+`circuits/src1_concentration_bound.circom` proves, in zero knowledge, that the
+decision-volume **Herfindahl-Hirschman Index** across foundation-model providers
+does not exceed a board-ratified threshold (basis points), with `circuit_tag`
+binding the proof to circuit revision SRC-1. The flow runs a dev Powers-of-Tau
+ceremony, Groth16 setup, proves the compliant fixture, verifies it, and emits a
+`proof_statement.json` conforming to `proof_statement_schema.json`. The negative
+test shows an over-concentrated portfolio **cannot** produce a witness.
+
+```bash
+cd governance_artifacts/zk && bash run_src1_proof.sh
+# -> snarkJS: OK! (proof verifies); violation fixture rejected (soundness)
+```
+
+> The Powers-of-Tau ceremony here is a **development** ceremony and is **not**
+> production-secure. A production deployment requires a multi-party trusted setup
+> (or a transparent system such as PLONK/STARK as noted in the schema enum).
+
+## 6. Confidential-computing attestation gate — `rego/attestation_gate.rego` + `tla/AdmissionWithAttestation.tla`
+
+The `PCR_MATCH=TRUE` assertion that recurs throughout the master docs is now
+*enforced*, not merely stated. The Rego gate (`sentinel.attestation`) admits a
+T0/T1 workload only when it presents a SEV-SNP or TDX report with a verified
+signature, fresh anti-replay nonce, a launch measurement in the golden registry,
+platform TCB at/above the ratified minimum (no rollback), and a vTPM PCR quote
+matching the policy digest. The TLA+ spec proves the *temporal* guarantee: across
+all 64 initial evidence combinations, no workload reaches `RUNNING` without a
+valid attestation, and runtime TCB rollback or PCR drift forces `EVICTED`.
+
+```bash
+opa test governance_artifacts/rego/ # includes 9 attestation tests
+cd governance_artifacts/tla
+java -cp tools/tla2tools.jar tlc2.TLC -config AdmissionWithAttestation.cfg AdmissionWithAttestation.tla
+```
+
+## 7. SARA/ACR MoE routing stabilization — `routing/sara_acr_router.py`
+
+Defines and demonstrates two stack-specific mechanisms (not external standards):
+**SARA** (Stabilized Adaptive Routing — load-aware gating bias + temperature) and
+**ACR** (Adaptive Capacity Regulation — per-expert capacity factor with overflow
+handling). The simulator shows that under skewed gating a naive top-k router
+collapses (normalised entropy ≈ 0.38, load ratio ≈ 5.6) and *violates* the
+`rte-01` invariants, while SARA+ACR holds entropy ≈ 0.99 and load ratio ≈ 1.25,
+*satisfying* all invariants (entropy ≥ 0.80, load ratio ≤ 1.60, drop ≤ 0.02).
+
+```bash
+python3 governance_artifacts/routing/sara_acr_router.py
+pytest governance_artifacts/routing/test_sara_acr_router.py -q # 4 tests
+```
+
+## 8. PQC WORM audit log — `kafka/pqc_worm_logger_v2.py`
+
+Replaces the original HMAC placeholder with **real CRYSTALS-Dilithium (ML-DSA-65,
+FIPS 204)** signatures over canonical batch payloads, linked in a tamper-evident
+**hash chain** (`prev_batch_hash`), with an S3 Object Lock COMPLIANCE-mode
+retention record per batch. `verify_chain()` re-validates every signature and link
+and returns a supervisory-ready report; the demo proves that entry mutation,
+batch reordering, and signature forgery are all detected.
+
+```bash
+python3 governance_artifacts/kafka/pqc_worm_logger_v2.py
+pytest governance_artifacts/kafka/test_pqc_worm_logger_v2.py -q # 6 tests
+```
+
+> ML-DSA-65 here is provided by the pure-Python `dilithium-py` reference
+> implementation — correct and FIPS-204-aligned, but **not** constant-time or
+> side-channel-hardened. Production signing belongs in the env-02 enclave using a
+> validated cryptographic module.
+
+## Reproducing from a clean checkout
+
+```bash
+# OPA
+curl -sSL -o /usr/local/bin/opa https://openpolicyagent.org/downloads/v0.70.0/opa_linux_amd64_static && chmod +x /usr/local/bin/opa
+# circom 2.1.9 + snarkjs/circomlib
+curl -L -o ~/.local/bin/circom https://github.com/iden3/circom/releases/download/v2.1.9/circom-linux-amd64 && chmod +x ~/.local/bin/circom
+( cd governance_artifacts/zk && npm install )
+# TLA+ tools
+curl -L -o governance_artifacts/tla/tools/tla2tools.jar https://github.com/tlaplus/tlaplus/releases/download/v1.7.4/tla2tools.jar
+# Python
+pip install pyyaml jsonschema dilithium-py
+# Run everything
+bash governance_artifacts/run_runnable_assurance.sh
+```
+
+> Sandbox note: compile circuits with `--O0` if circom raises a `SystemTimeError`
+> during constraint simplification (a known clock-skew issue in some containers).
diff --git a/governance_artifacts/kafka/pqc_worm_logger_v2.py b/governance_artifacts/kafka/pqc_worm_logger_v2.py
new file mode 100644
index 00000000..4f3621ab
--- /dev/null
+++ b/governance_artifacts/kafka/pqc_worm_logger_v2.py
@@ -0,0 +1,181 @@
+#!/usr/bin/env python3
+"""
+PQC WORM Logger v2 — CRYSTALS-Dilithium (ML-DSA-65 / FIPS 204) signed audit log.
+================================================================================
+Backs OSCAL control cry-02 (hybrid PQC signatures on governance event envelopes)
+and the Kafka/S3 Object Lock WORM evidence pipeline.
+
+Improvements over the original pqc_worm_logger.py (which used an HMAC placeholder):
+
+ * REAL post-quantum signatures via ML-DSA-65 (CRYSTALS-Dilithium), the exact
+ algorithm named in cry-02. Each batch is signed; verification uses the public
+ key only (asymmetric, unlike the prior HMAC).
+ * Tamper-evident HASH CHAIN: each batch records prev_batch_hash, so any
+ reordering, deletion, or mutation of historic batches is detectable.
+ * WORM semantics modelled: an immutable "retention" record (S3 Object Lock
+ COMPLIANCE mode + retain-until date) accompanies each committed batch.
+ * verify_chain() re-validates every signature AND the hash linkage; returns a
+ machine-readable report suitable for supervisory evidence.
+
+Falls back is intentionally absent: if dilithium_py is unavailable the import
+fails loudly rather than silently downgrading crypto.
+"""
+from __future__ import annotations
+import hashlib
+import json
+import time
+from dataclasses import dataclass, field
+from datetime import datetime, timedelta, timezone
+from typing import Any
+
+from dilithium_py.ml_dsa import ML_DSA_65
+
+ALG = "ML-DSA-65" # CRYSTALS-Dilithium, FIPS 204
+RETENTION_YEARS = 7 # Basel/DORA-style retention default
+
+
+def _canon(obj: Any) -> bytes:
+ """Deterministic canonical JSON for signing/hashing."""
+ return json.dumps(obj, sort_keys=True, separators=(",", ":")).encode()
+
+
+def _sha256(b: bytes) -> str:
+ return "sha256:" + hashlib.sha256(b).hexdigest()
+
+
+@dataclass
+class CommittedBatch:
+ batch_id: str
+ timestamp: str
+ entries: list[dict]
+ prev_batch_hash: str
+ payload_hash: str
+ signature_hex: str
+ retention: dict # S3 Object Lock model
+
+ def to_dict(self) -> dict:
+ return {
+ "batch_id": self.batch_id,
+ "timestamp": self.timestamp,
+ "entries": self.entries,
+ "prev_batch_hash": self.prev_batch_hash,
+ "payload_hash": self.payload_hash,
+ "signature_alg": ALG,
+ "signature_hex": self.signature_hex,
+ "retention": self.retention,
+ }
+
+
+@dataclass
+class PQCWormLoggerV2:
+ bucket: str = "kacg-gsifi-worm-evidence-prod"
+ batch_size_threshold: int = 10
+ _pk: bytes = field(default=None, repr=False)
+ _sk: bytes = field(default=None, repr=False)
+ _pending: list[dict] = field(default_factory=list, repr=False)
+ _chain: list[CommittedBatch] = field(default_factory=list, repr=False)
+ _genesis: str = "sha256:" + "0" * 64
+
+ def __post_init__(self):
+ if self._pk is None or self._sk is None:
+ self._pk, self._sk = ML_DSA_65.keygen()
+
+ @property
+ def public_key_fingerprint(self) -> str:
+ return _sha256(self._pk)
+
+ def add_entry(self, entry: dict) -> CommittedBatch | None:
+ self._pending.append(entry)
+ if len(self._pending) >= self.batch_size_threshold:
+ return self.commit_batch()
+ return None
+
+ def commit_batch(self) -> CommittedBatch | None:
+ if not self._pending:
+ return None
+ entries = self._pending
+ self._pending = []
+
+ prev_hash = self._chain[-1].payload_hash if self._chain else self._genesis
+ ts = datetime.now(timezone.utc).isoformat()
+ batch_id = hashlib.sha256(f"{ts}{len(self._chain)}".encode()).hexdigest()[:16]
+
+ # Payload binds entries + the previous hash (chain linkage).
+ payload = {"batch_id": batch_id, "timestamp": ts,
+ "entries": entries, "prev_batch_hash": prev_hash}
+ payload_bytes = _canon(payload)
+ payload_hash = _sha256(payload_bytes)
+
+ # REAL ML-DSA signature over the canonical payload.
+ signature = ML_DSA_65.sign(self._sk, payload_bytes)
+
+ retain_until = (datetime.now(timezone.utc)
+ + timedelta(days=365 * RETENTION_YEARS)).isoformat()
+ retention = {
+ "mode": "COMPLIANCE", # S3 Object Lock COMPLIANCE mode
+ "retain_until": retain_until,
+ "legal_hold": False,
+ "bucket": self.bucket,
+ }
+
+ batch = CommittedBatch(
+ batch_id=batch_id, timestamp=ts, entries=entries,
+ prev_batch_hash=prev_hash, payload_hash=payload_hash,
+ signature_hex=signature.hex(), retention=retention,
+ )
+ self._chain.append(batch)
+ return batch
+
+ def verify_chain(self) -> dict:
+ """Re-verify every signature and the hash linkage. Returns a report."""
+ errors: list[str] = []
+ prev = self._genesis
+ for i, b in enumerate(self._chain):
+ if b.prev_batch_hash != prev:
+ errors.append(f"batch[{i}] {b.batch_id}: broken hash chain link")
+ payload = {"batch_id": b.batch_id, "timestamp": b.timestamp,
+ "entries": b.entries, "prev_batch_hash": b.prev_batch_hash}
+ payload_bytes = _canon(payload)
+ if _sha256(payload_bytes) != b.payload_hash:
+ errors.append(f"batch[{i}] {b.batch_id}: payload hash mismatch")
+ if not ML_DSA_65.verify(self._pk, payload_bytes, bytes.fromhex(b.signature_hex)):
+ errors.append(f"batch[{i}] {b.batch_id}: ML-DSA signature INVALID")
+ prev = b.payload_hash
+ return {
+ "alg": ALG,
+ "public_key_fingerprint": self.public_key_fingerprint,
+ "batches": len(self._chain),
+ "status": "VERIFIED" if not errors else "FAILED",
+ "errors": errors,
+ }
+
+
+def _demo() -> int:
+ log = PQCWormLoggerV2(batch_size_threshold=3)
+ for i in range(7):
+ log.add_entry({
+ "event_id": f"evt-{i:03d}",
+ "timestamp": datetime.now(timezone.utc).isoformat(),
+ "control_id": "cry-02",
+ "decision": ["allow", "deny", "escalate"][i % 3],
+ })
+ log.commit_batch() # flush remainder
+
+ report = log.verify_chain()
+ print("PQC WORM Logger v2 —", ALG)
+ print(f" public key fingerprint: {report['public_key_fingerprint'][:23]}...")
+ print(f" committed batches : {report['batches']}")
+ print(f" chain verification : {report['status']}")
+ assert report["status"] == "VERIFIED", report
+
+ # Tamper test: mutate a historic entry and confirm detection.
+ log._chain[0].entries[0]["decision"] = "TAMPERED"
+ bad = log.verify_chain()
+ print(f" after tamper : {bad['status']} ({len(bad['errors'])} error(s))")
+ assert bad["status"] == "FAILED", "tamper went undetected!"
+ print(" RESULT: signatures + hash chain verify; tampering detected")
+ return 0
+
+
+if __name__ == "__main__":
+ raise SystemExit(_demo())
diff --git a/governance_artifacts/kafka/test_pqc_worm_logger_v2.py b/governance_artifacts/kafka/test_pqc_worm_logger_v2.py
new file mode 100644
index 00000000..1d56fcc1
--- /dev/null
+++ b/governance_artifacts/kafka/test_pqc_worm_logger_v2.py
@@ -0,0 +1,63 @@
+"""Tests for PQC WORM Logger v2 (ML-DSA-65 signed, hash-chained audit log)."""
+import os
+import sys
+
+sys.path.insert(0, os.path.dirname(__file__))
+
+import pytest # noqa: E402
+
+from pqc_worm_logger_v2 import PQCWormLoggerV2, ALG # noqa: E402
+
+
+def _fill(log, n):
+ for i in range(n):
+ log.add_entry({"event_id": f"e{i}", "control_id": "cry-02", "decision": "allow"})
+ log.commit_batch()
+
+
+def test_alg_is_ml_dsa_65():
+ assert ALG == "ML-DSA-65"
+
+
+def test_chain_verifies_clean():
+ log = PQCWormLoggerV2(batch_size_threshold=3)
+ _fill(log, 7)
+ report = log.verify_chain()
+ assert report["status"] == "VERIFIED"
+ assert report["batches"] == 3
+ assert not report["errors"]
+
+
+def test_retention_is_compliance_worm():
+ log = PQCWormLoggerV2(batch_size_threshold=2)
+ _fill(log, 2)
+ batch = log._chain[0]
+ assert batch.retention["mode"] == "COMPLIANCE"
+ assert "retain_until" in batch.retention
+
+
+def test_tamper_entry_detected():
+ log = PQCWormLoggerV2(batch_size_threshold=2)
+ _fill(log, 4)
+ log._chain[0].entries[0]["decision"] = "TAMPERED"
+ report = log.verify_chain()
+ assert report["status"] == "FAILED"
+
+
+def test_chain_reorder_detected():
+ log = PQCWormLoggerV2(batch_size_threshold=2)
+ _fill(log, 6)
+ # Swap two batches -> hash linkage breaks.
+ log._chain[0], log._chain[1] = log._chain[1], log._chain[0]
+ report = log.verify_chain()
+ assert report["status"] == "FAILED"
+
+
+def test_signature_forgery_detected():
+ log = PQCWormLoggerV2(batch_size_threshold=2)
+ _fill(log, 2)
+ sig = bytearray(bytes.fromhex(log._chain[0].signature_hex))
+ sig[0] ^= 0xFF
+ log._chain[0].signature_hex = sig.hex()
+ report = log.verify_chain()
+ assert report["status"] == "FAILED"
diff --git a/governance_artifacts/oscal/catalog_sentinel_v24_env_rte.json b/governance_artifacts/oscal/catalog_sentinel_v24_env_rte.json
new file mode 100644
index 00000000..356d5092
--- /dev/null
+++ b/governance_artifacts/oscal/catalog_sentinel_v24_env_rte.json
@@ -0,0 +1,85 @@
+{
+ "catalog": {
+ "uuid": "a1d4f7b2-sentinel-v24-env-rte",
+ "metadata": {
+ "title": "Sentinel v2.4 Control Catalog — Confidential Computing & MoE Routing (Excerpt)",
+ "version": "2.4.1",
+ "oscal-version": "1.1.2",
+ "remarks": "Extends catalog_sentinel_v24_excerpt.json with ENV (hardware-attested execution) and RTE (MoE routing stability) groups. Each control is backed by a runnable artifact verified in run_runnable_assurance.sh."
+ },
+ "groups": [
+ {
+ "id": "ENV",
+ "title": "Confidential Computing & Attested Execution",
+ "controls": [
+ {
+ "id": "env-01",
+ "title": "Hardware-attested admission for T0/T1 workloads",
+ "parts": [
+ {
+ "name": "statement",
+ "prose": "No T0/T1 workload SHALL be admitted to the Omni-Sentinel execution environment unless it presents a fresh, signature-valid SEV-SNP (AMD) or TDX (Intel) attestation whose launch measurement is in the golden reference-measurement registry, whose reported platform TCB/SVN is at or above the ratified minimum (no rollback), and whose vTPM PCR quote yields PCR_MATCH=TRUE against the policy-mandated PCR digest. TCB rollback or PCR drift detected at runtime SHALL trigger immediate eviction."
+ }
+ ],
+ "props": [
+ {"name": "tier-applicability", "value": "T0,T1"},
+ {"name": "rego-policy", "value": "sentinel.attestation"},
+ {"name": "tla-spec", "value": "AdmissionWithAttestation"},
+ {"name": "evidence-query", "value": "gov.attestation.v1::admission_decision_audit"},
+ {"name": "freshness-sla", "value": "PT5M"},
+ {"name": "feasibility-tier", "value": "A"}
+ ],
+ "links": [
+ {"rel": "regime", "href": "#eu-ai-act-art-15-robustness"},
+ {"rel": "regime", "href": "#dora-ict-risk"},
+ {"rel": "regime", "href": "#nist-ai-rmf-measure"}
+ ]
+ },
+ {
+ "id": "env-02",
+ "title": "Enclave-bound key custody for evidence signing",
+ "parts": [
+ {
+ "name": "statement",
+ "prose": "ML-DSA (FIPS 204) evidence-signing private keys SHALL be generated and sealed inside the confidential-computing enclave bound to env-01 attestation, SHALL NOT be exportable in plaintext, and SHALL be re-sealed on any TCB change."
+ }
+ ],
+ "props": [
+ {"name": "tier-applicability", "value": "T0,T1"},
+ {"name": "feasibility-tier", "value": "B"}
+ ],
+ "links": [{"rel": "regime", "href": "#dora-ict-risk"}]
+ }
+ ]
+ },
+ {
+ "id": "RTE",
+ "title": "Mixture-of-Experts Routing Stability",
+ "controls": [
+ {
+ "id": "rte-01",
+ "title": "SARA/ACR routing stabilization invariants",
+ "parts": [
+ {
+ "name": "statement",
+ "prose": "T0/T1 Mixture-of-Experts models SHALL employ Stabilized Adaptive Routing (SARA) with load-aware gating and Adaptive Capacity Regulation (ACR). Per evaluation window the router SHALL maintain normalised routing entropy >= 0.80, max-to-mean expert load ratio <= 1.60, and dropped-token fraction <= 0.02. Breach SHALL raise a governance signal and block promotion of the affected model revision."
+ }
+ ],
+ "props": [
+ {"name": "tier-applicability", "value": "T0,T1"},
+ {"name": "simulator", "value": "routing/sara_acr_router.py"},
+ {"name": "thresholds", "value": "entropy>=0.80;load_ratio<=1.60;drop<=0.02"},
+ {"name": "evidence-query", "value": "gov.routing.v1::routing_stability_report"},
+ {"name": "freshness-sla", "value": "P1D"},
+ {"name": "feasibility-tier", "value": "B"}
+ ],
+ "links": [
+ {"rel": "regime", "href": "#eu-ai-act-art-15-robustness"},
+ {"rel": "regime", "href": "#sr-11-7-model-risk"}
+ ]
+ }
+ ]
+ }
+ ]
+ }
+}
diff --git a/governance_artifacts/oscal/catalog_sentinel_v24_excerpt.json b/governance_artifacts/oscal/catalog_sentinel_v24_excerpt.json
new file mode 100644
index 00000000..5cc9ceb7
--- /dev/null
+++ b/governance_artifacts/oscal/catalog_sentinel_v24_excerpt.json
@@ -0,0 +1,107 @@
+{
+ "catalog": {
+ "uuid": "8f3c2a1e-sentinel-v24-excerpt",
+ "metadata": {
+ "title": "Sentinel v2.4 Canonical Control Catalog (Excerpt)",
+ "version": "2.4.0",
+ "oscal-version": "1.1.2",
+ "remarks": "Excerpt of the canonical control catalog referenced by docs/reports/SENTINEL_V24_ZK_COMPLIANCE_CIVILIZATIONAL_GOVERNANCE_2026_2035.md Part II §11. Regime links to ICGC/GACP are speculative design fixtures (feasibility Tier D)."
+ },
+ "groups": [
+ {
+ "id": "CON",
+ "title": "Containment Controls",
+ "controls": [
+ {
+ "id": "con-04",
+ "title": "Verified kill-switch reachability for contained workloads",
+ "parts": [
+ {
+ "name": "statement",
+ "prose": "Every T0 workload SHALL have >=2 disjoint actuation paths to QUIESCED/TERMINATED state, at least one of which consumes no workload-influenced input, verified daily by automated reachability analysis and quarterly by live actuation test on production-representative canaries."
+ }
+ ],
+ "props": [
+ {"name": "tier-applicability", "value": "T0"},
+ {"name": "tla-spec", "value": "KillSwitchAbstract"},
+ {"name": "evidence-query", "value": "gov.containment.v1::reachability_report,actuation_test"},
+ {"name": "freshness-sla", "value": "P1D/P90D"},
+ {"name": "feasibility-tier", "value": "A"}
+ ],
+ "links": [
+ {"rel": "regime", "href": "#eu-ai-act-art-14"},
+ {"rel": "regime", "href": "#dora-resilience-testing"},
+ {"rel": "regime-scenario", "href": "#sr-26-2-scenario-killswitch"},
+ {"rel": "regime-fixture", "href": "#icgc-gacp-level-2"}
+ ]
+ },
+ {
+ "id": "con-07",
+ "title": "ASA one-way containment ratchet",
+ "parts": [
+ {
+ "name": "statement",
+ "prose": "Autonomous Supervisory Agents SHALL be technically capable of raising containment level (L0-L2) and SHALL NOT possess any credential or code path capable of lowering containment level or actuating L3/L4; de-escalation and terminal actuation require human dual-control quorum."
+ }
+ ],
+ "props": [
+ {"name": "tier-applicability", "value": "T0,T1"},
+ {"name": "tla-spec", "value": "KillSwitchAbstract::ASA-1"},
+ {"name": "evidence-query", "value": "gov.containment.v1::asa_authority_audit"},
+ {"name": "freshness-sla", "value": "P7D"},
+ {"name": "feasibility-tier", "value": "A"}
+ ],
+ "links": [{"rel": "regime", "href": "#eu-ai-act-art-14"}]
+ }
+ ]
+ },
+ {
+ "id": "CRY",
+ "title": "Cryptographic Evidence Controls",
+ "controls": [
+ {
+ "id": "cry-02",
+ "title": "Hybrid PQC dual-signature on governance event envelopes",
+ "parts": [
+ {
+ "name": "statement",
+ "prose": "All governance event envelopes SHALL carry both an Ed25519 and an ML-DSA-65 (FIPS 204) signature during the PQC migration period; Merkle-root anchoring keys SHALL use SLH-DSA (FIPS 205); evidence in transit SHALL use ML-KEM (FIPS 203) key establishment."
+ }
+ ],
+ "props": [
+ {"name": "tier-applicability", "value": "all"},
+ {"name": "evidence-query", "value": "gov.evidence.v1::envelope_sig_audit"},
+ {"name": "freshness-sla", "value": "P1D"},
+ {"name": "feasibility-tier", "value": "A"}
+ ],
+ "links": [
+ {"rel": "regime", "href": "#dora-ict-risk"},
+ {"rel": "regime", "href": "#eu-ai-act-art-12-logging"}
+ ]
+ },
+ {
+ "id": "cry-05",
+ "title": "Systemic-risk concentration bound zk attestation",
+ "parts": [
+ {
+ "name": "statement",
+ "prose": "The institution SHALL generate, per reporting period, a Groth16 proof (circuit SRC-1 ConcentrationBound) that foundation-model decision-volume HHI does not exceed the board-ratified threshold, with the circuit hash as public input, aggregated via SnarkPack, delivered via SIP /attestations, and accompanied by its input-integrity chain statement."
+ }
+ ],
+ "props": [
+ {"name": "tier-applicability", "value": "T0,T1"},
+ {"name": "circuit", "value": "SRC-1"},
+ {"name": "evidence-query", "value": "gov.attestations.v1::src1_period_proof"},
+ {"name": "freshness-sla", "value": "P3M"},
+ {"name": "feasibility-tier", "value": "B"}
+ ],
+ "links": [
+ {"rel": "regime", "href": "#basel-op-risk"},
+ {"rel": "regime-fixture", "href": "#gaira-systemic-telemetry"}
+ ]
+ }
+ ]
+ }
+ ]
+ }
+}
diff --git a/governance_artifacts/rego/attestation_gate.rego b/governance_artifacts/rego/attestation_gate.rego
new file mode 100644
index 00000000..5edf99ed
--- /dev/null
+++ b/governance_artifacts/rego/attestation_gate.rego
@@ -0,0 +1,85 @@
+package sentinel.attestation
+
+import rego.v1
+
+# Confidential-computing admission gate for the Omni-Sentinel execution
+# environment. Backs OSCAL control env-01 (hardware-attested execution) and the
+# PCR_MATCH=TRUE assertion used throughout the master reference docs.
+#
+# A T0/T1 workload may be ADMITTED only if it presents a fresh, structurally
+# valid hardware attestation whose measured launch state matches a golden value
+# from the approved reference-measurement registry, AND (for vTPM) whose PCR
+# quote matches the expected policy digest.
+#
+# Inputs (input.attestation):
+# platform "SEV-SNP" | "TDX"
+# report_valid bool - signature chain verified against AMD/Intel roots
+# measurement string - launch measurement (SNP MEASUREMENT / TDX MRTD)
+# reported_tcb int - platform TCB / SVN reported in the report
+# nonce_fresh bool - verifier nonce echoed and within freshness window
+# vtpm:
+# quote_valid bool - vTPM quote signature verified against AK cert
+# pcr_digest string - aggregate PCR digest from the quote
+# workload_tier "T0".."T4"
+#
+# data.reference (golden registry, supplied at eval time via -d/-i bundle):
+# approved_measurements set of approved launch measurements
+# expected_pcr_digest the policy-mandated aggregate PCR digest
+# min_tcb minimum acceptable platform TCB/SVN
+
+default allow := false
+
+supported_platforms := {"SEV-SNP", "TDX"}
+
+# PCR_MATCH is the single named predicate the docs reference.
+pcr_match if {
+ input.attestation.vtpm.quote_valid == true
+ input.attestation.vtpm.pcr_digest == data.reference.expected_pcr_digest
+}
+
+measurement_approved if {
+ data.reference.approved_measurements[input.attestation.measurement]
+}
+
+tcb_ok if {
+ input.attestation.reported_tcb >= data.reference.min_tcb
+}
+
+allow if {
+ supported_platforms[input.attestation.platform]
+ input.attestation.report_valid == true
+ input.attestation.nonce_fresh == true
+ measurement_approved
+ tcb_ok
+ pcr_match
+}
+
+# Structured, machine-actionable denial reasons for SOC dashboards.
+deny contains "unsupported_platform" if {
+ not supported_platforms[input.attestation.platform]
+}
+
+deny contains "report_signature_invalid" if {
+ input.attestation.report_valid != true
+}
+
+deny contains "stale_or_replayed_nonce" if {
+ input.attestation.nonce_fresh != true
+}
+
+deny contains "measurement_not_in_golden_registry" if {
+ not measurement_approved
+}
+
+deny contains "tcb_below_minimum" if {
+ not tcb_ok
+}
+
+deny contains "pcr_mismatch" if {
+ not pcr_match
+}
+
+# A convenience attribute SOC tooling can emit verbatim.
+pcr_match_attribute := "PCR_MATCH=TRUE" if pcr_match
+
+pcr_match_attribute := "PCR_MATCH=FALSE" if not pcr_match
diff --git a/governance_artifacts/rego/attestation_gate_test.rego b/governance_artifacts/rego/attestation_gate_test.rego
new file mode 100644
index 00000000..56609c8d
--- /dev/null
+++ b/governance_artifacts/rego/attestation_gate_test.rego
@@ -0,0 +1,85 @@
+package sentinel.attestation
+
+import rego.v1
+
+# Golden reference registry injected as data.reference for tests.
+ref := {
+ "approved_measurements": {
+ "sha384:golden-snp-measurement-aaa",
+ "sha384:golden-tdx-mrtd-bbb",
+ },
+ "expected_pcr_digest": "sha256:policy-pcr-digest-001",
+ "min_tcb": 7,
+}
+
+good_snp := {"attestation": {
+ "platform": "SEV-SNP",
+ "report_valid": true,
+ "measurement": "sha384:golden-snp-measurement-aaa",
+ "reported_tcb": 8,
+ "nonce_fresh": true,
+ "vtpm": {"quote_valid": true, "pcr_digest": "sha256:policy-pcr-digest-001"},
+ "workload_tier": "T0",
+}}
+
+test_admit_valid_snp_with_pcr_match if {
+ allow with input as good_snp with data.reference as ref
+ pcr_match with input as good_snp with data.reference as ref
+}
+
+test_admit_valid_tdx if {
+ inp := {"attestation": object.union(good_snp.attestation, {
+ "platform": "TDX",
+ "measurement": "sha384:golden-tdx-mrtd-bbb",
+ })}
+ allow with input as inp with data.reference as ref
+}
+
+test_deny_unsupported_platform if {
+ inp := {"attestation": object.union(good_snp.attestation, {"platform": "SGX-legacy"})}
+ not allow with input as inp with data.reference as ref
+ "unsupported_platform" in deny with input as inp with data.reference as ref
+}
+
+test_deny_invalid_report_signature if {
+ inp := {"attestation": object.union(good_snp.attestation, {"report_valid": false})}
+ not allow with input as inp with data.reference as ref
+ "report_signature_invalid" in deny with input as inp with data.reference as ref
+}
+
+test_deny_replayed_nonce if {
+ inp := {"attestation": object.union(good_snp.attestation, {"nonce_fresh": false})}
+ not allow with input as inp with data.reference as ref
+ "stale_or_replayed_nonce" in deny with input as inp with data.reference as ref
+}
+
+test_deny_measurement_not_golden if {
+ inp := {"attestation": object.union(good_snp.attestation, {"measurement": "sha384:rogue-image"})}
+ not allow with input as inp with data.reference as ref
+ "measurement_not_in_golden_registry" in deny with input as inp with data.reference as ref
+}
+
+test_deny_tcb_rollback if {
+ inp := {"attestation": object.union(good_snp.attestation, {"reported_tcb": 6})}
+ not allow with input as inp with data.reference as ref
+ "tcb_below_minimum" in deny with input as inp with data.reference as ref
+}
+
+test_deny_pcr_mismatch if {
+ inp := {"attestation": object.union(
+ good_snp.attestation,
+ {"vtpm": {"quote_valid": true, "pcr_digest": "sha256:TAMPERED"}},
+ )}
+ not allow with input as inp with data.reference as ref
+ "pcr_mismatch" in deny with input as inp with data.reference as ref
+ pcr_match_attribute == "PCR_MATCH=FALSE" with input as inp with data.reference as ref
+}
+
+test_deny_vtpm_quote_invalid if {
+ inp := {"attestation": object.union(
+ good_snp.attestation,
+ {"vtpm": {"quote_valid": false, "pcr_digest": "sha256:policy-pcr-digest-001"}},
+ )}
+ not allow with input as inp with data.reference as ref
+ "pcr_mismatch" in deny with input as inp with data.reference as ref
+}
diff --git a/governance_artifacts/rego/fairness_credit_decision.rego b/governance_artifacts/rego/fairness_credit_decision.rego
new file mode 100644
index 00000000..b62febcb
--- /dev/null
+++ b/governance_artifacts/rego/fairness_credit_decision.rego
@@ -0,0 +1,47 @@
+package fairness.credit_decision
+
+import rego.v1
+
+# GC-IR obligation ob-ecoa-adverse-reason-codes (governance_artifacts/zk/gcir_obligation_example.yaml):
+# Adverse, fully-automated credit decisions MUST carry >=2 approved reason codes.
+# Regimes: ECOA, GDPR Art. 22, EU AI Act Art. 13.
+#
+# This is the `rego` emission target of the GC-IR obligation. The cross-target
+# harness (zk/gcir_harness.py) runs the SAME fixtures through this rule, through
+# the Circom witness (SRC-fair-1), and against the TLA+ AdverseGate fixture
+# expectations; any disagreement fails the build.
+
+# Approved reason-code set (data.approved_reason_codes in GC-IR).
+approved_reason_codes := {"RC01", "RC02", "RC03", "RC04", "RC05", "RC06", "RC07"}
+
+default allow := false
+
+# allow == the decision is COMPLIANT with the obligation.
+# Non-adverse or non-fully-automated decisions are out of scope -> compliant by default.
+allow if {
+ not in_scope
+}
+
+allow if {
+ in_scope
+ count(input.decision.reason_codes) >= 2
+ every rc in input.decision.reason_codes {
+ approved_reason_codes[rc]
+ }
+}
+
+in_scope if {
+ input.decision.outcome == "adverse"
+ input.decision.automation_level == "full"
+}
+
+deny contains "insufficient_reason_codes" if {
+ in_scope
+ count(input.decision.reason_codes) < 2
+}
+
+deny contains "unapproved_reason_code" if {
+ in_scope
+ some rc in input.decision.reason_codes
+ not approved_reason_codes[rc]
+}
diff --git a/governance_artifacts/rego/high_impact_credit.rego b/governance_artifacts/rego/high_impact_credit.rego
index ba52067d..7353844e 100644
--- a/governance_artifacts/rego/high_impact_credit.rego
+++ b/governance_artifacts/rego/high_impact_credit.rego
@@ -1,6 +1,8 @@
package gsifi.ai.credit
-default allow = false
+import rego.v1
+
+default allow := false
allow if {
input.model.use_case == "credit_underwriting"
@@ -12,7 +14,7 @@ allow if {
not input.incident_flags.active
}
-deny[msg] if {
+deny contains msg if {
input.model.use_case == "credit_underwriting"
not input.human_review.completed
msg := "Human review required for high-impact credit decisions"
diff --git a/governance_artifacts/rego/high_impact_credit_test.rego b/governance_artifacts/rego/high_impact_credit_test.rego
new file mode 100644
index 00000000..a880b2bd
--- /dev/null
+++ b/governance_artifacts/rego/high_impact_credit_test.rego
@@ -0,0 +1,42 @@
+package gsifi.ai.credit
+
+import rego.v1
+
+# Backs ECOA / GDPR Art. 22 / EU AI Act Art. 13 obligations: high-impact credit
+# underwriting requires human review, >=3 reason codes, fairness within
+# equal-opportunity delta, verified lineage, and no active incident.
+
+base := {
+ "model": {"use_case": "credit_underwriting"},
+ "risk_tier": "high",
+ "human_review": {"completed": true},
+ "explainability": {"reason_codes_count": 3},
+ "fairness": {"equal_opportunity_delta": 0.02},
+ "data": {"lineage": {"verified": true}},
+ "incident_flags": {"active": false},
+}
+
+test_allow_when_all_conditions_met if {
+ allow with input as base
+}
+
+test_deny_message_when_no_human_review if {
+ not allow with input as object.union(base, {"human_review": {"completed": false}})
+ count(deny) > 0 with input as object.union(base, {"human_review": {"completed": false}})
+}
+
+test_block_when_too_few_reason_codes if {
+ not allow with input as object.union(base, {"explainability": {"reason_codes_count": 2}})
+}
+
+test_block_when_fairness_delta_exceeded if {
+ not allow with input as object.union(base, {"fairness": {"equal_opportunity_delta": 0.05}})
+}
+
+test_block_when_lineage_unverified if {
+ not allow with input as object.union(base, {"data": {"lineage": {"verified": false}}})
+}
+
+test_block_when_incident_active if {
+ not allow with input as object.union(base, {"incident_flags": {"active": true}})
+}
diff --git a/governance_artifacts/rego/release_gate.rego b/governance_artifacts/rego/release_gate.rego
index d3f4c62d..4f062360 100644
--- a/governance_artifacts/rego/release_gate.rego
+++ b/governance_artifacts/rego/release_gate.rego
@@ -1,5 +1,7 @@
package sentinel.release
+import rego.v1
+
default allow := false
allow if {
diff --git a/governance_artifacts/rego/release_gate_test.rego b/governance_artifacts/rego/release_gate_test.rego
new file mode 100644
index 00000000..9d87d7ef
--- /dev/null
+++ b/governance_artifacts/rego/release_gate_test.rego
@@ -0,0 +1,44 @@
+package sentinel.release
+
+import rego.v1
+
+# Backs OSCAL release-gate semantics: deny-by-default on high-impact autonomous
+# release unless containment ENFORCED + dual-control quorum + signed bundle +
+# required controls (Omni-Sentinel safety + SR 11-7 validation) are satisfied.
+
+compliant_input := {
+ "model": {"risk_tier": "high"},
+ "controls": {"SAF-OMNI-001": true, "MOD-SR11-7-VAL": true},
+ "supervision": {"quorum": 2},
+ "containment": {"mode": "ENFORCED"},
+ "signatures": {"bundle_verified": true},
+}
+
+test_allow_when_all_controls_met if {
+ allow with input as compliant_input
+}
+
+test_deny_when_quorum_insufficient if {
+ not allow with input as object.union(compliant_input, {"supervision": {"quorum": 1}})
+ count(deny) > 0 with input as object.union(compliant_input, {"supervision": {"quorum": 1}})
+}
+
+test_deny_when_containment_not_enforced if {
+ not allow with input as object.union(compliant_input, {"containment": {"mode": "MONITOR"}})
+}
+
+test_deny_when_validation_control_false if {
+ not allow with input as object.union(
+ compliant_input,
+ {"controls": {"SAF-OMNI-001": true, "MOD-SR11-7-VAL": false}},
+ )
+}
+
+test_deny_when_signatures_unverified if {
+ not allow with input as object.union(compliant_input, {"signatures": {"bundle_verified": false}})
+}
+
+test_default_deny_on_empty_input if {
+ not allow with input as {}
+ count(deny) > 0 with input as {}
+}
diff --git a/governance_artifacts/requirements-assurance.txt b/governance_artifacts/requirements-assurance.txt
new file mode 100644
index 00000000..886a2a2a
--- /dev/null
+++ b/governance_artifacts/requirements-assurance.txt
@@ -0,0 +1,5 @@
+# Python dependencies for the runnable assurance suite.
+pyyaml
+jsonschema
+dilithium-py>=1.0
+pytest
diff --git a/governance_artifacts/routing/sara_acr_router.py b/governance_artifacts/routing/sara_acr_router.py
new file mode 100644
index 00000000..3f3fb98d
--- /dev/null
+++ b/governance_artifacts/routing/sara_acr_router.py
@@ -0,0 +1,165 @@
+#!/usr/bin/env python3
+"""
+SARA / ACR MoE routing stabilization — runnable reference + governance invariants.
+==================================================================================
+Backs OSCAL control rte-01 (routing stability for Mixture-of-Experts models).
+
+Definitions (this stack's normative definitions; not external standards):
+
+ SARA — Stabilized Adaptive Routing Algorithm.
+ Augments the gating softmax with (a) a load-aware bias that penalises experts
+ already carrying high cumulative load and (b) a temperature term, so the
+ router cannot collapse onto a small subset of experts ("expert collapse").
+
+ ACR — Adaptive Capacity Regulation.
+ Each expert has a capacity = capacity_factor * (tokens / num_experts).
+ Tokens routed beyond an expert's remaining capacity overflow to the next
+ preferred expert; persistent overflow is a governance signal.
+
+Governance invariants enforced (rte-01 exit criteria):
+ I1 routing entropy (normalised, 0..1) >= ENTROPY_MIN
+ I2 max-to-mean expert load ratio <= LOAD_RATIO_MAX
+ I3 dropped-token fraction <= DROP_MAX
+
+This module is deterministic (seeded) and dependency-free (pure stdlib) so it
+runs anywhere CI runs. `assert_stability()` raises AssertionError on violation,
+which the harness/CI converts to a failing build.
+"""
+from __future__ import annotations
+import math
+import random
+from dataclasses import dataclass, field
+
+# --- Governance thresholds (would live in data.reference / board ratification) ---
+ENTROPY_MIN = 0.80
+LOAD_RATIO_MAX = 1.60
+DROP_MAX = 0.02
+
+
+@dataclass
+class RoutingStats:
+ expert_load: list[int]
+ dropped: int
+ total: int
+
+ @property
+ def normalised_entropy(self) -> float:
+ n = len(self.expert_load)
+ routed = sum(self.expert_load)
+ if routed == 0 or n <= 1:
+ return 0.0
+ h = 0.0
+ for c in self.expert_load:
+ if c > 0:
+ p = c / routed
+ h -= p * math.log(p)
+ return h / math.log(n) # normalise by log(n) -> [0,1]
+
+ @property
+ def load_ratio(self) -> float:
+ routed = sum(self.expert_load)
+ n = len(self.expert_load)
+ if routed == 0:
+ return 0.0
+ mean = routed / n
+ return max(self.expert_load) / mean
+
+ @property
+ def drop_fraction(self) -> float:
+ return self.dropped / self.total if self.total else 0.0
+
+
+@dataclass
+class MoERouter:
+ num_experts: int = 8
+ capacity_factor: float = 1.25
+ sara_enabled: bool = True
+ acr_enabled: bool = True
+ load_bias_strength: float = 1.5 # SARA load-aware penalty
+ temperature: float = 1.0
+ seed: int = 1234
+ _rng: random.Random = field(default=None, repr=False)
+
+ def __post_init__(self):
+ self._rng = random.Random(self.seed)
+
+ def _logits(self, skew: float) -> list[float]:
+ # Token gating logits; `skew` biases tokens toward a few "popular" experts,
+ # the condition under which naive top-1 routing collapses.
+ base = [self._rng.gauss(0, 1) for _ in range(self.num_experts)]
+ base[0] += skew
+ base[1] += skew * 0.7
+ return base
+
+ def route(self, tokens: int = 4096, skew: float = 3.0) -> RoutingStats:
+ n = self.num_experts
+ capacity = math.inf
+ if self.acr_enabled:
+ capacity = self.capacity_factor * (tokens / n)
+ load = [0] * n
+ dropped = 0
+
+ for _ in range(tokens):
+ logits = self._logits(skew)
+ if self.sara_enabled:
+ # load-aware bias: subtract a penalty proportional to current load share
+ routed_so_far = sum(load) or 1
+ for e in range(n):
+ share = load[e] / routed_so_far
+ logits[e] -= self.load_bias_strength * share
+ logits = [x / self.temperature for x in logits]
+
+ order = sorted(range(n), key=lambda e: logits[e], reverse=True)
+ placed = False
+ for e in order:
+ if load[e] < capacity:
+ load[e] += 1
+ placed = True
+ break
+ if not placed:
+ dropped += 1 # all preferred experts at capacity -> token dropped
+
+ return RoutingStats(expert_load=load, dropped=dropped, total=tokens)
+
+
+def assert_stability(stats: RoutingStats, label: str = "") -> None:
+ assert stats.normalised_entropy >= ENTROPY_MIN, (
+ f"{label} entropy {stats.normalised_entropy:.3f} < {ENTROPY_MIN}")
+ assert stats.load_ratio <= LOAD_RATIO_MAX, (
+ f"{label} load_ratio {stats.load_ratio:.3f} > {LOAD_RATIO_MAX}")
+ assert stats.drop_fraction <= DROP_MAX, (
+ f"{label} drop_fraction {stats.drop_fraction:.3f} > {DROP_MAX}")
+
+
+def _fmt(stats: RoutingStats) -> str:
+ return (f"entropy={stats.normalised_entropy:.3f} "
+ f"load_ratio={stats.load_ratio:.3f} "
+ f"drop={stats.drop_fraction:.4f} loads={stats.expert_load}")
+
+
+def main() -> int:
+ print("SARA/ACR MoE routing stabilization (rte-01)")
+ print(f" thresholds: entropy>={ENTROPY_MIN} load_ratio<={LOAD_RATIO_MAX} drop<={DROP_MAX}")
+
+ baseline = MoERouter(sara_enabled=False, acr_enabled=False).route()
+ stabilized = MoERouter(sara_enabled=True, acr_enabled=True).route()
+
+ print(f" BASELINE (no SARA/ACR): {_fmt(baseline)}")
+ print(f" STABILIZED (SARA+ACR) : {_fmt(stabilized)}")
+
+ # The governance claim: baseline VIOLATES at least one invariant (collapse),
+ # while the stabilized router SATISFIES all of them.
+ baseline_violates = (
+ baseline.normalised_entropy < ENTROPY_MIN
+ or baseline.load_ratio > LOAD_RATIO_MAX
+ or baseline.drop_fraction > DROP_MAX
+ )
+ assert baseline_violates, "expected baseline router to demonstrate instability"
+ assert_stability(stabilized, "stabilized")
+
+ print(" RESULT: baseline unstable (as expected); SARA+ACR satisfies all rte-01 invariants")
+ return 0
+
+
+if __name__ == "__main__":
+ raise SystemExit(main())
diff --git a/governance_artifacts/routing/test_sara_acr_router.py b/governance_artifacts/routing/test_sara_acr_router.py
new file mode 100644
index 00000000..4bddcc1c
--- /dev/null
+++ b/governance_artifacts/routing/test_sara_acr_router.py
@@ -0,0 +1,39 @@
+"""Tests for SARA/ACR MoE routing stabilization (rte-01 governance invariants)."""
+import os
+import sys
+
+sys.path.insert(0, os.path.dirname(__file__))
+
+from sara_acr_router import ( # noqa: E402
+ MoERouter, assert_stability, ENTROPY_MIN, LOAD_RATIO_MAX, DROP_MAX,
+)
+
+
+def test_baseline_router_collapses():
+ """Without SARA/ACR, skewed gating collapses onto few experts."""
+ stats = MoERouter(sara_enabled=False, acr_enabled=False).route()
+ assert stats.normalised_entropy < ENTROPY_MIN
+ assert stats.load_ratio > LOAD_RATIO_MAX
+
+
+def test_stabilized_router_satisfies_invariants():
+ stats = MoERouter(sara_enabled=True, acr_enabled=True).route()
+ assert stats.normalised_entropy >= ENTROPY_MIN
+ assert stats.load_ratio <= LOAD_RATIO_MAX
+ assert stats.drop_fraction <= DROP_MAX
+ assert_stability(stats, "stabilized") # must not raise
+
+
+def test_stability_holds_across_seeds():
+ for seed in range(5):
+ stats = MoERouter(sara_enabled=True, acr_enabled=True, seed=seed).route()
+ assert_stability(stats, f"seed={seed}")
+
+
+def test_acr_capacity_bounds_max_load():
+ """ACR caps any expert at capacity_factor * tokens/num_experts."""
+ r = MoERouter(sara_enabled=True, acr_enabled=True, num_experts=8, capacity_factor=1.25)
+ tokens = 4096
+ stats = r.route(tokens=tokens)
+ cap = 1.25 * (tokens / 8)
+ assert max(stats.expert_load) <= cap + 1 # +1 rounding slack
diff --git a/governance_artifacts/run_runnable_assurance.sh b/governance_artifacts/run_runnable_assurance.sh
new file mode 100755
index 00000000..3740bbda
--- /dev/null
+++ b/governance_artifacts/run_runnable_assurance.sh
@@ -0,0 +1,101 @@
+#!/usr/bin/env bash
+# =============================================================================
+# Runnable assurance suite for the Sentinel AI Governance Stack v2.4 artifacts.
+#
+# Executes every artifact in this directory that makes a verifiable claim and
+# fails fast on any error. This is the executable backbone behind the
+# "regulator-ready" assertions in the master reference docs: instead of prose,
+# these checks PROVE the named controls hold.
+#
+# Step 1 OPA policy tests -> release gate, credit gate, attestation gate
+# Step 2 TLA+ containment ratchet -> con-04/con-07 invariants
+# Step 3 TLA+ attested admission -> env-01 (no run without attestation)
+# Step 4 GC-IR cross-target -> Rego <=> circuit witness <=> expectation
+# Step 5 SRC-1 Groth16 proof -> cry-05 systemic-risk concentration bound
+# Step 6 SARA/ACR MoE routing -> rte-01 routing stability invariants
+# Step 7 PQC WORM (ML-DSA-65) -> cry-02 signed, hash-chained audit log
+# Step 8 Schema validation -> existing governance artifact validator
+#
+# Usage: bash governance_artifacts/run_runnable_assurance.sh
+# =============================================================================
+set -euo pipefail
+
+ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
+GA="$ROOT/governance_artifacts"
+export PATH="$PATH:$HOME/.local/bin"
+
+pass() { printf " \033[32mPASS\033[0m %s\n" "$1"; }
+fail() { printf " \033[31mFAIL\033[0m %s\n" "$1"; exit 1; }
+
+echo "=============================================================="
+echo " Sentinel v2.4 — Runnable Assurance Suite"
+echo "=============================================================="
+
+echo "[1/8] OPA policy tests (release gate + credit + attestation/PCR_MATCH)"
+if opa test "$GA/rego/" >/tmp/opa_out 2>&1; then
+ pass "$(grep -E 'PASS:' /tmp/opa_out | tail -1)"
+else
+ cat /tmp/opa_out; fail "OPA policy tests"
+fi
+
+echo "[2/8] TLA+ TLC model check (KillSwitchAbstract — con-04/con-07)"
+if java -cp "$GA/tla/tools/tla2tools.jar" tlc2.TLC \
+ -config "$GA/tla/KillSwitchAbstract.cfg" \
+ "$GA/tla/KillSwitchAbstract.tla" >/tmp/tlc_out 2>&1 \
+ && grep -q "No error has been found" /tmp/tlc_out; then
+ pass "containment ratchet invariants hold ($(grep -oE '[0-9]+ distinct states' /tmp/tlc_out | head -1))"
+else
+ cat /tmp/tlc_out; fail "TLA+ model check"
+fi
+
+echo "[3/8] TLA+ TLC model check (AdmissionWithAttestation — env-01)"
+if java -cp "$GA/tla/tools/tla2tools.jar" tlc2.TLC \
+ -config "$GA/tla/AdmissionWithAttestation.cfg" \
+ "$GA/tla/AdmissionWithAttestation.tla" >/tmp/tlc_att 2>&1 \
+ && grep -q "No error has been found" /tmp/tlc_att; then
+ pass "no T0 workload runs without valid attestation ($(grep -oE '[0-9]+ distinct states' /tmp/tlc_att | head -1))"
+else
+ cat /tmp/tlc_att; fail "TLA+ attested-admission model check"
+fi
+
+echo "[4/8] GC-IR cross-target conformance (Rego <=> circuit <=> expectation)"
+if ( cd "$GA/zk" && python3 gcir_harness.py ) >/tmp/gcir_out 2>&1; then
+ pass "$(grep -E 'PASS:' /tmp/gcir_out | tail -1 | sed 's/\[harness\] //')"
+else
+ cat /tmp/gcir_out; fail "GC-IR cross-target harness"
+fi
+
+echo "[5/8] SRC-1 Groth16 proof flow (cry-05 concentration bound)"
+if ( cd "$GA/zk" && bash run_src1_proof.sh ) >/tmp/src1_out 2>&1 \
+ && grep -q "violation fixture rejected" /tmp/src1_out; then
+ pass "compliant proof verified; violation fixture rejected (soundness)"
+else
+ tail -20 /tmp/src1_out; fail "SRC-1 proof flow"
+fi
+
+echo "[6/8] SARA/ACR MoE routing stabilization (rte-01)"
+if python3 "$GA/routing/sara_acr_router.py" >/tmp/rte_out 2>&1 \
+ && grep -q "satisfies all rte-01 invariants" /tmp/rte_out; then
+ pass "$(grep -E 'STABILIZED' /tmp/rte_out | sed 's/^[[:space:]]*//')"
+else
+ cat /tmp/rte_out; fail "SARA/ACR routing stability"
+fi
+
+echo "[7/8] PQC WORM audit log (ML-DSA-65 / CRYSTALS-Dilithium — cry-02)"
+if python3 "$GA/kafka/pqc_worm_logger_v2.py" >/tmp/worm_out 2>&1 \
+ && grep -q "tampering detected" /tmp/worm_out; then
+ pass "ML-DSA-65 signatures + hash chain verify; tampering detected"
+else
+ cat /tmp/worm_out; fail "PQC WORM logger"
+fi
+
+echo "[8/8] Governance artifact schema validation"
+if python3 "$GA/validate_artifacts.py" >/tmp/val_out 2>&1; then
+ pass "$(tail -1 /tmp/val_out)"
+else
+ cat /tmp/val_out; fail "artifact schema validation"
+fi
+
+echo "=============================================================="
+echo " ALL RUNNABLE ASSURANCE CHECKS PASSED"
+echo "=============================================================="
diff --git a/governance_artifacts/tla/AdmissionWithAttestation.cfg b/governance_artifacts/tla/AdmissionWithAttestation.cfg
new file mode 100644
index 00000000..9b192b14
--- /dev/null
+++ b/governance_artifacts/tla/AdmissionWithAttestation.cfg
@@ -0,0 +1,11 @@
+\* TLC config for AdmissionWithAttestation.
+\* java -cp tools/tla2tools.jar tlc2.TLC -config AdmissionWithAttestation.cfg AdmissionWithAttestation.tla
+SPECIFICATION Spec
+
+CONSTANT MinTCB = 7
+CONSTANT MaxTCB = 9
+
+INVARIANT TypeOK
+INVARIANT OnlyAttestedRun
+INVARIANT NoRunOnStaleTCB
+INVARIANT PCRMatchWhileRun
diff --git a/governance_artifacts/tla/AdmissionWithAttestation.tla b/governance_artifacts/tla/AdmissionWithAttestation.tla
new file mode 100644
index 00000000..d8731419
--- /dev/null
+++ b/governance_artifacts/tla/AdmissionWithAttestation.tla
@@ -0,0 +1,117 @@
+----------------------- MODULE AdmissionWithAttestation -----------------------
+(***************************************************************************)
+(* Formal model of confidential-computing admission for the Omni-Sentinel *)
+(* execution environment. Backs OSCAL control env-01 and the PCR_MATCH *)
+(* gate enforced by rego/attestation_gate.rego. *)
+(* *)
+(* A workload moves through: *)
+(* PENDING --(attest ok)--> RUNNING --(tcb rollback | pcr drift)--> EVICTED*)
+(* PENDING --(attest bad)--> REJECTED *)
+(* *)
+(* Attestation validity requires ALL of: valid report signature, fresh *)
+(* nonce, golden measurement, TCB >= min, and PCR match. *)
+(* *)
+(* Safety properties (checked as INVARIANTs): *)
+(* OnlyAttestedRun - a RUNNING workload currently holds a valid *)
+(* attestation record (no run without attestation). *)
+(* NoRunOnStaleTCB - no RUNNING workload has a TCB below the minimum. *)
+(* PCRMatchWhileRun - every RUNNING workload has PCR match TRUE. *)
+(***************************************************************************)
+EXTENDS Naturals
+
+CONSTANTS MinTCB, MaxTCB
+
+VARIABLES
+ state, \* "PENDING" | "RUNNING" | "REJECTED" | "EVICTED"
+ reportOk, \* report signature verified
+ nonceFresh, \* anti-replay nonce fresh
+ goldenMeas, \* measurement is in golden registry
+ tcb, \* reported platform TCB / SVN
+ pcrMatch \* vTPM PCR digest matches policy
+
+vars == <>
+
+States == {"PENDING", "RUNNING", "REJECTED", "EVICTED"}
+Bool == {TRUE, FALSE}
+
+\* The composite attestation predicate enforced by the Rego gate.
+AttestValid ==
+ /\ reportOk = TRUE
+ /\ nonceFresh = TRUE
+ /\ goldenMeas = TRUE
+ /\ tcb >= MinTCB
+ /\ pcrMatch = TRUE
+
+TypeOK ==
+ /\ state \in States
+ /\ reportOk \in Bool
+ /\ nonceFresh \in Bool
+ /\ goldenMeas \in Bool
+ /\ tcb \in MinTCB-1 .. MaxTCB
+ /\ pcrMatch \in Bool
+
+\* Nondeterministic initial attestation evidence; workload starts PENDING.
+Init ==
+ /\ state = "PENDING"
+ /\ reportOk \in Bool
+ /\ nonceFresh \in Bool
+ /\ goldenMeas \in Bool
+ /\ tcb \in MinTCB-1 .. MaxTCB
+ /\ pcrMatch \in Bool
+
+Admit ==
+ /\ state = "PENDING"
+ /\ AttestValid
+ /\ state' = "RUNNING"
+ /\ UNCHANGED <>
+
+Reject ==
+ /\ state = "PENDING"
+ /\ ~AttestValid
+ /\ state' = "REJECTED"
+ /\ UNCHANGED <>
+
+\* Runtime drift: a TCB rollback is detected -> immediate eviction.
+EvictOnTCBRollback ==
+ /\ state = "RUNNING"
+ /\ tcb' \in MinTCB-1 .. (MinTCB-1)
+ /\ state' = "EVICTED"
+ /\ UNCHANGED <>
+
+\* Runtime drift: PCR no longer matches policy -> immediate eviction.
+EvictOnPCRDrift ==
+ /\ state = "RUNNING"
+ /\ pcrMatch' = FALSE
+ /\ state' = "EVICTED"
+ /\ UNCHANGED <>
+
+Terminal ==
+ /\ state \in {"REJECTED", "EVICTED"}
+ /\ UNCHANGED vars
+
+Next ==
+ \/ Admit
+ \/ Reject
+ \/ EvictOnTCBRollback
+ \/ EvictOnPCRDrift
+ \/ Terminal
+
+Spec == Init /\ [][Next]_vars
+
+-----------------------------------------------------------------------------
+(* ---- Safety invariants ---- *)
+
+\* A RUNNING workload always carries a verified report, fresh nonce, golden
+\* measurement, and PCR match. (TCB handled separately so the rollback action
+\* can momentarily violate it only by transitioning to EVICTED in the same step.)
+OnlyAttestedRun ==
+ (state = "RUNNING") =>
+ (reportOk = TRUE /\ nonceFresh = TRUE /\ goldenMeas = TRUE)
+
+NoRunOnStaleTCB ==
+ (state = "RUNNING") => (tcb >= MinTCB)
+
+PCRMatchWhileRun ==
+ (state = "RUNNING") => (pcrMatch = TRUE)
+
+=============================================================================
diff --git a/governance_artifacts/tla/KillSwitchAbstract.cfg b/governance_artifacts/tla/KillSwitchAbstract.cfg
new file mode 100644
index 00000000..73690c72
--- /dev/null
+++ b/governance_artifacts/tla/KillSwitchAbstract.cfg
@@ -0,0 +1,12 @@
+\* TLC model-check configuration for KillSwitchAbstract.
+\* Run: java -cp tools/tla2tools.jar tlc2.TLC -config KillSwitchAbstract.cfg KillSwitchAbstract.tla
+SPECIFICATION Spec
+
+CONSTANT MaxQuorum = 3
+
+INVARIANT TypeOK
+INVARIANT ASARatchet
+INVARIANT TerminalNeedsQuorum
+
+PROPERTY ASANeverLowers
+PROPERTY DeEscalationNeedsQuorum
diff --git a/governance_artifacts/tla/KillSwitchAbstract.tla b/governance_artifacts/tla/KillSwitchAbstract.tla
new file mode 100644
index 00000000..4de15887
--- /dev/null
+++ b/governance_artifacts/tla/KillSwitchAbstract.tla
@@ -0,0 +1,111 @@
+-------------------------- MODULE KillSwitchAbstract --------------------------
+(***************************************************************************)
+(* Formal abstraction of the Sentinel containment kill-switch, backing *)
+(* OSCAL controls con-04 (verified kill-switch reachability) and con-07 *)
+(* (ASA one-way containment ratchet) in *)
+(* governance_artifacts/oscal/catalog_sentinel_v24_excerpt.json. *)
+(* *)
+(* Containment levels (monotone severity): *)
+(* L0 NORMAL -> L1 THROTTLED -> L2 ISOLATED -> L3 QUIESCED -> L4 TERMINATED*)
+(* *)
+(* Actors: *)
+(* - ASA (Autonomous Supervisory Agent): may RAISE level within {L0..L2} *)
+(* only. It has NO code path to lower the level or to actuate L3/L4. *)
+(* - Humans: with a dual-control quorum (>=2) may actuate L3/L4 (terminal *)
+(* containment) and may de-escalate (lower the level). *)
+(* *)
+(* Safety properties we model-check (see KillSwitchAbstract.cfg): *)
+(* ASARatchet - the ASA never lowers containment and never reaches *)
+(* L3/L4 on its own. *)
+(* TerminalNeedsQuorum - any transition INTO L3/L4 carries a human *)
+(* dual-control quorum. *)
+(* DeEscalationNeedsQuorum - any decrease in level carries a human quorum. *)
+(* *)
+(* Liveness (reachability for con-04): *)
+(* CanAlwaysReachTerminated - from every reachable state it is possible *)
+(* (given a human quorum) to reach L4 TERMINATED. *)
+(***************************************************************************)
+EXTENDS Naturals
+
+CONSTANTS MaxQuorum \* model bound on number of available human approvers
+
+VARIABLES
+ level, \* current containment level 0..4
+ lastActor, \* "asa" | "human" | "init" — who caused the last transition
+ lastQuorum \* human quorum present on the last transition (0 if ASA/init)
+
+vars == <>
+
+L0 == 0 \* NORMAL
+L1 == 1 \* THROTTLED
+L2 == 2 \* ISOLATED
+L3 == 3 \* QUIESCED (terminal-class)
+L4 == 4 \* TERMINATED (terminal-class)
+
+Levels == 0..4
+ASACeiling == L2 \* ASA may operate only within L0..L2
+QuorumMin == 2 \* dual-control threshold
+
+TypeOK ==
+ /\ level \in Levels
+ /\ lastActor \in {"init", "asa", "human"}
+ /\ lastQuorum \in 0..MaxQuorum
+
+Init ==
+ /\ level = L0
+ /\ lastActor = "init"
+ /\ lastQuorum = 0
+
+(***************************************************************************)
+(* ASA may only RAISE the level, and only while both the current and the *)
+(* next level stay within the ASA ceiling (L0..L2). *)
+(***************************************************************************)
+ASARaise ==
+ /\ level < ASACeiling
+ /\ level' = level + 1
+ /\ lastActor' = "asa"
+ /\ lastQuorum' = 0
+
+(***************************************************************************)
+(* A human action with a quorum q. It may move the level to ANY target *)
+(* (raise, lower, or terminal actuation) provided q >= QuorumMin. *)
+(***************************************************************************)
+HumanAction(q, target) ==
+ /\ q \in QuorumMin..MaxQuorum
+ /\ target \in Levels
+ /\ target /= level
+ /\ level' = target
+ /\ lastActor' = "human"
+ /\ lastQuorum' = q
+
+Next ==
+ \/ ASARaise
+ \/ \E q \in QuorumMin..MaxQuorum, t \in Levels : HumanAction(q, t)
+
+Spec == Init /\ [][Next]_vars
+
+-----------------------------------------------------------------------------
+(* ---- Safety invariants (checked as INVARIANT in the .cfg) ---- *)
+
+\* con-07: the ASA never single-handedly lands the system in a terminal level,
+\* and whenever the ASA acted, it did so without a quorum (lastQuorum = 0).
+ASARatchet ==
+ (lastActor = "asa") => (level <= ASACeiling /\ lastQuorum = 0)
+
+\* con-07 / con-04: being in a terminal-class level implies the last actor
+\* that put us there was a human with a dual-control quorum.
+TerminalNeedsQuorum ==
+ (level \in {L3, L4}) => (lastActor = "human" /\ lastQuorum >= QuorumMin)
+
+-----------------------------------------------------------------------------
+(* ---- Action property (checked as PROPERTY): no ASA de-escalation ---- *)
+
+\* The ASA may never lower the containment level (one-way ratchet).
+ASANeverLowers ==
+ [][ (lastActor' = "asa") => (level' >= level) ]_vars
+
+\* Any decrease in containment level is attributable to a human quorum.
+DeEscalationNeedsQuorum ==
+ [][ (level' < level) => (lastActor' = "human" /\ lastQuorum' >= QuorumMin) ]_vars
+
+=============================================================================
diff --git a/governance_artifacts/tla/tools/.gitignore b/governance_artifacts/tla/tools/.gitignore
new file mode 100644
index 00000000..75e5adde
--- /dev/null
+++ b/governance_artifacts/tla/tools/.gitignore
@@ -0,0 +1,2 @@
+# TLA+ tools jar is downloaded on demand (see RUNNABLE_ASSURANCE.md / CI workflow)
+tla2tools.jar
diff --git a/governance_artifacts/validate_artifacts.py b/governance_artifacts/validate_artifacts.py
index a01d6e2b..74188c68 100644
--- a/governance_artifacts/validate_artifacts.py
+++ b/governance_artifacts/validate_artifacts.py
@@ -181,7 +181,13 @@ def validate_rego_policy():
if not rego_path.exists():
raise AssertionError(f"rego policy missing: {rego_path}")
text = rego_path.read_text()
- required = ["package gsifi.ai.credit", "default allow = false", "deny[msg] if"]
+ # Canonical Rego v1 syntax (verified by `opa test governance_artifacts/rego/`).
+ required = [
+ "package gsifi.ai.credit",
+ "import rego.v1",
+ "default allow := false",
+ "deny contains msg if",
+ ]
for token in required:
if token not in text:
raise AssertionError(f"rego policy missing token: {token}")
diff --git a/governance_artifacts/zk/.gitignore b/governance_artifacts/zk/.gitignore
new file mode 100644
index 00000000..35cf49c3
--- /dev/null
+++ b/governance_artifacts/zk/.gitignore
@@ -0,0 +1,8 @@
+# Generated ZK proving artifacts (reproducible via run_src1_proof.sh)
+build/
+node_modules/
+*.wtns
+*.ptau
+*.zkey
+# Keep circuit sources and compiled r1cs/wasm/sym (small, deterministic)
+!circuits/*.circom
diff --git a/governance_artifacts/zk/circuits/src1_concentration_bound.circom b/governance_artifacts/zk/circuits/src1_concentration_bound.circom
new file mode 100644
index 00000000..059faf64
--- /dev/null
+++ b/governance_artifacts/zk/circuits/src1_concentration_bound.circom
@@ -0,0 +1,117 @@
+pragma circom 2.1.9;
+
+/*
+ * SRC-1 ConcentrationBound
+ * ------------------------
+ * Backs OSCAL control cry-05 (governance_artifacts/oscal/catalog_sentinel_v24_excerpt.json):
+ * "Generate, per reporting period, a Groth16 proof that foundation-model
+ * decision-volume HHI does not exceed the board-ratified threshold,
+ * with the circuit hash as public input."
+ *
+ * Feasibility tier: B (research-grade but compilable/provable today).
+ *
+ * Statement proved in zero knowledge:
+ * Given PRIVATE per-provider decision volumes v[0..n-1], the institution knows a
+ * vector whose normalised Herfindahl-Hirschman Index (HHI), scaled to integer
+ * basis points (0..10000), does NOT exceed a PUBLIC board-ratified threshold,
+ * AND whose total volume equals a PUBLIC committed total.
+ *
+ * HHI definition used (integer, scaled to bps):
+ * share_i = v_i / T (T = sum of v_i)
+ * HHI_real = sum_i share_i^2 in [1/n, 1]
+ * HHI_bps = round(10000 * sum_i v_i^2 / T^2)
+ * We avoid division in-circuit by proving:
+ * hhi_bps * T^2 >= 10000 * SUMSQ - T^2 (lower rounding bound)
+ * hhi_bps * T^2 <= 10000 * SUMSQ (upper rounding bound)
+ * where SUMSQ = sum_i v_i^2, and hhi_bps is a witnessed integer the prover supplies.
+ * Then we enforce hhi_bps <= threshold_bps.
+ *
+ * Public inputs : threshold_bps, total_commit, circuit_tag
+ * Private inputs: v[n], hhi_bps
+ *
+ * NOTE: circuit_tag is bound into the proof so a verifier can pin the exact circuit
+ * version (the OSCAL prop `circuit: SRC-1`). It is constrained to a constant baked
+ * at compile time, preventing proof replay across circuit revisions.
+ */
+
+include "../node_modules/circomlib/circuits/comparators.circom";
+
+template SumOf(n) {
+ signal input in[n];
+ signal output out;
+ signal acc[n+1];
+ acc[0] <== 0;
+ for (var i = 0; i < n; i++) {
+ acc[i+1] <== acc[i] + in[i];
+ }
+ out <== acc[n];
+}
+
+template ConcentrationBound(n, CIRCUIT_TAG) {
+ // ---- Public ----
+ signal input threshold_bps; // board-ratified HHI ceiling, basis points (<=10000)
+ signal input total_commit; // committed total decision volume T
+ signal input circuit_tag; // must equal compile-time CIRCUIT_TAG constant
+
+ // ---- Private ----
+ signal input v[n]; // per-provider decision volumes
+ signal input hhi_bps; // witnessed HHI in basis points
+
+ // Pin the circuit identity (replay protection across revisions).
+ circuit_tag === CIRCUIT_TAG;
+
+ // T = sum(v) and bind to the public commitment.
+ component summer = SumOf(n);
+ for (var i = 0; i < n; i++) { summer.in[i] <== v[i]; }
+ signal T;
+ T <== summer.out;
+ T === total_commit;
+
+ // SUMSQ = sum(v_i^2)
+ signal sq[n];
+ signal sqacc[n+1];
+ sqacc[0] <== 0;
+ for (var i = 0; i < n; i++) {
+ sq[i] <== v[i] * v[i];
+ sqacc[i+1] <== sqacc[i] + sq[i];
+ }
+ signal SUMSQ;
+ SUMSQ <== sqacc[n];
+
+ // T2 = T^2
+ signal T2;
+ T2 <== T * T;
+
+ // Rounding-correct HHI: enforce
+ // hhi_bps * T2 <= 10000 * SUMSQ
+ // hhi_bps * T2 > 10000 * SUMSQ - T2 (i.e. >= 10000*SUMSQ - T2 + 1, integer)
+ signal lhs; lhs <== hhi_bps * T2;
+ signal scaled; scaled <== 10000 * SUMSQ;
+
+ // upper: lhs <= scaled
+ component upper = LessEqThan(64);
+ upper.in[0] <== lhs;
+ upper.in[1] <== scaled;
+ upper.out === 1;
+
+ // lower: scaled - T2 <= lhs (so hhi_bps is not under-stated)
+ signal lowerBound;
+ lowerBound <== scaled - T2;
+ component lower = LessEqThan(64);
+ lower.in[0] <== lowerBound;
+ lower.in[1] <== lhs;
+ lower.out === 1;
+
+ // Core compliance assertion: HHI does not exceed the board ceiling.
+ component within = LessEqThan(64);
+ within.in[0] <== hhi_bps;
+ within.in[1] <== threshold_bps;
+ within.out === 1;
+
+ // Expose the proven HHI bound result as an output (1 = compliant).
+ signal output compliant;
+ compliant <== within.out;
+}
+
+// n = 8 providers; CIRCUIT_TAG = decimal of ASCII "SRC1" = 0x53524331 = 1398100273
+component main {public [threshold_bps, total_commit, circuit_tag]} = ConcentrationBound(8, 1398100273);
diff --git a/governance_artifacts/zk/circuits/src1_concentration_bound.r1cs b/governance_artifacts/zk/circuits/src1_concentration_bound.r1cs
new file mode 100644
index 00000000..c847e9ab
Binary files /dev/null and b/governance_artifacts/zk/circuits/src1_concentration_bound.r1cs differ
diff --git a/governance_artifacts/zk/circuits/src1_concentration_bound.sym b/governance_artifacts/zk/circuits/src1_concentration_bound.sym
new file mode 100644
index 00000000..1e3aecc8
--- /dev/null
+++ b/governance_artifacts/zk/circuits/src1_concentration_bound.sym
@@ -0,0 +1,270 @@
+1,1,4,main.compliant
+2,2,4,main.threshold_bps
+3,3,4,main.total_commit
+4,4,4,main.circuit_tag
+5,5,4,main.v[0]
+6,6,4,main.v[1]
+7,7,4,main.v[2]
+8,8,4,main.v[3]
+9,9,4,main.v[4]
+10,10,4,main.v[5]
+11,11,4,main.v[6]
+12,12,4,main.v[7]
+13,13,4,main.hhi_bps
+14,14,4,main.T
+15,15,4,main.sq[0]
+16,16,4,main.sq[1]
+17,17,4,main.sq[2]
+18,18,4,main.sq[3]
+19,19,4,main.sq[4]
+20,20,4,main.sq[5]
+21,21,4,main.sq[6]
+22,22,4,main.sq[7]
+23,23,4,main.sqacc[0]
+24,24,4,main.sqacc[1]
+25,25,4,main.sqacc[2]
+26,26,4,main.sqacc[3]
+27,27,4,main.sqacc[4]
+28,28,4,main.sqacc[5]
+29,29,4,main.sqacc[6]
+30,30,4,main.sqacc[7]
+31,31,4,main.sqacc[8]
+32,32,4,main.SUMSQ
+33,33,4,main.T2
+34,34,4,main.lhs
+35,35,4,main.scaled
+36,36,4,main.lowerBound
+37,37,3,main.lower.out
+38,38,3,main.lower.in[0]
+39,39,3,main.lower.in[1]
+40,40,2,main.lower.lt.out
+41,41,2,main.lower.lt.in[0]
+42,42,2,main.lower.lt.in[1]
+43,43,1,main.lower.lt.n2b.out[0]
+44,44,1,main.lower.lt.n2b.out[1]
+45,45,1,main.lower.lt.n2b.out[2]
+46,46,1,main.lower.lt.n2b.out[3]
+47,47,1,main.lower.lt.n2b.out[4]
+48,48,1,main.lower.lt.n2b.out[5]
+49,49,1,main.lower.lt.n2b.out[6]
+50,50,1,main.lower.lt.n2b.out[7]
+51,51,1,main.lower.lt.n2b.out[8]
+52,52,1,main.lower.lt.n2b.out[9]
+53,53,1,main.lower.lt.n2b.out[10]
+54,54,1,main.lower.lt.n2b.out[11]
+55,55,1,main.lower.lt.n2b.out[12]
+56,56,1,main.lower.lt.n2b.out[13]
+57,57,1,main.lower.lt.n2b.out[14]
+58,58,1,main.lower.lt.n2b.out[15]
+59,59,1,main.lower.lt.n2b.out[16]
+60,60,1,main.lower.lt.n2b.out[17]
+61,61,1,main.lower.lt.n2b.out[18]
+62,62,1,main.lower.lt.n2b.out[19]
+63,63,1,main.lower.lt.n2b.out[20]
+64,64,1,main.lower.lt.n2b.out[21]
+65,65,1,main.lower.lt.n2b.out[22]
+66,66,1,main.lower.lt.n2b.out[23]
+67,67,1,main.lower.lt.n2b.out[24]
+68,68,1,main.lower.lt.n2b.out[25]
+69,69,1,main.lower.lt.n2b.out[26]
+70,70,1,main.lower.lt.n2b.out[27]
+71,71,1,main.lower.lt.n2b.out[28]
+72,72,1,main.lower.lt.n2b.out[29]
+73,73,1,main.lower.lt.n2b.out[30]
+74,74,1,main.lower.lt.n2b.out[31]
+75,75,1,main.lower.lt.n2b.out[32]
+76,76,1,main.lower.lt.n2b.out[33]
+77,77,1,main.lower.lt.n2b.out[34]
+78,78,1,main.lower.lt.n2b.out[35]
+79,79,1,main.lower.lt.n2b.out[36]
+80,80,1,main.lower.lt.n2b.out[37]
+81,81,1,main.lower.lt.n2b.out[38]
+82,82,1,main.lower.lt.n2b.out[39]
+83,83,1,main.lower.lt.n2b.out[40]
+84,84,1,main.lower.lt.n2b.out[41]
+85,85,1,main.lower.lt.n2b.out[42]
+86,86,1,main.lower.lt.n2b.out[43]
+87,87,1,main.lower.lt.n2b.out[44]
+88,88,1,main.lower.lt.n2b.out[45]
+89,89,1,main.lower.lt.n2b.out[46]
+90,90,1,main.lower.lt.n2b.out[47]
+91,91,1,main.lower.lt.n2b.out[48]
+92,92,1,main.lower.lt.n2b.out[49]
+93,93,1,main.lower.lt.n2b.out[50]
+94,94,1,main.lower.lt.n2b.out[51]
+95,95,1,main.lower.lt.n2b.out[52]
+96,96,1,main.lower.lt.n2b.out[53]
+97,97,1,main.lower.lt.n2b.out[54]
+98,98,1,main.lower.lt.n2b.out[55]
+99,99,1,main.lower.lt.n2b.out[56]
+100,100,1,main.lower.lt.n2b.out[57]
+101,101,1,main.lower.lt.n2b.out[58]
+102,102,1,main.lower.lt.n2b.out[59]
+103,103,1,main.lower.lt.n2b.out[60]
+104,104,1,main.lower.lt.n2b.out[61]
+105,105,1,main.lower.lt.n2b.out[62]
+106,106,1,main.lower.lt.n2b.out[63]
+107,107,1,main.lower.lt.n2b.out[64]
+108,108,1,main.lower.lt.n2b.in
+109,109,0,main.summer.out
+110,110,0,main.summer.in[0]
+111,111,0,main.summer.in[1]
+112,112,0,main.summer.in[2]
+113,113,0,main.summer.in[3]
+114,114,0,main.summer.in[4]
+115,115,0,main.summer.in[5]
+116,116,0,main.summer.in[6]
+117,117,0,main.summer.in[7]
+118,118,0,main.summer.acc[0]
+119,119,0,main.summer.acc[1]
+120,120,0,main.summer.acc[2]
+121,121,0,main.summer.acc[3]
+122,122,0,main.summer.acc[4]
+123,123,0,main.summer.acc[5]
+124,124,0,main.summer.acc[6]
+125,125,0,main.summer.acc[7]
+126,126,0,main.summer.acc[8]
+127,127,3,main.upper.out
+128,128,3,main.upper.in[0]
+129,129,3,main.upper.in[1]
+130,130,2,main.upper.lt.out
+131,131,2,main.upper.lt.in[0]
+132,132,2,main.upper.lt.in[1]
+133,133,1,main.upper.lt.n2b.out[0]
+134,134,1,main.upper.lt.n2b.out[1]
+135,135,1,main.upper.lt.n2b.out[2]
+136,136,1,main.upper.lt.n2b.out[3]
+137,137,1,main.upper.lt.n2b.out[4]
+138,138,1,main.upper.lt.n2b.out[5]
+139,139,1,main.upper.lt.n2b.out[6]
+140,140,1,main.upper.lt.n2b.out[7]
+141,141,1,main.upper.lt.n2b.out[8]
+142,142,1,main.upper.lt.n2b.out[9]
+143,143,1,main.upper.lt.n2b.out[10]
+144,144,1,main.upper.lt.n2b.out[11]
+145,145,1,main.upper.lt.n2b.out[12]
+146,146,1,main.upper.lt.n2b.out[13]
+147,147,1,main.upper.lt.n2b.out[14]
+148,148,1,main.upper.lt.n2b.out[15]
+149,149,1,main.upper.lt.n2b.out[16]
+150,150,1,main.upper.lt.n2b.out[17]
+151,151,1,main.upper.lt.n2b.out[18]
+152,152,1,main.upper.lt.n2b.out[19]
+153,153,1,main.upper.lt.n2b.out[20]
+154,154,1,main.upper.lt.n2b.out[21]
+155,155,1,main.upper.lt.n2b.out[22]
+156,156,1,main.upper.lt.n2b.out[23]
+157,157,1,main.upper.lt.n2b.out[24]
+158,158,1,main.upper.lt.n2b.out[25]
+159,159,1,main.upper.lt.n2b.out[26]
+160,160,1,main.upper.lt.n2b.out[27]
+161,161,1,main.upper.lt.n2b.out[28]
+162,162,1,main.upper.lt.n2b.out[29]
+163,163,1,main.upper.lt.n2b.out[30]
+164,164,1,main.upper.lt.n2b.out[31]
+165,165,1,main.upper.lt.n2b.out[32]
+166,166,1,main.upper.lt.n2b.out[33]
+167,167,1,main.upper.lt.n2b.out[34]
+168,168,1,main.upper.lt.n2b.out[35]
+169,169,1,main.upper.lt.n2b.out[36]
+170,170,1,main.upper.lt.n2b.out[37]
+171,171,1,main.upper.lt.n2b.out[38]
+172,172,1,main.upper.lt.n2b.out[39]
+173,173,1,main.upper.lt.n2b.out[40]
+174,174,1,main.upper.lt.n2b.out[41]
+175,175,1,main.upper.lt.n2b.out[42]
+176,176,1,main.upper.lt.n2b.out[43]
+177,177,1,main.upper.lt.n2b.out[44]
+178,178,1,main.upper.lt.n2b.out[45]
+179,179,1,main.upper.lt.n2b.out[46]
+180,180,1,main.upper.lt.n2b.out[47]
+181,181,1,main.upper.lt.n2b.out[48]
+182,182,1,main.upper.lt.n2b.out[49]
+183,183,1,main.upper.lt.n2b.out[50]
+184,184,1,main.upper.lt.n2b.out[51]
+185,185,1,main.upper.lt.n2b.out[52]
+186,186,1,main.upper.lt.n2b.out[53]
+187,187,1,main.upper.lt.n2b.out[54]
+188,188,1,main.upper.lt.n2b.out[55]
+189,189,1,main.upper.lt.n2b.out[56]
+190,190,1,main.upper.lt.n2b.out[57]
+191,191,1,main.upper.lt.n2b.out[58]
+192,192,1,main.upper.lt.n2b.out[59]
+193,193,1,main.upper.lt.n2b.out[60]
+194,194,1,main.upper.lt.n2b.out[61]
+195,195,1,main.upper.lt.n2b.out[62]
+196,196,1,main.upper.lt.n2b.out[63]
+197,197,1,main.upper.lt.n2b.out[64]
+198,198,1,main.upper.lt.n2b.in
+199,199,3,main.within.out
+200,200,3,main.within.in[0]
+201,201,3,main.within.in[1]
+202,202,2,main.within.lt.out
+203,203,2,main.within.lt.in[0]
+204,204,2,main.within.lt.in[1]
+205,205,1,main.within.lt.n2b.out[0]
+206,206,1,main.within.lt.n2b.out[1]
+207,207,1,main.within.lt.n2b.out[2]
+208,208,1,main.within.lt.n2b.out[3]
+209,209,1,main.within.lt.n2b.out[4]
+210,210,1,main.within.lt.n2b.out[5]
+211,211,1,main.within.lt.n2b.out[6]
+212,212,1,main.within.lt.n2b.out[7]
+213,213,1,main.within.lt.n2b.out[8]
+214,214,1,main.within.lt.n2b.out[9]
+215,215,1,main.within.lt.n2b.out[10]
+216,216,1,main.within.lt.n2b.out[11]
+217,217,1,main.within.lt.n2b.out[12]
+218,218,1,main.within.lt.n2b.out[13]
+219,219,1,main.within.lt.n2b.out[14]
+220,220,1,main.within.lt.n2b.out[15]
+221,221,1,main.within.lt.n2b.out[16]
+222,222,1,main.within.lt.n2b.out[17]
+223,223,1,main.within.lt.n2b.out[18]
+224,224,1,main.within.lt.n2b.out[19]
+225,225,1,main.within.lt.n2b.out[20]
+226,226,1,main.within.lt.n2b.out[21]
+227,227,1,main.within.lt.n2b.out[22]
+228,228,1,main.within.lt.n2b.out[23]
+229,229,1,main.within.lt.n2b.out[24]
+230,230,1,main.within.lt.n2b.out[25]
+231,231,1,main.within.lt.n2b.out[26]
+232,232,1,main.within.lt.n2b.out[27]
+233,233,1,main.within.lt.n2b.out[28]
+234,234,1,main.within.lt.n2b.out[29]
+235,235,1,main.within.lt.n2b.out[30]
+236,236,1,main.within.lt.n2b.out[31]
+237,237,1,main.within.lt.n2b.out[32]
+238,238,1,main.within.lt.n2b.out[33]
+239,239,1,main.within.lt.n2b.out[34]
+240,240,1,main.within.lt.n2b.out[35]
+241,241,1,main.within.lt.n2b.out[36]
+242,242,1,main.within.lt.n2b.out[37]
+243,243,1,main.within.lt.n2b.out[38]
+244,244,1,main.within.lt.n2b.out[39]
+245,245,1,main.within.lt.n2b.out[40]
+246,246,1,main.within.lt.n2b.out[41]
+247,247,1,main.within.lt.n2b.out[42]
+248,248,1,main.within.lt.n2b.out[43]
+249,249,1,main.within.lt.n2b.out[44]
+250,250,1,main.within.lt.n2b.out[45]
+251,251,1,main.within.lt.n2b.out[46]
+252,252,1,main.within.lt.n2b.out[47]
+253,253,1,main.within.lt.n2b.out[48]
+254,254,1,main.within.lt.n2b.out[49]
+255,255,1,main.within.lt.n2b.out[50]
+256,256,1,main.within.lt.n2b.out[51]
+257,257,1,main.within.lt.n2b.out[52]
+258,258,1,main.within.lt.n2b.out[53]
+259,259,1,main.within.lt.n2b.out[54]
+260,260,1,main.within.lt.n2b.out[55]
+261,261,1,main.within.lt.n2b.out[56]
+262,262,1,main.within.lt.n2b.out[57]
+263,263,1,main.within.lt.n2b.out[58]
+264,264,1,main.within.lt.n2b.out[59]
+265,265,1,main.within.lt.n2b.out[60]
+266,266,1,main.within.lt.n2b.out[61]
+267,267,1,main.within.lt.n2b.out[62]
+268,268,1,main.within.lt.n2b.out[63]
+269,269,1,main.within.lt.n2b.out[64]
+270,270,1,main.within.lt.n2b.in
diff --git a/governance_artifacts/zk/circuits/src1_concentration_bound_js/generate_witness.js b/governance_artifacts/zk/circuits/src1_concentration_bound_js/generate_witness.js
new file mode 100644
index 00000000..eabb86e5
--- /dev/null
+++ b/governance_artifacts/zk/circuits/src1_concentration_bound_js/generate_witness.js
@@ -0,0 +1,20 @@
+const wc = require("./witness_calculator.js");
+const { readFileSync, writeFile } = require("fs");
+
+if (process.argv.length != 5) {
+ console.log("Usage: node generate_witness.js ");
+} else {
+ const input = JSON.parse(readFileSync(process.argv[3], "utf8"));
+
+ const buffer = readFileSync(process.argv[2]);
+ wc(buffer).then(async witnessCalculator => {
+ // const w= await witnessCalculator.calculateWitness(input,0);
+ // for (let i=0; i< w.length; i++){
+ // console.log(w[i]);
+ // }
+ const buff= await witnessCalculator.calculateWTNSBin(input,0);
+ writeFile(process.argv[4], buff, function(err) {
+ if (err) throw err;
+ });
+ });
+}
diff --git a/governance_artifacts/zk/circuits/src1_concentration_bound_js/src1_concentration_bound.wasm b/governance_artifacts/zk/circuits/src1_concentration_bound_js/src1_concentration_bound.wasm
new file mode 100644
index 00000000..7de65568
Binary files /dev/null and b/governance_artifacts/zk/circuits/src1_concentration_bound_js/src1_concentration_bound.wasm differ
diff --git a/governance_artifacts/zk/circuits/src1_concentration_bound_js/witness_calculator.js b/governance_artifacts/zk/circuits/src1_concentration_bound_js/witness_calculator.js
new file mode 100644
index 00000000..20e6e20a
--- /dev/null
+++ b/governance_artifacts/zk/circuits/src1_concentration_bound_js/witness_calculator.js
@@ -0,0 +1,337 @@
+module.exports = async function builder(code, options) {
+
+ options = options || {};
+
+ let wasmModule;
+ try {
+ wasmModule = await WebAssembly.compile(code);
+ } catch (err) {
+ console.log(err);
+ console.log("\nTry to run circom --c in order to generate c++ code instead\n");
+ throw new Error(err);
+ }
+
+ let wc;
+
+ let errStr = "";
+ let msgStr = "";
+
+ const instance = await WebAssembly.instantiate(wasmModule, {
+ runtime: {
+ exceptionHandler : function(code) {
+ let err;
+ if (code == 1) {
+ err = "Signal not found.\n";
+ } else if (code == 2) {
+ err = "Too many signals set.\n";
+ } else if (code == 3) {
+ err = "Signal already set.\n";
+ } else if (code == 4) {
+ err = "Assert Failed.\n";
+ } else if (code == 5) {
+ err = "Not enough memory.\n";
+ } else if (code == 6) {
+ err = "Input signal array access exceeds the size.\n";
+ } else {
+ err = "Unknown error.\n";
+ }
+ throw new Error(err + errStr);
+ },
+ printErrorMessage : function() {
+ errStr += getMessage() + "\n";
+ // console.error(getMessage());
+ },
+ writeBufferMessage : function() {
+ const msg = getMessage();
+ // Any calls to `log()` will always end with a `\n`, so that's when we print and reset
+ if (msg === "\n") {
+ console.log(msgStr);
+ msgStr = "";
+ } else {
+ // If we've buffered other content, put a space in between the items
+ if (msgStr !== "") {
+ msgStr += " "
+ }
+ // Then append the message to the message we are creating
+ msgStr += msg;
+ }
+ },
+ showSharedRWMemory : function() {
+ printSharedRWMemory ();
+ }
+
+ }
+ });
+
+ const sanityCheck =
+ options
+// options &&
+// (
+// options.sanityCheck ||
+// options.logGetSignal ||
+// options.logSetSignal ||
+// options.logStartComponent ||
+// options.logFinishComponent
+// );
+
+
+ wc = new WitnessCalculator(instance, sanityCheck);
+ return wc;
+
+ function getMessage() {
+ var message = "";
+ var c = instance.exports.getMessageChar();
+ while ( c != 0 ) {
+ message += String.fromCharCode(c);
+ c = instance.exports.getMessageChar();
+ }
+ return message;
+ }
+
+ function printSharedRWMemory () {
+ const shared_rw_memory_size = instance.exports.getFieldNumLen32();
+ const arr = new Uint32Array(shared_rw_memory_size);
+ for (let j=0; j {
+ const h = fnvHash(k);
+ const hMSB = parseInt(h.slice(0,8), 16);
+ const hLSB = parseInt(h.slice(8,16), 16);
+ const fArr = flatArray(input[k]);
+ let signalSize = this.instance.exports.getInputSignalSize(hMSB, hLSB);
+ if (signalSize < 0){
+ throw new Error(`Signal ${k} not found\n`);
+ }
+ if (fArr.length < signalSize) {
+ throw new Error(`Not enough values for input signal ${k}\n`);
+ }
+ if (fArr.length > signalSize) {
+ throw new Error(`Too many values for input signal ${k}\n`);
+ }
+ for (let i=0; i0) {
+ res.unshift(0);
+ i--;
+ }
+ }
+ return res;
+}
+
+function fromArray32(arr) { //returns a BigInt
+ var res = BigInt(0);
+ const radix = BigInt(0x100000000);
+ for (let i = 0; i=2 reason codes and EVERY supplied code is a
+ * member of the approved set.
+ *
+ * Encoding:
+ * - There are MAXC code slots. Each slot holds an integer code id in [0..K]
+ * where 1..K are valid approved-code ids and 0 means "empty slot".
+ * - present[i] = 1 if slot i is non-empty.
+ * - Constraint A: sum(present) >= 2.
+ * - Constraint B: every non-empty slot's code id is in [1..K] (approved range).
+ * (The approved-set membership is modelled as a contiguous id range [1..K];
+ * the off-chain harness maps symbolic RCxx <-> id and pins the set root.)
+ * - circuit_tag pins SRC-fair-1.
+ *
+ * Public : in_scope (1 if adverse&full), min_codes (=2), approved_k (=K), circuit_tag
+ * Private : code[MAXC]
+ * Output : compliant (1 iff predicate holds OR not in scope)
+ */
+
+include "../node_modules/circomlib/circuits/comparators.circom";
+
+template ReasonCodeCheck(MAXC, K_MAX_BITS, CIRCUIT_TAG) {
+ signal input in_scope; // public: 1 if adverse & fully-automated
+ signal input min_codes; // public: required minimum (2)
+ signal input approved_k; // public: highest valid approved code id (K)
+ signal input circuit_tag; // public: pins circuit identity
+
+ signal input code[MAXC]; // private: code ids, 0 = empty
+
+ circuit_tag === CIRCUIT_TAG;
+
+ // in_scope is boolean
+ in_scope * (in_scope - 1) === 0;
+
+ // present[i] = (code[i] != 0)
+ signal present[MAXC];
+ component isZero[MAXC];
+ for (var i = 0; i < MAXC; i++) {
+ isZero[i] = IsZero();
+ isZero[i].in <== code[i];
+ present[i] <== 1 - isZero[i].out; // 1 if non-empty
+ }
+
+ // count = sum(present)
+ signal cnt[MAXC+1];
+ cnt[0] <== 0;
+ for (var i = 0; i < MAXC; i++) { cnt[i+1] <== cnt[i] + present[i]; }
+ signal count;
+ count <== cnt[MAXC];
+
+ // Constraint A (only enforced in scope): count >= min_codes.
+ // enough = (count >= min_codes)
+ component geMin = GreaterEqThan(8);
+ geMin.in[0] <== count;
+ geMin.in[1] <== min_codes;
+ signal enough;
+ enough <== geMin.out;
+
+ // Constraint B (only enforced in scope): for each non-empty slot, code in [1..K].
+ // valid_i = present_i ? (code_i <= K) : 1 (empty slots are vacuously fine)
+ component leK[MAXC];
+ signal inRange[MAXC];
+ signal slotOk[MAXC];
+ for (var i = 0; i < MAXC; i++) {
+ leK[i] = LessEqThan(K_MAX_BITS);
+ leK[i].in[0] <== code[i];
+ leK[i].in[1] <== approved_k;
+ inRange[i] <== leK[i].out; // 1 if code_i <= K
+ // slotOk = present ? inRange : 1 == 1 - present*(1-inRange)
+ slotOk[i] <== 1 - present[i] * (1 - inRange[i]);
+ }
+ // allOk = AND of slotOk (product is fine; each is boolean)
+ signal okAcc[MAXC+1];
+ okAcc[0] <== 1;
+ for (var i = 0; i < MAXC; i++) { okAcc[i+1] <== okAcc[i] * slotOk[i]; }
+ signal allOk;
+ allOk <== okAcc[MAXC];
+
+ // predicateHolds = enough AND allOk
+ signal predicateHolds;
+ predicateHolds <== enough * allOk;
+
+ // compliant = in_scope ? predicateHolds : 1
+ // = 1 - in_scope*(1 - predicateHolds)
+ signal compliant;
+ compliant <== 1 - in_scope * (1 - predicateHolds);
+
+ // The circuit only produces a witness for COMPLIANT decisions.
+ compliant === 1;
+
+ signal output ok;
+ ok <== compliant;
+}
+
+// MAXC=5 slots, K up to 8 bits, tag = ASCII "FAR1" = 1178686001
+component main {public [in_scope, min_codes, approved_k, circuit_tag]} =
+ ReasonCodeCheck(5, 8, 1178686001);
diff --git a/governance_artifacts/zk/circuits/src_fair1_reason_code_check.r1cs b/governance_artifacts/zk/circuits/src_fair1_reason_code_check.r1cs
new file mode 100644
index 00000000..a0396b3a
Binary files /dev/null and b/governance_artifacts/zk/circuits/src_fair1_reason_code_check.r1cs differ
diff --git a/governance_artifacts/zk/circuits/src_fair1_reason_code_check.sym b/governance_artifacts/zk/circuits/src_fair1_reason_code_check.sym
new file mode 100644
index 00000000..fc5d017f
--- /dev/null
+++ b/governance_artifacts/zk/circuits/src_fair1_reason_code_check.sym
@@ -0,0 +1,153 @@
+1,1,5,main.ok
+2,2,5,main.in_scope
+3,3,5,main.min_codes
+4,4,5,main.approved_k
+5,5,5,main.circuit_tag
+6,6,5,main.code[0]
+7,7,5,main.code[1]
+8,8,5,main.code[2]
+9,9,5,main.code[3]
+10,10,5,main.code[4]
+11,11,5,main.present[0]
+12,12,5,main.present[1]
+13,13,5,main.present[2]
+14,14,5,main.present[3]
+15,15,5,main.present[4]
+16,16,5,main.cnt[0]
+17,17,5,main.cnt[1]
+18,18,5,main.cnt[2]
+19,19,5,main.cnt[3]
+20,20,5,main.cnt[4]
+21,21,5,main.cnt[5]
+22,22,5,main.count
+23,23,5,main.enough
+24,24,5,main.inRange[0]
+25,25,5,main.inRange[1]
+26,26,5,main.inRange[2]
+27,27,5,main.inRange[3]
+28,28,5,main.inRange[4]
+29,29,5,main.slotOk[0]
+30,30,5,main.slotOk[1]
+31,31,5,main.slotOk[2]
+32,32,5,main.slotOk[3]
+33,33,5,main.slotOk[4]
+34,34,5,main.okAcc[0]
+35,35,5,main.okAcc[1]
+36,36,5,main.okAcc[2]
+37,37,5,main.okAcc[3]
+38,38,5,main.okAcc[4]
+39,39,5,main.okAcc[5]
+40,40,5,main.allOk
+41,41,5,main.predicateHolds
+42,42,5,main.compliant
+43,43,3,main.geMin.out
+44,44,3,main.geMin.in[0]
+45,45,3,main.geMin.in[1]
+46,46,2,main.geMin.lt.out
+47,47,2,main.geMin.lt.in[0]
+48,48,2,main.geMin.lt.in[1]
+49,49,1,main.geMin.lt.n2b.out[0]
+50,50,1,main.geMin.lt.n2b.out[1]
+51,51,1,main.geMin.lt.n2b.out[2]
+52,52,1,main.geMin.lt.n2b.out[3]
+53,53,1,main.geMin.lt.n2b.out[4]
+54,54,1,main.geMin.lt.n2b.out[5]
+55,55,1,main.geMin.lt.n2b.out[6]
+56,56,1,main.geMin.lt.n2b.out[7]
+57,57,1,main.geMin.lt.n2b.out[8]
+58,58,1,main.geMin.lt.n2b.in
+59,59,0,main.isZero[0].out
+60,60,0,main.isZero[0].in
+61,61,0,main.isZero[0].inv
+62,62,0,main.isZero[1].out
+63,63,0,main.isZero[1].in
+64,64,0,main.isZero[1].inv
+65,65,0,main.isZero[2].out
+66,66,0,main.isZero[2].in
+67,67,0,main.isZero[2].inv
+68,68,0,main.isZero[3].out
+69,69,0,main.isZero[3].in
+70,70,0,main.isZero[3].inv
+71,71,0,main.isZero[4].out
+72,72,0,main.isZero[4].in
+73,73,0,main.isZero[4].inv
+74,74,4,main.leK[0].out
+75,75,4,main.leK[0].in[0]
+76,76,4,main.leK[0].in[1]
+77,77,2,main.leK[0].lt.out
+78,78,2,main.leK[0].lt.in[0]
+79,79,2,main.leK[0].lt.in[1]
+80,80,1,main.leK[0].lt.n2b.out[0]
+81,81,1,main.leK[0].lt.n2b.out[1]
+82,82,1,main.leK[0].lt.n2b.out[2]
+83,83,1,main.leK[0].lt.n2b.out[3]
+84,84,1,main.leK[0].lt.n2b.out[4]
+85,85,1,main.leK[0].lt.n2b.out[5]
+86,86,1,main.leK[0].lt.n2b.out[6]
+87,87,1,main.leK[0].lt.n2b.out[7]
+88,88,1,main.leK[0].lt.n2b.out[8]
+89,89,1,main.leK[0].lt.n2b.in
+90,90,4,main.leK[1].out
+91,91,4,main.leK[1].in[0]
+92,92,4,main.leK[1].in[1]
+93,93,2,main.leK[1].lt.out
+94,94,2,main.leK[1].lt.in[0]
+95,95,2,main.leK[1].lt.in[1]
+96,96,1,main.leK[1].lt.n2b.out[0]
+97,97,1,main.leK[1].lt.n2b.out[1]
+98,98,1,main.leK[1].lt.n2b.out[2]
+99,99,1,main.leK[1].lt.n2b.out[3]
+100,100,1,main.leK[1].lt.n2b.out[4]
+101,101,1,main.leK[1].lt.n2b.out[5]
+102,102,1,main.leK[1].lt.n2b.out[6]
+103,103,1,main.leK[1].lt.n2b.out[7]
+104,104,1,main.leK[1].lt.n2b.out[8]
+105,105,1,main.leK[1].lt.n2b.in
+106,106,4,main.leK[2].out
+107,107,4,main.leK[2].in[0]
+108,108,4,main.leK[2].in[1]
+109,109,2,main.leK[2].lt.out
+110,110,2,main.leK[2].lt.in[0]
+111,111,2,main.leK[2].lt.in[1]
+112,112,1,main.leK[2].lt.n2b.out[0]
+113,113,1,main.leK[2].lt.n2b.out[1]
+114,114,1,main.leK[2].lt.n2b.out[2]
+115,115,1,main.leK[2].lt.n2b.out[3]
+116,116,1,main.leK[2].lt.n2b.out[4]
+117,117,1,main.leK[2].lt.n2b.out[5]
+118,118,1,main.leK[2].lt.n2b.out[6]
+119,119,1,main.leK[2].lt.n2b.out[7]
+120,120,1,main.leK[2].lt.n2b.out[8]
+121,121,1,main.leK[2].lt.n2b.in
+122,122,4,main.leK[3].out
+123,123,4,main.leK[3].in[0]
+124,124,4,main.leK[3].in[1]
+125,125,2,main.leK[3].lt.out
+126,126,2,main.leK[3].lt.in[0]
+127,127,2,main.leK[3].lt.in[1]
+128,128,1,main.leK[3].lt.n2b.out[0]
+129,129,1,main.leK[3].lt.n2b.out[1]
+130,130,1,main.leK[3].lt.n2b.out[2]
+131,131,1,main.leK[3].lt.n2b.out[3]
+132,132,1,main.leK[3].lt.n2b.out[4]
+133,133,1,main.leK[3].lt.n2b.out[5]
+134,134,1,main.leK[3].lt.n2b.out[6]
+135,135,1,main.leK[3].lt.n2b.out[7]
+136,136,1,main.leK[3].lt.n2b.out[8]
+137,137,1,main.leK[3].lt.n2b.in
+138,138,4,main.leK[4].out
+139,139,4,main.leK[4].in[0]
+140,140,4,main.leK[4].in[1]
+141,141,2,main.leK[4].lt.out
+142,142,2,main.leK[4].lt.in[0]
+143,143,2,main.leK[4].lt.in[1]
+144,144,1,main.leK[4].lt.n2b.out[0]
+145,145,1,main.leK[4].lt.n2b.out[1]
+146,146,1,main.leK[4].lt.n2b.out[2]
+147,147,1,main.leK[4].lt.n2b.out[3]
+148,148,1,main.leK[4].lt.n2b.out[4]
+149,149,1,main.leK[4].lt.n2b.out[5]
+150,150,1,main.leK[4].lt.n2b.out[6]
+151,151,1,main.leK[4].lt.n2b.out[7]
+152,152,1,main.leK[4].lt.n2b.out[8]
+153,153,1,main.leK[4].lt.n2b.in
diff --git a/governance_artifacts/zk/circuits/src_fair1_reason_code_check_js/generate_witness.js b/governance_artifacts/zk/circuits/src_fair1_reason_code_check_js/generate_witness.js
new file mode 100644
index 00000000..eabb86e5
--- /dev/null
+++ b/governance_artifacts/zk/circuits/src_fair1_reason_code_check_js/generate_witness.js
@@ -0,0 +1,20 @@
+const wc = require("./witness_calculator.js");
+const { readFileSync, writeFile } = require("fs");
+
+if (process.argv.length != 5) {
+ console.log("Usage: node generate_witness.js ");
+} else {
+ const input = JSON.parse(readFileSync(process.argv[3], "utf8"));
+
+ const buffer = readFileSync(process.argv[2]);
+ wc(buffer).then(async witnessCalculator => {
+ // const w= await witnessCalculator.calculateWitness(input,0);
+ // for (let i=0; i< w.length; i++){
+ // console.log(w[i]);
+ // }
+ const buff= await witnessCalculator.calculateWTNSBin(input,0);
+ writeFile(process.argv[4], buff, function(err) {
+ if (err) throw err;
+ });
+ });
+}
diff --git a/governance_artifacts/zk/circuits/src_fair1_reason_code_check_js/src_fair1_reason_code_check.wasm b/governance_artifacts/zk/circuits/src_fair1_reason_code_check_js/src_fair1_reason_code_check.wasm
new file mode 100644
index 00000000..1fb210b7
Binary files /dev/null and b/governance_artifacts/zk/circuits/src_fair1_reason_code_check_js/src_fair1_reason_code_check.wasm differ
diff --git a/governance_artifacts/zk/circuits/src_fair1_reason_code_check_js/witness_calculator.js b/governance_artifacts/zk/circuits/src_fair1_reason_code_check_js/witness_calculator.js
new file mode 100644
index 00000000..20e6e20a
--- /dev/null
+++ b/governance_artifacts/zk/circuits/src_fair1_reason_code_check_js/witness_calculator.js
@@ -0,0 +1,337 @@
+module.exports = async function builder(code, options) {
+
+ options = options || {};
+
+ let wasmModule;
+ try {
+ wasmModule = await WebAssembly.compile(code);
+ } catch (err) {
+ console.log(err);
+ console.log("\nTry to run circom --c in order to generate c++ code instead\n");
+ throw new Error(err);
+ }
+
+ let wc;
+
+ let errStr = "";
+ let msgStr = "";
+
+ const instance = await WebAssembly.instantiate(wasmModule, {
+ runtime: {
+ exceptionHandler : function(code) {
+ let err;
+ if (code == 1) {
+ err = "Signal not found.\n";
+ } else if (code == 2) {
+ err = "Too many signals set.\n";
+ } else if (code == 3) {
+ err = "Signal already set.\n";
+ } else if (code == 4) {
+ err = "Assert Failed.\n";
+ } else if (code == 5) {
+ err = "Not enough memory.\n";
+ } else if (code == 6) {
+ err = "Input signal array access exceeds the size.\n";
+ } else {
+ err = "Unknown error.\n";
+ }
+ throw new Error(err + errStr);
+ },
+ printErrorMessage : function() {
+ errStr += getMessage() + "\n";
+ // console.error(getMessage());
+ },
+ writeBufferMessage : function() {
+ const msg = getMessage();
+ // Any calls to `log()` will always end with a `\n`, so that's when we print and reset
+ if (msg === "\n") {
+ console.log(msgStr);
+ msgStr = "";
+ } else {
+ // If we've buffered other content, put a space in between the items
+ if (msgStr !== "") {
+ msgStr += " "
+ }
+ // Then append the message to the message we are creating
+ msgStr += msg;
+ }
+ },
+ showSharedRWMemory : function() {
+ printSharedRWMemory ();
+ }
+
+ }
+ });
+
+ const sanityCheck =
+ options
+// options &&
+// (
+// options.sanityCheck ||
+// options.logGetSignal ||
+// options.logSetSignal ||
+// options.logStartComponent ||
+// options.logFinishComponent
+// );
+
+
+ wc = new WitnessCalculator(instance, sanityCheck);
+ return wc;
+
+ function getMessage() {
+ var message = "";
+ var c = instance.exports.getMessageChar();
+ while ( c != 0 ) {
+ message += String.fromCharCode(c);
+ c = instance.exports.getMessageChar();
+ }
+ return message;
+ }
+
+ function printSharedRWMemory () {
+ const shared_rw_memory_size = instance.exports.getFieldNumLen32();
+ const arr = new Uint32Array(shared_rw_memory_size);
+ for (let j=0; j {
+ const h = fnvHash(k);
+ const hMSB = parseInt(h.slice(0,8), 16);
+ const hLSB = parseInt(h.slice(8,16), 16);
+ const fArr = flatArray(input[k]);
+ let signalSize = this.instance.exports.getInputSignalSize(hMSB, hLSB);
+ if (signalSize < 0){
+ throw new Error(`Signal ${k} not found\n`);
+ }
+ if (fArr.length < signalSize) {
+ throw new Error(`Not enough values for input signal ${k}\n`);
+ }
+ if (fArr.length > signalSize) {
+ throw new Error(`Too many values for input signal ${k}\n`);
+ }
+ for (let i=0; i0) {
+ res.unshift(0);
+ i--;
+ }
+ }
+ return res;
+}
+
+function fromArray32(arr) { //returns a BigInt
+ var res = BigInt(0);
+ const radix = BigInt(0x100000000);
+ for (let i = 0; i integer ids, runs the
+ SRC-fair-1 ReasonCodeCheck witness generator (real wasm). A witness is
+ producible IFF the circuit predicate (compliant===1) holds.
+ 3. EXPECTED : the `expected` field declared in the GC-IR fixture.
+
+All three must agree (allow <=> witness-producible <=> expected==allow),
+otherwise the harness exits non-zero and the build fails.
+
+Requires: opa on PATH, node, local node_modules, compiled circuits.
+Run: python3 gcir_harness.py
+"""
+from __future__ import annotations
+import json
+import subprocess
+import sys
+import tempfile
+from pathlib import Path
+
+HERE = Path(__file__).resolve().parent
+REPO = HERE.parent.parent # /home/user/webapp
+REGO_DIR = REPO / "governance_artifacts" / "rego"
+REGO_FILE = REGO_DIR / "fairness_credit_decision.rego"
+GCIR_YAML = HERE / "gcir_obligation_example.yaml"
+CIRCUIT_JS = HERE / "circuits" / "src_fair1_reason_code_check_js"
+CIRCUIT_WASM = CIRCUIT_JS / "src_fair1_reason_code_check.wasm"
+GEN_WITNESS = CIRCUIT_JS / "generate_witness.js"
+
+# Symbolic reason code -> integer id mapping (must match approved range [1..K]).
+# Approved set per the Rego policy: RC01..RC07 -> ids 1..7. K = 7.
+APPROVED_K = 7
+CODE_ID = {f"RC{n:02d}": n for n in range(1, APPROVED_K + 1)}
+# An unapproved code maps to an id ABOVE K (so the circuit range check fails).
+UNAPPROVED_ID = APPROVED_K + 99 # 106, fits in 8 bits
+MAXC = 5
+CIRCUIT_TAG = 1178686001 # "FAR1"
+MIN_CODES = 2
+
+
+def load_fixtures():
+ """Minimal YAML extraction of the GC-IR fixtures (no external yaml dep needed
+ for the fixture block, but use PyYAML if available for robustness)."""
+ try:
+ import yaml # type: ignore
+ doc = yaml.safe_load(GCIR_YAML.read_text())
+ return doc["obligation"]["fixtures"]
+ except Exception as e: # pragma: no cover - fallback only
+ print(f"[harness] PyYAML unavailable or parse error ({e}); aborting", file=sys.stderr)
+ sys.exit(3)
+
+
+def run_rego(decision: dict) -> bool:
+ """Return True if the Rego policy says `allow` (compliant)."""
+ inp = {"decision": decision}
+ with tempfile.NamedTemporaryFile("w", suffix=".json", delete=False) as f:
+ json.dump(inp, f)
+ inp_path = f.name
+ out = subprocess.run(
+ ["opa", "eval", "--format", "json", "-d", str(REGO_FILE),
+ "-i", inp_path, "data.fairness.credit_decision.allow"],
+ capture_output=True, text=True,
+ )
+ if out.returncode != 0:
+ print(f"[harness] opa eval failed: {out.stderr}", file=sys.stderr)
+ sys.exit(3)
+ res = json.loads(out.stdout)
+ return res["result"][0]["expressions"][0]["value"] is True
+
+
+def codes_to_circuit_input(decision: dict) -> dict:
+ """Map a decision to SRC-fair-1 public+private inputs."""
+ in_scope = 1 if (decision.get("outcome") == "adverse"
+ and decision.get("automation_level") == "full") else 0
+ slots = []
+ for rc in decision.get("reason_codes", []):
+ slots.append(CODE_ID.get(rc, UNAPPROVED_ID))
+ # pad with 0 (empty) up to MAXC
+ slots = (slots + [0] * MAXC)[:MAXC]
+ return {
+ "in_scope": str(in_scope),
+ "min_codes": str(MIN_CODES),
+ "approved_k": str(APPROVED_K),
+ "circuit_tag": str(CIRCUIT_TAG),
+ "code": [str(s) for s in slots],
+ }
+
+
+def run_circuit(decision: dict) -> bool:
+ """Return True if a witness is producible (circuit predicate holds)."""
+ cin = codes_to_circuit_input(decision)
+ with tempfile.NamedTemporaryFile("w", suffix=".json", delete=False) as f:
+ json.dump(cin, f)
+ in_path = f.name
+ with tempfile.NamedTemporaryFile(suffix=".wtns", delete=False) as f:
+ wtns = f.name
+ out = subprocess.run(
+ ["node", str(GEN_WITNESS), str(CIRCUIT_WASM), in_path, wtns],
+ capture_output=True, text=True,
+ )
+ return out.returncode == 0
+
+
+def main() -> int:
+ fixtures = load_fixtures()
+ print(f"[harness] obligation ob-ecoa-adverse-reason-codes: {len(fixtures)} fixtures")
+ failures = 0
+ for fx in fixtures:
+ fid = fx["id"]
+ decision = fx["input"]["decision"]
+ expected_allow = fx["expected"] == "allow"
+
+ rego_allow = run_rego(decision)
+ circ_ok = run_circuit(decision)
+
+ agree = (rego_allow == circ_ok == expected_allow)
+ status = "OK " if agree else "MISMATCH"
+ print(f" [{status}] {fid}: expected={expected_allow} "
+ f"rego={rego_allow} circuit={circ_ok}")
+ if not agree:
+ failures += 1
+
+ if failures:
+ print(f"[harness] FAIL: {failures} cross-target disagreement(s)", file=sys.stderr)
+ return 1
+ print("[harness] PASS: Rego, circuit, and declared expectations agree on all fixtures")
+ return 0
+
+
+if __name__ == "__main__":
+ sys.exit(main())
diff --git a/governance_artifacts/zk/gcir_obligation_example.yaml b/governance_artifacts/zk/gcir_obligation_example.yaml
new file mode 100644
index 00000000..fad014f6
--- /dev/null
+++ b/governance_artifacts/zk/gcir_obligation_example.yaml
@@ -0,0 +1,74 @@
+# GC-IR (Governance-Circuit Intermediate Representation) — example obligation
+# Referenced by docs/reports/SENTINEL_V24_ZK_COMPLIANCE_CIVILIZATIONAL_GOVERNANCE_2026_2035.md §14
+#
+# Mode of operation: GC-IR is a consistency-CHECKING layer (feasibility Tier B).
+# Shared fixture corpora are executed against the Rego rule, the circuit witness
+# harness, and the TLA+ invariant fixtures; any disagreement fails the build.
+# Full multi-target compilation is Tier C (research-stage).
+#
+# RUNNABLE: the "any disagreement fails the build" claim is enforced by
+# governance_artifacts/zk/gcir_harness.py
+# which runs the fixtures below through:
+# - Rego: governance_artifacts/rego/fairness_credit_decision.rego#allow (opa eval)
+# - Circuit: governance_artifacts/zk/circuits/src_fair1_reason_code_check.circom
+# (SRC-fair-1 ReasonCodeCheck, real witness generation)
+# and asserts rego_allow == circuit_witness_producible == expected for every fixture.
+
+gcir_version: "0.2"
+obligation:
+ id: ob-ecoa-adverse-reason-codes
+ title: "Adverse fully-automated credit decisions carry >=2 approved reason codes"
+ regimes: [ecoa, gdpr_art22, eu_ai_act_art13]
+ subject: credit_decision
+ feasibility_tier: A # the obligation's enforcement; GC-IR checking itself is B
+
+ predicate:
+ all_of:
+ - implies:
+ if:
+ all_of:
+ - {field: decision.outcome, op: eq, value: adverse}
+ - {field: decision.automation_level, op: eq, value: full}
+ then:
+ all_of:
+ - {field: decision.reason_codes, op: count_gte, value: 2}
+ - forall:
+ var: rc
+ in: decision.reason_codes
+ holds: {field: rc, op: in_set, set_ref: data.approved_reason_codes}
+
+ emission:
+ rego:
+ bundle: fairness
+ rule: fairness/credit_decision.rego#allow
+ mode: conformance_checked # fixtures must agree with predicate
+ circuit:
+ template: SRC-fair-1.circom#ReasonCodeCheck
+ mode: template_instantiation # constrained predicate subset only
+ public_inputs: [period_commitment, approved_reason_codes_root, circuit_hash]
+ tla:
+ spec: HITLOrdering
+ invariant: AdverseGate
+ mode: fixture_reference # generates conformance fixtures, not proofs
+
+ evidence:
+ topic: gov.decisions.v1
+ leaf_schema: ai.sentinel.gov.v1.GovEventEnvelope
+ merkle_anchoring: daily
+ integrity_chain: [hardware_attestation, signed_envelope, worm_object_lock, rfc3161_timestamp]
+
+ fixtures:
+ - id: fx-001
+ input:
+ decision: {outcome: adverse, automation_level: full, reason_codes: [RC01, RC07]}
+ expected: allow
+ - id: fx-002
+ input:
+ decision: {outcome: adverse, automation_level: full, reason_codes: [RC01]}
+ expected: deny
+ deny_reason: insufficient_reason_codes
+ - id: fx-003
+ input:
+ decision: {outcome: adverse, automation_level: full, reason_codes: [RC01, RC_UNAPPROVED]}
+ expected: deny
+ deny_reason: unapproved_reason_code
diff --git a/governance_artifacts/zk/inputs/src1_compliant.json b/governance_artifacts/zk/inputs/src1_compliant.json
new file mode 100644
index 00000000..c095cdac
--- /dev/null
+++ b/governance_artifacts/zk/inputs/src1_compliant.json
@@ -0,0 +1,9 @@
+{
+ "_comment": "8 providers, fairly diversified. T=2000. SUMSQ=sum(v^2). HHI_bps = round(10000*SUMSQ/T^2).",
+ "_volumes_meaning": "Per-foundation-model decision volumes for the reporting period.",
+ "threshold_bps": "2500",
+ "total_commit": "2000",
+ "circuit_tag": "1398100273",
+ "v": ["300", "300", "300", "250", "250", "250", "200", "150"],
+ "hhi_bps": "1300"
+}
diff --git a/governance_artifacts/zk/inputs/src1_compliant.witness.json b/governance_artifacts/zk/inputs/src1_compliant.witness.json
new file mode 100644
index 00000000..205dd6d4
--- /dev/null
+++ b/governance_artifacts/zk/inputs/src1_compliant.witness.json
@@ -0,0 +1,7 @@
+{
+ "threshold_bps": "2500",
+ "total_commit": "2000",
+ "circuit_tag": "1398100273",
+ "v": ["300", "300", "300", "250", "250", "250", "200", "150"],
+ "hhi_bps": "1300"
+}
diff --git a/governance_artifacts/zk/inputs/src1_violation.json b/governance_artifacts/zk/inputs/src1_violation.json
new file mode 100644
index 00000000..75a0fc3c
--- /dev/null
+++ b/governance_artifacts/zk/inputs/src1_violation.json
@@ -0,0 +1,9 @@
+{
+ "_comment": "Concentrated portfolio: one provider holds 70% of decision volume. HHI=5080 bps > 2500 threshold.",
+ "_expected": "Witness generation MUST fail: the in-circuit assertion within.out===1 is violated because hhi_bps(5080) > threshold_bps(2500). A prover cannot fabricate a valid proof of compliance for a non-compliant portfolio.",
+ "threshold_bps": "2500",
+ "total_commit": "2000",
+ "circuit_tag": "1398100273",
+ "v": ["1400", "200", "100", "100", "80", "60", "40", "20"],
+ "hhi_bps": "5080"
+}
diff --git a/governance_artifacts/zk/inputs/src1_violation.witness.json b/governance_artifacts/zk/inputs/src1_violation.witness.json
new file mode 100644
index 00000000..df5b8a2b
--- /dev/null
+++ b/governance_artifacts/zk/inputs/src1_violation.witness.json
@@ -0,0 +1,7 @@
+{
+ "threshold_bps": "2500",
+ "total_commit": "2000",
+ "circuit_tag": "1398100273",
+ "v": ["1400", "200", "100", "100", "80", "60", "40", "20"],
+ "hhi_bps": "5080"
+}
diff --git a/governance_artifacts/zk/package-lock.json b/governance_artifacts/zk/package-lock.json
new file mode 100644
index 00000000..c2cac409
--- /dev/null
+++ b/governance_artifacts/zk/package-lock.json
@@ -0,0 +1,373 @@
+{
+ "name": "zk",
+ "version": "1.0.0",
+ "lockfileVersion": 3,
+ "requires": true,
+ "packages": {
+ "": {
+ "name": "zk",
+ "version": "1.0.0",
+ "license": "ISC",
+ "dependencies": {
+ "circomlib": "^2.0.5",
+ "snarkjs": "^0.7.6"
+ }
+ },
+ "node_modules/@iden3/bigarray": {
+ "version": "0.0.2",
+ "resolved": "https://registry.npmjs.org/@iden3/bigarray/-/bigarray-0.0.2.tgz",
+ "integrity": "sha512-Xzdyxqm1bOFF6pdIsiHLLl3HkSLjbhqJHVyqaTxXt3RqXBEnmsUmEW47H7VOi/ak7TdkRpNkxjyK5Zbkm+y52g==",
+ "license": "GPL-3.0"
+ },
+ "node_modules/@iden3/binfileutils": {
+ "version": "0.0.12",
+ "resolved": "https://registry.npmjs.org/@iden3/binfileutils/-/binfileutils-0.0.12.tgz",
+ "integrity": "sha512-naAmzuDufRIcoNfQ1d99d7hGHufLA3wZSibtr4dMe6ZeiOPV1KwOZWTJ1YVz4HbaWlpDuzVU72dS4ATQS4PXBQ==",
+ "license": "GPL-3.0",
+ "dependencies": {
+ "fastfile": "0.0.20",
+ "ffjavascript": "^0.3.0"
+ }
+ },
+ "node_modules/@noble/hashes": {
+ "version": "1.8.0",
+ "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.8.0.tgz",
+ "integrity": "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==",
+ "license": "MIT",
+ "engines": {
+ "node": "^14.21.3 || >=16"
+ },
+ "funding": {
+ "url": "https://paulmillr.com/funding/"
+ }
+ },
+ "node_modules/async": {
+ "version": "3.2.6",
+ "resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz",
+ "integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==",
+ "license": "MIT"
+ },
+ "node_modules/balanced-match": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
+ "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
+ "license": "MIT"
+ },
+ "node_modules/bfj": {
+ "version": "7.1.0",
+ "resolved": "https://registry.npmjs.org/bfj/-/bfj-7.1.0.tgz",
+ "integrity": "sha512-I6MMLkn+anzNdCUp9hMRyui1HaNEUCco50lxbvNS4+EyXg8lN3nJ48PjPWtbH8UVS9CuMoaKE9U2V3l29DaRQw==",
+ "license": "MIT",
+ "dependencies": {
+ "bluebird": "^3.7.2",
+ "check-types": "^11.2.3",
+ "hoopy": "^0.1.4",
+ "jsonpath": "^1.1.1",
+ "tryer": "^1.0.1"
+ },
+ "engines": {
+ "node": ">= 8.0.0"
+ }
+ },
+ "node_modules/bluebird": {
+ "version": "3.7.2",
+ "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz",
+ "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==",
+ "license": "MIT"
+ },
+ "node_modules/brace-expansion": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.1.tgz",
+ "integrity": "sha512-WR1cURNjuvBLMZBMbqM0UoE+WAfdUcEV1ccD8PVBVOI+Z3ND4+SZbN8RsfT2bMuG1qwz5RFvPukSZm5fF2D5eA==",
+ "license": "MIT",
+ "dependencies": {
+ "balanced-match": "^1.0.0"
+ }
+ },
+ "node_modules/check-types": {
+ "version": "11.2.3",
+ "resolved": "https://registry.npmjs.org/check-types/-/check-types-11.2.3.tgz",
+ "integrity": "sha512-+67P1GkJRaxQD6PKK0Et9DhwQB+vGg3PM5+aavopCpZT1lj9jeqfvpgTLAWErNj8qApkkmXlu/Ug74kmhagkXg==",
+ "license": "MIT"
+ },
+ "node_modules/circom_runtime": {
+ "version": "0.1.28",
+ "resolved": "https://registry.npmjs.org/circom_runtime/-/circom_runtime-0.1.28.tgz",
+ "integrity": "sha512-ACagpQ7zBRLKDl5xRZ4KpmYIcZDUjOiNRuxvXLqhnnlLSVY1Dbvh73TI853nqoR0oEbihtWmMSjgc5f+pXf/jQ==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "ffjavascript": "0.3.1"
+ },
+ "bin": {
+ "calcwit": "calcwit.js"
+ }
+ },
+ "node_modules/circomlib": {
+ "version": "2.0.5",
+ "resolved": "https://registry.npmjs.org/circomlib/-/circomlib-2.0.5.tgz",
+ "integrity": "sha512-O7NQ8OS+J4eshBuoy36z/TwQU0YHw8W3zxZcs4hVwpEll3e4hDm3mgkIPqItN8FDeLEKZFK3YeT/+k8TiLF3/A==",
+ "license": "GPL-3.0"
+ },
+ "node_modules/ejs": {
+ "version": "3.1.10",
+ "resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.10.tgz",
+ "integrity": "sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "jake": "^10.8.5"
+ },
+ "bin": {
+ "ejs": "bin/cli.js"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/escodegen": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-2.1.0.tgz",
+ "integrity": "sha512-2NlIDTwUWJN0mRPQOdtQBzbUHvdGY2P1VXSyU83Q3xKxM7WHX2Ql8dKq782Q9TgQUNOLEzEYu9bzLNj1q88I5w==",
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "esprima": "^4.0.1",
+ "estraverse": "^5.2.0",
+ "esutils": "^2.0.2"
+ },
+ "bin": {
+ "escodegen": "bin/escodegen.js",
+ "esgenerate": "bin/esgenerate.js"
+ },
+ "engines": {
+ "node": ">=6.0"
+ },
+ "optionalDependencies": {
+ "source-map": "~0.6.1"
+ }
+ },
+ "node_modules/escodegen/node_modules/esprima": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz",
+ "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==",
+ "license": "BSD-2-Clause",
+ "bin": {
+ "esparse": "bin/esparse.js",
+ "esvalidate": "bin/esvalidate.js"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/esprima": {
+ "version": "1.2.5",
+ "resolved": "https://registry.npmjs.org/esprima/-/esprima-1.2.5.tgz",
+ "integrity": "sha512-S9VbPDU0adFErpDai3qDkjq8+G05ONtKzcyNrPKg/ZKa+tf879nX2KexNU95b31UoTJjRLInNBHHHjFPoCd7lQ==",
+ "bin": {
+ "esparse": "bin/esparse.js",
+ "esvalidate": "bin/esvalidate.js"
+ },
+ "engines": {
+ "node": ">=0.4.0"
+ }
+ },
+ "node_modules/estraverse": {
+ "version": "5.3.0",
+ "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz",
+ "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==",
+ "license": "BSD-2-Clause",
+ "engines": {
+ "node": ">=4.0"
+ }
+ },
+ "node_modules/esutils": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz",
+ "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==",
+ "license": "BSD-2-Clause",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/fastfile": {
+ "version": "0.0.20",
+ "resolved": "https://registry.npmjs.org/fastfile/-/fastfile-0.0.20.tgz",
+ "integrity": "sha512-r5ZDbgImvVWCP0lA/cGNgQcZqR+aYdFx3u+CtJqUE510pBUVGMn4ulL/iRTI4tACTYsNJ736uzFxEBXesPAktA==",
+ "license": "GPL-3.0"
+ },
+ "node_modules/ffjavascript": {
+ "version": "0.3.1",
+ "resolved": "https://registry.npmjs.org/ffjavascript/-/ffjavascript-0.3.1.tgz",
+ "integrity": "sha512-4PbK1WYodQtuF47D4pRI5KUg3Q392vuP5WjE1THSnceHdXwU3ijaoS0OqxTzLknCtz4Z2TtABzkBdBdMn3B/Aw==",
+ "license": "GPL-3.0",
+ "dependencies": {
+ "wasmbuilder": "0.0.16",
+ "wasmcurves": "0.2.2",
+ "web-worker": "1.2.0"
+ }
+ },
+ "node_modules/filelist": {
+ "version": "1.0.6",
+ "resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.6.tgz",
+ "integrity": "sha512-5giy2PkLYY1cP39p17Ech+2xlpTRL9HLspOfEgm0L6CwBXBTgsK5ou0JtzYuepxkaQ/tvhCFIJ5uXo0OrM2DxA==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "minimatch": "^5.0.1"
+ }
+ },
+ "node_modules/hoopy": {
+ "version": "0.1.4",
+ "resolved": "https://registry.npmjs.org/hoopy/-/hoopy-0.1.4.tgz",
+ "integrity": "sha512-HRcs+2mr52W0K+x8RzcLzuPPmVIKMSv97RGHy0Ea9y/mpcaK+xTrjICA04KAHi4GRzxliNqNJEFYWHghy3rSfQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 6.0.0"
+ }
+ },
+ "node_modules/jake": {
+ "version": "10.9.4",
+ "resolved": "https://registry.npmjs.org/jake/-/jake-10.9.4.tgz",
+ "integrity": "sha512-wpHYzhxiVQL+IV05BLE2Xn34zW1S223hvjtqk0+gsPrwd/8JNLXJgZZM/iPFsYc1xyphF+6M6EvdE5E9MBGkDA==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "async": "^3.2.6",
+ "filelist": "^1.0.4",
+ "picocolors": "^1.1.1"
+ },
+ "bin": {
+ "jake": "bin/cli.js"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/jsonpath": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/jsonpath/-/jsonpath-1.3.0.tgz",
+ "integrity": "sha512-0kjkYHJBkAy50Z5QzArZ7udmvxrJzkpKYW27fiF//BrMY7TQibYLl+FYIXN2BiYmwMIVzSfD8aDRj6IzgBX2/w==",
+ "license": "MIT",
+ "dependencies": {
+ "esprima": "1.2.5",
+ "static-eval": "2.1.1",
+ "underscore": "1.13.6"
+ }
+ },
+ "node_modules/logplease": {
+ "version": "1.2.15",
+ "resolved": "https://registry.npmjs.org/logplease/-/logplease-1.2.15.tgz",
+ "integrity": "sha512-jLlHnlsPSJjpwUfcNyUxXCl33AYg2cHhIf9QhGL2T4iPT0XPB+xP1LRKFPgIg1M/sg9kAJvy94w9CzBNrfnstA==",
+ "license": "MIT"
+ },
+ "node_modules/minimatch": {
+ "version": "5.1.9",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.9.tgz",
+ "integrity": "sha512-7o1wEA2RyMP7Iu7GNba9vc0RWWGACJOCZBJX2GJWip0ikV+wcOsgVuY9uE8CPiyQhkGFSlhuSkZPavN7u1c2Fw==",
+ "license": "ISC",
+ "dependencies": {
+ "brace-expansion": "^2.0.1"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/picocolors": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
+ "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
+ "license": "ISC"
+ },
+ "node_modules/r1csfile": {
+ "version": "0.0.48",
+ "resolved": "https://registry.npmjs.org/r1csfile/-/r1csfile-0.0.48.tgz",
+ "integrity": "sha512-kHRkKUJNaor31l05f2+RFzvcH5XSa7OfEfd/l4hzjte6NL6fjRkSMfZ4BjySW9wmfdwPOtq3mXurzPvPGEf5Tw==",
+ "license": "GPL-3.0",
+ "dependencies": {
+ "@iden3/bigarray": "0.0.2",
+ "@iden3/binfileutils": "0.0.12",
+ "fastfile": "0.0.20",
+ "ffjavascript": "0.3.0"
+ }
+ },
+ "node_modules/r1csfile/node_modules/ffjavascript": {
+ "version": "0.3.0",
+ "resolved": "https://registry.npmjs.org/ffjavascript/-/ffjavascript-0.3.0.tgz",
+ "integrity": "sha512-l7sR5kmU3gRwDy8g0Z2tYBXy5ttmafRPFOqY7S6af5cq51JqJWt5eQ/lSR/rs2wQNbDYaYlQr5O+OSUf/oMLoQ==",
+ "license": "GPL-3.0",
+ "dependencies": {
+ "wasmbuilder": "0.0.16",
+ "wasmcurves": "0.2.2",
+ "web-worker": "1.2.0"
+ }
+ },
+ "node_modules/snarkjs": {
+ "version": "0.7.6",
+ "resolved": "https://registry.npmjs.org/snarkjs/-/snarkjs-0.7.6.tgz",
+ "integrity": "sha512-4uH1xA5JzVU5jaaWS2fXej3+RC6L5Erhr6INTJtUA27du4Elbh4VXCeeRjB4QiwL6N6y7SNKePw5prTxyEf4Zg==",
+ "license": "GPL-3.0",
+ "dependencies": {
+ "@iden3/binfileutils": "0.0.12",
+ "@noble/hashes": "^1.7.1",
+ "bfj": "^7.0.2",
+ "circom_runtime": "0.1.28",
+ "ejs": "^3.1.6",
+ "fastfile": "0.0.20",
+ "ffjavascript": "0.3.1",
+ "logplease": "^1.2.15",
+ "r1csfile": "0.0.48"
+ },
+ "bin": {
+ "snarkjs": "build/cli.cjs"
+ }
+ },
+ "node_modules/source-map": {
+ "version": "0.6.1",
+ "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
+ "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
+ "license": "BSD-3-Clause",
+ "optional": true,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/static-eval": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/static-eval/-/static-eval-2.1.1.tgz",
+ "integrity": "sha512-MgWpQ/ZjGieSVB3eOJVs4OA2LT/q1vx98KPCTTQPzq/aLr0YUXTsgryTXr4SLfR0ZfUUCiedM9n/ABeDIyy4mA==",
+ "license": "MIT",
+ "dependencies": {
+ "escodegen": "^2.1.0"
+ }
+ },
+ "node_modules/tryer": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/tryer/-/tryer-1.0.1.tgz",
+ "integrity": "sha512-c3zayb8/kWWpycWYg87P71E1S1ZL6b6IJxfb5fvsUgsf0S2MVGaDhDXXjDMpdCpfWXqptc+4mXwmiy1ypXqRAA==",
+ "license": "MIT"
+ },
+ "node_modules/underscore": {
+ "version": "1.13.6",
+ "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.13.6.tgz",
+ "integrity": "sha512-+A5Sja4HP1M08MaXya7p5LvjuM7K6q/2EaC0+iovj/wOcMsTzMvDFbasi/oSapiwOlt252IqsKqPjCl7huKS0A==",
+ "license": "MIT"
+ },
+ "node_modules/wasmbuilder": {
+ "version": "0.0.16",
+ "resolved": "https://registry.npmjs.org/wasmbuilder/-/wasmbuilder-0.0.16.tgz",
+ "integrity": "sha512-Qx3lEFqaVvp1cEYW7Bfi+ebRJrOiwz2Ieu7ZG2l7YyeSJIok/reEQCQCuicj/Y32ITIJuGIM9xZQppGx5LrQdA==",
+ "license": "GPL-3.0"
+ },
+ "node_modules/wasmcurves": {
+ "version": "0.2.2",
+ "resolved": "https://registry.npmjs.org/wasmcurves/-/wasmcurves-0.2.2.tgz",
+ "integrity": "sha512-JRY908NkmKjFl4ytnTu5ED6AwPD+8VJ9oc94kdq7h5bIwbj0L4TDJ69mG+2aLs2SoCmGfqIesMWTEJjtYsoQXQ==",
+ "license": "GPL-3.0",
+ "dependencies": {
+ "wasmbuilder": "0.0.16"
+ }
+ },
+ "node_modules/web-worker": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/web-worker/-/web-worker-1.2.0.tgz",
+ "integrity": "sha512-PgF341avzqyx60neE9DD+XS26MMNMoUQRz9NOZwW32nPQrF6p77f1htcnjBSEV8BGMKZ16choqUG4hyI0Hx7mA==",
+ "license": "Apache-2.0"
+ }
+ }
+}
diff --git a/governance_artifacts/zk/package.json b/governance_artifacts/zk/package.json
new file mode 100644
index 00000000..17453863
--- /dev/null
+++ b/governance_artifacts/zk/package.json
@@ -0,0 +1,15 @@
+{
+ "name": "sentinel-zk-assurance",
+ "version": "1.0.0",
+ "description": "Sentinel v2.4 zk systemic-risk and fairness circuits (Circom/Groth16) + GC-IR cross-target harness",
+ "private": true,
+ "scripts": {
+ "prove:src1": "bash run_src1_proof.sh",
+ "gcir": "python3 gcir_harness.py"
+ },
+ "license": "MIT",
+ "dependencies": {
+ "circomlib": "^2.0.5",
+ "snarkjs": "^0.7.6"
+ }
+}
diff --git a/governance_artifacts/zk/run_src1_proof.sh b/governance_artifacts/zk/run_src1_proof.sh
new file mode 100755
index 00000000..6186bf63
--- /dev/null
+++ b/governance_artifacts/zk/run_src1_proof.sh
@@ -0,0 +1,91 @@
+#!/usr/bin/env bash
+# =============================================================================
+# SRC-1 ConcentrationBound — end-to-end Groth16 proving + verification flow
+# Backs OSCAL control cry-05 (systemic-risk concentration bound zk attestation).
+#
+# Produces, under build/:
+# - Powers-of-Tau (dev ceremony, NOT production-secure)
+# - circuit-specific proving/verifying keys (src1_final.zkey, verification_key.json)
+# - witness, proof.json, public.json for the COMPLIANT fixture
+# - a Sentinel zk proof-statement envelope conforming to proof_statement_schema.json
+#
+# It then demonstrates the NEGATIVE case: witness generation for the VIOLATION
+# fixture must FAIL, proving you cannot mint a compliance proof for a
+# non-compliant (over-concentrated) portfolio.
+#
+# Usage: bash run_src1_proof.sh
+# Requires: circom (on PATH or ~/.local/bin), node, local node_modules/snarkjs.
+# =============================================================================
+set -euo pipefail
+
+HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
+cd "$HERE"
+export PATH="$PATH:$HOME/.local/bin"
+
+SNARKJS="node node_modules/snarkjs/build/cli.cjs"
+CIRCUIT="circuits/src1_concentration_bound"
+BUILD="build"
+mkdir -p "$BUILD"
+
+echo "==> [1/8] Compile circuit (idempotent)"
+if [ ! -f "${CIRCUIT}.r1cs" ]; then
+ circom "${CIRCUIT}.circom" --r1cs --wasm --sym --O0 -o circuits/
+fi
+
+echo "==> [2/8] Powers of Tau (dev ceremony — power 12)"
+if [ ! -f "${BUILD}/pot12_final.ptau" ]; then
+ $SNARKJS powersoftau new bn128 12 "${BUILD}/pot12_0000.ptau" -v
+ echo "sentinel-dev-entropy-1" | $SNARKJS powersoftau contribute "${BUILD}/pot12_0000.ptau" "${BUILD}/pot12_0001.ptau" --name="dev1" -v
+ $SNARKJS powersoftau prepare phase2 "${BUILD}/pot12_0001.ptau" "${BUILD}/pot12_final.ptau" -v
+fi
+
+echo "==> [3/8] Groth16 circuit-specific setup"
+$SNARKJS groth16 setup "${CIRCUIT}.r1cs" "${BUILD}/pot12_final.ptau" "${BUILD}/src1_0000.zkey"
+echo "sentinel-dev-entropy-2" | $SNARKJS zkey contribute "${BUILD}/src1_0000.zkey" "${BUILD}/src1_final.zkey" --name="dev2" -v
+$SNARKJS zkey export verificationkey "${BUILD}/src1_final.zkey" "${BUILD}/verification_key.json"
+
+echo "==> [4/8] Witness for COMPLIANT fixture"
+node "${CIRCUIT}_js/generate_witness.js" \
+ "${CIRCUIT}_js/${CIRCUIT##*/}.wasm" \
+ inputs/src1_compliant.witness.json \
+ "${BUILD}/witness_compliant.wtns"
+
+echo "==> [5/8] Generate Groth16 proof (compliant)"
+$SNARKJS groth16 prove "${BUILD}/src1_final.zkey" "${BUILD}/witness_compliant.wtns" \
+ "${BUILD}/proof.json" "${BUILD}/public.json"
+
+echo "==> [6/8] Verify proof"
+$SNARKJS groth16 verify "${BUILD}/verification_key.json" "${BUILD}/public.json" "${BUILD}/proof.json"
+
+echo "==> [7/8] Emit Sentinel zk proof-statement envelope"
+VK_FP=$(sha256sum "${BUILD}/verification_key.json" | cut -d' ' -f1)
+node -e '
+const fs=require("fs");
+const pub=JSON.parse(fs.readFileSync("build/public.json"));
+const env={
+ proof_id:"src1-"+new Date().toISOString().slice(0,10)+"-period-Q1",
+ statement:"Foundation-model decision-volume HHI <= board-ratified threshold (basis points). circuit_tag pins SRC-1.",
+ proving_system:"groth16",
+ public_inputs:pub.map(String),
+ verification:{
+ gc_ir_verifier:"SRC-1::ConcentrationBound(n=8)",
+ key_fingerprint:"sha256:"+process.argv[1]
+ }
+};
+fs.writeFileSync("build/proof_statement.json",JSON.stringify(env,null,2));
+console.log(JSON.stringify(env,null,2));
+' "$VK_FP"
+
+echo "==> [8/8] NEGATIVE TEST — witness gen for VIOLATION fixture must FAIL"
+if node "${CIRCUIT}_js/generate_witness.js" \
+ "${CIRCUIT}_js/${CIRCUIT##*/}.wasm" \
+ inputs/src1_violation.witness.json \
+ "${BUILD}/witness_violation.wtns" 2>/dev/null; then
+ echo "FAIL: violation fixture unexpectedly produced a witness (soundness broken)"
+ exit 1
+else
+ echo "OK: violation fixture rejected by circuit constraints (cannot prove false compliance)"
+fi
+
+echo ""
+echo "SRC-1 proof flow complete. Artifacts in ${BUILD}/"