# Project export: Faultfix

This document was generated by HackStack to give an AI agent context about a hackathon project. Sections are labeled with their provenance; content marked as truncated was cut to keep this document small.

## Project metadata

- Hackathon: OpenAI Build Week
- Tagline: Faultfix is the evidence-bound authority layer for AI incident agents: it quarantines unsafe inputs and requires proof plus human review before production changes.
- Devpost: https://devpost.com/software/faultfix
- GitHub: https://github.com/jacklachan/faultfix
- Demo: https://huggingface.co/spaces/jacklachan/faultfix
- Video: https://www.youtube.com/embed/u__9EIQDYxk?enablejsapi=1&hl=en_US&rel=0&start=&version=3&wmode=transparent
- Team: 2 GitHub contributor(s) — jacklachan (58 commits), Codex (22 commits)

## Devpost submission (written by the team)

### Inspiration

AI incident tools are becoming very good at summarising logs and proposing fixes. But in a real outage, the dangerous question is not only “What caused this?” It is “Who is allowed to change production?” We built Faultfix around one rule: An agent may investigate. It must earn the right to act. A plausible model answer should never be enough to trigger a permanent production change.

### What it does

Faultfix is an evidence-bound authority layer for AI incident agents. It separates four things that are often mixed together: Evidence: only trusted, time-bounded facts may influence a decision. Model advice: a model may suggest the next check or hypothesis. Policy: deterministic rules decide ALLOW, REVIEW, or BLOCK. Authority: permanent changes require causal proof, reproduction, and human approval. The public demo includes: An Evidence Firewall that quarantines hostile instruction-like ticket content before model inference. An Authority Simulator where a visitor can test a non-sensitive scenario using structured trust, replay, action, and proof controls. A fingerprinted decision receipt for every ALLOW, REVIEW, or BLOCK result. A scoped, time-bounded Action Lease for reversible containment actions. Public incident packs based on bounded facts from Google Cloud and Cloudflare postmortems. An optional Hugging Face-hosted investigator whose output is validated and advisory only.

### How we built it

We built the product with Next.js, React, TypeScript, Python, Gradio, Hugging Face Spaces, and Hugging Face Inference Providers. The authority policy is intentionally deterministic. It does not ask a model whether a production action is safe. Instead, it evaluates fixed evidence trust, replay timing, action scope, and causal-proof state. We also shipped an installable offline CLI for terminal and CI use:

## README (from the GitHub repository)

<p align="center">
  <img src="docs/assets/faultfix-authority-map.svg" alt="Faultfix evidence-bound agent authority map: trusted evidence reaches a model adviser and deterministic policy gate; unsafe evidence is quarantined." width="100%" />
</p>

<h1 align="center">faultfix</h1>

<p align="center"><strong>Evidence-bound authority for AI incident agents.</strong></p>

<p align="center">
  <a href="https://huggingface.co/spaces/jacklachan/faultfix"><kbd>Open the live lab</kbd></a>
  &nbsp;
  <a href="docs/judge-demo.md"><kbd>Run the 2-minute demo</kbd></a>
  &nbsp;
  <a href="#run-locally"><kbd>Run locally</kbd></a>
</p>

> **An AI agent must earn the right to act.** Faultfix lets an agent investigate, but trusted evidence, deterministic policy, and human review decide whether an action is `ALLOW`, `REVIEW`, or `BLOCK`.

Faultfix is not another incident investigator. It is the authority layer beneath one: the component that decides what evidence can influence an agent, what action is in scope, and whether the action is permitted.

---

## The authority boundary

| Signal | What it can do | What it cannot do |
| --- | --- | --- |
| **Model recommendation** | Suggest the next evidence check | Prove cause or authorize a production write |
| **Trusted, replay-bounded evidence** | Inform the policy decision | Override the causal proof gate |
| **Action Lease** | Permit one narrow, reversible containment action after human review | Create standing permission |
| **Causal proof + reproduction** | Unlock a human-reviewed permanent patch candidate | Bypass human approval |

