Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions submissions/mcp-hackathon/shahadattest-exhaustive-gate/RIGHTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# Submission rights declaration

Project: `ExhaustiveGate`
Submission slug: `shahadattest-exhaustive-gate`
Submitter: `shahadattest`
Date: `2026-09-04`

The submitter confirms that they own, or have sufficient authorization for, the source code, dependencies, service, data, branding, and other materials submitted in this pull request.

Subject to the official program terms, the submitter authorizes X-Agent to retain, reproduce, audit, test, archive, and publish the submitted program artifact for judging, fraud prevention, dispute handling, ecosystem submission, and post-award accountability. Closing the pull request, deleting a fork, or deleting an external repository does not revoke the official archive rights attached to an accepted and rewarded entry.

Third-party components and their licenses: FastAPI (MIT), uvicorn (BSD), Pydantic v2 (MIT), SQLAlchemy (MIT), httpx (BSD), pytest (MIT), nginx (BSD) — see `source/` manifests.

Exceptions or restrictions: `none`

This template is an operational declaration, not a substitute for event terms reviewed by qualified counsel.
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
# ExhaustiveGate

## Capability

- **One-line description:** Verify an AI agent actually covered the required result set before it claims all, none, exactly N, cheapest, highest, or lowest — returns PROVEN / UNPROVEN / CONDITIONAL with blocking reasons and next actions.
- **Who it helps:** AI agents and developers that paginate third-party APIs and must avoid false exhaustive claims.
- **Capability boundary:** Sessions, scope hashing, pagination-chain validation, failure/snapshot tracking, proof obligations, verdicts, SHA-256 proof certificates. Does NOT modify upstream APIs, does NOT do security auditing, penetration testing, or risk scoring.

## Live API

- **API base URL:** `https://mean-capital-republican-understood.trycloudflare.com/v1` (local verified: `http://localhost:8100/v1`)
- **Health-check URL:** `https://mean-capital-republican-understood.trycloudflare.com/health`
- **Authentication:** none
- **Rate limits / known limits:** No auth limits; JSON body cap ~512KB. Core makes no upstream calls (evidence is posted by the agent).
- **API contract:** `source/docs/api.md`; interactive docs at `/docs`.

## Source and reproducibility

- **Source repository:** `https://github.com/ShahadatTest/exhaustive-gate`
- **Review commit:** `79a2a1c5f8a746584996b88c11aee746e079d48f`
- **Source submitted in this PR:** `source/`
- **Run tests:** `cd source/backend && pip install -r requirements.txt && python -m pytest tests/ -q` (15 passed)
- **Run locally:** `cd source && docker-compose up --build` (dashboard :8102, gate :8100, demo CRM :8101)
- **Deploy:** build `source/backend/Dockerfile`, set `GIT_COMMIT=<review-commit>` and `XAGENT_SLUG=shahadattest-exhaustive-gate`
- **Version binding:** `/health` returns `{"status":"ok","commit":"<review-commit>"}` and `/.well-known/xagent-verification.json` returns `{"schemaVersion":1,"slug":"shahadattest-exhaustive-gate","commit":"<review-commit>"}`

## Verification

Reproducible call instructions and redacted example responses are in `verification/README.md`.

- **Health-check result:** `{"status":"ok","service":"exhaustive-gate","version":"0.1.0","commit":"79a2a1c5f8a746584996b88c11aee746e079d48f"}`
- **Capability call:** `POST /v1/sessions` → observe 4 invoice pages → `POST /v1/sessions/{id}/verify` with `{"claim":{"type":"EXACT_COUNT","value":347}}` → `PROVEN` + proof certificate
- **Expected error behavior:** unknown session → 404; invalid pagination_type → 400; certificate before PROVEN → 404; incomplete evidence → `UNPROVEN` with `blocking_reasons` + `required_next_actions`

## Security and data handling

- **Data collected:** Retrieval-evidence metadata the reviewer posts (page/cursor/counts); no end-user data.
- **Purpose and retention:** Review/demo only, local SQLite file.
- **Third parties / outbound network calls:** none in core.
- **Secrets:** No secrets are committed. Review access is supplied only through an approved private channel when required.
- **Known risks / restrictions:** none; verdicts are deterministic functions of posted evidence.

## Support

- **Team / builder:** shahadattest (solo)
- **Contact:** via GitHub `shahadattest`
- **License / rights:** MIT (see `source/LICENSE`); submitter authorizes review and archival per RIGHTS.md.
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
PORT=8000
DATABASE_URL=sqlite:///./exhaustive_gate.db
GIT_COMMIT=dev-local
XAGENT_SLUG=team-exhaustive-gate
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
MIT License — ExhaustiveGate (hackathon MVP).
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
# ExhaustiveGate

AI agents often mistake partial retrieval for complete evidence.

ExhaustiveGate verifies whether an agent has actually covered the required result set before allowing claims such as:

- all
- none
- exactly N
- cheapest
- highest
- lowest

**“Finding no more evidence is not the same as proving there is no more evidence.”**

## Quick start

