From 27817fffd7475656f3d8cf2685606baa12548659 Mon Sep 17 00:00:00 2001 From: ZoaGrad <79272036+ZoaGrad@users.noreply.github.com> Date: Sat, 21 Feb 2026 08:21:22 -0700 Subject: [PATCH] =?UTF-8?q?feat:=20A.I.R.=20API=20layer=20=E2=80=94=20Vaul?= =?UTF-8?q?tNode=20operational=20stack?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - main.py: FastAPI app with lifespan DB pool, full interdiction logic wired to asyncpg. /event validates state transitions against ratified workflows, flags incidents on unauthorized transitions. - schema.sql: Five-table Postgres ledger (agents, sessions, workflows, events, incidents). Authoritative init script. - docker-compose.yml: Postgres 15-alpine container, schema mounted as init script, credentials sourced from .env (never hardcoded). - requirements.txt: Pinned deps — FastAPI 0.111, asyncpg 0.29, pydantic-settings 2.2. - .env.example: Credential template, safe to commit. - .gitignore: .env blocked from remote. CAGE: 17TJ5 | UEI: SVZVXPTM9AF4 Co-Authored-By: Claude Opus 4.6 --- .env.example | 6 ++ .gitignore | 6 ++ docker-compose.yml | 23 ++++++ main.py | 190 +++++++++++++++++++++++++++++++++++++++++++++ requirements.txt | 5 ++ schema.sql | 39 ++++++++++ 6 files changed, 269 insertions(+) create mode 100644 .env.example create mode 100644 .gitignore create mode 100644 docker-compose.yml create mode 100644 main.py create mode 100644 requirements.txt create mode 100644 schema.sql diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..f528f54 --- /dev/null +++ b/.env.example @@ -0,0 +1,6 @@ +# A.I.R. Environment — copy to .env and populate +# NEVER commit .env to version control + +AIR_DB_USER=architect +AIR_DB_PASSWORD=your_sovereign_password_here +AIR_DB_NAME=air_node diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..eb6d1f4 --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +.env +__pycache__/ +*.pyc +*.pyo +.venv/ +venv/ diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..6c630f5 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,23 @@ +# A.I.R. — Agent Incident Recorder +# VaultNode // Blackglass Continuum LLC +# CAGE: 17TJ5 | UEI: SVZVXPTM9AF4 + +version: '3.8' + +services: + air-db: + image: postgres:15-alpine + container_name: air_vault_db + environment: + POSTGRES_USER: ${AIR_DB_USER} + POSTGRES_PASSWORD: ${AIR_DB_PASSWORD} + POSTGRES_DB: ${AIR_DB_NAME} + ports: + - "5432:5432" + volumes: + - ./schema.sql:/docker-entrypoint-initdb.d/init.sql:ro + - air_db_data:/var/lib/postgresql/data + restart: unless-stopped + +volumes: + air_db_data: diff --git a/main.py b/main.py new file mode 100644 index 0000000..f8dc3dd --- /dev/null +++ b/main.py @@ -0,0 +1,190 @@ +# A.I.R. — Agent Incident Recorder +# VaultNode // Blackglass Continuum LLC +# CAGE: 17TJ5 | UEI: SVZVXPTM9AF4 +# Mission: Truth Preservation in Agentic Workflows + +from contextlib import asynccontextmanager +from typing import Any, Dict, List + +import asyncpg +from fastapi import FastAPI, HTTPException +from pydantic import BaseModel +from pydantic_settings import BaseSettings + + +# --------------------------------------------------------------------------- +# Configuration +# --------------------------------------------------------------------------- + +class Settings(BaseSettings): + air_db_user: str + air_db_password: str + air_db_name: str + air_db_host: str = "localhost" + air_db_port: int = 5432 + + class Config: + env_file = ".env" + +settings = Settings() + + +# --------------------------------------------------------------------------- +# Lifespan — DB pool init/teardown +# --------------------------------------------------------------------------- + +@asynccontextmanager +async def lifespan(app: FastAPI): + app.state.pool = await asyncpg.create_pool( + user=settings.air_db_user, + password=settings.air_db_password, + database=settings.air_db_name, + host=settings.air_db_host, + port=settings.air_db_port, + min_size=2, + max_size=10, + ) + yield + await app.state.pool.close() + + +# --------------------------------------------------------------------------- +# App +# --------------------------------------------------------------------------- + +app = FastAPI( + title="A.I.R. VaultNode API", + version="0.1.0", + description="Truth Preservation in Agentic Workflows", + lifespan=lifespan, +) + + +# --------------------------------------------------------------------------- +# Sovereign State Definitions +# --------------------------------------------------------------------------- + +class AgentEvent(BaseModel): + agent_id: str + session_id: str + action: str + state_before: str + state_after: str + metadata: Dict[str, Any] = {} + + +class WorkflowDef(BaseModel): + name: str + definition: Dict[str, List[str]] + + +# --------------------------------------------------------------------------- +# Endpoints +# --------------------------------------------------------------------------- + +@app.post("/event") +async def log_event(event: AgentEvent): + """ + Core interdiction vector. + 1. Fetch authorized workflow for this session. + 2. If state_after NOT IN valid_transitions[state_before] → flag incident. + 3. Else → commit event to ledger. + """ + async with app.state.pool.acquire() as conn: + # Fetch workflow bound to this session's agent + row = await conn.fetchrow( + """ + SELECT w.definition + FROM sessions s + JOIN workflows w ON w.name = s.agent_id + WHERE s.id = $1 + """, + event.session_id, + ) + + if row: + definition = row["definition"] + valid_next = definition.get(event.state_before, []) + if event.state_after not in valid_next: + # Unauthorized transition — generate incident + incident_id = f"INC-{event.session_id}-{event.action}" + await conn.execute( + """ + INSERT INTO incidents + (id, session_id, reason, observed_transition, expected_transition) + VALUES ($1, $2, $3, $4, $5) + ON CONFLICT (id) DO NOTHING + """, + incident_id, + event.session_id, + "unauthorized_state_transition", + f"{event.state_before} -> {event.state_after}", + valid_next, + ) + raise HTTPException( + status_code=409, + detail={ + "status": "incident_flagged", + "incident_id": incident_id, + "observed": f"{event.state_before} -> {event.state_after}", + "authorized": valid_next, + }, + ) + + # Authorized — commit to ledger + await conn.execute( + """ + INSERT INTO events + (id, session_id, action, state_before, state_after, raw_json) + VALUES (gen_random_uuid(), $1, $2, $3, $4, $5) + """, + event.session_id, + event.action, + event.state_before, + event.state_after, + event.metadata, + ) + + return {"status": "committed", "session_id": event.session_id} + + +@app.post("/workflow") +async def register_workflow(workflow: WorkflowDef): + """Commits a JSON rule-engine to the workflows table.""" + async with app.state.pool.acquire() as conn: + await conn.execute( + """ + INSERT INTO workflows (name, definition) + VALUES ($1, $2) + ON CONFLICT (name) DO UPDATE SET definition = EXCLUDED.definition + """, + workflow.name, + workflow.definition, + ) + return {"status": "workflow_locked", "workflow": workflow.name} + + +@app.get("/incidents") +async def list_incidents(): + """Returns the absolute proof of agent drift.""" + async with app.state.pool.acquire() as conn: + rows = await conn.fetch( + "SELECT * FROM incidents ORDER BY created_at DESC" + ) + return {"incidents": [dict(r) for r in rows]} + + +@app.get("/session/{session_id}") +async def replay_session(session_id: str): + """Reconstructs the exact chronological timeline of an agent's actions.""" + async with app.state.pool.acquire() as conn: + rows = await conn.fetch( + """ + SELECT id, timestamp, action, state_before, state_after + FROM events + WHERE session_id = $1 + ORDER BY timestamp ASC + """, + session_id, + ) + return {"session_id": session_id, "timeline": [dict(r) for r in rows]} diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..5498137 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,5 @@ +fastapi==0.111.0 +uvicorn[standard]==0.29.0 +asyncpg==0.29.0 +pydantic==2.7.1 +pydantic-settings==2.2.1 diff --git a/schema.sql b/schema.sql new file mode 100644 index 0000000..bdbba14 --- /dev/null +++ b/schema.sql @@ -0,0 +1,39 @@ +-- A.I.R. — Agent Incident Recorder +-- VaultNode // Blackglass Continuum LLC +-- CAGE: 17TJ5 | UEI: SVZVXPTM9AF4 +-- Schema Authority: Ratified Day One + +CREATE TABLE agents ( + id TEXT PRIMARY KEY, + name TEXT NOT NULL +); + +CREATE TABLE sessions ( + id TEXT PRIMARY KEY, + agent_id TEXT REFERENCES agents(id), + started_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP +); + +CREATE TABLE workflows ( + name TEXT PRIMARY KEY, + definition JSONB NOT NULL +); + +CREATE TABLE events ( + id UUID PRIMARY KEY, + session_id TEXT REFERENCES sessions(id), + timestamp TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + action TEXT NOT NULL, + state_before TEXT NOT NULL, + state_after TEXT NOT NULL, + raw_json JSONB +); + +CREATE TABLE incidents ( + id TEXT PRIMARY KEY, + session_id TEXT REFERENCES sessions(id), + reason TEXT NOT NULL, + observed_transition TEXT NOT NULL, + expected_transition JSONB NOT NULL, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP +);