File size: 10,193 Bytes
1ec4900
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2d521fd
 
 
 
 
1ec4900
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2d521fd
1ec4900
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2d521fd
1ec4900
2d521fd
1ec4900
 
2d521fd
 
 
 
1ec4900
2d521fd
 
1ec4900
 
 
 
2d521fd
 
 
1ec4900
 
 
 
 
 
 
 
 
 
 
 
 
2d521fd
1ec4900
2d521fd
1ec4900
2d521fd
 
 
1ec4900
6d20eab
1ec4900
2d521fd
 
 
 
 
6d20eab
 
1ec4900
 
 
 
6d20eab
 
1ec4900
 
 
6d20eab
1ec4900
 
 
 
 
 
 
6d20eab
 
 
 
1ec4900
 
6d20eab
 
1ec4900
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
"""
Database models for the ARF API Control Plane.

This module defines the SQLAlchemy ORM models for:
    - Intents (InfrastructureIntent evaluations)
    - Outcomes (recorded results of executed intents)
    - Beta state (conjugate Bayesian posteriors per tenant and category)
    - Audit logs (immutable decision records for compliance)
    - Tenants (multi‑tenant isolation)

All tables include a `tenant_id` column to enforce data partitioning.
The BetaStateDB now stores parameters per (tenant, category) pair.
"""

from sqlalchemy import (
    Column, Integer, String, DateTime, Boolean, Text, JSON,
    Float, ForeignKey, UniqueConstraint, Index
)
from sqlalchemy.orm import relationship
import datetime
from .base import Base


# ============================================================================
# Tenant table – root of multi‑tenancy
# ============================================================================

class TenantDB(Base):
    """
    Represents a customer tenant (organisation). All other tables
    reference this table via a foreign key `tenant_id`.

    Attributes:
        id (str): UUID of the tenant (primary key).
        name (str): Human‑readable organisation name.
        created_at (datetime): UTC timestamp of creation.
        created_by (str, optional): Email or user ID of the creator.
    """
    __tablename__ = "tenants"

    id = Column(String(64), primary_key=True, index=True)
    name = Column(String(256), nullable=False)
    created_at = Column(DateTime, default=datetime.datetime.utcnow, nullable=False)
    created_by = Column(String(128), nullable=True)

    # Relationships
    api_keys = relationship("APIKeyDB", back_populates="tenant", cascade="all, delete-orphan")
    intents = relationship("IntentDB", back_populates="tenant")
    beta_states = relationship("BetaStateDB", back_populates="tenant")
    audit_logs = relationship("DecisionAuditLogDB", back_populates="tenant")


# ============================================================================
# API keys (extended with tenant_id)
# ============================================================================

class APIKeyDB(Base):
    """
    Stores API keys for authentication and tiered quota. Each key belongs
    to exactly one tenant. The `tier` determines monthly evaluation limits.

    Attributes:
        key (str): The hashed API key (primary key).
        tenant_id (str): Foreign key to `tenants.id`.
        tier (str): Tier enumeration value (free, pro, premium, enterprise).
        created_at (datetime): UTC creation time.
        last_used_at (datetime, optional): Timestamp of last successful request.
        is_active (bool): Soft‑delete flag.
    """
    __tablename__ = "api_keys"

    key = Column(String(256), primary_key=True, index=True)
    tenant_id = Column(String(64), ForeignKey("tenants.id", ondelete="CASCADE"), nullable=False, index=True)
    tier = Column(String(32), nullable=False)
    created_at = Column(DateTime, default=datetime.datetime.utcnow, nullable=False)
    last_used_at = Column(DateTime, nullable=True)
    is_active = Column(Boolean, default=True, nullable=False)

    # Relationships
    tenant = relationship("TenantDB", back_populates="api_keys")
    usage_logs = relationship("UsageLogDB", back_populates="api_key")


# ============================================================================
# Intents (evaluations) – now tenant‑scoped
# ============================================================================

class IntentDB(Base):
    """
    Stores each InfrastructureIntent evaluation request and its resulting
    risk score. One‑to‑many with OutcomeDB.

    Attributes:
        id (int): Auto‑increment primary key.
        deterministic_id (str): Client‑provided idempotency identifier (unique).
        tenant_id (str): Tenant that owns this intent.
        intent_type (str): Type of intent (e.g., "provision_resource").
        payload (JSON): Original API request payload.
        oss_payload (JSON): Canonical OSS intent representation.
        environment (str, optional): Environment label (prod, staging, etc.).
        created_at (datetime): UTC timestamp of evaluation.
        evaluated_at (datetime, optional): When the risk engine processed it.
        risk_score (str, optional): String representation of the risk score.
    """
    __tablename__ = "intents"

    id = Column(Integer, primary_key=True, index=True)
    deterministic_id = Column(String(64), unique=True, index=True, nullable=False)
    tenant_id = Column(String(64), ForeignKey("tenants.id", ondelete="CASCADE"), nullable=False, index=True)
    intent_type = Column(String(64), nullable=False)
    payload = Column(JSON, nullable=False)
    oss_payload = Column(JSON, nullable=True)
    environment = Column(String(32), nullable=True)
    created_at = Column(DateTime, default=datetime.datetime.utcnow, nullable=False)
    evaluated_at = Column(DateTime, nullable=True)
    risk_score = Column(String(32), nullable=True)

    # Relationships
    tenant = relationship("TenantDB", back_populates="intents")
    outcomes = relationship("OutcomeDB", back_populates="intent", cascade="all, delete-orphan")


