Upload folder using huggingface_hub
#6
by petter2025 - opened
- Dockerfile +1 -5
- README.md +3 -8
- app/api/routes_incidents.py +1 -1
- app/database/models_intents.py +194 -36
Dockerfile
CHANGED
|
@@ -1,11 +1,7 @@
|
|
| 1 |
FROM python:3.12-slim
|
| 2 |
RUN apt-get update && apt-get install -y git && rm -rf /var/lib/apt/lists/*
|
| 3 |
WORKDIR /app
|
| 4 |
-
|
| 5 |
-
ARG CLASSIC_TOKEN=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
|
| 6 |
-
RUN git config --global url."https://x-access-token:${CLASSIC_TOKEN}@github.com/".insteadOf "https://github.com/"
|
| 7 |
-
|
| 8 |
COPY requirements.txt .
|
| 9 |
RUN pip install --no-cache-dir -r requirements.txt
|
| 10 |
COPY . .
|
| 11 |
-
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "7860"]
|
|
|
|
| 1 |
FROM python:3.12-slim
|
| 2 |
RUN apt-get update && apt-get install -y git && rm -rf /var/lib/apt/lists/*
|
| 3 |
WORKDIR /app
|
|
|
|
|
|
|
|
|
|
|
|
|
| 4 |
COPY requirements.txt .
|
| 5 |
RUN pip install --no-cache-dir -r requirements.txt
|
| 6 |
COPY . .
|
| 7 |
+
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "7860"]
|
README.md
CHANGED
|
@@ -1,9 +1,3 @@
|
|
| 1 |
-
---
|
| 2 |
-
title: ARF API Control Plane
|
| 3 |
-
sdk: docker
|
| 4 |
-
colorFrom: blue
|
| 5 |
-
colorTo: green
|
| 6 |
-
---
|
| 7 |
# arf-api
|
| 8 |
|
| 9 |
ARF API Control Plane (FastAPI)
|
|
@@ -87,7 +81,7 @@ curl -X POST "http://localhost:8000/api/v1/v1/incidents/evaluate" -H "Content-
|
|
| 87 |
"justification": "Causal: If we apply restart_container instead of no_action, latency would change from 600.00 to 510.00 (Δ = -90.00). Based on heuristic causal model.",
|
| 88 |
"confidence": 0.85,
|
| 89 |
"risk_score": 0.54,
|
| 90 |
-
"status": "
|
| 91 |
},
|
| 92 |
"causal_explanation": {
|
| 93 |
"factual_outcome": 600,
|
|
@@ -123,4 +117,5 @@ Notes
|
|
| 123 |
-----
|
| 124 |
|
| 125 |
- The governance endpoints use an in-process `RiskEngine` initialized at startup.
|
| 126 |
-
- The outcome recording endpoint is not implemented in this repository and returns HTTP 501.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
# arf-api
|
| 2 |
|
| 3 |
ARF API Control Plane (FastAPI)
|
|
|
|
| 81 |
"justification": "Causal: If we apply restart_container instead of no_action, latency would change from 600.00 to 510.00 (Δ = -90.00). Based on heuristic causal model.",
|
| 82 |
"confidence": 0.85,
|
| 83 |
"risk_score": 0.54,
|
| 84 |
+
"status": "success"
|
| 85 |
},
|
| 86 |
"causal_explanation": {
|
| 87 |
"factual_outcome": 600,
|
|
|
|
| 117 |
-----
|
| 118 |
|
| 119 |
- The governance endpoints use an in-process `RiskEngine` initialized at startup.
|
| 120 |
+
- The outcome recording endpoint is not implemented in this repository and returns HTTP 501.
|
| 121 |
+
|
app/api/routes_incidents.py
CHANGED
|
@@ -198,7 +198,7 @@ async def evaluate_incident(
|
|
| 198 |
),
|
| 199 |
"confidence": 1.0 - result.get("uncertainty", 0.0),
|
| 200 |
"risk_score": result["risk_score"],
|
| 201 |
-
"status": "
|
| 202 |
}
|
| 203 |
|
| 204 |
response_data = {
|
|
|
|
| 198 |
),
|
| 199 |
"confidence": 1.0 - result.get("uncertainty", 0.0),
|
| 200 |
"risk_score": result["risk_score"],
|
| 201 |
+
"status": "success",
|
| 202 |
}
|
| 203 |
|
| 204 |
response_data = {
|
app/database/models_intents.py
CHANGED
|
@@ -1,50 +1,149 @@
|
|
| 1 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2 |
from sqlalchemy.orm import relationship
|
| 3 |
import datetime
|
| 4 |
from .base import Base
|
| 5 |
|
| 6 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 7 |
class IntentDB(Base):
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 8 |
__tablename__ = "intents"
|
|
|
|
| 9 |
id = Column(Integer, primary_key=True, index=True)
|
| 10 |
-
deterministic_id = Column(
|
| 11 |
-
|
| 12 |
-
unique=True,
|
| 13 |
-
index=True,
|
| 14 |
-
nullable=False)
|
| 15 |
intent_type = Column(String(64), nullable=False)
|
| 16 |
payload = Column(JSON, nullable=False)
|
| 17 |
oss_payload = Column(JSON, nullable=True)
|
| 18 |
environment = Column(String(32), nullable=True)
|
| 19 |
-
created_at = Column(
|
| 20 |
-
DateTime,
|
| 21 |
-
default=datetime.datetime.utcnow,
|
| 22 |
-
nullable=False)
|
| 23 |
evaluated_at = Column(DateTime, nullable=True)
|
| 24 |
risk_score = Column(String(32), nullable=True)
|
| 25 |
-
|
| 26 |
-
|
| 27 |
-
|
| 28 |
-
|
| 29 |
|
| 30 |
|
| 31 |
class OutcomeDB(Base):
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 32 |
__tablename__ = "intent_outcomes"
|
|
|
|
| 33 |
id = Column(Integer, primary_key=True, index=True)
|
| 34 |
-
intent_id = Column(
|
| 35 |
-
Integer,
|
| 36 |
-
ForeignKey(
|
| 37 |
-
"intents.id",
|
| 38 |
-
ondelete="CASCADE"),
|
| 39 |
-
nullable=False)
|
| 40 |
success = Column(Boolean, nullable=False)
|
| 41 |
recorded_by = Column(String(128), nullable=True)
|
| 42 |
notes = Column(Text, nullable=True)
|
| 43 |
-
recorded_at = Column(
|
| 44 |
-
DateTime,
|
| 45 |
-
default=datetime.datetime.utcnow,
|
| 46 |
-
nullable=False)
|
| 47 |
idempotency_key = Column(String(128), unique=True, nullable=True)
|
|
|
|
| 48 |
intent = relationship("IntentDB", back_populates="outcomes")
|
| 49 |
|
| 50 |
__table_args__ = (
|
|
@@ -52,24 +151,83 @@ class OutcomeDB(Base):
|
|
| 52 |
)
|
| 53 |
|
| 54 |
|
| 55 |
-
#
|
| 56 |
-
#
|
| 57 |
-
#
|
|
|
|
| 58 |
class BetaStateDB(Base):
|
| 59 |
"""
|
| 60 |
-
Stores the
|
| 61 |
-
|
|
|
|
| 62 |
|
| 63 |
-
|
| 64 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 65 |
"""
|
| 66 |
__tablename__ = "beta_state"
|
| 67 |
|
| 68 |
id = Column(Integer, primary_key=True, index=True)
|
| 69 |
-
|
|
|
|
| 70 |
alpha = Column(Float, nullable=False)
|
| 71 |
beta = Column(Float, nullable=False)
|
| 72 |
-
updated_at = Column(
|
| 73 |
-
|
| 74 |
-
|
| 75 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Database models for the ARF API Control Plane.
|
| 3 |
+
|
| 4 |
+
This module defines the SQLAlchemy ORM models for:
|
| 5 |
+
- Intents (InfrastructureIntent evaluations)
|
| 6 |
+
- Outcomes (recorded results of executed intents)
|
| 7 |
+
- Beta state (conjugate Bayesian posteriors per tenant and category)
|
| 8 |
+
- Audit logs (immutable decision records for compliance)
|
| 9 |
+
- Tenants (multi‑tenant isolation)
|
| 10 |
+
|
| 11 |
+
All tables include a `tenant_id` column to enforce data partitioning.
|
| 12 |
+
The BetaStateDB now stores parameters per (tenant, category) pair.
|
| 13 |
+
"""
|
| 14 |
+
|
| 15 |
+
from sqlalchemy import (
|
| 16 |
+
Column, Integer, String, DateTime, Boolean, Text, JSON,
|
| 17 |
+
Float, ForeignKey, UniqueConstraint, Index
|
| 18 |
+
)
|
| 19 |
from sqlalchemy.orm import relationship
|
| 20 |
import datetime
|
| 21 |
from .base import Base
|
| 22 |
|
| 23 |
|
| 24 |
+
# ============================================================================
|
| 25 |
+
# Tenant table – root of multi‑tenancy
|
| 26 |
+
# ============================================================================
|
| 27 |
+
|
| 28 |
+
class TenantDB(Base):
|
| 29 |
+
"""
|
| 30 |
+
Represents a customer tenant (organisation). All other tables
|
| 31 |
+
reference this table via a foreign key `tenant_id`.
|
| 32 |
+
|
| 33 |
+
Attributes:
|
| 34 |
+
id (str): UUID of the tenant (primary key).
|
| 35 |
+
name (str): Human‑readable organisation name.
|
| 36 |
+
created_at (datetime): UTC timestamp of creation.
|
| 37 |
+
created_by (str, optional): Email or user ID of the creator.
|
| 38 |
+
"""
|
| 39 |
+
__tablename__ = "tenants"
|
| 40 |
+
|
| 41 |
+
id = Column(String(64), primary_key=True, index=True)
|
| 42 |
+
name = Column(String(256), nullable=False)
|
| 43 |
+
created_at = Column(DateTime, default=datetime.datetime.utcnow, nullable=False)
|
| 44 |
+
created_by = Column(String(128), nullable=True)
|
| 45 |
+
|
| 46 |
+
# Relationships
|
| 47 |
+
api_keys = relationship("APIKeyDB", back_populates="tenant", cascade="all, delete-orphan")
|
| 48 |
+
intents = relationship("IntentDB", back_populates="tenant")
|
| 49 |
+
beta_states = relationship("BetaStateDB", back_populates="tenant")
|
| 50 |
+
audit_logs = relationship("DecisionAuditLogDB", back_populates="tenant")
|
| 51 |
+
|
| 52 |
+
|
| 53 |
+
# ============================================================================
|
| 54 |
+
# API keys (extended with tenant_id)
|
| 55 |
+
# ============================================================================
|
| 56 |
+
|
| 57 |
+
class APIKeyDB(Base):
|
| 58 |
+
"""
|
| 59 |
+
Stores API keys for authentication and tiered quota. Each key belongs
|
| 60 |
+
to exactly one tenant. The `tier` determines monthly evaluation limits.
|
| 61 |
+
|
| 62 |
+
Attributes:
|
| 63 |
+
key (str): The hashed API key (primary key).
|
| 64 |
+
tenant_id (str): Foreign key to `tenants.id`.
|
| 65 |
+
tier (str): Tier enumeration value (free, pro, premium, enterprise).
|
| 66 |
+
created_at (datetime): UTC creation time.
|
| 67 |
+
last_used_at (datetime, optional): Timestamp of last successful request.
|
| 68 |
+
is_active (bool): Soft‑delete flag.
|
| 69 |
+
"""
|
| 70 |
+
__tablename__ = "api_keys"
|
| 71 |
+
|
| 72 |
+
key = Column(String(256), primary_key=True, index=True)
|
| 73 |
+
tenant_id = Column(String(64), ForeignKey("tenants.id", ondelete="CASCADE"), nullable=False, index=True)
|
| 74 |
+
tier = Column(String(32), nullable=False)
|
| 75 |
+
created_at = Column(DateTime, default=datetime.datetime.utcnow, nullable=False)
|
| 76 |
+
last_used_at = Column(DateTime, nullable=True)
|
| 77 |
+
is_active = Column(Boolean, default=True, nullable=False)
|
| 78 |
+
|
| 79 |
+
# Relationships
|
| 80 |
+
tenant = relationship("TenantDB", back_populates="api_keys")
|
| 81 |
+
usage_logs = relationship("UsageLogDB", back_populates="api_key")
|
| 82 |
+
|
| 83 |
+
|
| 84 |
+
# ============================================================================
|
| 85 |
+
# Intents (evaluations) – now tenant‑scoped
|
| 86 |
+
# ============================================================================
|
| 87 |
+
|
| 88 |
class IntentDB(Base):
|
| 89 |
+
"""
|
| 90 |
+
Stores each InfrastructureIntent evaluation request and its resulting
|
| 91 |
+
risk score. One‑to‑many with OutcomeDB.
|
| 92 |
+
|
| 93 |
+
Attributes:
|
| 94 |
+
id (int): Auto‑increment primary key.
|
| 95 |
+
deterministic_id (str): Client‑provided idempotency identifier (unique).
|
| 96 |
+
tenant_id (str): Tenant that owns this intent.
|
| 97 |
+
intent_type (str): Type of intent (e.g., "provision_resource").
|
| 98 |
+
payload (JSON): Original API request payload.
|
| 99 |
+
oss_payload (JSON): Canonical OSS intent representation.
|
| 100 |
+
environment (str, optional): Environment label (prod, staging, etc.).
|
| 101 |
+
created_at (datetime): UTC timestamp of evaluation.
|
| 102 |
+
evaluated_at (datetime, optional): When the risk engine processed it.
|
| 103 |
+
risk_score (str, optional): String representation of the risk score.
|
| 104 |
+
"""
|
| 105 |
__tablename__ = "intents"
|
| 106 |
+
|
| 107 |
id = Column(Integer, primary_key=True, index=True)
|
| 108 |
+
deterministic_id = Column(String(64), unique=True, index=True, nullable=False)
|
| 109 |
+
tenant_id = Column(String(64), ForeignKey("tenants.id", ondelete="CASCADE"), nullable=False, index=True)
|
|
|
|
|
|
|
|
|
|
| 110 |
intent_type = Column(String(64), nullable=False)
|
| 111 |
payload = Column(JSON, nullable=False)
|
| 112 |
oss_payload = Column(JSON, nullable=True)
|
| 113 |
environment = Column(String(32), nullable=True)
|
| 114 |
+
created_at = Column(DateTime, default=datetime.datetime.utcnow, nullable=False)
|
|
|
|
|
|
|
|
|
|
| 115 |
evaluated_at = Column(DateTime, nullable=True)
|
| 116 |
risk_score = Column(String(32), nullable=True)
|
| 117 |
+
|
| 118 |
+
# Relationships
|
| 119 |
+
tenant = relationship("TenantDB", back_populates="intents")
|
| 120 |
+
outcomes = relationship("OutcomeDB", back_populates="intent", cascade="all, delete-orphan")
|
| 121 |
|
| 122 |
|
| 123 |
class OutcomeDB(Base):
|
| 124 |
+
"""
|
| 125 |
+
Records the outcome (success/failure) of a previously evaluated intent.
|
| 126 |
+
Only one outcome per intent is allowed (unique constraint on intent_id).
|
| 127 |
+
|
| 128 |
+
Attributes:
|
| 129 |
+
id (int): Primary key.
|
| 130 |
+
intent_id (int): Foreign key to `intents.id`.
|
| 131 |
+
success (bool): Whether the executed action succeeded.
|
| 132 |
+
recorded_by (str, optional): Identity of the caller (e.g., API key owner).
|
| 133 |
+
notes (str, optional): Free‑text notes.
|
| 134 |
+
recorded_at (datetime): UTC timestamp.
|
| 135 |
+
idempotency_key (str, optional): Unique idempotency key for this outcome.
|
| 136 |
+
"""
|
| 137 |
__tablename__ = "intent_outcomes"
|
| 138 |
+
|
| 139 |
id = Column(Integer, primary_key=True, index=True)
|
| 140 |
+
intent_id = Column(Integer, ForeignKey("intents.id", ondelete="CASCADE"), nullable=False)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 141 |
success = Column(Boolean, nullable=False)
|
| 142 |
recorded_by = Column(String(128), nullable=True)
|
| 143 |
notes = Column(Text, nullable=True)
|
| 144 |
+
recorded_at = Column(DateTime, default=datetime.datetime.utcnow, nullable=False)
|
|
|
|
|
|
|
|
|
|
| 145 |
idempotency_key = Column(String(128), unique=True, nullable=True)
|
| 146 |
+
|
| 147 |
intent = relationship("IntentDB", back_populates="outcomes")
|
| 148 |
|
| 149 |
__table_args__ = (
|
|
|
|
| 151 |
)
|
| 152 |
|
| 153 |
|
| 154 |
+
# ============================================================================
|
| 155 |
+
# Bayesian conjugate state – now per tenant and per category
|
| 156 |
+
# ============================================================================
|
| 157 |
+
|
| 158 |
class BetaStateDB(Base):
|
| 159 |
"""
|
| 160 |
+
Stores the posterior parameters (α, β) of the conjugate Beta model
|
| 161 |
+
for each (tenant, category) pair. This allows online learning to be
|
| 162 |
+
isolated per customer.
|
| 163 |
|
| 164 |
+
Attributes:
|
| 165 |
+
id (int): Primary key.
|
| 166 |
+
tenant_id (str): Tenant that owns this state.
|
| 167 |
+
category (str): ActionCategory value (e.g., "database", "compute").
|
| 168 |
+
alpha (float): α parameter of the Beta distribution.
|
| 169 |
+
beta (float): β parameter of the Beta distribution.
|
| 170 |
+
updated_at (datetime): Last update timestamp (auto‑set).
|
| 171 |
"""
|
| 172 |
__tablename__ = "beta_state"
|
| 173 |
|
| 174 |
id = Column(Integer, primary_key=True, index=True)
|
| 175 |
+
tenant_id = Column(String(64), ForeignKey("tenants.id", ondelete="CASCADE"), nullable=False, index=True)
|
| 176 |
+
category = Column(String(32), nullable=False, index=True)
|
| 177 |
alpha = Column(Float, nullable=False)
|
| 178 |
beta = Column(Float, nullable=False)
|
| 179 |
+
updated_at = Column(DateTime, default=datetime.datetime.utcnow, onupdate=datetime.datetime.utcnow)
|
| 180 |
+
|
| 181 |
+
# Composite unique constraint: (tenant_id, category)
|
| 182 |
+
__table_args__ = (
|
| 183 |
+
UniqueConstraint("tenant_id", "category", name="uq_beta_state_tenant_category"),
|
| 184 |
+
)
|
| 185 |
+
|
| 186 |
+
# Relationships
|
| 187 |
+
tenant = relationship("TenantDB", back_populates="beta_states")
|
| 188 |
+
|
| 189 |
+
|
| 190 |
+
# ============================================================================
|
| 191 |
+
# NEW: Audit log for compliance (immutable decision records)
|
| 192 |
+
# ============================================================================
|
| 193 |
+
|
| 194 |
+
class DecisionAuditLogDB(Base):
|
| 195 |
+
"""
|
| 196 |
+
Immutable, tamper‑evident record of every governance decision.
|
| 197 |
+
Designed for compliance (SOC2, ISO) and forensic analysis.
|
| 198 |
+
|
| 199 |
+
Attributes:
|
| 200 |
+
id (str): UUID primary key.
|
| 201 |
+
tenant_id (str): Tenant that owns the decision.
|
| 202 |
+
deterministic_id (str): Intent identifier (idempotency key).
|
| 203 |
+
timestamp (datetime): UTC decision time.
|
| 204 |
+
risk_score (float): Fused Bayesian risk score (0‑1).
|
| 205 |
+
action (str): Selected action (approve, deny, escalate).
|
| 206 |
+
justification (str): Human‑readable explanation.
|
| 207 |
+
memory_success_rate (float, optional): Memory‑based correction value.
|
| 208 |
+
memory_weight (float, optional): Weight assigned to memory.
|
| 209 |
+
counterfactual (JSON, optional): Structured counterfactual explanation.
|
| 210 |
+
trace_id (str, optional): OpenTelemetry trace ID for debugging.
|
| 211 |
+
signature (str, optional): Ed25519 signature for tamper‑proofing.
|
| 212 |
+
"""
|
| 213 |
+
__tablename__ = "decision_audit_log"
|
| 214 |
+
|
| 215 |
+
id = Column(String(64), primary_key=True, default=lambda: str(uuid.uuid4()))
|
| 216 |
+
tenant_id = Column(String(64), ForeignKey("tenants.id", ondelete="CASCADE"), nullable=False, index=True)
|
| 217 |
+
deterministic_id = Column(String(64), nullable=False, index=True)
|
| 218 |
+
timestamp = Column(DateTime, default=datetime.datetime.utcnow, nullable=False, index=True)
|
| 219 |
+
risk_score = Column(Float, nullable=False)
|
| 220 |
+
action = Column(String(32), nullable=False)
|
| 221 |
+
justification = Column(Text, nullable=False)
|
| 222 |
+
memory_success_rate = Column(Float, nullable=True)
|
| 223 |
+
memory_weight = Column(Float, nullable=True)
|
| 224 |
+
counterfactual = Column(JSON, nullable=True)
|
| 225 |
+
trace_id = Column(String(128), nullable=True)
|
| 226 |
+
signature = Column(String(256), nullable=True)
|
| 227 |
+
|
| 228 |
+
# Composite index for fast filtered queries
|
| 229 |
+
__table_args__ = (
|
| 230 |
+
Index("idx_audit_tenant_time", "tenant_id", "timestamp"),
|
| 231 |
+
)
|
| 232 |
+
|
| 233 |
+
tenant = relationship("TenantDB", back_populates="audit_logs")
|