feat(platform): close AI expense value loop
Add tenant-safe value, telemetry, connector, commercial, and production-readiness foundations.
This commit is contained in:
@@ -0,0 +1,237 @@
|
||||
"""add append-only real release telemetry and review labels
|
||||
|
||||
Revision ID: 20260716_0018
|
||||
Revises: 20260716_0017
|
||||
Create Date: 2026-07-16 22:00:00
|
||||
"""
|
||||
|
||||
from collections.abc import Sequence
|
||||
|
||||
import sqlalchemy as sa
|
||||
|
||||
from alembic import op
|
||||
|
||||
revision: str = "20260716_0018"
|
||||
down_revision: str | None = "20260716_0017"
|
||||
branch_labels: str | Sequence[str] | None = None
|
||||
depends_on: str | Sequence[str] | None = None
|
||||
|
||||
|
||||
_TELEMETRY_TABLES = (
|
||||
"agent_asset_release_observations",
|
||||
"agent_asset_release_labels",
|
||||
)
|
||||
|
||||
|
||||
def _require_postgresql() -> None:
|
||||
dialect_name = op.get_bind().dialect.name
|
||||
if dialect_name != "postgresql":
|
||||
raise RuntimeError(
|
||||
"20260716_0018 only supports PostgreSQL; "
|
||||
f"refusing to mutate {dialect_name} without transactional constraint DDL"
|
||||
)
|
||||
|
||||
|
||||
def _require_empty_telemetry_domain_for_downgrade() -> None:
|
||||
bind = op.get_bind()
|
||||
counts = {
|
||||
table_name: int(bind.scalar(sa.text(f"SELECT COUNT(*) FROM {table_name}")) or 0)
|
||||
for table_name in _TELEMETRY_TABLES
|
||||
}
|
||||
if any(counts.values()):
|
||||
summary = ", ".join(f"{name}={count}" for name, count in counts.items())
|
||||
raise RuntimeError(
|
||||
"cannot downgrade release telemetry: immutable observations or labels exist "
|
||||
f"({summary})"
|
||||
)
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
_require_postgresql()
|
||||
op.create_table(
|
||||
"agent_asset_release_observations",
|
||||
sa.Column("id", sa.String(length=36), nullable=False),
|
||||
sa.Column("tenant_id", sa.String(length=64), nullable=False),
|
||||
sa.Column("asset_id", sa.String(length=36), nullable=False),
|
||||
sa.Column("release_id", sa.String(length=64), nullable=False),
|
||||
sa.Column("stage", sa.String(length=16), nullable=False),
|
||||
sa.Column("version", sa.String(length=30), nullable=False),
|
||||
sa.Column("rule_code", sa.String(length=100), nullable=False),
|
||||
sa.Column("business_stage", sa.String(length=40), nullable=False),
|
||||
sa.Column(
|
||||
"source_kind",
|
||||
sa.String(length=32),
|
||||
server_default="expense_claim_risk",
|
||||
nullable=False,
|
||||
),
|
||||
sa.Column("source_fingerprint", sa.String(length=64), nullable=False),
|
||||
sa.Column("candidate_hit", sa.Boolean(), nullable=False),
|
||||
sa.Column("baseline_hit", sa.Boolean(), nullable=True),
|
||||
sa.Column("runtime_status", sa.String(length=16), nullable=False),
|
||||
sa.Column(
|
||||
"failure_code",
|
||||
sa.String(length=40),
|
||||
server_default="none",
|
||||
nullable=False,
|
||||
),
|
||||
sa.Column("idempotency_key", sa.String(length=80), nullable=False),
|
||||
sa.Column("payload_fingerprint", sa.String(length=64), nullable=False),
|
||||
sa.Column(
|
||||
"created_at",
|
||||
sa.DateTime(timezone=True),
|
||||
server_default=sa.func.now(),
|
||||
nullable=False,
|
||||
),
|
||||
sa.CheckConstraint(
|
||||
"stage IN ('shadow', 'canary', 'active')",
|
||||
name="ck_agent_asset_release_observations_stage",
|
||||
),
|
||||
sa.CheckConstraint(
|
||||
"runtime_status IN ('completed', 'failed')",
|
||||
name="ck_agent_asset_release_observations_runtime_status",
|
||||
),
|
||||
sa.CheckConstraint(
|
||||
"source_kind IN ('expense_claim_risk')",
|
||||
name="ck_agent_asset_release_observations_source_kind",
|
||||
),
|
||||
sa.PrimaryKeyConstraint("id"),
|
||||
sa.UniqueConstraint(
|
||||
"tenant_id",
|
||||
"id",
|
||||
name="uq_agent_asset_release_observations_tenant_id",
|
||||
),
|
||||
sa.UniqueConstraint(
|
||||
"tenant_id",
|
||||
"idempotency_key",
|
||||
name="uq_agent_asset_release_observations_tenant_idempotency",
|
||||
),
|
||||
sa.UniqueConstraint(
|
||||
"tenant_id",
|
||||
"id",
|
||||
"asset_id",
|
||||
"release_id",
|
||||
"stage",
|
||||
"version",
|
||||
name="uq_agent_asset_release_observations_release_identity",
|
||||
),
|
||||
)
|
||||
op.create_index(
|
||||
"ix_agent_asset_release_observations_release",
|
||||
"agent_asset_release_observations",
|
||||
["tenant_id", "asset_id", "release_id", "stage", "version", "created_at"],
|
||||
)
|
||||
op.create_index(
|
||||
"ix_agent_asset_release_observations_source",
|
||||
"agent_asset_release_observations",
|
||||
["tenant_id", "source_fingerprint"],
|
||||
)
|
||||
|
||||
op.create_table(
|
||||
"agent_asset_release_labels",
|
||||
sa.Column("id", sa.String(length=36), nullable=False),
|
||||
sa.Column("tenant_id", sa.String(length=64), nullable=False),
|
||||
sa.Column("observation_id", sa.String(length=36), nullable=False),
|
||||
sa.Column("asset_id", sa.String(length=36), nullable=False),
|
||||
sa.Column("release_id", sa.String(length=64), nullable=False),
|
||||
sa.Column("stage", sa.String(length=16), nullable=False),
|
||||
sa.Column("version", sa.String(length=30), nullable=False),
|
||||
sa.Column("label", sa.String(length=24), nullable=False),
|
||||
sa.Column("verification_source", sa.String(length=32), nullable=False),
|
||||
sa.Column("source_event_fingerprint", sa.String(length=64), nullable=False),
|
||||
sa.Column("actor_fingerprint", sa.String(length=64), nullable=False),
|
||||
sa.Column("idempotency_key", sa.String(length=80), nullable=False),
|
||||
sa.Column("payload_fingerprint", sa.String(length=64), nullable=False),
|
||||
sa.Column(
|
||||
"created_at",
|
||||
sa.DateTime(timezone=True),
|
||||
server_default=sa.func.now(),
|
||||
nullable=False,
|
||||
),
|
||||
sa.CheckConstraint(
|
||||
"label IN ('confirmed', 'false_positive')",
|
||||
name="ck_agent_asset_release_labels_label",
|
||||
),
|
||||
sa.CheckConstraint(
|
||||
"verification_source IN ('typed_risk_disposition', 'release_review')",
|
||||
name="ck_agent_asset_release_labels_source",
|
||||
),
|
||||
sa.ForeignKeyConstraint(
|
||||
["tenant_id", "observation_id", "asset_id", "release_id", "stage", "version"],
|
||||
[
|
||||
"agent_asset_release_observations.tenant_id",
|
||||
"agent_asset_release_observations.id",
|
||||
"agent_asset_release_observations.asset_id",
|
||||
"agent_asset_release_observations.release_id",
|
||||
"agent_asset_release_observations.stage",
|
||||
"agent_asset_release_observations.version",
|
||||
],
|
||||
name="fk_agent_asset_release_labels_release_observation",
|
||||
ondelete="RESTRICT",
|
||||
),
|
||||
sa.PrimaryKeyConstraint("id"),
|
||||
sa.UniqueConstraint(
|
||||
"tenant_id",
|
||||
"id",
|
||||
name="uq_agent_asset_release_labels_tenant_id",
|
||||
),
|
||||
sa.UniqueConstraint(
|
||||
"tenant_id",
|
||||
"idempotency_key",
|
||||
name="uq_agent_asset_release_labels_tenant_idempotency",
|
||||
),
|
||||
)
|
||||
op.create_index(
|
||||
"ix_agent_asset_release_labels_observation_time",
|
||||
"agent_asset_release_labels",
|
||||
["tenant_id", "observation_id", "created_at"],
|
||||
)
|
||||
op.create_index(
|
||||
"ix_agent_asset_release_labels_release",
|
||||
"agent_asset_release_labels",
|
||||
["tenant_id", "asset_id", "release_id", "stage", "version"],
|
||||
)
|
||||
|
||||
op.execute(
|
||||
"""
|
||||
CREATE FUNCTION reject_agent_asset_release_telemetry_mutation()
|
||||
RETURNS trigger AS $$
|
||||
BEGIN
|
||||
RAISE EXCEPTION 'agent asset release telemetry is append-only';
|
||||
END;
|
||||
$$ LANGUAGE plpgsql;
|
||||
"""
|
||||
)
|
||||
for table_name in _TELEMETRY_TABLES:
|
||||
op.execute(
|
||||
f"""
|
||||
CREATE TRIGGER trg_{table_name}_append_only
|
||||
BEFORE UPDATE OR DELETE ON {table_name}
|
||||
FOR EACH ROW EXECUTE FUNCTION reject_agent_asset_release_telemetry_mutation();
|
||||
"""
|
||||
)
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
_require_postgresql()
|
||||
_require_empty_telemetry_domain_for_downgrade()
|
||||
for table_name in reversed(_TELEMETRY_TABLES):
|
||||
op.execute(f"DROP TRIGGER IF EXISTS trg_{table_name}_append_only ON {table_name}")
|
||||
op.execute("DROP FUNCTION IF EXISTS reject_agent_asset_release_telemetry_mutation()")
|
||||
op.drop_index(
|
||||
"ix_agent_asset_release_labels_release",
|
||||
table_name="agent_asset_release_labels",
|
||||
)
|
||||
op.drop_index(
|
||||
"ix_agent_asset_release_labels_observation_time",
|
||||
table_name="agent_asset_release_labels",
|
||||
)
|
||||
op.drop_table("agent_asset_release_labels")
|
||||
op.drop_index(
|
||||
"ix_agent_asset_release_observations_source",
|
||||
table_name="agent_asset_release_observations",
|
||||
)
|
||||
op.drop_index(
|
||||
"ix_agent_asset_release_observations_release",
|
||||
table_name="agent_asset_release_observations",
|
||||
)
|
||||
op.drop_table("agent_asset_release_observations")
|
||||
Reference in New Issue
Block a user