feat(org): model grants, org memory, MCP gateway (v2 phases A1-3..5)

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
mjalmoudy94
2026-10-14 15:51:44 +03:30
co-authored by Claude Sonnet 5.5
parent c93b25741e
commit dee2f72c46
39 changed files with 9060 additions and 95 deletions
+147 -2
View File
@@ -24,7 +24,17 @@ from __future__ import annotations
from datetime import datetime
from sqlalchemy import DateTime, ForeignKey, Index, String, Text
from sqlalchemy import (
Boolean,
DateTime,
Float,
ForeignKey,
Index,
Integer,
String,
Text,
UniqueConstraint,
)
from sqlalchemy.orm import Mapped, mapped_column
from app.core.database import Base
@@ -103,4 +113,139 @@ class OrgSetupDraftRow(Base):
)
__all__ = ["OrgSetupDraftRow", "PlatformInviteRow"]
class DefaultModelGrantRow(Base):
"""The platform owner's default grant set, copied into every new organization.
Kept in a table of its own (never a column on ``ai_models``) so the model
catalogue stays a pure, owner-managed list.
"""
__tablename__ = "default_model_grants"
__table_args__ = (UniqueConstraint("ai_model_id", name="uq_default_model_grant"),)
id: Mapped[str] = pk()
ai_model_id: Mapped[str] = mapped_column(
ForeignKey("ai_models.id", ondelete="CASCADE"), nullable=False
)
daily_credit_cap: Mapped[float | None] = mapped_column(Float, nullable=True)
is_active: Mapped[bool] = mapped_column(Boolean, default=True, nullable=False)
created_by_user_id: Mapped[str | None] = mapped_column(
ForeignKey("users.id"), nullable=True
)
created_at: Mapped[datetime] = created()
class OrgPolicyRow(Base):
"""Per-organization policy the owner sets: limits and automation switches."""
__tablename__ = "org_policies"
organization_id: Mapped[str] = mapped_column(
ForeignKey("organizations.id", ondelete="CASCADE"), primary_key=True
)
max_employees: Mapped[int] = mapped_column(Integer, default=5, nullable=False)
#: ``None`` means "no explicit budget" (an empty/unlimited wallet view).
monthly_credit_budget: Mapped[float | None] = mapped_column(Float, nullable=True)
automation_enabled: Mapped[bool] = mapped_column(Boolean, default=True, nullable=False)
#: MCP access for this organization (Phase 5); the owner may switch it off.
mcp_enabled: Mapped[bool] = mapped_column(Boolean, default=True, nullable=False)
updated_at: Mapped[datetime] = mapped_column(
DateTime(timezone=True), default=utcnow, onupdate=utcnow, nullable=False
)
class OrgMemoryRow(Base):
"""One thing the organization remembers about itself.
A memory is *small* by design (≤ 1,500 characters): longer material is a
document and belongs in the Knowledge Base. ``content_hash`` is what makes
"teach the same thing twice" a correction instead of a duplicate, and
``key`` gives a rule a stable address so the engine can update it.
"""
__tablename__ = "org_memories"
__table_args__ = (
Index("ix_org_memories_org_kind", "organization_id", "kind"),
Index("ix_org_memories_org_key", "organization_id", "key"),
)
id: Mapped[str] = pk()
organization_id: Mapped[str] = mapped_column(
ForeignKey("organizations.id", ondelete="CASCADE"), nullable=False, index=True
)
kind: Mapped[str] = mapped_column(String(16), nullable=False)
#: Optional stable handle, unique in spirit within one org.
key: Mapped[str | None] = mapped_column(String(80), nullable=True)
content: Mapped[str] = mapped_column(String(1500), nullable=False)
#: A digest of the normalised content — the dedupe key the domain defines.
content_hash: Mapped[str] = mapped_column(String(32), nullable=False, default="")
source: Mapped[str] = mapped_column(String(16), default="user", nullable=False)
evidence_task_id: Mapped[str | None] = mapped_column(
ForeignKey("tasks.id", ondelete="SET NULL"), nullable=True
)
pinned: Mapped[bool] = mapped_column(Boolean, default=False, nullable=False)
confidence: Mapped[float] = mapped_column(Float, default=1.0, nullable=False)
expires_at: Mapped[datetime | None] = mapped_column(
DateTime(timezone=True), nullable=True
)
hits: Mapped[int] = mapped_column(Integer, default=0, nullable=False)
last_used_at: Mapped[datetime | None] = mapped_column(
DateTime(timezone=True), nullable=True
)
created_by_user_id: Mapped[str | None] = mapped_column(
ForeignKey("users.id"), nullable=True
)
created_at: Mapped[datetime] = created()
updated_at: Mapped[datetime] = mapped_column(
DateTime(timezone=True), default=utcnow, onupdate=utcnow, nullable=False
)
#: Soft delete: a memory an admin removed stays in the audit story.
deleted_at: Mapped[datetime | None] = mapped_column(
DateTime(timezone=True), nullable=True
)
class McpTokenRow(Base):
"""A scoped token an external AI agent uses to call the MCP gateway.
The token itself is shown exactly once, at creation; only its hash and a
six-character prefix (for display) are stored. Every call is attributed to
that prefix in the audit trail, so a leaked token can be identified and
revoked long before anyone can reuse it.
"""
__tablename__ = "mcp_tokens"
id: Mapped[str] = pk()
organization_id: Mapped[str] = mapped_column(
ForeignKey("organizations.id", ondelete="CASCADE"), nullable=False, index=True
)
name: Mapped[str] = mapped_column(String(120), nullable=False)
token_hash: Mapped[str] = mapped_column(String(128), unique=True, nullable=False)
#: First six characters, for display only. Never enough to authenticate.
prefix: Mapped[str] = mapped_column(String(16), nullable=False)
#: Comma-separated scopes; an empty string means "no scopes", never "all".
scopes: Mapped[str] = mapped_column(String(255), default="", nullable=False)
created_by_user_id: Mapped[str | None] = mapped_column(
ForeignKey("users.id"), nullable=True
)
expires_at: Mapped[datetime | None] = mapped_column(
DateTime(timezone=True), nullable=True
)
last_used_at: Mapped[datetime | None] = mapped_column(
DateTime(timezone=True), nullable=True
)
revoked_at: Mapped[datetime | None] = mapped_column(
DateTime(timezone=True), nullable=True
)
created_at: Mapped[datetime] = created()
__all__ = [
"DefaultModelGrantRow",
"McpTokenRow",
"OrgMemoryRow",
"OrgPolicyRow",
"OrgSetupDraftRow",
"PlatformInviteRow",
]