class OutcomeDB(Base):
    """
    Records the outcome (success/failure) of a previously evaluated intent.
    Only one outcome per intent is allowed (unique constraint on intent_id).

    Attributes:
        id (int): Primary key.
        intent_id (int): Foreign key to `intents.id`.
        success (bool): Whether the executed action succeeded.
        recorded_by (str, optional): Identity of the caller (e.g., API key owner).
        notes (str, optional): Free‑text notes.
        recorded_at (datetime): UTC timestamp.
        idempotency_key (str, optional): Unique idempotency key for this outcome.
    """
    __tablename__ = "intent_outcomes"

    id = Column(Integer, primary_key=True, index=True)
    intent_id = Column(Integer, ForeignKey("intents.id", ondelete="CASCADE"), nullable=False)
    success = Column(Boolean, nullable=False)
    recorded_by = Column(String(128), nullable=True)
    notes = Column(Text, nullable=True)
    recorded_at = Column(DateTime, default=datetime.datetime.utcnow, nullable=False)
    idempotency_key = Column(String(128), unique=True, nullable=True)

    intent = relationship("IntentDB", back_populates="outcomes")

    __table_args__ = (
        UniqueConstraint("intent_id", name="uq_outcome_intentid"),
    )


# ============================================================================
# Bayesian conjugate state – now per tenant and per category
# ============================================================================

class BetaStateDB(Base):
    """
    Stores the posterior parameters (α, β) of the conjugate Beta model
    for each (tenant, category) pair. This allows online learning to be
    isolated per customer.

    Attributes:
        id (int): Primary key.
        tenant_id (str): Tenant that owns this state.
        category (str): ActionCategory value (e.g., "database", "compute").
        alpha (float): α parameter of the Beta distribution.
        beta (float): β parameter of the Beta distribution.
        updated_at (datetime): Last update timestamp (auto‑set).
    """
    __tablename__ = "beta_state"

    id = Column(Integer, primary_key=True, index=True)
    tenant_id = Column(String(64), ForeignKey("tenants.id", ondelete="CASCADE"), nullable=False, index=True)
    category = Column(String(32), nullable=False, index=True)
    alpha = Column(Float, nullable=False)
    beta = Column(Float, nullable=False)
    updated_at = Column(DateTime, default=datetime.datetime.utcnow, onupdate=datetime.datetime.utcnow)

    # Composite unique constraint: (tenant_id, category)
    __table_args__ = (
        UniqueConstraint("tenant_id", "category", name="uq_beta_state_tenant_category"),
    )

    # Relationships
    tenant = relationship("TenantDB", back_populates="beta_states")


# ============================================================================
# NEW: Audit log for compliance (immutable decision records)
# ============================================================================

class DecisionAuditLogDB(Base):
    """
    Immutable, tamper‑evident record of every governance decision.
    Designed for compliance (SOC2, ISO) and forensic analysis.

    Attributes:
        id (str): UUID primary key.
        tenant_id (str): Tenant that owns the decision.
        deterministic_id (str): Intent identifier (idempotency key).
        timestamp (datetime): UTC decision time.
        risk_score (float): Fused Bayesian risk score (0‑1).
        action (str): Selected action (approve, deny, escalate).
        justification (str): Human‑readable explanation.
        memory_success_rate (float, optional): Memory‑based correction value.
        memory_weight (float, optional): Weight assigned to memory.
        counterfactual (JSON, optional): Structured counterfactual explanation.
        trace_id (str, optional): OpenTelemetry trace ID for debugging.
        signature (str, optional): Ed25519 signature for tamper‑proofing.
    """
    __tablename__ = "decision_audit_log"

    id = Column(String(64), primary_key=True, default=lambda: str(uuid.uuid4()))
    tenant_id = Column(String(64), ForeignKey("tenants.id", ondelete="CASCADE"), nullable=False, index=True)
    deterministic_id = Column(String(64), nullable=False, index=True)
    timestamp = Column(DateTime, default=datetime.datetime.utcnow, nullable=False, index=True)
    risk_score = Column(Float, nullable=False)
    action = Column(String(32), nullable=False)
    justification = Column(Text, nullable=False)
    memory_success_rate = Column(Float, nullable=True)
    memory_weight = Column(Float, nullable=True)
    counterfactual = Column(JSON, nullable=True)
    trace_id = Column(String(128), nullable=True)
    signature = Column(String(256), nullable=True)

    # Composite index for fast filtered queries
    __table_args__ = (
        Index("idx_audit_tenant_time", "tenant_id", "timestamp"),
    )

    tenant = relationship("TenantDB", back_populates="audit_logs")