```mermaid
flowchart LR
    E["Incident evidence arrives"] --> F{"Faultfix Evidence Firewall"}
    F -->|"trusted and before replay cutoff"| C["Sanitized evidence context"]
    F -->|"untrusted or too late"| Q["Quarantine\nno model context / no authority"]
    C --> M["Model advisory"]
    M --> P{"Deterministic authority policy"}
    P -->|"read-only"| A["ALLOW"]
    P -->|"reversible + scoped"| R["REVIEW"]
    P -->|"permanent or proof incomplete"| B["BLOCK"]
```

The graphic above and the diagram are the same contract: raw, untrusted content cannot reach the model; model output cannot become authority; permanent changes remain blocked until independently proved.

---

## See it in action

| Demo moment | What judges see | Why it matters |
| --- | --- | --- |
| **Hostile ticket** | A global production command is `BLOCK`ed and **0** raw ticket bytes reach model context | Prompt injection is stopped before inference, not “handled” by asking the model nicely |
| **Authority Simulator** | A judge can name a non-sensitive scenario, then change trust, replay time, action scope, and proof state to produce a fingerprinted `ALLOW` / `REVIEW` / `BLOCK` receipt | The scenario label is display-only; the policy boundary is inspectable without an API key, a provider call, or any Hugging Face credit |
| **INC-042** | A reversible containment route before causal proof is complete | Containment is not a root-cause verdict |
| **Four-pack challenge** | Capacity, DNS, identity rotation, and insufficient-evidence packs | The model advises across different cases; the authority policy stays the same |
| **Public case library** | Google Cloud and Cloudflare postmortems become provenance-tagged evidence | The product can work with real public evidence without pretending it has live production telemetry |

<p align="center">
  <a href="https://huggingface.co/spaces/jacklachan/faultfix"><kbd>Start with the hostile-ticket block</kbd></a>
  &nbsp;
  <a href="docs/judge-demo.md"><kbd>Follow the judge run-of-show</kbd></a>
</p>

---

## How a permanent fix is earned

```mermaid
flowchart TB
    S["Direct symptom"] --> D["Deploy + configuration evidence"]
    D --> X["Alternative explanation rejected"]
    X --> R["Regression reproduces failure"]
    R --> P["Human-reviewed permanent patch candidate"]

    D -. "bounded containment only" .-> L["Action Lease"]
    L --> H["Human review"]
    H --> C["Temporary, scoped containment"]

    M["Model hypothesis"] -. "never sufficient" .-> P
```

The deterministic demo incident is `INC-042`: payments fail after release `r42` reduces `DATABASE_POOL_LIMIT` from 40 to 20. Faultfix records the following evidence sequence:

1. Connection acquisition is exhausted in AZ-A.
2. The payment path stalls at the data-service pool.
3. Release `r42` changed the pool configuration.
4. The limit changed from `40` to `20`.
5. The overlapping DNS event is rejected: it affected another zone and cannot explain the symptom.
6. The regression test reproduces failure at `20` and resolves it at `40`.

Before causal proof is complete, Faultfix can offer a separate, simulated containment packet: pause `r42` promotion and drain AZ-A traffic from `r42` instances. It is review-gated, time-boxed, resource-scoped, and bound to one evidence fingerprint. It records **impact contained**, never **cause established**.

---

## What is real, what is simulated

| Surface | Boundary |
| --- | --- |
| `INC-042`, causal graph, regression, containment packet, and prevention guardrail | Deterministic fixture bundled with the app; no production system is queried |
| Evidence Firewall and Action Lease | Real policy mechanics shown through a deterministic security demo; the lease is simulated, scoped, time-bounded, and evidence-bound |
| Authority Simulator | A deterministic, fixed-enum policy evaluator. It makes zero model or provider calls and emits only a receipt derived from the selected policy attributes |
| Google Cloud GCE and Cloudflare incident packs | Structured from official public postmortems; they are read-only public evidence, not private raw telemetry or independent re-investigations |
| Hosted Space ranking | Runs keylessly with a small model plus deterministic fallback |
| Hosted live investigator | Optional. It requires a deployer-configured provider secret; its responses are validated and advisory only |