```bash
cd exhaustive-gate/backend
pip install -r requirements.txt
python -m uvicorn app.main:app --port 8100
cd ../examples/demo-crm
python -m uvicorn main:app --port 8101
# open ../frontend/index.html
```

## Docker

```bash
cd exhaustive-gate
docker-compose up --build
# frontend :8102, gate :8100, CRM :8101
```

## Example

```bash
curl -X POST localhost:8100/v1/sessions -H 'Content-Type: application/json' \
-d '{"resource_type":"invoice","scope":{"status":"unpaid"}}'
```

See `docs/api.md`, `docs/proof-model.md`, `docs/demo.md`. Security: validated inputs,
body caps, no code execution, no upstream fetching in core. License: MIT.
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
FROM python:3.12-slim
WORKDIR /code
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY app ./app
COPY tests ./tests
ENV PORT=8000
EXPOSE 8000
CMD ["sh","-c","python -m uvicorn app.main:app --host 0.0.0.0 --port ${PORT:-8000}"]
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
import json
import uuid
from fastapi import APIRouter, HTTPException
from sqlalchemy.orm import Session

from app.models.db import SessionRow, engine
from app.schemas.api import FailureRecord, ObservePage, ParseIn, SessionCreate, VerifyIn
from app.services.certificates import issue_certificate
from app.services.claim_parser import parse_claim
from app.services.scope import scope_hash
from app.services.verifier import verify

router = APIRouter()


def _db() -> Session:
return Session(engine, expire_on_commit=False)


def _load(row: SessionRow) -> dict:
return {"id": row.id, "resource_type": row.resource_type, "source": row.source,
"scope": json.loads(row.scope_json or "{}"), "scope_hash": row.scope_hash,
"pagination_type": row.pagination_type, "snapshot_strategy": row.snapshot_strategy,
"status": row.status, "observations": json.loads(row.observations_json or "[]"),
"failures": json.loads(row.failures_json or "[]")}


@router.post("/sessions")
async def create_session(body: SessionCreate):
if body.pagination_type not in ("cursor", "offset", "page", "single"):
raise HTTPException(400, "pagination_type must be cursor|offset|page|single")
if body.snapshot_strategy not in ("STRICT", "BEST_EFFORT", "UNKNOWN"):
raise HTTPException(400, "snapshot_strategy must be STRICT|BEST_EFFORT|UNKNOWN")
sid = "sess_" + uuid.uuid4().hex[:12]
db = _db()
row = SessionRow(id=sid, resource_type=body.resource_type, source=body.source,
scope_json=json.dumps(body.scope), scope_hash=scope_hash(body.scope),
pagination_type=body.pagination_type, snapshot_strategy=body.snapshot_strategy)
db.add(row)
db.commit()
db.close()
return {"session_id": sid, "status": "collecting", "scope_hash": row.scope_hash}


@router.get("/sessions/{sid}")
async def get_session(sid: str):
db = _db()
row = db.get(SessionRow, sid)
if not row:
db.close()
raise HTTPException(404, "session not found")
out = _load(row)
out["observation_count"] = len(out["observations"])
out["failure_count"] = len(out["failures"])
db.close()
return out


@router.post("/sessions/{sid}/observe")
async def observe(sid: str, body: ObservePage):
db = _db()
row = db.get(SessionRow, sid)
if not row:
db.close()
raise HTTPException(404, "session not found")
obs = json.loads(row.observations_json or "[]")
scope = body.scope if body.scope is not None else json.loads(row.scope_json or "{}")
entry = {"page_number": body.page_number, "offset": body.offset, "cursor_in": body.cursor_in,
"cursor_out": body.cursor_out, "has_more": body.has_more, "records_seen": body.records_seen,
"items": body.items, "scope": scope, "scope_hash": scope_hash(scope),
"snapshot_id": body.snapshot_id, "authoritative_total": body.authoritative_total}
obs.append(entry)
row.observations_json = json.dumps(obs)
row.status = "complete" if not body.has_more else "collecting"
db.add(row)
db.commit()
last = obs[-1]
nxt = last.get("cursor_out") if last.get("has_more") else None
db.close()
return {"accepted": True, "coverage_status": "COMPLETE" if not body.has_more else "INCOMPLETE",
"next_expected_cursor": nxt, "pages_seen": len(obs)}


@router.post("/sessions/{sid}/failure")
async def record_failure(sid: str, body: FailureRecord):
db = _db()
row = db.get(SessionRow, sid)
if not row:
db.close()
raise HTTPException(404, "session not found")
fails = json.loads(row.failures_json or "[]")
fails.append({"page_number": body.page_number, "kind": body.kind, "message": body.message})
row.failures_json = json.dumps(fails)
row.status = "collecting"
db.add(row)
db.commit()
db.close()
return {"accepted": True, "unresolved_failures": len(fails)}


@router.get("/sessions/{sid}/observations")
async def list_observations(sid: str):
db = _db()
row = db.get(SessionRow, sid)
if not row:
db.close()
raise HTTPException(404, "session not found")
out = {"observations": json.loads(row.observations_json or "[]"),
"failures": json.loads(row.failures_json or "[]")}
db.close()
return out