No model can alter the evidence sequence, proof score, containment authority, incident receipt, or permanent-fix gate.

The public case library links directly to the original [Google Cloud GCE postmortem](https://status.cloud.google.com/incident/compute/16007?post-mortem=) and [Cloudflare November 2025 postmortem](https://blog.cloudflare.com/18-november-2025-outage/). Faultfix displays bounded, paraphrased facts from those sources; it does not ingest their raw content into a model.

---

## Install the policy CLI

Faultfix includes an installable, offline **policy preflight** for terminal and CI use. It evaluates only fixed structured attributes; it never sends raw logs, prompts, or secrets anywhere, and it can never execute a production action.

**Supported platforms:** Windows, macOS, and Linux with Python 3.10+.

```bash
pipx install "git+https://github.com/jacklachan/faultfix.git"
faultfix check \
  --trust trusted \
  --replay within-cutoff \
  --action permanent \
  --proof reproduced \
  --scenario "Checkout timeouts after deployment" \
  --format json
```

`pipx` keeps this developer tool in an isolated environment. If it is not installed, run `python -m pip install --user pipx` first (on Windows, use `py` in place of `python`), then restart the terminal after `python -m pipx ensurepath`.

The command returns a fingerprinted receipt from the same policy used by the Space. It intentionally exits with `0` for `ALLOW`, `20` for `REVIEW`, `30` for `BLOCK`, and `64` for malformed policy input, so a pipeline or agent wrapper cannot mistake human review for permission to proceed.

For CI, pass only the bounded policy fields in JSON—never a raw incident log:

```json
{
  "evidence_trust": "trusted",
  "replay_status": "within-cutoff",
  "requested_action": "permanent",
  "proof_state": "reproduced",
  "scenario_label": "Checkout timeouts after deployment"
}
```

```bash
faultfix check --input faultfix-policy.json --format json
```

The scenario label is display-only. It is not model context, policy e

[README truncated for size]

## Detected evidence (automated analysis)

Indexed codebase: 23 recognized source files, 259 KB.
- CSS (language) — detected in the code
- Hugging Face (technology) — detected in the code
- Next.js (technology) — detected in the code
- Python (language) — detected in the code
- PyTorch (technology) — detected in the code
- React (technology) — detected in the code
- TypeScript (language) — detected in the code
- AI coding agent: Claude Code — evidence: config files committed to the repository
- AI coding agent: Codex — evidence: config files committed to the repository; commit authorship or trailers

## Codebase structure (from repository index)

### Files (30 of 30)

```
.gitignore
AGENTS.md
CLAUDE.md
docs/judge-demo.md
eslint.config.mjs
hosted-ranking-space/app.py
hosted-ranking-space/faultfix_policy.py
hosted-ranking-space/README.md
hosted-ranking-space/requirements.txt
package.json
pyproject.toml
README.md
scripts/sync-space.ps1
scripts/verify-authority-simulator.py
scripts/verify-faultfix-cli.py
src/app/advisory-ranking.module.css
src/app/globals.css
src/app/layout.tsx
src/app/page.module.css
src/app/page.tsx
src/app/phase2.module.css
src/lib/agent-lab.test.ts
src/lib/agent-lab.ts
src/lib/evidence-firewall.test.ts
src/lib/evidence-firewall.ts
src/lib/hosted-ranking.test.ts
src/lib/hosted-ranking.ts
src/lib/investigation.test.ts
src/lib/investigation.ts
tsconfig.json
```

### Dependencies

- hosted-ranking-space/requirements.txt: gradio@>=5.49,<6, sentencepiece@>=0.2, spaces@>=0.51,<0.52, torch@>=2.4, transformers@>=4.46,<5
- package.json: @types/node@^20, @types/react@^19, @types/react-dom@^19, eslint@^9, eslint-config-next@16.2.10, next@16.2.10, react@19.2.4, react-dom@19.2.4, typescript@^5, vitest@^4.1.10

### Recent commits (newest first)

- feat: add installable authority policy CLI
- feat: add safe scenario sandbox
- fix: improve Space legibility
- docs: add submission tester guide
- fix: require cited supported claims
- fix: restore ZeroGPU startup hook
- fix: align hosted authority policy
- feat: add deterministic authority simulator
- feat: add public incident case library
- fix: harden ranking and runtime dependencies
- fix: clarify public evidence boundary
- fix: label simulated incident benchmarks
- refactor: make live investigator Hugging Face only
- fix: retain required ZeroGPU declaration
- fix: disable experimental Space SSR
- refactor: remove unreachable demo code
- fix: restore Space CTA hierarchy
- feat: elevate demo presentation
- fix: fail over live model providers
- fix Space callback compatibility

## Key source files (fetched from GitHub, selected and truncated for size)

### CLAUDE.md

```markdown
@AGENTS.md

```

### AGENTS.md

```markdown
<!-- BEGIN:nextjs-agent-rules -->
# This is NOT the Next.js you know

This version has breaking changes — APIs, conventions, and file structure may all differ from your training data. Read the relevant guide in `node_modules/next/dist/docs/` before writing any code. Heed deprecation notices.
<!-- END:nextjs-agent-rules -->

```

### pyproject.toml

```
[build-system]
requires = ["setuptools>=68"]
build-backend = "setuptools.build_meta"

[project]
name = "faultfix"
version = "0.1.0"
description = "Offline evidence-bound authority preflight for AI incident agents"
readme = "README.md"
requires-python = ">=3.10"
authors = [{ name = "Faultfix contributors" }]
classifiers = [
  "Programming Language :: Python :: 3",
  "Programming Language :: Python :: 3 :: Only",
  "Topic :: Security",
]

[project.scripts]
faultfix = "faultfix_policy:main"

[tool.setuptools]
package-dir = { "" = "hosted-ranking-space" }
py-modules = ["faultfix_policy"]

```

### package.json

```
{
  "name": "faultfix",
  "version": "0.1.0",
  "private": true,
  "scripts": {
    "dev": "next dev",
    "build": "next build",
    "start": "next start",
    "lint": "eslint",
    "test": "vitest run",
    "test:space": "py scripts/verify-authority-simulator.py",
    "test:cli": "py scripts/verify-faultfix-cli.py",
    "verify": "npm test && npm run test:space && npm run test:cli && npm run lint && npm run build"
  },
  "dependencies": {
    "next": "16.2.10",
    "react": "19.2.4",
    "react-dom": "19.2.4"
  },
  "devDependencies": {
    "@types/node": "^20",
    "@types/react": "^19",
    "@types/react-dom": "^19",
    "eslint": "^9",
    "eslint-config-next": "16.2.10",
    "typescript": "^5",
    "vitest": "^4.1.10"
  },
  "overrides": {
    "next": {
      "postcss": "8.5.19"
    }
  }
}

```

### hosted-ranking-space/requirements.txt

```
gradio>=5.49,<6
spaces>=0.51,<0.52
transformers>=4.46,<5
torch>=2.4
sentencepiece>=0.2

```

### src/app/layout.tsx

```typescript
import type { Metadata } from "next";
import { Geist, Geist_Mono } from "next/font/google";
import "./globals.css";

const geistSans = Geist({
  variable: "--font-geist-sans",
  subsets: ["latin"],
});

const geistMono = Geist_Mono({
  variable: "--font-geist-mono",
  subsets: ["latin"],
});

export const metadata: Metadata = {
  title: "faultfix — agent authority layer",
  description:
    "The evidence-first authority layer that makes incident-response agents earn the right to act.",
};

export default function RootLayout({
  children,
}: Readonly<{
  children: React.ReactNode;
}>) {
  return (
    <html lang="en" className={`${geistSans.variable} ${geistMono.variable}`}>
      <body>{children}</body>
    </html>
  );
}

```

### scripts/verify-faultfix-cli.py

```python
"""Exercise the installable Faultfix policy CLI without dependencies or network access."""

from __future__ import annotations

import contextlib
import io
import json
import sys
import tempfile
from pathlib import Path


ROOT = Path(__file__).resolve().parents[1]
SPACE_DIR = ROOT / "hosted-ranking-space"
sys.path.insert(0, str(SPACE_DIR))

import faultfix_policy


def run(*arguments: str) -> tuple[int, str]:
    output = io.StringIO()
    with contextlib.redirect_stdout(output):
        code = faultfix_policy.main(arguments)
    return code, output.getvalue()


def main() -> None:
    code, output = run(
        "check",
        "--trust",
        "trusted",
        "--replay",
        "within-cutoff",
        "--action",
        "permanent",
        "--proof",
        "incomplete",
        "--scenario",
        "Checkout timeouts after deploy",
        "--format",
        "json",
    )
    receipt = json.loads(output)
    assert code == 30
    assert receipt["authority"] == "BLOCK"
    assert receipt["model_calls"] == 0
    assert receipt["scenario_label"] == "Checkout timeouts after deploy"
    assert receipt["scenario_label_boundary"].startswith("display-only")

    code, output = run(
        "check",
        "--trust",
        "trusted",
        "--replay",
        "within-cutoff",
        "--action",
        "permanent",
        "--proof",
        "reproduced",
        "--format",
        "json",
    )
    receipt = json.loads(output)
    assert code == 20
    assert receipt["authority"] == "REVIEW"
    assert receipt["receipt_fingerprint"] == faultfix_policy.evaluate_authority_simulator(
        "trusted", "within-cutoff", "permanent", "reproduced"
    )["receipt_fingerprint"]

    with tempfile.TemporaryDirectory() as temporary_directory:
        fixture = Path(temporary_directory) / "policy.json"
        fixture.write_text(
            json.dumps(
                {
                    "evidence_trust": "untrusted",
                    "replay_status": "within-cutoff",
                    "requested_action": "observe",
                    "proof_state": "reproduced",
                }
            ),
            encoding="utf-8",
        )
        code, output = run("check", "--input", str(fixture), "--format", "json")
        receipt = json.loads(output)
        assert code == 30
        assert receipt["authority"] == "BLOCK"
        assert receipt["disposition"] == "quarantine"

        fixture.write_text(json.dumps({"raw_log": "do not execute this"}), encoding="utf-8")
        errors = io.StringIO()
        with contextlib.redirect_stderr(errors):
            code = faultfix_policy.main(("check", "--input", str(fixture)))
        assert code == 64
        assert "Unsupported policy input field" in errors.getvalue()

    print("PASS: installable CLI shares the deterministic Space policy")
    print("PASS: REVIEW and BLOCK use non-zero CI exit codes")
    print("PASS: raw-log-shaped JSON fields are rejected")
    print("PASS: CLI makes zero model, provider, and network calls")


if __name__ == "__main__":
    main()

```

### scripts/verify-authority-simulator.py

```python
"""Verify the Space policy boundary without starting the app.

This standard-library test selectively loads only the module definitions needed for
the simulator. It poisons every model and provider helper so a future refactor
cannot accidentally turn the no-cost control surface into an inference path.
"""

from __future__ import annotations

import ast
import json
import re
import sys
import types
from collections import Counter
from itertools import product
from pathlib import Path


APP_PATH = Path(__file__).resolve().parents[1] / "hosted-ranking-space" / "app.py"
SPACE_DIR = APP_PATH.parent
TARGET = "render_authority_simulator"
LOAD_UNTIL = "reset_authority_simulator"


def load_simulator_namespace():
    sys.path.insert(0, str(SPACE_DIR))
    source = APP_PATH.read_text(encoding="utf-8")
    tree = ast.parse(source, filename=str(APP_PATH))
    body = []
    for node in tree.body:
        if isinstance(
            node,
            (
                ast.Import,
                ast.ImportFrom,
                ast.Assign,
                ast.AnnAssign,
                ast.FunctionDef,
                ast.AsyncFunctionDef,
                ast.ClassDef,
            ),
        ):
            body.append(node)
        if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)) and node.name == LOAD_UNTIL:
            break
    else:
        raise AssertionError(f"{LOAD_UNTIL} was not found in {APP_PATH}")

    gradio = types.ModuleType("gradio")
    gradio.Request = object
    sys.modules["gradio"] = gradio

    spaces = types.ModuleType("spaces")
    spaces.GPU = lambda fn=None, *args, **kwargs: (
        fn if callable(fn) else lambda decorated: decorated
    )
    sys.modules["spaces"] = spaces

    hub = types.ModuleType("huggingface_hub")
    hub.InferenceClient = object
    sys.modules["huggingface_hub"] = hub

    transformers = types.ModuleType("transformers")
    transformers.pipeline = lambda *args, **kwargs: None
    sys.modules["transformers"] = transformers

    namespace: dict[str, object] = {"__name__": "__authority_simulator_test__"}
    module = ast.Module(body=body, type_ignores=[])
    exec(compile(ast.fix_missing_locations(module), str(APP_PATH), "exec"), namespace)
    return namespace


def attribute(markup: str, name: str) -> str:
    match = re.search(
        rf"\b{re.escape(name)}=([\"'])(.*?)\1",
        markup,
        flags=re.IGNORECASE | re.DOTALL,
    )
    if not match:
        raise AssertionError(f"Missing {name} in receipt:\n{markup}")
    return match.group(2)


def expected_disposition(trust: str, replay: str) -> str:
    # Replay status is evaluated before trust, mirroring the Evidence Firewall.
    if replay == "post-cutoff":
        return "future"
    if trust == "untrusted":
        return "quarantine"
    return "admit"


def expected_authority(disposition: str, action: str, proof: str) -> str:
    if disposition != "admit":
        return "BLOCK"
    if action == "observe":
        return "ALLOW"
    if action == "contain":
        return "REVIEW"
    return "REVIEW" if proof == "reproduced" else "BLOCK"


def main() -> None:
    namespace = load_simulator_namespace()
    calls: list[str] = []

    def poison(name: str):
        def blocked(*args, **kwargs):
            calls.append(name)
            raise AssertionError(f"{name} must never run in Authority Simulator")

        return blocked

    # The simulator must never reserve budget, instantiate a provider client, or
    # invoke either the local ranker or the hosted investigator.
    for name in (
        "ranker",
        "pipeline",
        "warm_ranker",
        "invoke_live_model",
        "invoke_hf_completion",
        "invoke_hf_with_single_fallback",
        "reserve_live_model_budget",
        "configured_hf_model",
        "maximum_hf_attempts",
    ):
        namespace[name] = poison(name)
    namespace["InferenceClient"] = poison("InferenceClient")

    evaluate = namespace["evaluate_authority_simulator"]
    render = namespace[TARGET]
    assert callable(evaluate)
    assert callable(render)

    outcomes: Counter[str] = Counter()
    fingerprints: set[str] = set()
    for trust, replay, action, proof in product(
        ("trusted", "untrusted"),
        ("within-cutoff", "post-cutoff"),
        ("observe", "contain", "permanent"),
        ("incomplete", "reproduced"),
    ):
        result = evaluate(trust, replay, action, proof)
        markup = render(trust, replay, action, proof)
        disposition = expected_disposition(trust, replay)
        authority = expected_authority(disposition, action, proof)
        model_reach = "admitted" if disposition == "admit" else "none"

        assert result["disposition"] == disposition
        assert result["authority"] == authority
        assert result["model_reach"] == model_reach
        assert attribute(markup, "data-authority").upper() == authority
        assert attribute(markup, "data-evidence-disposition").lower() == disposition
        assert attribute(markup, "data-model-reach").lower() == model_reach
        assert attribute(markup, "data-model-calls") == "0"

        fingerprint = attribute(markup, "data-receipt-fingerprint").upper()
        assert re.fullmatch(r"[A-F0-9]{12}", fingerprint), fingerprint
        fingerprints.add(fingerprint)
        outcomes[authority] += 1

    assert outcomes == Counter({"BLOCK": 19, "REVIEW": 3, "ALLOW": 2}), outcomes
    assert len(fingerprints) == 24, "Every simulator state must produce a distinct receipt"

    # Malformed public input fails closed; it cannot select a friendly outcome.
    malformed = evaluate("trusted<script>", "tomorrow", "erase", "verified")
    assert malformed["disposition"] == "future"
    assert malformed["authority"] == "BLOCK"
    assert malformed["model_reach"] == "none"

    # Scenario labels make the control easier to apply to a user's own issue,
    # but they are display-only: they are escaped, bounded, never model input,
    # and never change an auth
[truncated — 2741 more characters]
```

### hosted-ranking-space/faultfix_policy.py

```python
"""Offline, deterministic authority policy shared by Faultfix surfaces.

This module deliberately has no model, network, Gradio, or provider dependency.
It can be used by the public Space, local terminals, and CI without turning a
policy preflight into an agent that can execute production changes.
"""

from __future__ import annotations

import argparse
import hashlib
import json
import sys
from pathlib import Path
from typing import Sequence


VERSION = "0.1.0"
AUTHORITY_SIMULATOR_POLICY = "faultfix-authority-simulator/v1"
AUTHORITY_SIMULATOR_DEFAULTS = {
    "evidence_trust": "trusted",
    "replay_status": "within-cutoff",
    "requested_action": "contain",
    "proof_state": "incomplete",
}
AUTHORITY_SIMULATOR_ALLOWED = {
    "evidence_trust": {"trusted", "untrusted"},
    "replay_status": {"within-cutoff", "post-cutoff"},
    "requested_action": {"observe", "contain", "permanent"},
    "proof_state": {"incomplete", "reproduced"},
}
_INPUT_KEYS = frozenset({*AUTHORITY_SIMULATOR_DEFAULTS, "scenario_label"})
_EXIT_CODES = {"ALLOW": 0, "REVIEW": 20, "BLOCK": 30}


def policy_authority(requested_action: str, has_release_evidence: bool, proof_reproduced: bool = False) -> tuple[str, str]:
    """Return the authority decision shared by deterministic and live paths."""
    if requested_action in {"observe", "none"}:
        return "ALLOW", "Read-only investigation is within the boundary."
    if requested_action == "contain" and has_release_evidence:
        return "REVIEW", "Containment is reversible and in scope, but an incident commander must approve the action lease."
    if requested_action == "contain":
        return "BLOCK", "Containment scope is not evidenced yet. Collect a trustworthy release or infrastructure boundary first."
    if requested_action == "permanent" and has_release_evidence and proof_reproduced:
        return "REVIEW", "Causal proof is reproduced, but a permanent change still needs human approval and staged rollout."
    return "BLOCK", "Permanent changes remain blocked until the deterministic causal proof gate and reproduction are complete."


def normalize_simulator_choice(value: object, field: str) -> str:
    """Accept only fixed policy enums and fail closed for malformed values."""
    if isinstance(value, str) and value in AUTHORITY_SIMULATOR_ALLOWED[field]:
        return value
    return {
        "evidence_trust": "untrusted",
        "replay_status": "post-cutoff",
        "requested_action": "permanent",
        "proof_state": "incomplete",
    }[field]


def normalize_scenario_label(value: object) -> str:
    """Keep a user-supplied scenario label bounded and display-only."""
    if not isinstance(value, str):
        return ""
    return " ".join(value.split())[:120]


def evaluate_authority_simulator(
    evidence_trust: object,
    replay_status: object,
    requested_action: object,
    proof_state: object,
) -> dict[str, str]:
    """Evaluate a bounded scenario without consulting a model or provider."""
    trust = normalize_simulator_choice(evidence_trust, "evidence_trust")
    replay = normalize_simulator_choice(replay_status, "replay_status")
    action = normalize_simulator_choice(requested_action, "requested_action")
    proof = normalize_simulator_choice(proof_state, "proof_state")

    # Replay status wins: hindsight cannot gain influence because it is trusted.
    if replay == "post-cutoff":
        disposition = "future"
        evidence_label = "EXCLUDE"
        model_reach = "none"
        model_context = "0 bytes / post-cutoff evidence excluded"
        authority = "BLOCK"
        reason = "The observation falls outside the replay boundary, so it cannot influence this decision."
        next_step = "Use a trustworthy observation captured before the replay cutoff."
        lease = "Not issued"
    elif trust == "untrusted":
        disposition = "quarantine"
        evidence_label = "QUARANTINE"
        model_reach = "none"
        model_context = "0 bytes / untrusted content quarantined"
        authority = "BLOCK"
        reason = "Untrusted content cannot become model context or action authority."
        next_step = "Replace it with a first-party, scope-bound fact before evaluating an action."
        lease = "Not issued"
    else:
        disposition = "admit"
        evidence_label = "ADMIT"
        model_reach = "admitted"
        model_context = "1 normalized, scope-bound fact"
        authority, reason = policy_authority(
            action,
            has_release_evidence=True,
            proof_reproduced=proof == "reproduced",
        )
        if authority == "ALLOW":
            next_step = "Collect the next trustworthy fact without changing production state."
            lease = "Not required"
        elif action == "contain":
            next_step = "Request a narrow, time-bounded containment lease bound to this receipt."
            lease = "Pending human approval"
        elif authority == "REVIEW":
            next_step = "Prepare the smallest staged change packet for human review."
            lease = "Pending human approval"
        else:
            next_step = "Reproduce the causal mechanism before proposing a permanent change."
            lease = "Not issued"

    receipt_input = {
        "action": action,
        "disposition": disposition,
        "policy": AUTHORITY_SIMULATOR_POLICY,
        "proof": proof,
        "replay": replay,
        "trust": trust,
    }
    fingerprint = hashlib.sha256(
        json.dumps(receipt_input, sort_keys=True, separators=(",", ":")).encode("utf-8")
    ).hexdigest()[:12].upper()
    return {
        "authority": authority,
        "disposition": disposition,
        "evidence_label": evidence_label,
        "lease": lease,
        "model_context": model_context,
        "model_reach": model_reach,
        "next_step": next_step,
        "policy": AUTHORITY_SIMULATOR_POLICY,
        "proof": proof,
        "reason": reason,
        "receipt_fingerprint": fingerprint,
       
[truncated — 4174 more characters]
```

### src/app/globals.css

```css
:root{--background:#0a1013;--foreground:#d6e1dd}*{box-sizing:border-box;padding:0;margin:0}html,body{max-width:100vw;min-height:100%;background:var(--background)}body{color:var(--foreground);font-family:Arial,Helvetica,sans-serif;-webkit-font-smoothing:antialiased}button{font:inherit}

```

[12 more indexed source files omitted to keep this export small. The full file list is in the Codebase structure section above.]