@router.post("/sessions/{sid}/verify")
async def verify_claim(sid: str, body: VerifyIn):
db = _db()
row = db.get(SessionRow, sid)
if not row:
db.close()
raise HTTPException(404, "session not found")
sess = _load(row)
claim = body.claim.model_dump(exclude_none=False)
result = verify(sess, claim)
row.result_json = json.dumps(result)
if result["verdict"] == "PROVEN":
row.certificate_json = json.dumps(issue_certificate(sess, claim, result))
row.status = "proven"
else:
row.certificate_json = "{}"
row.status = "collecting"
db.add(row)
db.commit()
cert = json.loads(row.certificate_json or "{}")
db.close()
result["certificate"] = cert or None
return result


@router.get("/sessions/{sid}/certificate")
async def get_certificate(sid: str):
db = _db()
row = db.get(SessionRow, sid)
if not row:
db.close()
raise HTTPException(404, "session not found")
cert = json.loads(row.certificate_json or "{}")
db.close()
if not cert:
raise HTTPException(404, "no certificate (claim not PROVEN yet)")
return cert


@router.post("/claims/parse")
async def parse(body: ParseIn):
return parse_claim(body.text)
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import os

APP_NAME = "exhaustive-gate"
APP_VERSION = "0.1.0"
DATABASE_URL = os.getenv("DATABASE_URL", "sqlite:///./exhaustive_gate.db")
GIT_COMMIT = os.getenv("GIT_COMMIT", "dev-local")
XAGENT_SLUG = os.getenv("XAGENT_SLUG", os.getenv("PROJECT_SLUG", "exhaustive-gate"))
MAX_BODY_BYTES = int(os.getenv("MAX_BODY_BYTES", "524288"))
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware

from app.api.sessions import router as sessions_router
from app.core.config import APP_VERSION, GIT_COMMIT, XAGENT_SLUG
from app.models.db import init_db

app = FastAPI(title="ExhaustiveGate", version=APP_VERSION)
app.add_middleware(CORSMiddleware, allow_origins=["*"], allow_methods=["*"], allow_headers=["*"])

init_db()
app.include_router(sessions_router, prefix="/v1")


@app.get("/health")
async def health():
return {"status": "ok", "service": "exhaustive-gate", "version": APP_VERSION, "commit": GIT_COMMIT}


@app.get("/.well-known/xagent-verification.json")
async def verification():
return {"schemaVersion": 1, "slug": XAGENT_SLUG, "commit": GIT_COMMIT}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
from sqlalchemy import String, Text, create_engine
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column

from app.core.config import DATABASE_URL

engine = create_engine(DATABASE_URL, connect_args={"check_same_thread": False} if DATABASE_URL.startswith("sqlite") else {})


class Base(DeclarativeBase):
pass


class SessionRow(Base):
__tablename__ = "sessions"
id: Mapped[str] = mapped_column(String(32), primary_key=True)
resource_type: Mapped[str] = mapped_column(String(128), default="")
source: Mapped[str] = mapped_column(String(128), default="")
scope_json: Mapped[str] = mapped_column(Text, default="{}")
scope_hash: Mapped[str] = mapped_column(String(64), default="")
pagination_type: Mapped[str] = mapped_column(String(32), default="cursor")
snapshot_strategy: Mapped[str] = mapped_column(String(32), default="STRICT")
status: Mapped[str] = mapped_column(String(32), default="collecting")
observations_json: Mapped[str] = mapped_column(Text, default="[]")
failures_json: Mapped[str] = mapped_column(Text, default="[]")
result_json: Mapped[str] = mapped_column(Text, default="{}")
certificate_json: Mapped[str] = mapped_column(Text, default="{}")


def init_db() -> None:
Base.metadata.create_all(engine)
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
from typing import Any, Optional
from pydantic import BaseModel, Field


class SessionCreate(BaseModel):
resource_type: str = Field(min_length=1, max_length=128)
source: str = Field(default="demo-crm", max_length=128)
scope: dict[str, Any] = Field(default_factory=dict)
pagination_type: str = Field(default="cursor")
snapshot_strategy: str = Field(default="STRICT")


class ObservePage(BaseModel):
page_number: Optional[int] = None
offset: Optional[int] = None
cursor_in: Optional[str] = None
cursor_out: Optional[str] = None
has_more: bool = False
records_seen: int = Field(ge=0, default=0)
items: list[dict[str, Any]] = Field(default_factory=list)
scope: Optional[dict[str, Any]] = None
snapshot_id: Optional[str] = None
authoritative_total: Optional[int] = None


class FailureRecord(BaseModel):
page_number: Optional[int] = None
kind: str = Field(default="unknown")
message: str = Field(default="", max_length=500)


class ClaimIn(BaseModel):
type: str
resource: Optional[str] = None
scope: Optional[dict[str, Any]] = None
value: Optional[Any] = None
field: Optional[str] = None
candidate_id: Optional[str] = None


class VerifyIn(BaseModel):
claim: ClaimIn


class ParseIn(BaseModel):
text: str = Field(min_length=1, max_length=500)
Loading
Loading