Initial public ModelForge release

This commit is contained in:
Jens
2026-09-01 21:30:16 +02:00
commit 7082ab955a
490 changed files with 104252 additions and 0 deletions
+5
View File
@@ -0,0 +1,5 @@
__pycache__
*.pyc
.pytest_cache
.ruff_cache
tests
+52
View File
@@ -0,0 +1,52 @@
FROM python:3.12-alpine3.23@sha256:31a768b01976652c222e318fe5bd6e7c252f056cbf489c88fa256f1bf0af58e3
ENV PYTHONDONTWRITEBYTECODE=1 \
PYTHONUNBUFFERED=1
# PostgreSQL 17 client tools. ModelForge owns its own consistent logical backup and restore and
# must never fall back to copying a live data directory, so pg_dump/pg_restore/psql ship with the
# control plane and are pinned to the same major version as the server. Upgrade the signed Alpine
# repository packages during the build so a digest-pinned base does not retain already-fixed CVEs.
ARG POSTGRES_MAJOR=17
RUN set -eux; \
apk upgrade --no-cache; \
apk add --no-cache ca-certificates "postgresql${POSTGRES_MAJOR}-client"
# Build identity. These are stamped in at build time so a running container can say exactly
# where it came from; an argument that is never passed stays empty and is reported as null
# rather than becoming a claimed commit.
ARG MODELFORGE_VERSION=0.0.0
ARG MODELFORGE_COMMIT=""
ARG MODELFORGE_BUILT_AT=""
ENV MODELFORGE_BUILD_COMMIT=${MODELFORGE_COMMIT}
ENV MODELFORGE_BUILD_TIMESTAMP=${MODELFORGE_BUILT_AT}
LABEL org.opencontainers.image.title="ITWorx ModelForge control plane"
LABEL org.opencontainers.image.description="Capability-first local AI ModelOps and GPU control plane"
LABEL org.opencontainers.image.version="${MODELFORGE_VERSION}"
LABEL org.opencontainers.image.revision="${MODELFORGE_COMMIT}"
LABEL org.opencontainers.image.created="${MODELFORGE_BUILT_AT}"
LABEL org.opencontainers.image.source="https://git.example.com/example/modelforge.git"
LABEL org.opencontainers.image.vendor="ITWorx"
LABEL org.opencontainers.image.licenses="AGPL-3.0-or-later"
WORKDIR /app
COPY pyproject.toml ./
COPY src ./src
COPY alembic.ini ./
COPY alembic ./alembic
# Upgrade the installer before it resolves anything: the pinned base image ships a pip
# carrying archive-extraction advisories. pip never runs at runtime, but a release image
# should not carry a known-vulnerable installer.
RUN pip install --no-cache-dir --upgrade pip "setuptools>=78.1.1" "msgpack>=1.2.1" \
&& pip install --no-cache-dir . \
&& pip check \
&& python -m pip uninstall --yes pip setuptools
RUN addgroup -S modelforge && adduser -S -G modelforge -h /app modelforge \
&& mkdir -p /data/state /data/hf-cache /data/artifacts /data/quarantine \
/data/backups /data/restore \
&& chown -R modelforge:modelforge /app /data
USER modelforge
EXPOSE 8000
CMD ["uvicorn", "modelforge_api.main:app", "--host", "0.0.0.0", "--port", "8000"]
+34
View File
@@ -0,0 +1,34 @@
[alembic]
script_location = alembic
prepend_sys_path = src
# Left empty deliberately. The URL comes from -x db_url=..., from a programmatic
# set_main_option, or from MODELFORGE_DATABASE_URL — in that order. A hardcoded value
# here is one an operator can migrate the wrong database with.
sqlalchemy.url =
[loggers]
keys = root,sqlalchemy,alembic
[handlers]
keys = console
[formatters]
keys = generic
[logger_root]
level = WARN
handlers = console
qualname =
[logger_sqlalchemy]
level = WARN
handlers =
qualname = sqlalchemy.engine
[logger_alembic]
level = INFO
handlers =
qualname = alembic
[handler_console]
class = StreamHandler
args = (sys.stderr,)
level = NOTSET
formatter = generic
[formatter_generic]
format = %(levelname)-5.5s [%(name)s] %(message)s
datefmt = %H:%M:%S
+76
View File
@@ -0,0 +1,76 @@
from logging.config import fileConfig
from sqlalchemy import engine_from_config, pool
from alembic import context
from modelforge_api.persistence.models import Base
from modelforge_api.settings import get_settings
config = context.config
if config.config_file_name is not None:
fileConfig(config.config_file_name)
def _database_url() -> str:
"""Where the migration runs, in order of explicitness.
1. `-x db_url=...` on the command line
2. a URL the caller set programmatically, or in alembic.ini
3. MODELFORGE_MIGRATION_DATABASE_URL
4. MODELFORGE_DATABASE_URL only outside production
env.py used to overwrite whatever the caller had set with the settings default
unconditionally, so both explicit forms were silently discarded. A bootstrap or upgrade
rehearsal aimed at an isolated copy would have migrated the deployment's own database while
reporting success against the copy.
"""
supplied = context.get_x_argument(as_dictionary=True).get("db_url")
if supplied:
return str(supplied)
configured = config.get_main_option("sqlalchemy.url", None)
if configured:
return configured
settings = get_settings()
if settings.migration_database_url is not None:
return settings.migration_database_url.get_secret_value()
if settings.env == "production":
raise RuntimeError(
"production migrations require MODELFORGE_MIGRATION_DATABASE_URL; the API runtime "
"credential is intentionally not a migration credential"
)
return settings.database_url
config.set_main_option("sqlalchemy.url", _database_url())
target_metadata = Base.metadata
def run_migrations_offline() -> None:
context.configure(
url=config.get_main_option("sqlalchemy.url"),
target_metadata=target_metadata,
literal_binds=True,
dialect_opts={"paramstyle": "named"},
compare_type=True,
)
with context.begin_transaction():
context.run_migrations()
def run_migrations_online() -> None:
connectable = engine_from_config(
config.get_section(config.config_ini_section, {}),
prefix="sqlalchemy.",
poolclass=pool.NullPool,
)
with connectable.connect() as connection:
context.configure(connection=connection, target_metadata=target_metadata, compare_type=True)
with context.begin_transaction():
context.run_migrations()
if context.is_offline_mode():
run_migrations_offline()
else:
run_migrations_online()
+20
View File
@@ -0,0 +1,20 @@
"""${message}
Revision ID: ${up_revision}
Revises: ${down_revision | comma,n}
Create Date: ${create_date}
"""
from typing import Sequence, Union
from alembic import op
import sqlalchemy as sa
${imports if imports else ""}
revision: str = ${repr(up_revision)}
down_revision: Union[str, None] = ${repr(down_revision)}
branch_labels: Union[str, Sequence[str], None] = ${repr(branch_labels)}
depends_on: Union[str, Sequence[str], None] = ${repr(depends_on)}
def upgrade() -> None:
${upgrades if upgrades else "pass"}
def downgrade() -> None:
${downgrades if downgrades else "pass"}
@@ -0,0 +1,222 @@
"""M0 domain foundation.
Revision ID: 20260824_0001
Revises: None
"""
import sqlalchemy as sa
from alembic import op
from modelforge_api.persistence.models import Base
revision = "20260824_0001"
down_revision = None
branch_labels = None
depends_on = None
def upgrade() -> None:
# The declarative metadata is the reviewed M0 schema contract. create_all is
# used only from this baseline migration; later changes require explicit ops.
Base.metadata.create_all(bind=op.get_bind(), checkfirst=False)
# Freeze the original M0 shape even though the declarative metadata evolves.
# Remove newer foreign-key columns before dropping the tables they target.
for column in (
"profile_config",
"health_contract",
"device_policy",
"modality",
"dtype",
"version",
"artifact_set_id",
"runtime_environment_id",
):
op.drop_column("runtime_profiles", column)
for table in (
# Later milestone tables are present in the evolving declarative metadata
# used to build this baseline. Drop dependants before their M5/M0 targets
# so a fresh upgrade still reconstructs each historical revision exactly.
"audit_chain_heads",
"node_decommission_operations",
# M15 recovery tables come first: artifact_recovery_operations references
# artifact_jobs, download_plans, artifact_sets, model_revisions and storage_roots,
# all of which are dropped further down this list.
"artifact_recovery_operations",
"restore_operation_events",
"restore_operations",
"restore_plans",
"backup_manifest_entries",
"backup_sets",
"recovery_asset_records",
"recovery_policy_revisions",
"incident_timeline_events",
"alert_history_events",
"operational_alerts",
"operational_incidents",
"maintenance_windows",
"alert_rule_revisions",
"slo_evaluations",
"slo_policy_revisions",
"service_level_indicators",
"capacity_aggregates",
"capacity_snapshots",
"migration_events",
"migration_cutover_operations",
"migration_rollback_snapshots",
"migration_shadow_sessions",
"migration_validation_snapshots",
"migration_batch_checkpoints",
"migration_plans",
"migration_validation_policy_revisions",
"artifact_location_removal_records",
"lifecycle_canary_runs",
"lifecycle_operations",
"lifecycle_rollback_snapshots",
"lifecycle_promotion_plans",
"lifecycle_approval_evidence",
"lifecycle_approval_requests",
"lifecycle_cleanup_plans",
"lifecycle_retention_records",
"retention_policy_revisions",
"lifecycle_events",
"lifecycle_policy_revisions",
"lifecycle_subjects",
"project_fit_evidence",
"scheduler_evictions",
"co_residency_evidence",
"placement_plans",
"scheduler_policy_revisions",
"scheduler_accelerator_states",
"capability_evaluation_runs",
"capability_evaluation_suites",
"reranking_case_results",
"reranking_evaluation_runs",
"retrieval_candidate_pools",
"advisor_recommendations",
"retrieval_pipeline_identities",
"discovery_candidate_assessments",
"advisor_policies",
"model_comparisons",
"evaluation_comparisons",
"evaluation_case_results",
"evaluation_runs",
"evaluation_cases",
"evaluation_suite_revisions",
"evaluation_suites",
"embedding_migrations",
"project_evaluation_bindings",
"serving_gpu_leases",
"serving_jobs",
"gateway_requests",
"residency_allocations",
"service_credentials",
"service_clients",
"capability_resource_envelopes",
"capability_experiment_routes",
"capability_deployments",
"embedding_spaces",
"production_execution_approvals",
"deployment_candidates",
"runtime_probe_metrics",
"runtime_probes",
"execution_approvals",
"runtime_compatibility_assessments",
"artifact_set_members",
"artifact_inspections",
"artifact_job_attempts",
"artifact_jobs",
"download_plan_files",
"download_plans",
"artifact_sets",
"upstream_files",
"upstream_snapshots",
"artifact_locations",
"derived_artifact_sources",
"storage_roots",
):
op.drop_table(table)
op.drop_table("runtime_environments")
for column in ("source_type", "upstream_metadata", "local_metadata", "interpretation_metadata", "description"):
op.drop_column("models", column)
op.drop_column("model_revisions", "archived_at")
op.drop_column("audit_events", "canonical_payload")
op.drop_column("audit_events", "hash_format")
for column in ("status", "verification_details", "archived_at"):
op.drop_column("model_artifacts", column)
for column in ("transformation_type", "environment_snapshot", "status", "verification_details"):
op.drop_column("derived_artifacts", column)
for table in (
"node_credentials",
"node_enrollments",
"hardware_inventory_runs",
"storage_volume_states",
"accelerator_telemetry_latest",
"host_telemetry_latest",
):
op.drop_table(table)
for column in (
"inventory_at",
"last_seen_at",
"first_seen_at",
"inventory_source",
"status_reason",
"status",
"mig_mode_current",
"total_vram_bytes",
"compute_capability_minor",
"compute_capability_major",
"architecture",
"vendor",
"pci_bus_id",
):
op.drop_column("accelerators", column)
for column in (
"decommissioned_by",
"decommission_reason",
"decommissioned_at",
"generation",
"telemetry_sequence",
"inventory_sequence",
"last_connection_error",
"last_telemetry_observed_at",
"last_telemetry_received_at",
"last_inventory_observed_at",
"last_inventory_received_at",
"last_heartbeat_at",
"agent_started_at",
"agent_capabilities",
"agent_protocol_version",
"liveness_state",
"observation_source",
"benchmark_eligible",
"lab_eligible",
"production_eligible",
"labels",
"role",
"enabled",
):
op.drop_column("compute_nodes", column)
for column in (
"inventory_at",
"first_seen_at",
"status_reason",
"agent_version",
"total_ram_bytes",
"physical_core_count",
"logical_cpu_count",
"cpu_model",
"kernel_version",
"architecture",
"os_version",
"os_name",
"identity_source",
"display_name",
):
op.drop_column("compute_nodes", column)
def downgrade() -> None:
existing = set(sa.inspect(op.get_bind()).get_table_names())
for table in reversed(Base.metadata.sorted_tables):
if table.name in existing:
op.drop_table(table.name)
@@ -0,0 +1,194 @@
"""M1 hardware plane persistence.
Revision ID: 20260824_0002
Revises: 20260824_0001
"""
import sqlalchemy as sa
from alembic import op
revision = "20260824_0002"
down_revision = "20260824_0001"
branch_labels = None
depends_on = None
def upgrade() -> None:
for column in (
sa.Column("display_name", sa.String(255), nullable=False, server_default=""),
sa.Column("identity_source", sa.String(32), nullable=False, server_default="unknown"),
sa.Column("os_name", sa.String(128)),
sa.Column("os_version", sa.Text()),
sa.Column("architecture", sa.String(128)),
sa.Column("kernel_version", sa.String(255)),
sa.Column("cpu_model", sa.String(255)),
sa.Column("logical_cpu_count", sa.Integer()),
sa.Column("physical_core_count", sa.Integer()),
sa.Column("total_ram_bytes", sa.BigInteger()),
sa.Column("agent_version", sa.String(64)),
sa.Column("status_reason", sa.Text()),
sa.Column(
"first_seen_at",
sa.DateTime(timezone=True),
nullable=False,
server_default=sa.func.now(),
),
sa.Column("inventory_at", sa.DateTime(timezone=True)),
):
op.add_column("compute_nodes", column)
for column in (
sa.Column("pci_bus_id", sa.String(64)),
sa.Column("vendor", sa.String(64), nullable=False, server_default="NVIDIA"),
sa.Column("architecture", sa.String(64)),
sa.Column("compute_capability_major", sa.Integer()),
sa.Column("compute_capability_minor", sa.Integer()),
sa.Column("total_vram_bytes", sa.BigInteger()),
sa.Column("mig_mode_current", sa.Boolean()),
sa.Column("status", sa.String(32), nullable=False, server_default="active"),
sa.Column("status_reason", sa.Text()),
sa.Column("inventory_source", sa.String(32), nullable=False, server_default="nvidia_nvml"),
sa.Column(
"first_seen_at",
sa.DateTime(timezone=True),
nullable=False,
server_default=sa.func.now(),
),
sa.Column("last_seen_at", sa.DateTime(timezone=True)),
sa.Column("inventory_at", sa.DateTime(timezone=True)),
):
op.add_column("accelerators", column)
op.create_table(
"host_telemetry_latest",
sa.Column(
"compute_node_id",
sa.Uuid(),
sa.ForeignKey("compute_nodes.id", ondelete="CASCADE"),
nullable=False,
unique=True,
),
sa.Column("available_ram_bytes", sa.BigInteger()),
sa.Column("availability", sa.JSON(), nullable=False),
sa.Column("observed_at", sa.DateTime(timezone=True), nullable=False),
sa.Column("id", sa.Uuid(), primary_key=True),
)
op.create_index(
"ix_host_telemetry_latest_compute_node_id",
"host_telemetry_latest",
["compute_node_id"],
unique=True,
)
op.create_table(
"accelerator_telemetry_latest",
sa.Column(
"accelerator_id",
sa.Uuid(),
sa.ForeignKey("accelerators.id", ondelete="CASCADE"),
nullable=False,
unique=True,
),
sa.Column("used_vram_bytes", sa.BigInteger()),
sa.Column("free_vram_bytes", sa.BigInteger()),
sa.Column("gpu_utilization_percent", sa.Integer()),
sa.Column("memory_utilization_percent", sa.Integer()),
sa.Column("temperature_c", sa.Integer()),
sa.Column("power_draw_w", sa.Float()),
sa.Column("power_limit_w", sa.Float()),
sa.Column("graphics_clock_mhz", sa.Integer()),
sa.Column("memory_clock_mhz", sa.Integer()),
sa.Column("fan_speed_percent", sa.Integer()),
sa.Column("performance_state", sa.String(32)),
sa.Column("availability", sa.JSON(), nullable=False),
sa.Column("observed_at", sa.DateTime(timezone=True), nullable=False),
sa.Column("id", sa.Uuid(), primary_key=True),
)
op.create_index(
"ix_accelerator_telemetry_latest_accelerator_id",
"accelerator_telemetry_latest",
["accelerator_id"],
unique=True,
)
op.create_table(
"storage_volume_states",
sa.Column(
"compute_node_id",
sa.Uuid(),
sa.ForeignKey("compute_nodes.id", ondelete="CASCADE"),
nullable=False,
),
sa.Column("purpose", sa.String(64), nullable=False),
sa.Column("path", sa.Text(), nullable=False),
sa.Column("total_bytes", sa.BigInteger()),
sa.Column("used_bytes", sa.BigInteger()),
sa.Column("free_bytes", sa.BigInteger()),
sa.Column("availability", sa.JSON(), nullable=False),
sa.Column("observed_at", sa.DateTime(timezone=True), nullable=False),
sa.Column("id", sa.Uuid(), primary_key=True),
sa.UniqueConstraint("compute_node_id", "purpose", "path", name="uq_node_storage_path"),
)
op.create_index(
"ix_storage_volume_states_compute_node_id", "storage_volume_states", ["compute_node_id"]
)
op.create_table(
"hardware_inventory_runs",
sa.Column(
"compute_node_id", sa.Uuid(), sa.ForeignKey("compute_nodes.id", ondelete="SET NULL")
),
sa.Column("status", sa.String(32), nullable=False),
sa.Column("source", sa.String(64), nullable=False),
sa.Column("fingerprint", sa.String(64)),
sa.Column("summary", sa.JSON(), nullable=False),
sa.Column("error", sa.Text()),
sa.Column("started_at", sa.DateTime(timezone=True), nullable=False),
sa.Column("completed_at", sa.DateTime(timezone=True)),
sa.Column("id", sa.Uuid(), primary_key=True),
)
op.create_index(
"ix_hardware_inventory_runs_compute_node_id", "hardware_inventory_runs", ["compute_node_id"]
)
op.create_index(
"ix_hardware_inventory_runs_fingerprint", "hardware_inventory_runs", ["fingerprint"]
)
def downgrade() -> None:
for table in (
"hardware_inventory_runs",
"storage_volume_states",
"accelerator_telemetry_latest",
"host_telemetry_latest",
):
op.drop_table(table)
for column in (
"inventory_at",
"last_seen_at",
"first_seen_at",
"inventory_source",
"status_reason",
"status",
"mig_mode_current",
"total_vram_bytes",
"compute_capability_minor",
"compute_capability_major",
"architecture",
"vendor",
"pci_bus_id",
):
op.drop_column("accelerators", column)
for column in (
"inventory_at",
"first_seen_at",
"status_reason",
"agent_version",
"total_ram_bytes",
"physical_core_count",
"logical_cpu_count",
"cpu_model",
"kernel_version",
"architecture",
"os_version",
"os_name",
"identity_source",
"display_name",
):
op.drop_column("compute_nodes", column)
@@ -0,0 +1,141 @@
"""M1.5 remote compute node agent.
Revision ID: 20260825_0003
Revises: 20260824_0002
"""
import sqlalchemy as sa
from alembic import op
revision = "20260825_0003"
down_revision = "20260824_0002"
branch_labels = None
depends_on = None
def upgrade() -> None:
for column in (
sa.Column("enabled", sa.Boolean(), nullable=False, server_default=sa.true()),
sa.Column("role", sa.String(64)),
sa.Column("labels", sa.JSON(), nullable=False, server_default=sa.text("'{}'")),
sa.Column("production_eligible", sa.Boolean(), nullable=False, server_default=sa.false()),
sa.Column("lab_eligible", sa.Boolean(), nullable=False, server_default=sa.true()),
sa.Column("benchmark_eligible", sa.Boolean(), nullable=False, server_default=sa.false()),
sa.Column(
"observation_source",
sa.String(32),
nullable=False,
server_default="local_control_plane",
),
sa.Column("liveness_state", sa.String(32), nullable=False, server_default="offline"),
sa.Column("agent_protocol_version", sa.Integer()),
sa.Column("agent_capabilities", sa.JSON(), nullable=False, server_default=sa.text("'[]'")),
sa.Column("agent_started_at", sa.DateTime(timezone=True)),
sa.Column("last_heartbeat_at", sa.DateTime(timezone=True)),
sa.Column("last_inventory_received_at", sa.DateTime(timezone=True)),
sa.Column("last_inventory_observed_at", sa.DateTime(timezone=True)),
sa.Column("last_telemetry_received_at", sa.DateTime(timezone=True)),
sa.Column("last_telemetry_observed_at", sa.DateTime(timezone=True)),
sa.Column("last_connection_error", sa.Text()),
sa.Column("inventory_sequence", sa.BigInteger(), nullable=False, server_default="0"),
sa.Column("telemetry_sequence", sa.BigInteger(), nullable=False, server_default="0"),
):
op.add_column("compute_nodes", column)
for table in (
"host_telemetry_latest",
"accelerator_telemetry_latest",
"storage_volume_states",
):
op.add_column(table, sa.Column("received_at", sa.DateTime(timezone=True)))
for column in (
sa.Column("observation_sequence", sa.BigInteger()),
sa.Column("observed_at", sa.DateTime(timezone=True)),
sa.Column("received_at", sa.DateTime(timezone=True)),
):
op.add_column("hardware_inventory_runs", column)
op.create_table(
"node_enrollments",
sa.Column("token_hash", sa.String(64), nullable=False, unique=True),
sa.Column("scope", sa.String(64), nullable=False),
sa.Column("requested_metadata", sa.JSON(), nullable=False),
sa.Column(
"created_at", sa.DateTime(timezone=True), nullable=False, server_default=sa.func.now()
),
sa.Column("expires_at", sa.DateTime(timezone=True), nullable=False),
sa.Column("used_at", sa.DateTime(timezone=True)),
sa.Column("revoked_at", sa.DateTime(timezone=True)),
sa.Column(
"enrolled_node_id",
sa.Uuid(),
sa.ForeignKey("compute_nodes.id", ondelete="SET NULL"),
),
sa.Column("id", sa.Uuid(), primary_key=True),
)
op.create_index("ix_node_enrollments_expires_at", "node_enrollments", ["expires_at"])
op.create_index(
"ix_node_enrollments_enrolled_node_id", "node_enrollments", ["enrolled_node_id"]
)
op.create_table(
"node_credentials",
sa.Column(
"compute_node_id",
sa.Uuid(),
sa.ForeignKey("compute_nodes.id", ondelete="CASCADE"),
nullable=False,
),
sa.Column("secret_hash", sa.String(64), nullable=False, unique=True),
sa.Column("scope", sa.String(64), nullable=False),
sa.Column(
"created_at", sa.DateTime(timezone=True), nullable=False, server_default=sa.func.now()
),
sa.Column("last_used_at", sa.DateTime(timezone=True)),
sa.Column("revoked_at", sa.DateTime(timezone=True)),
sa.Column("id", sa.Uuid(), primary_key=True),
)
op.create_index("ix_node_credentials_compute_node_id", "node_credentials", ["compute_node_id"])
op.create_index(
"uq_active_node_credential",
"node_credentials",
["compute_node_id"],
unique=True,
postgresql_where=sa.text("revoked_at IS NULL"),
sqlite_where=sa.text("revoked_at IS NULL"),
)
def downgrade() -> None:
op.drop_table("node_credentials")
op.drop_table("node_enrollments")
for column in ("received_at", "observed_at", "observation_sequence"):
op.drop_column("hardware_inventory_runs", column)
for table in (
"storage_volume_states",
"accelerator_telemetry_latest",
"host_telemetry_latest",
):
op.drop_column(table, "received_at")
for column in (
"telemetry_sequence",
"inventory_sequence",
"last_connection_error",
"last_telemetry_observed_at",
"last_telemetry_received_at",
"last_inventory_observed_at",
"last_inventory_received_at",
"last_heartbeat_at",
"agent_started_at",
"agent_capabilities",
"agent_protocol_version",
"liveness_state",
"observation_source",
"benchmark_eligible",
"lab_eligible",
"production_eligible",
"labels",
"role",
"enabled",
):
op.drop_column("compute_nodes", column)
@@ -0,0 +1,170 @@
"""M2 operational model registry and artifact provenance.
Revision ID: 20260825_0004
Revises: 20260825_0003
"""
import sqlalchemy as sa
from alembic import op
revision = "20260825_0004"
down_revision = "20260825_0003"
branch_labels = None
depends_on = None
def upgrade() -> None:
for column in (
sa.Column("source_type", sa.String(32), nullable=False, server_default="huggingface"),
sa.Column("upstream_metadata", sa.JSON(), nullable=False, server_default=sa.text("'{}'")),
sa.Column("local_metadata", sa.JSON(), nullable=False, server_default=sa.text("'{}'")),
sa.Column(
"interpretation_metadata", sa.JSON(), nullable=False, server_default=sa.text("'{}'")
),
sa.Column("description", sa.Text()),
):
op.add_column("models", column)
op.add_column("model_revisions", sa.Column("archived_at", sa.DateTime(timezone=True)))
for column in (
sa.Column("status", sa.String(32), nullable=False, server_default="remote"),
sa.Column(
"verification_details", sa.JSON(), nullable=False, server_default=sa.text("'{}'")
),
sa.Column("archived_at", sa.DateTime(timezone=True)),
):
op.add_column("model_artifacts", column)
op.alter_column("model_artifacts", "storage_uri", existing_type=sa.Text(), nullable=True)
for column in (
sa.Column(
"transformation_type", sa.String(64), nullable=False, server_default="conversion"
),
sa.Column(
"environment_snapshot", sa.JSON(), nullable=False, server_default=sa.text("'{}'")
),
sa.Column("status", sa.String(32), nullable=False, server_default="remote"),
sa.Column(
"verification_details", sa.JSON(), nullable=False, server_default=sa.text("'{}'")
),
):
op.add_column("derived_artifacts", column)
op.alter_column("derived_artifacts", "storage_uri", existing_type=sa.Text(), nullable=True)
op.alter_column(
"derived_artifacts", "source_artifact_id", existing_type=sa.Uuid(), nullable=True
)
op.create_table(
"storage_roots",
sa.Column("compute_node_id", sa.Uuid(), nullable=False),
sa.Column("name", sa.String(128), nullable=False),
sa.Column("purpose", sa.String(64), nullable=False, server_default="model_artifacts"),
sa.Column("path", sa.Text(), nullable=False),
sa.Column("status", sa.String(32), nullable=False, server_default="unknown"),
sa.Column("writable", sa.Boolean(), nullable=False, server_default=sa.false()),
sa.Column("capacity_bytes", sa.BigInteger()),
sa.Column("free_bytes", sa.BigInteger()),
sa.Column("reserve_bytes", sa.BigInteger(), nullable=False, server_default="0"),
sa.Column("reserve_percent", sa.Integer(), nullable=False, server_default="10"),
sa.Column("capacity_observed_at", sa.DateTime(timezone=True)),
sa.Column("validation_details", sa.JSON(), nullable=False, server_default=sa.text("'{}'")),
sa.Column("deprecated_at", sa.DateTime(timezone=True)),
sa.Column("id", sa.Uuid(), primary_key=True),
sa.Column("created_at", sa.DateTime(timezone=True), server_default=sa.func.now()),
sa.Column("updated_at", sa.DateTime(timezone=True), server_default=sa.func.now()),
sa.ForeignKeyConstraint(["compute_node_id"], ["compute_nodes.id"], ondelete="RESTRICT"),
sa.UniqueConstraint("compute_node_id", "path", name="uq_storage_root_node_path"),
sa.CheckConstraint("reserve_bytes >= 0", name="ck_storage_root_reserve_bytes"),
sa.CheckConstraint(
"reserve_percent >= 0 AND reserve_percent <= 100",
name="ck_storage_root_reserve_percent",
),
)
op.create_index("ix_storage_roots_compute_node_id", "storage_roots", ["compute_node_id"])
op.create_table(
"derived_artifact_sources",
sa.Column("derived_artifact_id", sa.Uuid(), nullable=False),
sa.Column("source_artifact_id", sa.Uuid(), nullable=False),
sa.Column("ordinal", sa.Integer(), nullable=False, server_default="0"),
sa.Column("source_sha256", sa.String(64), nullable=False),
sa.ForeignKeyConstraint(
["derived_artifact_id"], ["derived_artifacts.id"], ondelete="RESTRICT"
),
sa.ForeignKeyConstraint(
["source_artifact_id"], ["model_artifacts.id"], ondelete="RESTRICT"
),
sa.PrimaryKeyConstraint("derived_artifact_id", "source_artifact_id"),
sa.UniqueConstraint(
"derived_artifact_id", "source_artifact_id", name="uq_derived_artifact_source"
),
)
op.create_table(
"artifact_locations",
sa.Column("artifact_id", sa.Uuid()),
sa.Column("derived_artifact_id", sa.Uuid()),
sa.Column("storage_root_id", sa.Uuid(), nullable=False),
sa.Column("relative_path", sa.Text(), nullable=False),
sa.Column("status", sa.String(32), nullable=False, server_default="unknown"),
sa.Column("size_bytes", sa.BigInteger()),
sa.Column("observed_sha256", sa.String(64)),
sa.Column("last_checked_at", sa.DateTime(timezone=True)),
sa.Column("id", sa.Uuid(), primary_key=True),
sa.Column("created_at", sa.DateTime(timezone=True), server_default=sa.func.now()),
sa.Column("updated_at", sa.DateTime(timezone=True), server_default=sa.func.now()),
sa.ForeignKeyConstraint(["artifact_id"], ["model_artifacts.id"], ondelete="RESTRICT"),
sa.ForeignKeyConstraint(
["derived_artifact_id"], ["derived_artifacts.id"], ondelete="RESTRICT"
),
sa.ForeignKeyConstraint(["storage_root_id"], ["storage_roots.id"], ondelete="RESTRICT"),
sa.CheckConstraint(
"(artifact_id IS NOT NULL AND derived_artifact_id IS NULL) OR "
"(artifact_id IS NULL AND derived_artifact_id IS NOT NULL)",
name="ck_artifact_location_one_owner",
),
sa.UniqueConstraint(
"storage_root_id", "relative_path", name="uq_storage_root_relative_path"
),
)
op.create_index("ix_artifact_locations_artifact_id", "artifact_locations", ["artifact_id"])
op.create_index(
"ix_artifact_locations_derived_artifact_id",
"artifact_locations",
["derived_artifact_id"],
)
op.create_index(
"ix_artifact_locations_storage_root_id", "artifact_locations", ["storage_root_id"]
)
def downgrade() -> None:
op.drop_table("artifact_locations")
op.drop_table("derived_artifact_sources")
op.drop_table("storage_roots")
op.execute(
"UPDATE model_artifacts SET storage_uri = 'registry://artifact/' || id::text "
"WHERE storage_uri IS NULL"
)
op.execute(
"UPDATE derived_artifacts SET storage_uri = 'registry://derived/' || id::text "
"WHERE storage_uri IS NULL"
)
op.alter_column("derived_artifacts", "source_artifact_id", existing_type=sa.Uuid(), nullable=False)
op.alter_column("derived_artifacts", "storage_uri", existing_type=sa.Text(), nullable=False)
for column in (
"verification_details",
"status",
"environment_snapshot",
"transformation_type",
):
op.drop_column("derived_artifacts", column)
op.alter_column("model_artifacts", "storage_uri", existing_type=sa.Text(), nullable=False)
for column in ("archived_at", "verification_details", "status"):
op.drop_column("model_artifacts", column)
op.drop_column("model_revisions", "archived_at")
for column in (
"description",
"interpretation_metadata",
"local_metadata",
"upstream_metadata",
"source_type",
):
op.drop_column("models", column)
@@ -0,0 +1,210 @@
"""M3 Hugging Face discovery and node-local artifact acquisition.
Revision ID: 20260825_0005
Revises: 20260825_0004
"""
import sqlalchemy as sa
from alembic import op
revision = "20260825_0005"
down_revision = "20260825_0004"
branch_labels = None
depends_on = None
def _identity() -> list[sa.Column]:
return [
sa.Column("id", sa.Uuid(), primary_key=True),
sa.Column("created_at", sa.DateTime(timezone=True), server_default=sa.func.now()),
sa.Column("updated_at", sa.DateTime(timezone=True), server_default=sa.func.now()),
]
def upgrade() -> None:
op.add_column("storage_roots", sa.Column("agent_path", sa.Text()))
op.create_table(
"upstream_snapshots",
sa.Column("model_id", sa.Uuid()),
sa.Column("provider", sa.String(64), nullable=False, server_default="huggingface"),
sa.Column("repository_id", sa.String(255), nullable=False),
sa.Column("requested_revision", sa.String(255), nullable=False, server_default="main"),
sa.Column("resolved_commit_sha", sa.String(64), nullable=False),
sa.Column("access_state", sa.String(32), nullable=False, server_default="public"),
sa.Column("metadata_snapshot", sa.JSON(), nullable=False, server_default=sa.text("'{}'")),
sa.Column("card_metadata", sa.JSON(), nullable=False, server_default=sa.text("'{}'")),
sa.Column("security_metadata", sa.JSON(), nullable=False, server_default=sa.text("'{}'")),
sa.Column("source_updated_at", sa.DateTime(timezone=True)),
sa.Column("observed_at", sa.DateTime(timezone=True), nullable=False, server_default=sa.func.now()),
sa.Column("stale_after", sa.DateTime(timezone=True), nullable=False),
sa.Column("id", sa.Uuid(), primary_key=True),
sa.ForeignKeyConstraint(["model_id"], ["models.id"], ondelete="RESTRICT"),
sa.CheckConstraint("length(resolved_commit_sha) >= 40", name="ck_snapshot_commit_length"),
)
op.create_index("ix_upstream_snapshots_model_id", "upstream_snapshots", ["model_id"])
op.create_index("ix_upstream_snapshots_repository_id", "upstream_snapshots", ["repository_id"])
op.create_index("ix_upstream_snapshots_resolved_commit_sha", "upstream_snapshots", ["resolved_commit_sha"])
op.create_table(
"upstream_files",
sa.Column("snapshot_id", sa.Uuid(), nullable=False),
sa.Column("path", sa.Text(), nullable=False),
sa.Column("size_bytes", sa.BigInteger()),
sa.Column("blob_id", sa.String(128)),
sa.Column("upstream_sha256", sa.String(64)),
sa.Column("file_format", sa.String(64), nullable=False, server_default="unknown"),
sa.Column("role", sa.String(64), nullable=False, server_default="other"),
sa.Column("risk_flags", sa.JSON(), nullable=False, server_default=sa.text("'[]'")),
sa.Column("metadata_snapshot", sa.JSON(), nullable=False, server_default=sa.text("'{}'")),
sa.Column("id", sa.Uuid(), primary_key=True),
sa.ForeignKeyConstraint(["snapshot_id"], ["upstream_snapshots.id"], ondelete="RESTRICT"),
sa.UniqueConstraint("snapshot_id", "path", name="uq_upstream_file_snapshot_path"),
sa.CheckConstraint("size_bytes IS NULL OR size_bytes >= 0", name="ck_upstream_file_size"),
)
op.create_index("ix_upstream_files_snapshot_id", "upstream_files", ["snapshot_id"])
op.create_table(
"artifact_sets",
sa.Column("revision_id", sa.Uuid(), nullable=False),
sa.Column("snapshot_id", sa.Uuid(), nullable=False),
sa.Column("variant_key", sa.String(128), nullable=False),
sa.Column("label", sa.String(255), nullable=False),
sa.Column("selection_reason", sa.Text(), nullable=False),
sa.Column("selected_paths", sa.JSON(), nullable=False, server_default=sa.text("'[]'")),
sa.Column("total_size_bytes", sa.BigInteger(), nullable=False, server_default="0"),
sa.Column("file_count", sa.Integer(), nullable=False, server_default="0"),
sa.Column("availability", sa.String(32), nullable=False, server_default="remote"),
sa.Column("status", sa.String(32), nullable=False, server_default="remote"),
sa.Column("completeness", sa.String(32), nullable=False, server_default="planned"),
sa.Column("security_status", sa.String(32), nullable=False, server_default="unverified"),
sa.Column("license_status", sa.String(32), nullable=False, server_default="unknown"),
sa.Column("immutable_at", sa.DateTime(timezone=True), nullable=False),
*_identity(),
sa.ForeignKeyConstraint(["revision_id"], ["model_revisions.id"], ondelete="RESTRICT"),
sa.ForeignKeyConstraint(["snapshot_id"], ["upstream_snapshots.id"], ondelete="RESTRICT"),
sa.UniqueConstraint("revision_id", "variant_key", name="uq_artifact_set_revision_variant"),
sa.CheckConstraint("total_size_bytes >= 0", name="ck_artifact_set_size"),
)
op.create_index("ix_artifact_sets_revision_id", "artifact_sets", ["revision_id"])
op.create_index("ix_artifact_sets_snapshot_id", "artifact_sets", ["snapshot_id"])
op.create_table(
"download_plans",
sa.Column("artifact_set_id", sa.Uuid(), nullable=False),
sa.Column("compute_node_id", sa.Uuid(), nullable=False),
sa.Column("storage_root_id", sa.Uuid(), nullable=False),
sa.Column("repository_id", sa.String(255), nullable=False),
sa.Column("resolved_commit_sha", sa.String(64), nullable=False),
sa.Column("total_size_bytes", sa.BigInteger(), nullable=False),
sa.Column("file_count", sa.Integer(), nullable=False),
sa.Column("status", sa.String(32), nullable=False, server_default="ready"),
sa.Column("idempotency_key", sa.String(64), nullable=False),
sa.Column("preflight", sa.JSON(), nullable=False, server_default=sa.text("'{}'")),
sa.Column("immutable_payload", sa.JSON(), nullable=False),
sa.Column("planned_at", sa.DateTime(timezone=True), nullable=False, server_default=sa.func.now()),
sa.Column("expires_at", sa.DateTime(timezone=True), nullable=False),
sa.Column("immutable_at", sa.DateTime(timezone=True), nullable=False),
*_identity(),
sa.ForeignKeyConstraint(["artifact_set_id"], ["artifact_sets.id"], ondelete="RESTRICT"),
sa.ForeignKeyConstraint(["compute_node_id"], ["compute_nodes.id"], ondelete="RESTRICT"),
sa.ForeignKeyConstraint(["storage_root_id"], ["storage_roots.id"], ondelete="RESTRICT"),
sa.UniqueConstraint("idempotency_key", name="uq_download_plan_idempotency"),
sa.CheckConstraint("total_size_bytes >= 0", name="ck_download_plan_size"),
)
for col in ("artifact_set_id", "compute_node_id", "storage_root_id"):
op.create_index(f"ix_download_plans_{col}", "download_plans", [col])
op.create_table(
"download_plan_files",
sa.Column("plan_id", sa.Uuid(), nullable=False),
sa.Column("ordinal", sa.Integer(), nullable=False),
sa.Column("path", sa.Text(), nullable=False),
sa.Column("size_bytes", sa.BigInteger(), nullable=False),
sa.Column("upstream_sha256", sa.String(64)),
sa.Column("file_format", sa.String(64), nullable=False),
sa.Column("role", sa.String(64), nullable=False),
sa.Column("risk_flags", sa.JSON(), nullable=False, server_default=sa.text("'[]'")),
sa.Column("id", sa.Uuid(), primary_key=True),
sa.ForeignKeyConstraint(["plan_id"], ["download_plans.id"], ondelete="RESTRICT"),
sa.UniqueConstraint("plan_id", "path", name="uq_download_plan_file_path"),
sa.UniqueConstraint("plan_id", "ordinal", name="uq_download_plan_file_ordinal"),
)
op.create_index("ix_download_plan_files_plan_id", "download_plan_files", ["plan_id"])
op.create_table(
"artifact_jobs",
sa.Column("plan_id", sa.Uuid(), nullable=False),
sa.Column("compute_node_id", sa.Uuid(), nullable=False),
sa.Column("storage_root_id", sa.Uuid(), nullable=False),
sa.Column("status", sa.String(32), nullable=False, server_default="queued"),
sa.Column("idempotency_key", sa.String(64), nullable=False),
sa.Column("attempt_count", sa.Integer(), nullable=False, server_default="0"),
sa.Column("lease_token_hash", sa.String(64)),
sa.Column("lease_expires_at", sa.DateTime(timezone=True)),
sa.Column("progress_bytes", sa.BigInteger(), nullable=False, server_default="0"),
sa.Column("total_bytes", sa.BigInteger(), nullable=False),
sa.Column("current_file", sa.Text()),
sa.Column("cancel_requested", sa.Boolean(), nullable=False, server_default=sa.false()),
sa.Column("quarantine_relative_path", sa.Text()),
sa.Column("promoted_relative_path", sa.Text()),
sa.Column("error_code", sa.String(64)),
sa.Column("error_message", sa.Text()),
sa.Column("result", sa.JSON(), nullable=False, server_default=sa.text("'{}'")),
sa.Column("started_at", sa.DateTime(timezone=True)),
sa.Column("completed_at", sa.DateTime(timezone=True)),
*_identity(),
sa.ForeignKeyConstraint(["plan_id"], ["download_plans.id"], ondelete="RESTRICT"),
sa.ForeignKeyConstraint(["compute_node_id"], ["compute_nodes.id"], ondelete="RESTRICT"),
sa.ForeignKeyConstraint(["storage_root_id"], ["storage_roots.id"], ondelete="RESTRICT"),
sa.UniqueConstraint("plan_id", name="uq_artifact_job_plan"),
sa.UniqueConstraint("idempotency_key", name="uq_artifact_job_idempotency"),
)
for col in ("plan_id", "compute_node_id", "storage_root_id", "status"):
op.create_index(f"ix_artifact_jobs_{col}", "artifact_jobs", [col])
op.create_table(
"artifact_job_attempts",
sa.Column("job_id", sa.Uuid(), nullable=False),
sa.Column("attempt", sa.Integer(), nullable=False),
sa.Column("status", sa.String(32), nullable=False),
sa.Column("started_at", sa.DateTime(timezone=True), nullable=False, server_default=sa.func.now()),
sa.Column("completed_at", sa.DateTime(timezone=True)),
sa.Column("details", sa.JSON(), nullable=False, server_default=sa.text("'{}'")),
sa.Column("id", sa.Uuid(), primary_key=True),
sa.ForeignKeyConstraint(["job_id"], ["artifact_jobs.id"], ondelete="RESTRICT"),
)
op.create_index("ix_artifact_job_attempts_job_id", "artifact_job_attempts", ["job_id"])
op.create_table(
"artifact_inspections",
sa.Column("job_id", sa.Uuid(), nullable=False),
sa.Column("file_path", sa.Text(), nullable=False),
sa.Column("inspection_type", sa.String(64), nullable=False),
sa.Column("status", sa.String(32), nullable=False),
sa.Column("severity", sa.String(32), nullable=False),
sa.Column("evidence", sa.JSON(), nullable=False, server_default=sa.text("'{}'")),
sa.Column("observed_at", sa.DateTime(timezone=True), nullable=False, server_default=sa.func.now()),
sa.Column("id", sa.Uuid(), primary_key=True),
sa.ForeignKeyConstraint(["job_id"], ["artifact_jobs.id"], ondelete="RESTRICT"),
)
op.create_index("ix_artifact_inspections_job_id", "artifact_inspections", ["job_id"])
op.create_table(
"artifact_set_members",
sa.Column("artifact_set_id", sa.Uuid(), nullable=False),
sa.Column("artifact_id", sa.Uuid(), nullable=False),
sa.Column("ordinal", sa.Integer(), nullable=False),
sa.Column("required", sa.Boolean(), nullable=False, server_default=sa.true()),
sa.ForeignKeyConstraint(["artifact_set_id"], ["artifact_sets.id"], ondelete="RESTRICT"),
sa.ForeignKeyConstraint(["artifact_id"], ["model_artifacts.id"], ondelete="RESTRICT"),
sa.PrimaryKeyConstraint("artifact_set_id", "artifact_id"),
)
def downgrade() -> None:
for table in (
"artifact_set_members",
"artifact_inspections",
"artifact_job_attempts",
"artifact_jobs",
"download_plan_files",
"download_plans",
"artifact_sets",
"upstream_files",
"upstream_snapshots",
):
op.drop_table(table)
op.drop_column("storage_roots", "agent_path")
@@ -0,0 +1,221 @@
"""M4 runtime compatibility and runtime plane.
Revision ID: 20260825_0006
Revises: 20260825_0005
"""
import sqlalchemy as sa
from alembic import op
revision = "20260825_0006"
down_revision = "20260825_0005"
branch_labels = None
depends_on = None
def _timestamps() -> list[sa.Column]:
return [
sa.Column("created_at", sa.DateTime(timezone=True), server_default=sa.func.now()),
sa.Column("updated_at", sa.DateTime(timezone=True), server_default=sa.func.now()),
]
def upgrade() -> None:
op.create_table(
"runtime_environments",
sa.Column("name", sa.String(255), nullable=False),
sa.Column("adapter", sa.String(64), nullable=False),
sa.Column("runtime_version", sa.String(128), nullable=False),
sa.Column("image_repository", sa.String(255), nullable=False),
sa.Column("image_digest", sa.String(71), nullable=False),
sa.Column("python_version", sa.String(64), nullable=False),
sa.Column("cuda_runtime_version", sa.String(64)),
sa.Column("package_versions", sa.JSON(), nullable=False, server_default=sa.text("'{}'")),
sa.Column("supported_model_types", sa.JSON(), nullable=False, server_default=sa.text("'[]'")),
sa.Column("supported_formats", sa.JSON(), nullable=False, server_default=sa.text("'[]'")),
sa.Column("supported_modalities", sa.JSON(), nullable=False, server_default=sa.text("'[]'")),
sa.Column("network_policy", sa.String(64), nullable=False),
sa.Column("fingerprint", sa.String(64), nullable=False),
sa.Column("immutable_at", sa.DateTime(timezone=True), nullable=False),
sa.Column("created_at", sa.DateTime(timezone=True), server_default=sa.func.now()),
sa.Column("id", sa.Uuid(), primary_key=True),
sa.UniqueConstraint("fingerprint", name="uq_runtime_environment_fingerprint"),
)
op.create_index("ix_runtime_environments_adapter", "runtime_environments", ["adapter"])
for column in (
sa.Column("runtime_environment_id", sa.Uuid()),
sa.Column("artifact_set_id", sa.Uuid()),
sa.Column("version", sa.Integer(), nullable=False, server_default="1"),
sa.Column("dtype", sa.String(32)),
sa.Column("modality", sa.String(32)),
sa.Column("device_policy", sa.String(32), nullable=False, server_default="cuda_required"),
sa.Column("health_contract", sa.JSON(), nullable=False, server_default=sa.text("'{}'")),
sa.Column("profile_config", sa.JSON(), nullable=False, server_default=sa.text("'{}'")),
):
op.add_column("runtime_profiles", column)
op.create_foreign_key(
"fk_runtime_profiles_environment",
"runtime_profiles",
"runtime_environments",
["runtime_environment_id"],
["id"],
ondelete="RESTRICT",
)
op.create_foreign_key(
"fk_runtime_profiles_artifact_set",
"runtime_profiles",
"artifact_sets",
["artifact_set_id"],
["id"],
ondelete="RESTRICT",
)
op.create_index("ix_runtime_profiles_runtime_environment_id", "runtime_profiles", ["runtime_environment_id"])
op.create_index("ix_runtime_profiles_artifact_set_id", "runtime_profiles", ["artifact_set_id"])
op.create_table(
"runtime_compatibility_assessments",
sa.Column("artifact_set_id", sa.Uuid(), nullable=False),
sa.Column("runtime_profile_id", sa.Uuid(), nullable=False),
sa.Column("compute_node_id", sa.Uuid(), nullable=False),
sa.Column("adapter", sa.String(64), nullable=False),
sa.Column("runtime_version", sa.String(128), nullable=False),
sa.Column("status", sa.String(32), nullable=False),
sa.Column("static_result", sa.JSON(), nullable=False, server_default=sa.text("'{}'")),
sa.Column("evidence", sa.JSON(), nullable=False, server_default=sa.text("'{}'")),
sa.Column("blockers", sa.JSON(), nullable=False, server_default=sa.text("'[]'")),
sa.Column("warnings", sa.JSON(), nullable=False, server_default=sa.text("'[]'")),
sa.Column("required_approvals", sa.JSON(), nullable=False, server_default=sa.text("'[]'")),
sa.Column("hardware_facts", sa.JSON(), nullable=False, server_default=sa.text("'{}'")),
sa.Column("artifact_facts", sa.JSON(), nullable=False, server_default=sa.text("'{}'")),
sa.Column("environment_fingerprint", sa.String(64), nullable=False),
sa.Column("stale", sa.Boolean(), nullable=False, server_default=sa.false()),
sa.Column("stale_reason", sa.Text()),
sa.Column("created_at", sa.DateTime(timezone=True), server_default=sa.func.now()),
sa.Column("id", sa.Uuid(), primary_key=True),
sa.ForeignKeyConstraint(["artifact_set_id"], ["artifact_sets.id"], ondelete="RESTRICT"),
sa.ForeignKeyConstraint(["runtime_profile_id"], ["runtime_profiles.id"], ondelete="RESTRICT"),
sa.ForeignKeyConstraint(["compute_node_id"], ["compute_nodes.id"], ondelete="RESTRICT"),
)
for column in ("artifact_set_id", "runtime_profile_id", "compute_node_id", "status", "environment_fingerprint"):
op.create_index(f"ix_runtime_compatibility_assessments_{column}", "runtime_compatibility_assessments", [column])
op.create_table(
"execution_approvals",
sa.Column("artifact_set_id", sa.Uuid(), nullable=False),
sa.Column("scope", sa.String(32), nullable=False),
sa.Column("status", sa.String(32), nullable=False),
sa.Column("evidence_fingerprint", sa.String(64), nullable=False),
sa.Column("reason", sa.Text(), nullable=False),
sa.Column("approved_by", sa.String(255), nullable=False),
sa.Column("approved_at", sa.DateTime(timezone=True), server_default=sa.func.now()),
sa.Column("expires_at", sa.DateTime(timezone=True)),
sa.Column("revoked_at", sa.DateTime(timezone=True)),
sa.Column("id", sa.Uuid(), primary_key=True),
sa.ForeignKeyConstraint(["artifact_set_id"], ["artifact_sets.id"], ondelete="RESTRICT"),
)
op.create_index("ix_execution_approvals_artifact_set_id", "execution_approvals", ["artifact_set_id"])
op.create_index("ix_execution_approvals_status", "execution_approvals", ["status"])
op.create_table(
"runtime_probes",
sa.Column("artifact_set_id", sa.Uuid(), nullable=False),
sa.Column("runtime_profile_id", sa.Uuid(), nullable=False),
sa.Column("compute_node_id", sa.Uuid(), nullable=False),
sa.Column("compatibility_assessment_id", sa.Uuid(), nullable=False),
sa.Column("execution_approval_id", sa.Uuid(), nullable=False),
sa.Column("status", sa.String(32), nullable=False, server_default="queued"),
sa.Column("phase", sa.String(64)),
sa.Column("probe_input", sa.Text(), nullable=False),
sa.Column("idempotency_key", sa.String(64), nullable=False),
sa.Column("attempt_count", sa.Integer(), nullable=False, server_default="0"),
sa.Column("lease_token_hash", sa.String(64)),
sa.Column("lease_expires_at", sa.DateTime(timezone=True)),
sa.Column("cancel_requested", sa.Boolean(), nullable=False, server_default=sa.false()),
sa.Column("load_result", sa.JSON(), nullable=False, server_default=sa.text("'{}'")),
sa.Column("health_result", sa.JSON(), nullable=False, server_default=sa.text("'{}'")),
sa.Column("inference_result", sa.JSON(), nullable=False, server_default=sa.text("'{}'")),
sa.Column("unload_result", sa.JSON(), nullable=False, server_default=sa.text("'{}'")),
sa.Column("measured_resources", sa.JSON(), nullable=False, server_default=sa.text("'{}'")),
sa.Column("runtime_facts", sa.JSON(), nullable=False, server_default=sa.text("'{}'")),
sa.Column("environment_fingerprint", sa.String(64), nullable=False),
sa.Column("failure_code", sa.String(64)),
sa.Column("failure_message", sa.Text()),
sa.Column("started_at", sa.DateTime(timezone=True)),
sa.Column("finished_at", sa.DateTime(timezone=True)),
sa.Column("id", sa.Uuid(), primary_key=True),
*_timestamps(),
sa.ForeignKeyConstraint(["artifact_set_id"], ["artifact_sets.id"], ondelete="RESTRICT"),
sa.ForeignKeyConstraint(["runtime_profile_id"], ["runtime_profiles.id"], ondelete="RESTRICT"),
sa.ForeignKeyConstraint(["compute_node_id"], ["compute_nodes.id"], ondelete="RESTRICT"),
sa.ForeignKeyConstraint(["compatibility_assessment_id"], ["runtime_compatibility_assessments.id"], ondelete="RESTRICT"),
sa.ForeignKeyConstraint(["execution_approval_id"], ["execution_approvals.id"], ondelete="RESTRICT"),
sa.UniqueConstraint("idempotency_key", name="uq_runtime_probe_idempotency"),
)
for column in ("artifact_set_id", "runtime_profile_id", "compute_node_id", "compatibility_assessment_id", "execution_approval_id", "status"):
op.create_index(f"ix_runtime_probes_{column}", "runtime_probes", [column])
op.create_table(
"runtime_probe_metrics",
sa.Column("runtime_probe_id", sa.Uuid(), nullable=False),
sa.Column("phase", sa.String(64), nullable=False),
sa.Column("measurement_type", sa.String(64), nullable=False),
sa.Column("source", sa.String(64), nullable=False),
sa.Column("values", sa.JSON(), nullable=False, server_default=sa.text("'{}'")),
sa.Column("observed_at", sa.DateTime(timezone=True), nullable=False),
sa.Column("id", sa.Uuid(), primary_key=True),
sa.ForeignKeyConstraint(["runtime_probe_id"], ["runtime_probes.id"], ondelete="RESTRICT"),
)
op.create_index("ix_runtime_probe_metrics_runtime_probe_id", "runtime_probe_metrics", ["runtime_probe_id"])
op.create_table(
"deployment_candidates",
sa.Column("artifact_set_id", sa.Uuid(), nullable=False),
sa.Column("runtime_profile_id", sa.Uuid(), nullable=False),
sa.Column("compute_node_id", sa.Uuid(), nullable=False),
sa.Column("compatibility_assessment_id", sa.Uuid(), nullable=False),
sa.Column("runtime_probe_id", sa.Uuid(), nullable=False),
sa.Column("channel", sa.String(32), nullable=False, server_default="lab"),
sa.Column("status", sa.String(32), nullable=False, server_default="lab_ready"),
sa.Column("production", sa.Boolean(), nullable=False, server_default=sa.false()),
sa.Column("health_contract", sa.JSON(), nullable=False, server_default=sa.text("'{}'")),
sa.Column("measured_resources", sa.JSON(), nullable=False, server_default=sa.text("'{}'")),
sa.Column("created_at", sa.DateTime(timezone=True), server_default=sa.func.now()),
sa.Column("id", sa.Uuid(), primary_key=True),
sa.ForeignKeyConstraint(["artifact_set_id"], ["artifact_sets.id"], ondelete="RESTRICT"),
sa.ForeignKeyConstraint(["runtime_profile_id"], ["runtime_profiles.id"], ondelete="RESTRICT"),
sa.ForeignKeyConstraint(["compute_node_id"], ["compute_nodes.id"], ondelete="RESTRICT"),
sa.ForeignKeyConstraint(["compatibility_assessment_id"], ["runtime_compatibility_assessments.id"], ondelete="RESTRICT"),
sa.ForeignKeyConstraint(["runtime_probe_id"], ["runtime_probes.id"], ondelete="RESTRICT"),
sa.UniqueConstraint("runtime_probe_id", name="uq_deployment_candidate_probe"),
)
for column in ("artifact_set_id", "runtime_profile_id", "compute_node_id", "runtime_probe_id", "status"):
op.create_index(f"ix_deployment_candidates_{column}", "deployment_candidates", [column])
def downgrade() -> None:
for table in (
"deployment_candidates",
"runtime_probe_metrics",
"runtime_probes",
"execution_approvals",
"runtime_compatibility_assessments",
):
op.drop_table(table)
op.drop_index("ix_runtime_profiles_artifact_set_id", table_name="runtime_profiles")
op.drop_index("ix_runtime_profiles_runtime_environment_id", table_name="runtime_profiles")
op.drop_constraint("fk_runtime_profiles_artifact_set", "runtime_profiles", type_="foreignkey")
op.drop_constraint("fk_runtime_profiles_environment", "runtime_profiles", type_="foreignkey")
for column in (
"profile_config",
"health_contract",
"device_policy",
"modality",
"dtype",
"version",
"artifact_set_id",
"runtime_environment_id",
):
op.drop_column("runtime_profiles", column)
op.drop_table("runtime_environments")
@@ -0,0 +1,28 @@
"""M4 bounded runtime log reference.
Revision ID: 20260825_0007
Revises: 20260825_0006
"""
from collections.abc import Sequence
import sqlalchemy as sa
from alembic import op
revision: str = "20260825_0007"
down_revision: str | None = "20260825_0006"
branch_labels: str | Sequence[str] | None = None
depends_on: str | Sequence[str] | None = None
def upgrade() -> None:
op.add_column("runtime_probes", sa.Column("logs_reference", sa.String(512)))
op.execute(
"UPDATE runtime_probes SET logs_reference = "
"'runtime-worker://historical-probe/' || CAST(id AS VARCHAR)"
)
def downgrade() -> None:
op.drop_column("runtime_probes", "logs_reference")
@@ -0,0 +1,382 @@
"""M5 capability serving, gateway and GPU scheduling.
Revision ID: 20260825_0008
Revises: 20260825_0007
"""
import sqlalchemy as sa
from alembic import op
revision = "20260825_0008"
down_revision = "20260825_0007"
branch_labels = None
depends_on = None
def _identity_columns() -> list[sa.Column]:
return [sa.Column("id", sa.Uuid(), primary_key=True)]
def _timestamps() -> list[sa.Column]:
return [
sa.Column("created_at", sa.DateTime(timezone=True), server_default=sa.func.now()),
sa.Column("updated_at", sa.DateTime(timezone=True), server_default=sa.func.now()),
]
def upgrade() -> None:
op.create_table(
"production_execution_approvals",
*_identity_columns(),
sa.Column("deployment_candidate_id", sa.Uuid(), nullable=False),
sa.Column("capability_contract_id", sa.Uuid(), nullable=False),
sa.Column("artifact_set_id", sa.Uuid(), nullable=False),
sa.Column("runtime_profile_id", sa.Uuid(), nullable=False),
sa.Column("compute_node_id", sa.Uuid(), nullable=False),
sa.Column("deployment_config", sa.JSON(), nullable=False),
sa.Column("supply_chain_evidence", sa.JSON(), nullable=False),
sa.Column("evidence_fingerprint", sa.String(64), nullable=False),
sa.Column("status", sa.String(32), nullable=False, server_default="approved"),
sa.Column("approved_by", sa.String(255), nullable=False),
sa.Column("reason", sa.Text(), nullable=False),
sa.Column("approved_at", sa.DateTime(timezone=True), server_default=sa.func.now()),
sa.Column("immutable_at", sa.DateTime(timezone=True), server_default=sa.func.now()),
sa.Column("revoked_at", sa.DateTime(timezone=True)),
sa.ForeignKeyConstraint(["deployment_candidate_id"], ["deployment_candidates.id"]),
sa.ForeignKeyConstraint(["capability_contract_id"], ["capability_contracts.id"]),
sa.ForeignKeyConstraint(["artifact_set_id"], ["artifact_sets.id"]),
sa.ForeignKeyConstraint(["runtime_profile_id"], ["runtime_profiles.id"]),
sa.ForeignKeyConstraint(["compute_node_id"], ["compute_nodes.id"]),
sa.UniqueConstraint("evidence_fingerprint", name="uq_production_approval_fingerprint"),
)
for column in (
"deployment_candidate_id",
"capability_contract_id",
"artifact_set_id",
"runtime_profile_id",
"compute_node_id",
"status",
):
op.create_index(
f"ix_production_execution_approvals_{column}",
"production_execution_approvals",
[column],
)
op.create_table(
"embedding_spaces",
*_identity_columns(),
sa.Column("capability_contract_id", sa.Uuid(), nullable=False),
sa.Column("artifact_set_id", sa.Uuid(), nullable=False),
sa.Column("runtime_profile_id", sa.Uuid(), nullable=False),
sa.Column("identity_digest", sa.String(64), nullable=False),
sa.Column("dimension", sa.Integer(), nullable=False),
sa.Column("normalized", sa.Boolean(), nullable=False),
sa.Column("migration_class", sa.String(32), nullable=False),
sa.Column("identity_facts", sa.JSON(), nullable=False),
sa.Column("created_at", sa.DateTime(timezone=True), server_default=sa.func.now()),
sa.Column("immutable_at", sa.DateTime(timezone=True), server_default=sa.func.now()),
sa.ForeignKeyConstraint(["capability_contract_id"], ["capability_contracts.id"]),
sa.ForeignKeyConstraint(["artifact_set_id"], ["artifact_sets.id"]),
sa.ForeignKeyConstraint(["runtime_profile_id"], ["runtime_profiles.id"]),
sa.UniqueConstraint("identity_digest", name="uq_embedding_space_digest"),
)
for column in ("capability_contract_id", "artifact_set_id", "runtime_profile_id"):
op.create_index(f"ix_embedding_spaces_{column}", "embedding_spaces", [column])
op.create_table(
"capability_deployments",
*_identity_columns(),
*_timestamps(),
sa.Column("capability_contract_id", sa.Uuid(), nullable=False),
sa.Column("deployment_candidate_id", sa.Uuid(), nullable=False),
sa.Column("production_approval_id", sa.Uuid(), nullable=False),
sa.Column("embedding_space_id", sa.Uuid(), nullable=False),
sa.Column("artifact_set_id", sa.Uuid(), nullable=False),
sa.Column("runtime_profile_id", sa.Uuid(), nullable=False),
sa.Column("compute_node_id", sa.Uuid(), nullable=False),
sa.Column("accelerator_id", sa.Uuid(), nullable=False),
sa.Column("channel", sa.String(32), nullable=False, server_default="stable"),
sa.Column("status", sa.String(32), nullable=False, server_default="approved"),
sa.Column("production", sa.Boolean(), nullable=False, server_default=sa.false()),
sa.Column("health_status", sa.String(32), nullable=False, server_default="ready_on_demand"),
sa.Column("routing_weight", sa.Integer(), nullable=False, server_default="100"),
sa.Column("fallback_policy", sa.JSON(), nullable=False),
sa.Column("residency_policy", sa.String(32), nullable=False),
sa.Column("keep_warm_seconds", sa.Integer(), nullable=False),
sa.Column("max_concurrency", sa.Integer(), nullable=False),
sa.Column("max_queue_depth", sa.Integer(), nullable=False),
sa.Column("config_fingerprint", sa.String(64), nullable=False),
sa.Column("provenance", sa.JSON(), nullable=False),
sa.Column("rollback_policy", sa.JSON(), nullable=False),
sa.Column("promoted_at", sa.DateTime(timezone=True)),
sa.Column("draining_at", sa.DateTime(timezone=True)),
sa.Column("deprecated_at", sa.DateTime(timezone=True)),
sa.ForeignKeyConstraint(["capability_contract_id"], ["capability_contracts.id"]),
sa.ForeignKeyConstraint(["deployment_candidate_id"], ["deployment_candidates.id"]),
sa.ForeignKeyConstraint(["production_approval_id"], ["production_execution_approvals.id"]),
sa.ForeignKeyConstraint(["embedding_space_id"], ["embedding_spaces.id"]),
sa.ForeignKeyConstraint(["artifact_set_id"], ["artifact_sets.id"]),
sa.ForeignKeyConstraint(["runtime_profile_id"], ["runtime_profiles.id"]),
sa.ForeignKeyConstraint(["compute_node_id"], ["compute_nodes.id"]),
sa.ForeignKeyConstraint(["accelerator_id"], ["accelerators.id"]),
sa.UniqueConstraint("config_fingerprint", name="uq_capability_deployment_fingerprint"),
)
for column in (
"capability_contract_id",
"deployment_candidate_id",
"production_approval_id",
"embedding_space_id",
"artifact_set_id",
"runtime_profile_id",
"compute_node_id",
"accelerator_id",
"channel",
"status",
):
op.create_index(f"ix_capability_deployments_{column}", "capability_deployments", [column])
op.create_index(
"ix_capability_deployments_contract_channel_status",
"capability_deployments",
["capability_contract_id", "channel", "status"],
)
op.create_table(
"capability_resource_envelopes",
*_identity_columns(),
*_timestamps(),
sa.Column("capability_deployment_id", sa.Uuid(), nullable=False),
sa.Column("runtime_probe_id", sa.Uuid(), nullable=False),
sa.Column("accelerator_kind", sa.String(255), nullable=False),
sa.Column("accelerator_uuid", sa.String(255), nullable=False),
sa.Column("environment_fingerprint", sa.String(64), nullable=False),
sa.Column("concurrency", sa.Integer(), nullable=False),
sa.Column("batch_size", sa.Integer(), nullable=False),
sa.Column("max_sequence_length", sa.Integer(), nullable=False),
sa.Column("baseline_vram_bytes", sa.BigInteger(), nullable=False),
sa.Column("resident_vram_bytes", sa.BigInteger(), nullable=False),
sa.Column("peak_vram_bytes", sa.BigInteger(), nullable=False),
sa.Column("required_vram_bytes", sa.BigInteger(), nullable=False),
sa.Column("cold_load_time_ms", sa.Float(), nullable=False),
sa.Column("inference_latency_ms", sa.Float(), nullable=False),
sa.Column("evidence", sa.JSON(), nullable=False),
sa.Column("stale", sa.Boolean(), nullable=False, server_default=sa.false()),
sa.Column("stale_reason", sa.Text()),
sa.Column("immutable_at", sa.DateTime(timezone=True), server_default=sa.func.now()),
sa.ForeignKeyConstraint(["capability_deployment_id"], ["capability_deployments.id"]),
sa.ForeignKeyConstraint(["runtime_probe_id"], ["runtime_probes.id"]),
sa.UniqueConstraint("capability_deployment_id", name="uq_capability_resource_envelope"),
)
op.create_index(
"ix_capability_resource_envelopes_capability_deployment_id",
"capability_resource_envelopes",
["capability_deployment_id"],
)
op.create_index(
"ix_capability_resource_envelopes_runtime_probe_id",
"capability_resource_envelopes",
["runtime_probe_id"],
)
op.create_index(
"ix_capability_resource_envelopes_environment_fingerprint",
"capability_resource_envelopes",
["environment_fingerprint"],
)
op.create_table(
"service_clients",
*_identity_columns(),
*_timestamps(),
sa.Column("name", sa.String(255), nullable=False, unique=True),
sa.Column("status", sa.String(32), nullable=False, server_default="active"),
sa.Column("allowed_capabilities", sa.JSON(), nullable=False),
sa.Column("requests_per_minute", sa.Integer(), nullable=False),
sa.Column("max_concurrent_requests", sa.Integer(), nullable=False),
sa.Column("last_used_at", sa.DateTime(timezone=True)),
sa.Column("disabled_at", sa.DateTime(timezone=True)),
)
op.create_index("ix_service_clients_status", "service_clients", ["status"])
op.create_table(
"service_credentials",
*_identity_columns(),
sa.Column("service_client_id", sa.Uuid(), nullable=False),
sa.Column("secret_hash", sa.String(64), nullable=False, unique=True),
sa.Column("secret_prefix", sa.String(16), nullable=False),
sa.Column("created_at", sa.DateTime(timezone=True), server_default=sa.func.now()),
sa.Column("expires_at", sa.DateTime(timezone=True)),
sa.Column("last_used_at", sa.DateTime(timezone=True)),
sa.Column("revoked_at", sa.DateTime(timezone=True)),
sa.ForeignKeyConstraint(["service_client_id"], ["service_clients.id"], ondelete="CASCADE"),
)
op.create_index(
"ix_service_credentials_service_client_id", "service_credentials", ["service_client_id"]
)
op.create_index(
"uq_active_service_client_credential",
"service_credentials",
["service_client_id"],
unique=True,
postgresql_where=sa.text("revoked_at IS NULL"),
)
op.create_table(
"residency_allocations",
*_identity_columns(),
*_timestamps(),
sa.Column("capability_deployment_id", sa.Uuid(), nullable=False),
sa.Column("compute_node_id", sa.Uuid(), nullable=False),
sa.Column("accelerator_id", sa.Uuid(), nullable=False),
sa.Column("state", sa.String(32), nullable=False, server_default="cold"),
sa.Column("worker_instance_id", sa.String(255)),
sa.Column("load_count", sa.Integer(), nullable=False, server_default="0"),
sa.Column("active_requests", sa.Integer(), nullable=False, server_default="0"),
sa.Column(
"measured_resident_vram_bytes", sa.BigInteger(), nullable=False, server_default="0"
),
sa.Column(
"external_baseline_vram_bytes", sa.BigInteger(), nullable=False, server_default="0"
),
sa.Column("health", sa.JSON(), nullable=False),
sa.Column("resident_since", sa.DateTime(timezone=True)),
sa.Column("last_used_at", sa.DateTime(timezone=True)),
sa.Column("transition_started_at", sa.DateTime(timezone=True)),
sa.Column("failure_code", sa.String(64)),
sa.Column("failure_message", sa.Text()),
sa.ForeignKeyConstraint(["capability_deployment_id"], ["capability_deployments.id"]),
sa.ForeignKeyConstraint(["compute_node_id"], ["compute_nodes.id"]),
sa.ForeignKeyConstraint(["accelerator_id"], ["accelerators.id"]),
sa.UniqueConstraint("capability_deployment_id", name="uq_residency_deployment"),
)
for column in ("capability_deployment_id", "compute_node_id", "accelerator_id", "state"):
op.create_index(f"ix_residency_allocations_{column}", "residency_allocations", [column])
op.create_table(
"serving_gpu_leases",
*_identity_columns(),
sa.Column("accelerator_id", sa.Uuid(), nullable=False),
sa.Column("capability_deployment_id", sa.Uuid(), nullable=False),
sa.Column("request_id", sa.Uuid()),
sa.Column("reserved_vram_bytes", sa.BigInteger(), nullable=False),
sa.Column("priority", sa.String(32), nullable=False),
sa.Column("state", sa.String(32), nullable=False, server_default="pending"),
sa.Column("owner", sa.String(255), nullable=False),
sa.Column("created_at", sa.DateTime(timezone=True), server_default=sa.func.now()),
sa.Column("expires_at", sa.DateTime(timezone=True), nullable=False),
sa.Column("acquired_at", sa.DateTime(timezone=True)),
sa.Column("heartbeat_at", sa.DateTime(timezone=True)),
sa.Column("released_at", sa.DateTime(timezone=True)),
sa.Column("failure_code", sa.String(64)),
sa.ForeignKeyConstraint(["accelerator_id"], ["accelerators.id"]),
sa.ForeignKeyConstraint(["capability_deployment_id"], ["capability_deployments.id"]),
)
for column in (
"accelerator_id",
"capability_deployment_id",
"request_id",
"state",
"expires_at",
):
op.create_index(f"ix_serving_gpu_leases_{column}", "serving_gpu_leases", [column])
op.create_table(
"gateway_requests",
*_identity_columns(),
sa.Column("request_id", sa.Uuid(), nullable=False, unique=True),
sa.Column("service_client_id", sa.Uuid()),
sa.Column("capability_key", sa.String(128), nullable=False),
sa.Column("capability_version", sa.Integer(), nullable=False),
sa.Column("capability_deployment_id", sa.Uuid()),
sa.Column("compute_node_id", sa.Uuid()),
sa.Column("status", sa.String(32), nullable=False),
sa.Column("priority", sa.String(32), nullable=False),
sa.Column("input_sha256", sa.String(64), nullable=False),
sa.Column("input_count", sa.Integer(), nullable=False),
sa.Column("cold", sa.Boolean()),
sa.Column("queue_time_ms", sa.Float()),
sa.Column("load_time_ms", sa.Float()),
sa.Column("inference_time_ms", sa.Float()),
sa.Column("total_latency_ms", sa.Float()),
sa.Column("failure_code", sa.String(64)),
sa.Column("decision_evidence", sa.JSON(), nullable=False),
sa.Column("created_at", sa.DateTime(timezone=True), server_default=sa.func.now()),
sa.Column("finished_at", sa.DateTime(timezone=True)),
sa.ForeignKeyConstraint(["service_client_id"], ["service_clients.id"], ondelete="SET NULL"),
sa.ForeignKeyConstraint(
["capability_deployment_id"], ["capability_deployments.id"], ondelete="SET NULL"
),
sa.ForeignKeyConstraint(["compute_node_id"], ["compute_nodes.id"], ondelete="SET NULL"),
)
for column in (
"request_id",
"service_client_id",
"capability_key",
"capability_deployment_id",
"compute_node_id",
"status",
"created_at",
):
op.create_index(f"ix_gateway_requests_{column}", "gateway_requests", [column])
op.create_index(
"ix_gateway_requests_client_created",
"gateway_requests",
["service_client_id", "created_at"],
)
op.create_table(
"serving_jobs",
*_identity_columns(),
*_timestamps(),
sa.Column("capability_deployment_id", sa.Uuid(), nullable=False),
sa.Column("compute_node_id", sa.Uuid(), nullable=False),
sa.Column("gateway_request_id", sa.Uuid()),
sa.Column("operation", sa.String(32), nullable=False),
sa.Column("status", sa.String(32), nullable=False, server_default="queued"),
sa.Column("priority", sa.String(32), nullable=False),
sa.Column("idempotency_key", sa.String(64), nullable=False),
sa.Column("payload_reference", sa.String(128)),
sa.Column("attempt_count", sa.Integer(), nullable=False, server_default="0"),
sa.Column("lease_token_hash", sa.String(64)),
sa.Column("lease_expires_at", sa.DateTime(timezone=True)),
sa.Column("result_reference", sa.String(128)),
sa.Column("result_summary", sa.JSON(), nullable=False),
sa.Column("failure_code", sa.String(64)),
sa.Column("failure_message", sa.Text()),
sa.Column("started_at", sa.DateTime(timezone=True)),
sa.Column("finished_at", sa.DateTime(timezone=True)),
sa.ForeignKeyConstraint(["capability_deployment_id"], ["capability_deployments.id"]),
sa.ForeignKeyConstraint(["compute_node_id"], ["compute_nodes.id"]),
sa.ForeignKeyConstraint(
["gateway_request_id"], ["gateway_requests.id"], ondelete="SET NULL"
),
sa.UniqueConstraint("idempotency_key", name="uq_serving_job_idempotency"),
)
for column in (
"capability_deployment_id",
"compute_node_id",
"gateway_request_id",
"operation",
"status",
):
op.create_index(f"ix_serving_jobs_{column}", "serving_jobs", [column])
op.create_index(
"ix_serving_jobs_node_status_priority",
"serving_jobs",
["compute_node_id", "status", "priority"],
)
def downgrade() -> None:
for table in (
"serving_jobs",
"gateway_requests",
"serving_gpu_leases",
"residency_allocations",
"service_credentials",
"service_clients",
"capability_resource_envelopes",
"capability_deployments",
"embedding_spaces",
"production_execution_approvals",
):
op.drop_table(table)
@@ -0,0 +1,165 @@
"""M6 project evaluation and isolated embedding migrations.
Revision ID: 20260825_0009
Revises: 20260825_0008
"""
import sqlalchemy as sa
from alembic import op
revision = "20260825_0009"
down_revision = "20260825_0008"
branch_labels = None
depends_on = None
def _id() -> sa.Column:
return sa.Column("id", sa.Uuid(), primary_key=True)
def _created() -> sa.Column:
return sa.Column("created_at", sa.DateTime(timezone=True), server_default=sa.func.now())
def upgrade() -> None:
op.add_column(
"service_clients",
sa.Column("workload_priority", sa.String(32), nullable=False, server_default="production"),
)
op.create_table(
"project_evaluation_bindings", _id(), _created(),
sa.Column("updated_at", sa.DateTime(timezone=True), server_default=sa.func.now()),
sa.Column("project_id", sa.Uuid(), nullable=False),
sa.Column("adapter_kind", sa.String(64), nullable=False),
sa.Column("endpoint", sa.Text(), nullable=False),
sa.Column("current_target", sa.String(128), nullable=False),
sa.Column("shadow_target", sa.String(128)),
sa.Column("enabled", sa.Boolean(), nullable=False, server_default=sa.true()),
sa.Column("configuration", sa.JSON(), nullable=False),
sa.ForeignKeyConstraint(["project_id"], ["projects.id"], ondelete="RESTRICT"),
sa.UniqueConstraint("project_id", name="uq_project_evaluation_binding"),
)
op.create_index("ix_project_evaluation_bindings_project_id", "project_evaluation_bindings", ["project_id"])
op.create_table(
"embedding_migrations", _id(), _created(),
sa.Column("updated_at", sa.DateTime(timezone=True), server_default=sa.func.now()),
sa.Column("project_id", sa.Uuid(), nullable=False),
sa.Column("source_embedding_space", sa.String(255), nullable=False),
sa.Column("target_embedding_space_id", sa.Uuid(), nullable=False),
sa.Column("source_index_ref", sa.Text(), nullable=False),
sa.Column("target_index_ref", sa.Text(), nullable=False, unique=True),
sa.Column("corpus_revision", sa.String(128), nullable=False),
sa.Column("status", sa.String(32), nullable=False, server_default="planned"),
sa.Column("total_chunks", sa.Integer(), nullable=False, server_default="0"),
sa.Column("completed_chunks", sa.Integer(), nullable=False, server_default="0"),
sa.Column("failed_chunks", sa.Integer(), nullable=False, server_default="0"),
sa.Column("retried_chunks", sa.Integer(), nullable=False, server_default="0"),
sa.Column("batch_size", sa.Integer(), nullable=False),
sa.Column("concurrency", sa.Integer(), nullable=False),
sa.Column("priority", sa.String(32), nullable=False, server_default="background"),
sa.Column("preflight_evidence", sa.JSON(), nullable=False),
sa.Column("progress_evidence", sa.JSON(), nullable=False),
sa.Column("validation_evidence", sa.JSON(), nullable=False),
sa.Column("operational_metrics", sa.JSON(), nullable=False),
sa.Column("evaluation_eligibility", sa.Boolean(), nullable=False, server_default=sa.false()),
sa.Column("cancel_requested", sa.Boolean(), nullable=False, server_default=sa.false()),
sa.Column("failure_code", sa.String(64)), sa.Column("failure_message", sa.Text()),
sa.Column("started_at", sa.DateTime(timezone=True)),
sa.Column("finished_at", sa.DateTime(timezone=True)),
sa.ForeignKeyConstraint(["project_id"], ["projects.id"], ondelete="RESTRICT"),
sa.ForeignKeyConstraint(["target_embedding_space_id"], ["embedding_spaces.id"], ondelete="RESTRICT"),
)
for column in ("project_id", "target_embedding_space_id", "status"):
op.create_index(f"ix_embedding_migrations_{column}", "embedding_migrations", [column])
op.create_table(
"evaluation_suites", _id(), _created(),
sa.Column("updated_at", sa.DateTime(timezone=True), server_default=sa.func.now()),
sa.Column("project_id", sa.Uuid(), nullable=False), sa.Column("key", sa.String(128), nullable=False),
sa.Column("name", sa.String(255), nullable=False), sa.Column("description", sa.Text(), nullable=False),
sa.ForeignKeyConstraint(["project_id"], ["projects.id"], ondelete="RESTRICT"),
sa.UniqueConstraint("project_id", "key", name="uq_evaluation_suite_key"),
)
op.create_index("ix_evaluation_suites_project_id", "evaluation_suites", ["project_id"])
op.create_table(
"evaluation_suite_revisions", _id(), _created(),
sa.Column("suite_id", sa.Uuid(), nullable=False), sa.Column("revision", sa.String(128), nullable=False),
sa.Column("dataset_revision", sa.String(128), nullable=False),
sa.Column("definition_digest", sa.String(64), nullable=False), sa.Column("metrics", sa.JSON(), nullable=False),
sa.Column("top_k", sa.Integer(), nullable=False), sa.Column("retrieval_settings", sa.JSON(), nullable=False),
sa.Column("thresholds", sa.JSON(), nullable=False),
sa.Column("immutable_at", sa.DateTime(timezone=True), server_default=sa.func.now()),
sa.ForeignKeyConstraint(["suite_id"], ["evaluation_suites.id"], ondelete="RESTRICT"),
sa.UniqueConstraint("suite_id", "revision", name="uq_evaluation_suite_revision"),
sa.UniqueConstraint("definition_digest", name="uq_evaluation_revision_digest"),
)
op.create_index("ix_evaluation_suite_revisions_suite_id", "evaluation_suite_revisions", ["suite_id"])
op.create_table(
"evaluation_cases", _id(), _created(),
sa.Column("suite_revision_id", sa.Uuid(), nullable=False), sa.Column("case_key", sa.String(128), nullable=False),
sa.Column("query", sa.Text(), nullable=False), sa.Column("relevant_chunk_ids", sa.JSON(), nullable=False),
sa.Column("relevant_document_ids", sa.JSON(), nullable=False), sa.Column("relevance_grades", sa.JSON(), nullable=False),
sa.Column("label_provenance", sa.JSON(), nullable=False), sa.Column("critical", sa.Boolean(), nullable=False),
sa.Column("review_status", sa.String(32), nullable=False),
sa.Column("immutable_at", sa.DateTime(timezone=True), server_default=sa.func.now()),
sa.ForeignKeyConstraint(["suite_revision_id"], ["evaluation_suite_revisions.id"], ondelete="RESTRICT"),
sa.UniqueConstraint("suite_revision_id", "case_key", name="uq_evaluation_case_key"),
)
op.create_index("ix_evaluation_cases_suite_revision_id", "evaluation_cases", ["suite_revision_id"])
op.create_index("ix_evaluation_cases_critical", "evaluation_cases", ["critical"])
op.create_table(
"evaluation_runs", _id(), _created(),
sa.Column("project_id", sa.Uuid(), nullable=False), sa.Column("suite_revision_id", sa.Uuid(), nullable=False),
sa.Column("target_kind", sa.String(32), nullable=False), sa.Column("target_index_ref", sa.Text(), nullable=False),
sa.Column("embedding_space_ref", sa.String(255), nullable=False), sa.Column("capability_deployment_id", sa.Uuid()),
sa.Column("status", sa.String(32), nullable=False), sa.Column("corpus_revision", sa.String(128), nullable=False),
sa.Column("retrieval_config_digest", sa.String(64), nullable=False),
sa.Column("environment_fingerprint", sa.JSON(), nullable=False), sa.Column("environment_digest", sa.String(64), nullable=False),
sa.Column("expected_cases", sa.Integer(), nullable=False), sa.Column("completed_cases", sa.Integer(), nullable=False),
sa.Column("error_count", sa.Integer(), nullable=False), sa.Column("aggregate_metrics", sa.JSON(), nullable=False),
sa.Column("started_at", sa.DateTime(timezone=True)), sa.Column("completed_at", sa.DateTime(timezone=True)),
sa.ForeignKeyConstraint(["project_id"], ["projects.id"], ondelete="RESTRICT"),
sa.ForeignKeyConstraint(["suite_revision_id"], ["evaluation_suite_revisions.id"], ondelete="RESTRICT"),
sa.ForeignKeyConstraint(["capability_deployment_id"], ["capability_deployments.id"], ondelete="RESTRICT"),
)
for column in ("project_id", "suite_revision_id", "capability_deployment_id", "status", "environment_digest"):
op.create_index(f"ix_evaluation_runs_{column}", "evaluation_runs", [column])
op.create_table(
"evaluation_case_results", _id(), _created(),
sa.Column("run_id", sa.Uuid(), nullable=False), sa.Column("case_id", sa.Uuid(), nullable=False),
sa.Column("ranked_results", sa.JSON(), nullable=False), sa.Column("relevant_results", sa.JSON(), nullable=False),
sa.Column("first_relevant_rank", sa.Integer()), sa.Column("metrics", sa.JSON(), nullable=False),
sa.Column("latency_ms", sa.Float(), nullable=False), sa.Column("error_code", sa.String(64)),
sa.ForeignKeyConstraint(["run_id"], ["evaluation_runs.id"], ondelete="RESTRICT"),
sa.ForeignKeyConstraint(["case_id"], ["evaluation_cases.id"], ondelete="RESTRICT"),
sa.UniqueConstraint("run_id", "case_id", name="uq_evaluation_case_result"),
)
op.create_index("ix_evaluation_case_results_run_id", "evaluation_case_results", ["run_id"])
op.create_index("ix_evaluation_case_results_case_id", "evaluation_case_results", ["case_id"])
op.create_table(
"evaluation_comparisons", _id(), _created(),
sa.Column("project_id", sa.Uuid(), nullable=False), sa.Column("baseline_run_id", sa.Uuid(), nullable=False),
sa.Column("candidate_run_id", sa.Uuid(), nullable=False), sa.Column("comparability", sa.String(32), nullable=False),
sa.Column("comparability_evidence", sa.JSON(), nullable=False), sa.Column("metric_deltas", sa.JSON(), nullable=False),
sa.Column("improved_cases", sa.Integer(), nullable=False), sa.Column("unchanged_cases", sa.Integer(), nullable=False),
sa.Column("regressed_cases", sa.Integer(), nullable=False), sa.Column("critical_regressions", sa.Integer(), nullable=False),
sa.Column("case_comparisons", sa.JSON(), nullable=False), sa.Column("promotion_eligibility", sa.String(32), nullable=False),
sa.Column("eligibility_evidence", sa.JSON(), nullable=False),
sa.ForeignKeyConstraint(["project_id"], ["projects.id"], ondelete="RESTRICT"),
sa.ForeignKeyConstraint(["baseline_run_id"], ["evaluation_runs.id"], ondelete="RESTRICT"),
sa.ForeignKeyConstraint(["candidate_run_id"], ["evaluation_runs.id"], ondelete="RESTRICT"),
sa.UniqueConstraint("baseline_run_id", "candidate_run_id", name="uq_evaluation_comparison"),
)
for column in ("project_id", "baseline_run_id", "candidate_run_id"):
op.create_index(f"ix_evaluation_comparisons_{column}", "evaluation_comparisons", [column])
def downgrade() -> None:
for table in (
"evaluation_comparisons", "evaluation_case_results", "evaluation_runs", "evaluation_cases",
"evaluation_suite_revisions", "evaluation_suites", "embedding_migrations", "project_evaluation_bindings",
):
op.drop_table(table)
op.drop_column("service_clients", "workload_priority")
@@ -0,0 +1,103 @@
"""M7 candidate capability routes and lab approval provenance.
Revision ID: 20260825_0010
Revises: 20260825_0009
"""
from collections.abc import Sequence
import sqlalchemy as sa
from alembic import op
revision: str = "20260825_0010"
down_revision: str | None = "20260825_0009"
branch_labels: str | Sequence[str] | None = None
depends_on: str | Sequence[str] | None = None
def upgrade() -> None:
op.alter_column(
"capability_deployments",
"production_approval_id",
existing_type=sa.Uuid(),
nullable=True,
)
op.add_column(
"capability_deployments",
sa.Column("execution_approval_id", sa.Uuid(), nullable=True),
)
op.create_index(
"ix_capability_deployments_execution_approval_id",
"capability_deployments",
["execution_approval_id"],
)
op.create_foreign_key(
"fk_capability_deployments_execution_approval_id",
"capability_deployments",
"execution_approvals",
["execution_approval_id"],
["id"],
ondelete="RESTRICT",
)
op.create_table(
"capability_experiment_routes",
sa.Column("route_key", sa.String(length=128), nullable=False),
sa.Column("capability_contract_id", sa.Uuid(), nullable=False),
sa.Column("capability_deployment_id", sa.Uuid(), nullable=False),
sa.Column("status", sa.String(length=32), nullable=False),
sa.Column("purpose", sa.Text(), nullable=False),
sa.Column("evidence", sa.JSON(), nullable=False),
sa.Column("id", sa.Uuid(), nullable=False),
sa.Column("created_at", sa.DateTime(timezone=True), server_default=sa.func.now(), nullable=False),
sa.Column("updated_at", sa.DateTime(timezone=True), server_default=sa.func.now(), nullable=False),
sa.ForeignKeyConstraint(
["capability_contract_id"], ["capability_contracts.id"], ondelete="RESTRICT"
),
sa.ForeignKeyConstraint(
["capability_deployment_id"], ["capability_deployments.id"], ondelete="RESTRICT"
),
sa.PrimaryKeyConstraint("id"),
sa.UniqueConstraint("route_key"),
)
op.create_index(
"ix_capability_experiment_routes_route_key",
"capability_experiment_routes",
["route_key"],
unique=True,
)
op.create_index(
"ix_capability_experiment_routes_capability_contract_id",
"capability_experiment_routes",
["capability_contract_id"],
)
op.create_index(
"ix_capability_experiment_routes_capability_deployment_id",
"capability_experiment_routes",
["capability_deployment_id"],
)
op.create_index(
"ix_capability_experiment_routes_status",
"capability_experiment_routes",
["status"],
)
def downgrade() -> None:
op.drop_table("capability_experiment_routes")
op.drop_constraint(
"fk_capability_deployments_execution_approval_id",
"capability_deployments",
type_="foreignkey",
)
op.drop_index(
"ix_capability_deployments_execution_approval_id",
table_name="capability_deployments",
)
op.drop_column("capability_deployments", "execution_approval_id")
op.alter_column(
"capability_deployments",
"production_approval_id",
existing_type=sa.Uuid(),
nullable=False,
)
@@ -0,0 +1,109 @@
"""M7 first-class model comparisons and deterministic advisor evidence.
Revision ID: 20260825_0011
Revises: 20260825_0010
"""
from collections.abc import Sequence
import sqlalchemy as sa
from alembic import op
revision: str = "20260825_0011"
down_revision: str | None = "20260825_0010"
branch_labels: str | Sequence[str] | None = None
depends_on: str | Sequence[str] | None = None
def upgrade() -> None:
op.create_table(
"model_comparisons",
sa.Column("project_id", sa.Uuid(), nullable=False),
sa.Column("capability_contract_id", sa.Uuid(), nullable=False),
sa.Column("suite_revision_id", sa.Uuid(), nullable=False),
sa.Column("current_run_id", sa.Uuid(), nullable=False),
sa.Column("title", sa.String(length=255), nullable=False),
sa.Column("candidates", sa.JSON(), nullable=False),
sa.Column("comparability", sa.String(length=32), nullable=False),
sa.Column("evidence_fingerprint", sa.String(length=64), nullable=False),
sa.Column("id", sa.Uuid(), nullable=False),
sa.Column("created_at", sa.DateTime(timezone=True), server_default=sa.func.now(), nullable=False),
sa.ForeignKeyConstraint(["project_id"], ["projects.id"], ondelete="RESTRICT"),
sa.ForeignKeyConstraint(["capability_contract_id"], ["capability_contracts.id"], ondelete="RESTRICT"),
sa.ForeignKeyConstraint(["suite_revision_id"], ["evaluation_suite_revisions.id"], ondelete="RESTRICT"),
sa.ForeignKeyConstraint(["current_run_id"], ["evaluation_runs.id"], ondelete="RESTRICT"),
sa.PrimaryKeyConstraint("id"),
sa.UniqueConstraint("evidence_fingerprint"),
)
op.create_index("ix_model_comparisons_project_id", "model_comparisons", ["project_id"])
op.create_index("ix_model_comparisons_capability_contract_id", "model_comparisons", ["capability_contract_id"])
op.create_index("ix_model_comparisons_suite_revision_id", "model_comparisons", ["suite_revision_id"])
op.create_table(
"advisor_policies",
sa.Column("key", sa.String(length=128), nullable=False),
sa.Column("required_evidence_level", sa.String(length=8), nullable=False),
sa.Column("critical_regression_hard_block", sa.Boolean(), nullable=False),
sa.Column("maximum_latency_p95_regression_ratio", sa.Float(), nullable=False),
sa.Column("minimum_metric_deltas", sa.JSON(), nullable=False),
sa.Column("require_verified_supply_chain", sa.Boolean(), nullable=False),
sa.Column("require_runtime_fit", sa.Boolean(), nullable=False),
sa.Column("rationale", sa.Text(), nullable=False),
sa.Column("version", sa.Integer(), nullable=False),
sa.Column("id", sa.Uuid(), nullable=False),
sa.Column("created_at", sa.DateTime(timezone=True), server_default=sa.func.now(), nullable=False),
sa.Column("updated_at", sa.DateTime(timezone=True), server_default=sa.func.now(), nullable=False),
sa.PrimaryKeyConstraint("id"),
sa.UniqueConstraint("key"),
)
op.create_table(
"advisor_recommendations",
sa.Column("comparison_id", sa.Uuid(), nullable=False),
sa.Column("project_id", sa.Uuid(), nullable=False),
sa.Column("capability_contract_id", sa.Uuid(), nullable=False),
sa.Column("policy_id", sa.Uuid(), nullable=False),
sa.Column("candidate_key", sa.String(length=128), nullable=False),
sa.Column("current_deployment_id", sa.Uuid(), nullable=True),
sa.Column("candidate_deployment_id", sa.Uuid(), nullable=True),
sa.Column("current_embedding_space", sa.String(length=255), nullable=False),
sa.Column("candidate_embedding_space", sa.String(length=255), nullable=True),
sa.Column("verdict", sa.String(length=64), nullable=False),
sa.Column("confidence", sa.String(length=16), nullable=False),
sa.Column("evidence_level", sa.String(length=8), nullable=False),
sa.Column("quality_deltas", sa.JSON(), nullable=False),
sa.Column("latency_deltas", sa.JSON(), nullable=False),
sa.Column("resource_deltas", sa.JSON(), nullable=False),
sa.Column("migration_impact", sa.JSON(), nullable=False),
sa.Column("security_state", sa.JSON(), nullable=False),
sa.Column("key_improvements", sa.JSON(), nullable=False),
sa.Column("blockers", sa.JSON(), nullable=False),
sa.Column("policy_snapshot", sa.JSON(), nullable=False),
sa.Column("evidence_fingerprint", sa.String(length=64), nullable=False),
sa.Column("status", sa.String(length=32), nullable=False),
sa.Column("generated_at", sa.DateTime(timezone=True), nullable=False),
sa.Column("dismissed_at", sa.DateTime(timezone=True), nullable=True),
sa.Column("dismissed_by", sa.String(length=255), nullable=True),
sa.Column("dismissal_reason", sa.Text(), nullable=True),
sa.Column("id", sa.Uuid(), nullable=False),
sa.ForeignKeyConstraint(["comparison_id"], ["model_comparisons.id"], ondelete="RESTRICT"),
sa.ForeignKeyConstraint(["project_id"], ["projects.id"], ondelete="RESTRICT"),
sa.ForeignKeyConstraint(["capability_contract_id"], ["capability_contracts.id"], ondelete="RESTRICT"),
sa.ForeignKeyConstraint(["policy_id"], ["advisor_policies.id"], ondelete="RESTRICT"),
sa.ForeignKeyConstraint(["current_deployment_id"], ["capability_deployments.id"], ondelete="RESTRICT"),
sa.ForeignKeyConstraint(["candidate_deployment_id"], ["capability_deployments.id"], ondelete="RESTRICT"),
sa.PrimaryKeyConstraint("id"),
sa.UniqueConstraint("evidence_fingerprint"),
)
op.create_index("ix_advisor_recommendations_comparison_id", "advisor_recommendations", ["comparison_id"])
op.create_index("ix_advisor_recommendations_project_id", "advisor_recommendations", ["project_id"])
op.create_index("ix_advisor_recommendations_capability_contract_id", "advisor_recommendations", ["capability_contract_id"])
op.create_index("ix_advisor_recommendations_verdict", "advisor_recommendations", ["verdict"])
op.create_index("ix_advisor_recommendations_status", "advisor_recommendations", ["status"])
def downgrade() -> None:
op.drop_table("advisor_recommendations")
op.drop_table("advisor_policies")
op.drop_table("model_comparisons")
@@ -0,0 +1,276 @@
"""M8 frozen reranking pools and retrieval pipeline evidence.
Revision ID: 20260825_0012
Revises: 20260825_0011
"""
from collections.abc import Sequence
import sqlalchemy as sa
from alembic import op
revision: str = "20260825_0012"
down_revision: str | None = "20260825_0011"
branch_labels: str | Sequence[str] | None = None
depends_on: str | Sequence[str] | None = None
def upgrade() -> None:
op.alter_column("capability_deployments", "embedding_space_id", nullable=True)
op.create_table(
"retrieval_candidate_pools",
sa.Column("project_id", sa.Uuid(), nullable=False),
sa.Column("suite_revision_id", sa.Uuid(), nullable=False),
sa.Column("evaluation_case_id", sa.Uuid(), nullable=False),
sa.Column("source_embedding_space", sa.String(length=255), nullable=False),
sa.Column("source_index_ref", sa.Text(), nullable=False),
sa.Column("corpus_revision", sa.String(length=128), nullable=False),
sa.Column("retrieval_config_digest", sa.String(length=64), nullable=False),
sa.Column("candidate_count", sa.Integer(), nullable=False),
sa.Column("ordered_candidates", sa.JSON(), nullable=False),
sa.Column("fingerprint", sa.String(length=64), nullable=False),
sa.Column("id", sa.Uuid(), nullable=False),
sa.Column(
"created_at", sa.DateTime(timezone=True), server_default=sa.func.now(), nullable=False
),
sa.Column(
"immutable_at", sa.DateTime(timezone=True), server_default=sa.func.now(), nullable=False
),
sa.CheckConstraint(
"candidate_count > 0 AND candidate_count <= 40", name="ck_candidate_pool_count"
),
sa.ForeignKeyConstraint(["project_id"], ["projects.id"], ondelete="RESTRICT"),
sa.ForeignKeyConstraint(
["suite_revision_id"], ["evaluation_suite_revisions.id"], ondelete="RESTRICT"
),
sa.ForeignKeyConstraint(
["evaluation_case_id"], ["evaluation_cases.id"], ondelete="RESTRICT"
),
sa.PrimaryKeyConstraint("id"),
sa.UniqueConstraint(
"evaluation_case_id",
"source_index_ref",
"fingerprint",
name="uq_candidate_pool_case_source_fingerprint",
),
sa.UniqueConstraint("fingerprint"),
)
op.create_index("ix_candidate_pools_project_id", "retrieval_candidate_pools", ["project_id"])
op.create_index(
"ix_candidate_pools_suite_revision_id", "retrieval_candidate_pools", ["suite_revision_id"]
)
op.create_index(
"ix_candidate_pools_evaluation_case_id", "retrieval_candidate_pools", ["evaluation_case_id"]
)
op.create_table(
"retrieval_pipeline_identities",
sa.Column("project_id", sa.Uuid(), nullable=False),
sa.Column("embedding_space_ref", sa.String(length=255), nullable=False),
sa.Column("sparse_config_digest", sa.String(length=64), nullable=False),
sa.Column("fusion_config_digest", sa.String(length=64), nullable=False),
sa.Column("reranker_deployment_id", sa.Uuid(), nullable=True),
sa.Column("reranker_config_digest", sa.String(length=64), nullable=True),
sa.Column("candidate_k", sa.Integer(), nullable=False),
sa.Column("output_k", sa.Integer(), nullable=False),
sa.Column("identity_digest", sa.String(length=64), nullable=False),
sa.Column("configuration", sa.JSON(), nullable=False),
sa.Column("migration_class", sa.String(length=32), nullable=False),
sa.Column("id", sa.Uuid(), nullable=False),
sa.Column(
"created_at", sa.DateTime(timezone=True), server_default=sa.func.now(), nullable=False
),
sa.Column(
"immutable_at", sa.DateTime(timezone=True), server_default=sa.func.now(), nullable=False
),
sa.CheckConstraint("candidate_k > 0 AND candidate_k <= 40", name="ck_pipeline_candidate_k"),
sa.CheckConstraint("output_k > 0 AND output_k <= candidate_k", name="ck_pipeline_output_k"),
sa.ForeignKeyConstraint(["project_id"], ["projects.id"], ondelete="RESTRICT"),
sa.ForeignKeyConstraint(
["reranker_deployment_id"], ["capability_deployments.id"], ondelete="RESTRICT"
),
sa.PrimaryKeyConstraint("id"),
sa.UniqueConstraint("identity_digest"),
)
op.create_index(
"ix_pipeline_identities_project_id", "retrieval_pipeline_identities", ["project_id"]
)
op.create_index(
"ix_pipeline_identities_reranker_deployment_id",
"retrieval_pipeline_identities",
["reranker_deployment_id"],
)
op.create_table(
"reranking_evaluation_runs",
sa.Column("project_id", sa.Uuid(), nullable=False),
sa.Column("suite_revision_id", sa.Uuid(), nullable=False),
sa.Column("pipeline_identity_id", sa.Uuid(), nullable=False),
sa.Column("control_pipeline_identity_id", sa.Uuid(), nullable=False),
sa.Column("reranker_deployment_id", sa.Uuid(), nullable=True),
sa.Column("status", sa.String(length=32), nullable=False),
sa.Column("corpus_revision", sa.String(length=128), nullable=False),
sa.Column("candidate_pool_set_fingerprint", sa.String(length=64), nullable=False),
sa.Column("environment_fingerprint", sa.JSON(), nullable=False),
sa.Column("environment_digest", sa.String(length=64), nullable=False),
sa.Column("expected_cases", sa.Integer(), nullable=False),
sa.Column("completed_cases", sa.Integer(), nullable=False),
sa.Column("error_count", sa.Integer(), nullable=False),
sa.Column("aggregate_metrics", sa.JSON(), nullable=False),
sa.Column("latency_metrics", sa.JSON(), nullable=False),
sa.Column("started_at", sa.DateTime(timezone=True), nullable=True),
sa.Column("completed_at", sa.DateTime(timezone=True), nullable=True),
sa.Column("id", sa.Uuid(), nullable=False),
sa.Column(
"created_at", sa.DateTime(timezone=True), server_default=sa.func.now(), nullable=False
),
sa.ForeignKeyConstraint(["project_id"], ["projects.id"], ondelete="RESTRICT"),
sa.ForeignKeyConstraint(
["suite_revision_id"], ["evaluation_suite_revisions.id"], ondelete="RESTRICT"
),
sa.ForeignKeyConstraint(
["pipeline_identity_id"], ["retrieval_pipeline_identities.id"], ondelete="RESTRICT"
),
sa.ForeignKeyConstraint(
["control_pipeline_identity_id"],
["retrieval_pipeline_identities.id"],
ondelete="RESTRICT",
),
sa.ForeignKeyConstraint(
["reranker_deployment_id"], ["capability_deployments.id"], ondelete="RESTRICT"
),
sa.PrimaryKeyConstraint("id"),
)
op.create_index("ix_reranking_runs_project_id", "reranking_evaluation_runs", ["project_id"])
op.create_index(
"ix_reranking_runs_suite_revision_id", "reranking_evaluation_runs", ["suite_revision_id"]
)
op.create_index(
"ix_reranking_runs_pipeline_identity_id",
"reranking_evaluation_runs",
["pipeline_identity_id"],
)
op.create_index("ix_reranking_runs_status", "reranking_evaluation_runs", ["status"])
op.create_table(
"reranking_case_results",
sa.Column("run_id", sa.Uuid(), nullable=False),
sa.Column("case_id", sa.Uuid(), nullable=False),
sa.Column("candidate_pool_id", sa.Uuid(), nullable=False),
sa.Column("ranked_results", sa.JSON(), nullable=False),
sa.Column("relevant_results", sa.JSON(), nullable=False),
sa.Column("first_relevant_rank", sa.Integer(), nullable=True),
sa.Column("metrics", sa.JSON(), nullable=False),
sa.Column("retrieval_latency_ms", sa.Float(), nullable=False),
sa.Column("rerank_latency_ms", sa.Float(), nullable=False),
sa.Column("total_latency_ms", sa.Float(), nullable=False),
sa.Column("error_code", sa.String(length=64), nullable=True),
sa.Column("id", sa.Uuid(), nullable=False),
sa.Column(
"created_at", sa.DateTime(timezone=True), server_default=sa.func.now(), nullable=False
),
sa.ForeignKeyConstraint(["run_id"], ["reranking_evaluation_runs.id"], ondelete="RESTRICT"),
sa.ForeignKeyConstraint(["case_id"], ["evaluation_cases.id"], ondelete="RESTRICT"),
sa.ForeignKeyConstraint(
["candidate_pool_id"], ["retrieval_candidate_pools.id"], ondelete="RESTRICT"
),
sa.PrimaryKeyConstraint("id"),
sa.UniqueConstraint("run_id", "case_id", name="uq_reranking_case_result"),
)
op.create_index("ix_reranking_case_results_run_id", "reranking_case_results", ["run_id"])
op.create_index("ix_reranking_case_results_case_id", "reranking_case_results", ["case_id"])
op.create_index(
"ix_reranking_case_results_pool_id", "reranking_case_results", ["candidate_pool_id"]
)
op.create_table(
"discovery_candidate_assessments",
sa.Column("model_id", sa.Uuid(), nullable=True),
sa.Column("upstream_snapshot_id", sa.Uuid(), nullable=False),
sa.Column("candidate_key", sa.String(length=128), nullable=False),
sa.Column("repository_id", sa.String(length=255), nullable=False),
sa.Column("resolved_commit_sha", sa.String(length=64), nullable=False),
sa.Column("artifact_evidence", sa.JSON(), nullable=False),
sa.Column("security_state", sa.JSON(), nullable=False),
sa.Column("license_state", sa.JSON(), nullable=False),
sa.Column("gpu_fit", sa.JSON(), nullable=False),
sa.Column("status", sa.String(length=32), nullable=False),
sa.Column("rationale", sa.Text(), nullable=False),
sa.Column("evidence_fingerprint", sa.String(length=64), nullable=False),
sa.Column("id", sa.Uuid(), nullable=False),
sa.Column(
"created_at", sa.DateTime(timezone=True), server_default=sa.func.now(), nullable=False
),
sa.Column(
"immutable_at", sa.DateTime(timezone=True), server_default=sa.func.now(), nullable=False
),
sa.ForeignKeyConstraint(["model_id"], ["models.id"], ondelete="RESTRICT"),
sa.ForeignKeyConstraint(
["upstream_snapshot_id"], ["upstream_snapshots.id"], ondelete="RESTRICT"
),
sa.PrimaryKeyConstraint("id"),
sa.UniqueConstraint("evidence_fingerprint"),
)
op.create_index(
"ix_discovery_assessments_model_id", "discovery_candidate_assessments", ["model_id"]
)
op.create_index(
"ix_discovery_assessments_snapshot_id",
"discovery_candidate_assessments",
["upstream_snapshot_id"],
)
op.create_index(
"ix_discovery_assessments_status", "discovery_candidate_assessments", ["status"]
)
op.add_column(
"advisor_recommendations",
sa.Column(
"target_kind",
sa.String(length=32),
nullable=False,
server_default="embedding_deployment",
),
)
op.add_column(
"advisor_recommendations",
sa.Column("current_pipeline_identity_id", sa.Uuid(), nullable=True),
)
op.add_column(
"advisor_recommendations",
sa.Column("candidate_pipeline_identity_id", sa.Uuid(), nullable=True),
)
op.create_foreign_key(
"fk_advisor_current_pipeline",
"advisor_recommendations",
"retrieval_pipeline_identities",
["current_pipeline_identity_id"],
["id"],
ondelete="RESTRICT",
)
op.create_foreign_key(
"fk_advisor_candidate_pipeline",
"advisor_recommendations",
"retrieval_pipeline_identities",
["candidate_pipeline_identity_id"],
["id"],
ondelete="RESTRICT",
)
def downgrade() -> None:
op.drop_constraint(
"fk_advisor_candidate_pipeline", "advisor_recommendations", type_="foreignkey"
)
op.drop_constraint("fk_advisor_current_pipeline", "advisor_recommendations", type_="foreignkey")
op.drop_column("advisor_recommendations", "candidate_pipeline_identity_id")
op.drop_column("advisor_recommendations", "current_pipeline_identity_id")
op.drop_column("advisor_recommendations", "target_kind")
op.drop_table("discovery_candidate_assessments")
op.drop_table("reranking_case_results")
op.drop_table("reranking_evaluation_runs")
op.drop_table("retrieval_pipeline_identities")
op.drop_table("retrieval_candidate_pools")
op.alter_column("capability_deployments", "embedding_space_id", nullable=False)
@@ -0,0 +1,74 @@
"""M9 multimodal capability evaluation foundation.
Revision ID: 20260826_0013
Revises: 20260825_0012
"""
from collections.abc import Sequence
import sqlalchemy as sa
from alembic import op
revision: str = "20260826_0013"
down_revision: str | None = "20260825_0012"
branch_labels: str | Sequence[str] | None = None
depends_on: str | Sequence[str] | None = None
def upgrade() -> None:
op.create_table(
"capability_evaluation_suites",
sa.Column("id", sa.Uuid(), nullable=False),
sa.Column("capability_contract_id", sa.Uuid(), nullable=False),
sa.Column("key", sa.String(length=128), nullable=False),
sa.Column("evaluation_type", sa.String(length=32), nullable=False),
sa.Column("revision", sa.String(length=128), nullable=False),
sa.Column("dataset_revision", sa.String(length=128), nullable=False),
sa.Column("definition_digest", sa.String(length=64), nullable=False),
sa.Column("metric_definitions", sa.JSON(), nullable=False),
sa.Column("case_definitions", sa.JSON(), nullable=False),
sa.Column("thresholds", sa.JSON(), nullable=False),
sa.Column("created_at", sa.DateTime(timezone=True), server_default=sa.func.now(), nullable=False),
sa.Column("immutable_at", sa.DateTime(timezone=True), server_default=sa.func.now(), nullable=False),
sa.ForeignKeyConstraint(["capability_contract_id"], ["capability_contracts.id"], ondelete="RESTRICT"),
sa.PrimaryKeyConstraint("id"),
sa.UniqueConstraint("capability_contract_id", "key", "revision", name="uq_capability_eval_suite"),
sa.UniqueConstraint("definition_digest", name="uq_capability_eval_definition_digest"),
)
op.create_index("ix_capability_evaluation_suites_capability_contract_id", "capability_evaluation_suites", ["capability_contract_id"])
op.create_index("ix_capability_evaluation_suites_evaluation_type", "capability_evaluation_suites", ["evaluation_type"])
op.create_table(
"capability_evaluation_runs",
sa.Column("id", sa.Uuid(), nullable=False),
sa.Column("suite_id", sa.Uuid(), nullable=False),
sa.Column("capability_deployment_id", sa.Uuid(), nullable=False),
sa.Column("status", sa.String(length=32), nullable=False),
sa.Column("metric_values", sa.JSON(), nullable=False),
sa.Column("case_results", sa.JSON(), nullable=False),
sa.Column("resource_metrics", sa.JSON(), nullable=False),
sa.Column("environment_fingerprint", sa.String(length=64), nullable=False),
sa.Column("evidence_digest", sa.String(length=64), nullable=False),
sa.Column("evidence", sa.JSON(), nullable=False),
sa.Column("started_at", sa.DateTime(timezone=True), nullable=False),
sa.Column("completed_at", sa.DateTime(timezone=True), nullable=False),
sa.Column("created_at", sa.DateTime(timezone=True), server_default=sa.func.now(), nullable=False),
sa.ForeignKeyConstraint(["suite_id"], ["capability_evaluation_suites.id"], ondelete="RESTRICT"),
sa.ForeignKeyConstraint(["capability_deployment_id"], ["capability_deployments.id"], ondelete="RESTRICT"),
sa.PrimaryKeyConstraint("id"),
sa.UniqueConstraint("evidence_digest", name="uq_capability_eval_evidence"),
)
op.create_index("ix_capability_evaluation_runs_suite_id", "capability_evaluation_runs", ["suite_id"])
op.create_index("ix_capability_evaluation_runs_capability_deployment_id", "capability_evaluation_runs", ["capability_deployment_id"])
op.create_index("ix_capability_evaluation_runs_status", "capability_evaluation_runs", ["status"])
op.create_index("ix_capability_evaluation_runs_environment_fingerprint", "capability_evaluation_runs", ["environment_fingerprint"])
def downgrade() -> None:
op.drop_index("ix_capability_evaluation_runs_environment_fingerprint", table_name="capability_evaluation_runs")
op.drop_index("ix_capability_evaluation_runs_status", table_name="capability_evaluation_runs")
op.drop_index("ix_capability_evaluation_runs_capability_deployment_id", table_name="capability_evaluation_runs")
op.drop_index("ix_capability_evaluation_runs_suite_id", table_name="capability_evaluation_runs")
op.drop_table("capability_evaluation_runs")
op.drop_index("ix_capability_evaluation_suites_evaluation_type", table_name="capability_evaluation_suites")
op.drop_index("ix_capability_evaluation_suites_capability_contract_id", table_name="capability_evaluation_suites")
op.drop_table("capability_evaluation_suites")
@@ -0,0 +1,232 @@
"""M10 advanced residency and GPU scheduling.
Revision ID: 20260826_0014
Revises: 20260826_0013
"""
import uuid
from collections.abc import Sequence
import sqlalchemy as sa
from alembic import op
revision: str = "20260826_0014"
down_revision: str | None = "20260826_0013"
branch_labels: str | Sequence[str] | None = None
depends_on: str | Sequence[str] | None = None
def upgrade() -> None:
op.add_column(
"residency_allocations",
sa.Column("generation", sa.Integer(), server_default="1", nullable=False),
)
op.add_column(
"residency_allocations", sa.Column("transition_reason", sa.String(length=64), nullable=True)
)
op.add_column(
"serving_gpu_leases",
sa.Column(
"lease_type", sa.String(length=32), server_default="request_execution", nullable=False
),
)
op.add_column(
"serving_gpu_leases",
sa.Column("materialized_vram_bytes", sa.BigInteger(), server_default="0", nullable=False),
)
op.add_column("serving_gpu_leases", sa.Column("serving_job_id", sa.Uuid(), nullable=True))
op.add_column(
"serving_gpu_leases",
sa.Column("generation", sa.Integer(), server_default="1", nullable=False),
)
op.create_foreign_key(
"fk_serving_gpu_leases_job",
"serving_gpu_leases",
"serving_jobs",
["serving_job_id"],
["id"],
ondelete="SET NULL",
)
op.create_index(
"ix_serving_gpu_leases_serving_job_id", "serving_gpu_leases", ["serving_job_id"]
)
op.create_table(
"scheduler_policy_revisions",
sa.Column("id", sa.Uuid(), nullable=False),
sa.Column("revision", sa.String(length=64), nullable=False),
sa.Column("configuration", sa.JSON(), nullable=False),
sa.Column("active", sa.Boolean(), nullable=False),
sa.Column(
"created_at", sa.DateTime(timezone=True), server_default=sa.func.now(), nullable=False
),
sa.PrimaryKeyConstraint("id"),
sa.UniqueConstraint("revision"),
)
op.create_index(
"ix_scheduler_policy_revisions_active", "scheduler_policy_revisions", ["active"]
)
policy_table = sa.table(
"scheduler_policy_revisions",
sa.column("id", sa.Uuid()),
sa.column("revision", sa.String()),
sa.column("configuration", sa.JSON()),
sa.column("active", sa.Boolean()),
)
op.bulk_insert(
policy_table,
[
{
"id": uuid.UUID("8e4d6a85-8e50-4f78-a182-000000000010"),
"revision": "m10-v1",
"configuration": {
"reserve_minimum_bytes": 1073741824,
"reserve_percentage": 0.05,
"runtime_margin_bytes": 268435456,
"deployment_margin_minimum_bytes": 134217728,
"deployment_margin_percentage": 0.1,
"request_execution_floor_bytes": 67108864,
"pressure_stable_seconds": 30,
"eviction_cooldown_seconds": 60,
"global_queue_limit": 128,
"placement_history_limit": 500,
"lab_paused": False,
},
"active": True,
}
],
)
op.create_table(
"scheduler_accelerator_states",
sa.Column("id", sa.Uuid(), nullable=False),
sa.Column("accelerator_id", sa.Uuid(), nullable=False),
sa.Column("pressure_state", sa.String(length=16), nullable=False),
sa.Column("recovery_candidate", sa.String(length=16), nullable=True),
sa.Column("recovery_candidate_since", sa.DateTime(timezone=True), nullable=True),
sa.Column(
"pressure_changed_at",
sa.DateTime(timezone=True),
server_default=sa.func.now(),
nullable=False,
),
sa.Column(
"last_observed_at",
sa.DateTime(timezone=True),
server_default=sa.func.now(),
nullable=False,
),
sa.ForeignKeyConstraint(["accelerator_id"], ["accelerators.id"], ondelete="CASCADE"),
sa.PrimaryKeyConstraint("id"),
sa.UniqueConstraint("accelerator_id"),
)
op.create_index(
"ix_scheduler_accelerator_states_accelerator_id",
"scheduler_accelerator_states",
["accelerator_id"],
unique=True,
)
op.create_table(
"placement_plans",
sa.Column("id", sa.Uuid(), nullable=False),
sa.Column("capability_deployment_id", sa.Uuid(), nullable=False),
sa.Column("request_id", sa.Uuid(), nullable=True),
sa.Column("policy_revision", sa.String(length=64), nullable=False),
sa.Column("verdict", sa.String(length=32), nullable=False),
sa.Column("reason_codes", sa.JSON(), nullable=False),
sa.Column("evidence", sa.JSON(), nullable=False),
sa.Column("evidence_fingerprint", sa.String(length=64), nullable=False),
sa.Column("dry_run", sa.Boolean(), nullable=False),
sa.Column(
"created_at", sa.DateTime(timezone=True), server_default=sa.func.now(), nullable=False
),
sa.ForeignKeyConstraint(
["capability_deployment_id"], ["capability_deployments.id"], ondelete="RESTRICT"
),
sa.PrimaryKeyConstraint("id"),
)
for column in (
"capability_deployment_id",
"request_id",
"verdict",
"evidence_fingerprint",
"created_at",
):
op.create_index(f"ix_placement_plans_{column}", "placement_plans", [column])
op.create_table(
"co_residency_evidence",
sa.Column("id", sa.Uuid(), nullable=False),
sa.Column("left_deployment_id", sa.Uuid(), nullable=False),
sa.Column("right_deployment_id", sa.Uuid(), nullable=False),
sa.Column("status", sa.String(length=32), nullable=False),
sa.Column("expected_combined_bytes", sa.BigInteger(), nullable=False),
sa.Column("measured_combined_bytes", sa.BigInteger(), nullable=True),
sa.Column("evidence", sa.JSON(), nullable=False),
sa.Column("measured_at", sa.DateTime(timezone=True), nullable=True),
sa.Column(
"created_at", sa.DateTime(timezone=True), server_default=sa.func.now(), nullable=False
),
sa.ForeignKeyConstraint(
["left_deployment_id"], ["capability_deployments.id"], ondelete="RESTRICT"
),
sa.ForeignKeyConstraint(
["right_deployment_id"], ["capability_deployments.id"], ondelete="RESTRICT"
),
sa.PrimaryKeyConstraint("id"),
sa.UniqueConstraint(
"left_deployment_id", "right_deployment_id", name="uq_co_residency_pair"
),
)
for column in ("left_deployment_id", "right_deployment_id", "status"):
op.create_index(f"ix_co_residency_evidence_{column}", "co_residency_evidence", [column])
op.create_table(
"scheduler_evictions",
sa.Column("id", sa.Uuid(), nullable=False),
sa.Column("capability_deployment_id", sa.Uuid(), nullable=False),
sa.Column("requested_deployment_id", sa.Uuid(), nullable=True),
sa.Column("reason_code", sa.String(length=64), nullable=False),
sa.Column("expected_reclaimed_bytes", sa.BigInteger(), nullable=False),
sa.Column("actual_reclaimed_bytes", sa.BigInteger(), nullable=True),
sa.Column("state", sa.String(length=32), nullable=False),
sa.Column(
"created_at", sa.DateTime(timezone=True), server_default=sa.func.now(), nullable=False
),
sa.ForeignKeyConstraint(
["capability_deployment_id"], ["capability_deployments.id"], ondelete="RESTRICT"
),
sa.ForeignKeyConstraint(
["requested_deployment_id"], ["capability_deployments.id"], ondelete="SET NULL"
),
sa.PrimaryKeyConstraint("id"),
)
for column in ("capability_deployment_id", "requested_deployment_id", "reason_code"):
op.create_index(f"ix_scheduler_evictions_{column}", "scheduler_evictions", [column])
def downgrade() -> None:
op.drop_index(
"ix_scheduler_accelerator_states_accelerator_id", table_name="scheduler_accelerator_states"
)
op.drop_table("scheduler_accelerator_states")
for column in ("capability_deployment_id", "requested_deployment_id", "reason_code"):
op.drop_index(f"ix_scheduler_evictions_{column}", table_name="scheduler_evictions")
op.drop_table("scheduler_evictions")
for column in ("left_deployment_id", "right_deployment_id", "status"):
op.drop_index(f"ix_co_residency_evidence_{column}", table_name="co_residency_evidence")
op.drop_table("co_residency_evidence")
for column in (
"capability_deployment_id",
"request_id",
"verdict",
"evidence_fingerprint",
"created_at",
):
op.drop_index(f"ix_placement_plans_{column}", table_name="placement_plans")
op.drop_table("placement_plans")
op.drop_index("ix_scheduler_policy_revisions_active", table_name="scheduler_policy_revisions")
op.drop_table("scheduler_policy_revisions")
op.drop_index("ix_serving_gpu_leases_serving_job_id", table_name="serving_gpu_leases")
op.drop_constraint("fk_serving_gpu_leases_job", "serving_gpu_leases", type_="foreignkey")
for column in ("generation", "serving_job_id", "materialized_vram_bytes", "lease_type"):
op.drop_column("serving_gpu_leases", column)
op.drop_column("residency_allocations", "transition_reason")
op.drop_column("residency_allocations", "generation")
@@ -0,0 +1,54 @@
"""Use 64-bit scheduler and worker generations.
Revision ID: 20260826_0015
Revises: 20260826_0014
"""
from collections.abc import Sequence
import sqlalchemy as sa
from alembic import op
revision: str = "20260826_0015"
down_revision: str | None = "20260826_0014"
branch_labels: str | Sequence[str] | None = None
depends_on: str | Sequence[str] | None = None
def upgrade() -> None:
op.alter_column(
"residency_allocations",
"generation",
existing_type=sa.Integer(),
type_=sa.BigInteger(),
existing_nullable=False,
existing_server_default="1",
)
op.alter_column(
"serving_gpu_leases",
"generation",
existing_type=sa.Integer(),
type_=sa.BigInteger(),
existing_nullable=False,
existing_server_default="1",
)
def downgrade() -> None:
op.alter_column(
"serving_gpu_leases",
"generation",
existing_type=sa.BigInteger(),
type_=sa.Integer(),
existing_nullable=False,
existing_server_default="1",
)
op.alter_column(
"residency_allocations",
"generation",
existing_type=sa.BigInteger(),
type_=sa.Integer(),
existing_nullable=False,
existing_server_default="1",
)
@@ -0,0 +1,65 @@
"""Add operational project integrations and project-fit evidence.
Revision ID: 20260826_0016
Revises: 20260826_0015
"""
from collections.abc import Sequence
import sqlalchemy as sa
from alembic import op
revision: str = "20260826_0016"
down_revision: str | None = "20260826_0015"
branch_labels: str | Sequence[str] | None = None
depends_on: str | Sequence[str] | None = None
def upgrade() -> None:
op.add_column("service_clients", sa.Column("project_binding_id", sa.Uuid(), nullable=True))
op.add_column("service_clients", sa.Column("integration_environment", sa.String(32), nullable=True))
op.add_column("service_clients", sa.Column("purpose", sa.Text(), nullable=True))
op.create_foreign_key(
"fk_service_clients_project_binding",
"service_clients",
"project_bindings",
["project_binding_id"],
["id"],
ondelete="RESTRICT",
)
op.create_index("ix_service_clients_project_binding_id", "service_clients", ["project_binding_id"])
op.create_table(
"project_fit_evidence",
sa.Column("id", sa.Uuid(), nullable=False),
sa.Column("project_binding_id", sa.Uuid(), nullable=False),
sa.Column("capability_deployment_id", sa.Uuid(), nullable=True),
sa.Column("environment", sa.String(32), nullable=False),
sa.Column("recommendation", sa.String(32), nullable=False),
sa.Column("case_count", sa.Integer(), nullable=False),
sa.Column("metric_values", sa.JSON(), nullable=False),
sa.Column("critical_errors", sa.Integer(), nullable=False),
sa.Column("blockers", sa.JSON(), nullable=False),
sa.Column("evidence_digest", sa.String(64), nullable=False),
sa.Column("evidence", sa.JSON(), nullable=False),
sa.Column("created_at", sa.DateTime(timezone=True), server_default=sa.func.now(), nullable=False),
sa.ForeignKeyConstraint(["project_binding_id"], ["project_bindings.id"], ondelete="RESTRICT"),
sa.ForeignKeyConstraint(["capability_deployment_id"], ["capability_deployments.id"], ondelete="SET NULL"),
sa.PrimaryKeyConstraint("id"),
sa.UniqueConstraint("evidence_digest", name="uq_project_fit_evidence_digest"),
)
op.create_index("ix_project_fit_evidence_project_binding_id", "project_fit_evidence", ["project_binding_id"])
op.create_index("ix_project_fit_evidence_capability_deployment_id", "project_fit_evidence", ["capability_deployment_id"])
op.create_index("ix_project_fit_evidence_recommendation", "project_fit_evidence", ["recommendation"])
def downgrade() -> None:
op.drop_index("ix_project_fit_evidence_recommendation", table_name="project_fit_evidence")
op.drop_index("ix_project_fit_evidence_capability_deployment_id", table_name="project_fit_evidence")
op.drop_index("ix_project_fit_evidence_project_binding_id", table_name="project_fit_evidence")
op.drop_table("project_fit_evidence")
op.drop_index("ix_service_clients_project_binding_id", table_name="service_clients")
op.drop_constraint("fk_service_clients_project_binding", "service_clients", type_="foreignkey")
op.drop_column("service_clients", "purpose")
op.drop_column("service_clients", "integration_environment")
op.drop_column("service_clients", "project_binding_id")
@@ -0,0 +1,82 @@
"""Classify project-fit evidence and defer production validation explicitly.
Revision ID: 20260826_0017
Revises: 20260826_0016
"""
from collections.abc import Sequence
import sqlalchemy as sa
from alembic import op
revision: str = "20260826_0017"
down_revision: str | None = "20260826_0016"
branch_labels: str | Sequence[str] | None = None
depends_on: str | Sequence[str] | None = None
def upgrade() -> None:
op.add_column(
"project_fit_evidence",
sa.Column(
"evidence_class",
sa.String(32),
server_default="CATALOG_REFERENCE",
nullable=False,
),
)
op.add_column(
"project_fit_evidence",
sa.Column(
"engineering_integration",
sa.String(32),
server_default="PASS",
nullable=False,
),
)
op.add_column(
"project_fit_evidence",
sa.Column(
"production_validation",
sa.String(48),
server_default="DEFERRED_EXTERNAL_VALIDATION",
nullable=False,
),
)
op.add_column(
"project_fit_evidence",
sa.Column("production_action", sa.String(32), server_default="NONE", nullable=False),
)
op.create_index(
"ix_project_fit_evidence_evidence_class",
"project_fit_evidence",
["evidence_class"],
)
op.create_index(
"ix_project_fit_evidence_production_validation",
"project_fit_evidence",
["production_validation"],
)
for column in (
"evidence_class",
"engineering_integration",
"production_validation",
"production_action",
):
op.alter_column("project_fit_evidence", column, server_default=None)
def downgrade() -> None:
op.drop_index(
"ix_project_fit_evidence_production_validation",
table_name="project_fit_evidence",
)
op.drop_index(
"ix_project_fit_evidence_evidence_class",
table_name="project_fit_evidence",
)
op.drop_column("project_fit_evidence", "production_action")
op.drop_column("project_fit_evidence", "production_validation")
op.drop_column("project_fit_evidence", "engineering_integration")
op.drop_column("project_fit_evidence", "evidence_class")
@@ -0,0 +1,431 @@
"""Add evidence-bound lifecycle, rollback, retention and cleanup records.
Revision ID: 20260826_0018
Revises: 20260826_0017
"""
from collections.abc import Sequence
import sqlalchemy as sa
from alembic import op
revision: str = "20260826_0018"
down_revision: str | None = "20260826_0017"
branch_labels: str | Sequence[str] | None = None
depends_on: str | Sequence[str] | None = None
def id_column() -> sa.Column[object]:
return sa.Column("id", sa.Uuid(), primary_key=True, nullable=False)
def created_at() -> sa.Column[object]:
return sa.Column("created_at", sa.DateTime(timezone=True), server_default=sa.func.now())
def upgrade() -> None:
op.create_table(
"lifecycle_subjects",
id_column(),
sa.Column("target_type", sa.String(48), nullable=False),
sa.Column("target_ref", sa.String(255), nullable=False),
sa.Column("environment", sa.String(32), nullable=False),
sa.Column("state", sa.String(48), nullable=False),
sa.Column("version", sa.Integer(), nullable=False, server_default="1"),
sa.Column("superseded_by_ref", sa.String(255)),
created_at(),
sa.Column("updated_at", sa.DateTime(timezone=True), server_default=sa.func.now()),
sa.CheckConstraint("version >= 1", name="ck_lifecycle_subject_version"),
sa.UniqueConstraint(
"target_type", "target_ref", "environment", name="uq_lifecycle_subject_target"
),
)
op.create_index("ix_lifecycle_subjects_target_type", "lifecycle_subjects", ["target_type"])
op.create_index("ix_lifecycle_subjects_target_ref", "lifecycle_subjects", ["target_ref"])
op.create_index("ix_lifecycle_subjects_environment", "lifecycle_subjects", ["environment"])
op.create_index("ix_lifecycle_subjects_state", "lifecycle_subjects", ["state"])
op.create_table(
"lifecycle_policy_revisions",
id_column(),
sa.Column("key", sa.String(128), nullable=False),
sa.Column("revision", sa.Integer(), nullable=False),
sa.Column("scope", sa.String(48), nullable=False),
sa.Column("requirements", sa.JSON(), nullable=False),
sa.Column("fingerprint", sa.String(64), nullable=False),
sa.Column("active", sa.Boolean(), nullable=False, server_default=sa.true()),
sa.Column("created_by", sa.String(255), nullable=False),
created_at(),
sa.Column("immutable_at", sa.DateTime(timezone=True), server_default=sa.func.now()),
sa.UniqueConstraint("key", "revision", name="uq_lifecycle_policy_revision"),
sa.UniqueConstraint("fingerprint", name="uq_lifecycle_policy_fingerprint"),
)
op.create_index("ix_lifecycle_policy_revisions_key", "lifecycle_policy_revisions", ["key"])
op.create_index("ix_lifecycle_policy_revisions_scope", "lifecycle_policy_revisions", ["scope"])
op.create_index(
"ix_lifecycle_policy_revisions_active", "lifecycle_policy_revisions", ["active"]
)
op.create_table(
"lifecycle_approval_requests",
id_column(),
sa.Column(
"policy_revision_id",
sa.Uuid(),
sa.ForeignKey("lifecycle_policy_revisions.id", ondelete="RESTRICT"),
nullable=False,
),
sa.Column(
"subject_id",
sa.Uuid(),
sa.ForeignKey("lifecycle_subjects.id", ondelete="RESTRICT"),
),
sa.Column("target_type", sa.String(48), nullable=False),
sa.Column("target_ref", sa.String(255), nullable=False),
sa.Column("environment", sa.String(32), nullable=False),
sa.Column("requested_transition", sa.String(48), nullable=False),
sa.Column("evidence_snapshot", sa.JSON(), nullable=False),
sa.Column("evidence_fingerprint", sa.String(64), nullable=False),
sa.Column("status", sa.String(32), nullable=False),
sa.Column("blockers", sa.JSON(), nullable=False),
sa.Column("warnings", sa.JSON(), nullable=False),
sa.Column("requested_by", sa.String(255), nullable=False),
sa.Column("approved_by", sa.String(255)),
sa.Column("reason", sa.Text(), nullable=False),
sa.Column("expires_at", sa.DateTime(timezone=True)),
sa.Column("stale_at", sa.DateTime(timezone=True)),
sa.Column("decided_at", sa.DateTime(timezone=True)),
sa.Column("version", sa.Integer(), nullable=False, server_default="1"),
created_at(),
sa.CheckConstraint("version >= 1", name="ck_lifecycle_approval_version"),
)
for column in (
"policy_revision_id",
"subject_id",
"target_type",
"target_ref",
"environment",
"evidence_fingerprint",
"status",
"expires_at",
"created_at",
):
op.create_index(
f"ix_lifecycle_approval_requests_{column}", "lifecycle_approval_requests", [column]
)
op.create_table(
"lifecycle_approval_evidence",
sa.Column(
"approval_request_id",
sa.Uuid(),
sa.ForeignKey("lifecycle_approval_requests.id", ondelete="RESTRICT"),
primary_key=True,
),
sa.Column("evidence_type", sa.String(64), primary_key=True),
sa.Column("evidence_ref", sa.String(255), primary_key=True),
sa.Column("evidence_digest", sa.String(64), nullable=False),
)
op.create_index(
"ix_lifecycle_approval_evidence_evidence_ref",
"lifecycle_approval_evidence",
["evidence_ref"],
)
op.create_table(
"lifecycle_promotion_plans",
id_column(),
sa.Column(
"approval_request_id",
sa.Uuid(),
sa.ForeignKey("lifecycle_approval_requests.id", ondelete="RESTRICT"),
nullable=False,
),
sa.Column(
"subject_id",
sa.Uuid(),
sa.ForeignKey("lifecycle_subjects.id", ondelete="RESTRICT"),
nullable=False,
),
sa.Column("current_state", sa.String(48), nullable=False),
sa.Column("desired_state", sa.String(48), nullable=False),
sa.Column("migration_class", sa.String(32), nullable=False),
sa.Column(
"candidate_deployment_id",
sa.Uuid(),
sa.ForeignKey("capability_deployments.id", ondelete="RESTRICT"),
),
sa.Column("rollback_target_ref", sa.String(255), nullable=False),
sa.Column("project_consumers", sa.JSON(), nullable=False),
sa.Column("affected_identities", sa.JSON(), nullable=False),
sa.Column("impact_analysis", sa.JSON(), nullable=False),
sa.Column(
"migration_id",
sa.Uuid(),
sa.ForeignKey("embedding_migrations.id", ondelete="RESTRICT"),
),
sa.Column("canary_strategy", sa.JSON(), nullable=False),
sa.Column("drain_strategy", sa.JSON(), nullable=False),
sa.Column("health_gates", sa.JSON(), nullable=False),
sa.Column("automatic_abort_conditions", sa.JSON(), nullable=False),
sa.Column("plan_fingerprint", sa.String(64), nullable=False),
sa.Column("status", sa.String(32), nullable=False, server_default="DRAFT"),
sa.Column("version", sa.Integer(), nullable=False, server_default="1"),
sa.Column("created_by", sa.String(255), nullable=False),
sa.Column("approved_by", sa.String(255)),
sa.Column("immutable_at", sa.DateTime(timezone=True)),
created_at(),
sa.CheckConstraint("version >= 1", name="ck_lifecycle_plan_version"),
sa.UniqueConstraint("plan_fingerprint", name="uq_lifecycle_plan_fingerprint"),
)
for column in (
"approval_request_id",
"subject_id",
"migration_class",
"candidate_deployment_id",
"migration_id",
"status",
"created_at",
):
op.create_index(
f"ix_lifecycle_promotion_plans_{column}", "lifecycle_promotion_plans", [column]
)
op.create_table(
"lifecycle_rollback_snapshots",
id_column(),
sa.Column(
"promotion_plan_id",
sa.Uuid(),
sa.ForeignKey("lifecycle_promotion_plans.id", ondelete="RESTRICT"),
nullable=False,
),
sa.Column("snapshot", sa.JSON(), nullable=False),
sa.Column("snapshot_digest", sa.String(64), nullable=False),
created_at(),
sa.Column("immutable_at", sa.DateTime(timezone=True), server_default=sa.func.now()),
sa.UniqueConstraint("promotion_plan_id", name="uq_lifecycle_rollback_plan"),
sa.UniqueConstraint("snapshot_digest", name="uq_lifecycle_rollback_digest"),
)
op.create_index(
"ix_lifecycle_rollback_snapshots_promotion_plan_id",
"lifecycle_rollback_snapshots",
["promotion_plan_id"],
)
op.create_table(
"lifecycle_operations",
id_column(),
sa.Column(
"promotion_plan_id",
sa.Uuid(),
sa.ForeignKey("lifecycle_promotion_plans.id", ondelete="RESTRICT"),
nullable=False,
),
sa.Column("stage", sa.String(32), nullable=False),
sa.Column("idempotency_key", sa.String(128), nullable=False),
sa.Column("expected_subject_version", sa.Integer(), nullable=False),
sa.Column("requester", sa.String(255), nullable=False),
sa.Column("approver", sa.String(255), nullable=False),
sa.Column("executor", sa.String(255), nullable=False),
sa.Column("failure_code", sa.String(64)),
sa.Column("failure_details", sa.JSON(), nullable=False),
sa.Column("rollback_duration_ms", sa.Float()),
sa.Column("version", sa.Integer(), nullable=False, server_default="1"),
sa.Column("started_at", sa.DateTime(timezone=True), server_default=sa.func.now()),
sa.Column("finished_at", sa.DateTime(timezone=True)),
sa.CheckConstraint("version >= 1", name="ck_lifecycle_operation_version"),
sa.UniqueConstraint("idempotency_key", name="uq_lifecycle_operation_idempotency"),
)
op.create_index(
"ix_lifecycle_operations_promotion_plan_id", "lifecycle_operations", ["promotion_plan_id"]
)
op.create_index("ix_lifecycle_operations_stage", "lifecycle_operations", ["stage"])
op.create_table(
"lifecycle_canary_runs",
id_column(),
sa.Column(
"operation_id",
sa.Uuid(),
sa.ForeignKey("lifecycle_operations.id", ondelete="RESTRICT"),
nullable=False,
),
sa.Column("mode", sa.String(48), nullable=False),
sa.Column("traffic_percent", sa.Integer(), nullable=False, server_default="0"),
sa.Column("eligible_clients", sa.JSON(), nullable=False),
sa.Column("eligible_projects", sa.JSON(), nullable=False),
sa.Column("target_request_count", sa.Integer(), nullable=False),
sa.Column("thresholds", sa.JSON(), nullable=False),
sa.Column("status", sa.String(48), nullable=False),
sa.Column("request_count", sa.Integer(), nullable=False, server_default="0"),
sa.Column("error_count", sa.Integer(), nullable=False, server_default="0"),
sa.Column("latency_p95_ms", sa.Float()),
sa.Column("abort_trigger", sa.String(64)),
sa.Column("result", sa.JSON(), nullable=False),
sa.Column("started_at", sa.DateTime(timezone=True), server_default=sa.func.now()),
sa.Column("finished_at", sa.DateTime(timezone=True)),
sa.UniqueConstraint("operation_id", name="uq_lifecycle_canary_operation"),
)
op.create_index(
"ix_lifecycle_canary_runs_operation_id", "lifecycle_canary_runs", ["operation_id"]
)
op.create_index("ix_lifecycle_canary_runs_status", "lifecycle_canary_runs", ["status"])
op.create_table(
"retention_policy_revisions",
id_column(),
sa.Column("key", sa.String(128), nullable=False),
sa.Column("revision", sa.Integer(), nullable=False),
sa.Column("minimum_rollback_days", sa.Integer(), nullable=False),
sa.Column("requirements", sa.JSON(), nullable=False),
sa.Column("fingerprint", sa.String(64), nullable=False),
sa.Column("active", sa.Boolean(), nullable=False, server_default=sa.true()),
sa.Column("created_by", sa.String(255), nullable=False),
created_at(),
sa.Column("immutable_at", sa.DateTime(timezone=True), server_default=sa.func.now()),
sa.UniqueConstraint("key", "revision", name="uq_retention_policy_revision"),
sa.UniqueConstraint("fingerprint", name="uq_retention_policy_fingerprint"),
)
op.create_index("ix_retention_policy_revisions_key", "retention_policy_revisions", ["key"])
op.create_index(
"ix_retention_policy_revisions_active", "retention_policy_revisions", ["active"]
)
op.create_table(
"lifecycle_retention_records",
id_column(),
sa.Column(
"policy_revision_id",
sa.Uuid(),
sa.ForeignKey("retention_policy_revisions.id", ondelete="RESTRICT"),
nullable=False,
),
sa.Column("target_type", sa.String(48), nullable=False),
sa.Column("target_ref", sa.String(255), nullable=False),
sa.Column("state", sa.String(32), nullable=False),
sa.Column("retained_until", sa.DateTime(timezone=True)),
sa.Column("legal_hold", sa.Boolean(), nullable=False, server_default=sa.false()),
sa.Column("reason", sa.Text(), nullable=False),
created_at(),
sa.UniqueConstraint("target_type", "target_ref", name="uq_lifecycle_retention_target"),
)
for column in ("policy_revision_id", "target_type", "target_ref", "state", "retained_until"):
op.create_index(
f"ix_lifecycle_retention_records_{column}", "lifecycle_retention_records", [column]
)
op.create_table(
"lifecycle_cleanup_plans",
id_column(),
sa.Column("target_type", sa.String(48), nullable=False),
sa.Column("target_ref", sa.String(255), nullable=False),
sa.Column("action", sa.String(48), nullable=False),
sa.Column("dependencies", sa.JSON(), nullable=False),
sa.Column("dependency_digest", sa.String(64), nullable=False),
sa.Column("reclaimable_bytes", sa.BigInteger(), nullable=False),
sa.Column("retention_state", sa.String(32), nullable=False),
sa.Column("blockers", sa.JSON(), nullable=False),
sa.Column("status", sa.String(32), nullable=False),
sa.Column("created_by", sa.String(255), nullable=False),
created_at(),
sa.Column("executed_at", sa.DateTime(timezone=True)),
)
for column in ("target_type", "target_ref", "status", "created_at"):
op.create_index(f"ix_lifecycle_cleanup_plans_{column}", "lifecycle_cleanup_plans", [column])
op.create_table(
"artifact_location_removal_records",
id_column(),
sa.Column(
"artifact_location_id",
sa.Uuid(),
sa.ForeignKey("artifact_locations.id", ondelete="RESTRICT"),
nullable=False,
),
sa.Column(
"cleanup_plan_id",
sa.Uuid(),
sa.ForeignKey("lifecycle_cleanup_plans.id", ondelete="RESTRICT"),
nullable=False,
),
sa.Column("prior_status", sa.String(32), nullable=False),
sa.Column("prior_observed_sha256", sa.String(64)),
sa.Column("prior_size_bytes", sa.BigInteger()),
sa.Column("removed_by", sa.String(255), nullable=False),
sa.Column("removed_at", sa.DateTime(timezone=True), server_default=sa.func.now()),
)
op.create_index(
"ix_artifact_location_removal_records_artifact_location_id",
"artifact_location_removal_records",
["artifact_location_id"],
)
op.create_index(
"ix_artifact_location_removal_records_cleanup_plan_id",
"artifact_location_removal_records",
["cleanup_plan_id"],
)
op.create_table(
"lifecycle_events",
id_column(),
sa.Column("event_type", sa.String(64), nullable=False),
sa.Column("object_type", sa.String(48), nullable=False),
sa.Column("object_ref", sa.String(255), nullable=False),
sa.Column("from_state", sa.String(48)),
sa.Column("to_state", sa.String(48)),
sa.Column("actor", sa.String(255), nullable=False),
sa.Column("actor_role", sa.String(32), nullable=False),
sa.Column(
"policy_revision_id",
sa.Uuid(),
sa.ForeignKey("lifecycle_policy_revisions.id", ondelete="RESTRICT"),
),
sa.Column("evidence_ids", sa.JSON(), nullable=False),
sa.Column("reason", sa.Text(), nullable=False),
sa.Column("change_id", sa.String(64), nullable=False),
sa.Column("details", sa.JSON(), nullable=False),
sa.Column("occurred_at", sa.DateTime(timezone=True), server_default=sa.func.now()),
)
for column in (
"event_type",
"object_type",
"object_ref",
"policy_revision_id",
"change_id",
"occurred_at",
):
op.create_index(f"ix_lifecycle_events_{column}", "lifecycle_events", [column])
op.create_index(
"uq_capability_deployment_one_production_stable",
"capability_deployments",
["capability_contract_id"],
unique=True,
postgresql_where=sa.text("production = true AND status = 'stable'"),
sqlite_where=sa.text("production = 1 AND status = 'stable'"),
)
def downgrade() -> None:
op.drop_index(
"uq_capability_deployment_one_production_stable",
table_name="capability_deployments",
)
for table in (
"lifecycle_events",
"artifact_location_removal_records",
"lifecycle_cleanup_plans",
"lifecycle_retention_records",
"retention_policy_revisions",
"lifecycle_canary_runs",
"lifecycle_operations",
"lifecycle_rollback_snapshots",
"lifecycle_promotion_plans",
"lifecycle_approval_evidence",
"lifecycle_approval_requests",
"lifecycle_policy_revisions",
"lifecycle_subjects",
):
op.drop_table(table)
@@ -0,0 +1,415 @@
"""Add typed resumable migration-engine records.
Revision ID: 20260826_0019
Revises: 20260826_0018
"""
from collections.abc import Sequence
import sqlalchemy as sa
from alembic import op
revision: str = "20260826_0019"
down_revision: str | None = "20260826_0018"
branch_labels: str | Sequence[str] | None = None
depends_on: str | Sequence[str] | None = None
def _id() -> sa.Column[object]:
return sa.Column("id", sa.Uuid(), primary_key=True, nullable=False)
def _created() -> sa.Column[object]:
return sa.Column("created_at", sa.DateTime(timezone=True), server_default=sa.func.now())
def upgrade() -> None:
op.create_table(
"migration_validation_policy_revisions",
_id(),
sa.Column("key", sa.String(128), nullable=False),
sa.Column("revision", sa.Integer(), nullable=False),
sa.Column("required_completeness", sa.Float(), nullable=False),
sa.Column("allowed_failures", sa.Integer(), nullable=False),
sa.Column("required_evaluation", sa.Boolean(), nullable=False),
sa.Column("critical_regressions_allowed", sa.Integer(), nullable=False),
sa.Column("maximum_latency_regression_ratio", sa.Float()),
sa.Column("require_project_fit", sa.Boolean(), nullable=False),
sa.Column("require_external_validation", sa.Boolean(), nullable=False),
sa.Column("require_security_approved", sa.Boolean(), nullable=False),
sa.Column("allow_isolated_lab_cutover", sa.Boolean(), nullable=False),
sa.Column("fingerprint", sa.String(64), nullable=False),
sa.Column("active", sa.Boolean(), nullable=False, server_default=sa.true()),
sa.Column("created_by", sa.String(255), nullable=False),
_created(),
sa.Column("immutable_at", sa.DateTime(timezone=True), server_default=sa.func.now()),
sa.UniqueConstraint("key", "revision", name="uq_migration_validation_policy_revision"),
sa.UniqueConstraint("fingerprint", name="uq_migration_validation_policy_fingerprint"),
)
op.create_index(
"ix_migration_validation_policy_revisions_key",
"migration_validation_policy_revisions",
["key"],
)
op.create_index(
"ix_migration_validation_policy_revisions_active",
"migration_validation_policy_revisions",
["active"],
)
op.create_table(
"migration_plans",
_id(),
sa.Column(
"project_id",
sa.Uuid(),
sa.ForeignKey("projects.id", ondelete="RESTRICT"),
nullable=False,
),
sa.Column(
"project_binding_id",
sa.Uuid(),
sa.ForeignKey("project_bindings.id", ondelete="RESTRICT"),
nullable=False,
),
sa.Column(
"capability_contract_id",
sa.Uuid(),
sa.ForeignKey("capability_contracts.id", ondelete="RESTRICT"),
nullable=False,
),
sa.Column("migration_class", sa.String(32), nullable=False),
sa.Column("environment", sa.String(32), nullable=False),
sa.Column("adapter", sa.JSON(), nullable=False),
sa.Column("source_identity", sa.JSON(), nullable=False),
sa.Column("target_identity", sa.JSON(), nullable=False),
sa.Column("source_data_target", sa.Text(), nullable=False),
sa.Column("target_shadow_target", sa.Text(), nullable=False),
sa.Column("source_space_ref", sa.String(255), nullable=False),
sa.Column(
"target_space_id",
sa.Uuid(),
sa.ForeignKey("embedding_spaces.id", ondelete="RESTRICT"),
nullable=False,
),
sa.Column("corpus_revision", sa.String(128), nullable=False),
sa.Column("migration_policy_revision", sa.String(128), nullable=False),
sa.Column(
"validation_policy_revision_id",
sa.Uuid(),
sa.ForeignKey("migration_validation_policy_revisions.id", ondelete="RESTRICT"),
nullable=False,
),
sa.Column(
"lifecycle_approval_id",
sa.Uuid(),
sa.ForeignKey("lifecycle_approval_requests.id", ondelete="RESTRICT"),
nullable=False,
),
sa.Column(
"promotion_plan_id",
sa.Uuid(),
sa.ForeignKey("lifecycle_promotion_plans.id", ondelete="RESTRICT"),
),
sa.Column("rollback_target_ref", sa.Text(), nullable=False),
sa.Column("total_expected_items", sa.Integer(), nullable=False),
sa.Column("batch_size", sa.Integer(), nullable=False),
sa.Column("max_in_flight_batches", sa.Integer(), nullable=False),
sa.Column("concurrency", sa.Integer(), nullable=False),
sa.Column("priority", sa.String(32), nullable=False),
sa.Column("target_storage", sa.JSON(), nullable=False),
sa.Column("shadow_policy", sa.JSON(), nullable=False),
sa.Column("cutover_policy", sa.JSON(), nullable=False),
sa.Column("rollback_retention_days", sa.Integer(), nullable=False),
sa.Column("environment_fingerprint", sa.String(64), nullable=False),
sa.Column("idempotency_key", sa.String(128), nullable=False),
sa.Column("created_by", sa.String(255), nullable=False),
sa.Column("irreversible", sa.Boolean(), nullable=False, server_default=sa.false()),
sa.Column("schema_steps", sa.JSON(), nullable=False),
sa.Column("state", sa.String(48), nullable=False, server_default="PLANNED"),
sa.Column("version", sa.Integer(), nullable=False, server_default="1"),
sa.Column("generation", sa.BigInteger(), nullable=False, server_default="1"),
sa.Column("plan_fingerprint", sa.String(64), nullable=False),
sa.Column("approval_fingerprint", sa.String(64), nullable=False),
sa.Column("completed_items", sa.Integer(), nullable=False, server_default="0"),
sa.Column("failed_items", sa.Integer(), nullable=False, server_default="0"),
sa.Column("retryable_items", sa.Integer(), nullable=False, server_default="0"),
sa.Column("permanent_failed_items", sa.Integer(), nullable=False, server_default="0"),
sa.Column("last_cursor", sa.String(255)),
sa.Column("cancel_requested", sa.Boolean(), nullable=False, server_default=sa.false()),
sa.Column("failure_code", sa.String(64)),
sa.Column("failure_details", sa.JSON(), nullable=False),
sa.Column("started_at", sa.DateTime(timezone=True)),
sa.Column("completed_at", sa.DateTime(timezone=True)),
sa.Column("immutable_at", sa.DateTime(timezone=True), server_default=sa.func.now()),
_created(),
sa.Column("updated_at", sa.DateTime(timezone=True), server_default=sa.func.now()),
sa.UniqueConstraint("idempotency_key", name="uq_migration_plan_idempotency"),
sa.UniqueConstraint("plan_fingerprint", name="uq_migration_plan_fingerprint"),
sa.UniqueConstraint("target_shadow_target", name="uq_migration_plans_target_shadow_target"),
sa.CheckConstraint("version >= 1", name="ck_migration_plan_version"),
sa.CheckConstraint("generation >= 1", name="ck_migration_plan_generation"),
sa.CheckConstraint(
"completed_items >= 0 AND failed_items >= 0", name="ck_migration_plan_progress"
),
)
for column in (
"project_id",
"project_binding_id",
"capability_contract_id",
"migration_class",
"environment",
"target_space_id",
"corpus_revision",
"validation_policy_revision_id",
"lifecycle_approval_id",
"promotion_plan_id",
"state",
):
op.create_index(f"ix_migration_plans_{column}", "migration_plans", [column])
op.create_index(
"uq_migration_one_production_active",
"migration_plans",
["project_id", "capability_contract_id"],
unique=True,
postgresql_where=sa.text(
"environment = 'PRODUCTION' AND state NOT IN "
"('CUTOVER_COMMITTED','ROLLED_BACK','FAILED','CANCELLED')"
),
)
op.create_table(
"migration_batch_checkpoints",
_id(),
sa.Column(
"migration_plan_id",
sa.Uuid(),
sa.ForeignKey("migration_plans.id", ondelete="RESTRICT"),
nullable=False,
),
sa.Column("batch_number", sa.Integer(), nullable=False),
sa.Column("generation", sa.BigInteger(), nullable=False),
sa.Column("cursor_start", sa.String(255), nullable=False),
sa.Column("cursor_end", sa.String(255), nullable=False),
sa.Column("item_count", sa.Integer(), nullable=False),
sa.Column("completed_items", sa.Integer(), nullable=False, server_default="0"),
sa.Column("failed_items", sa.Integer(), nullable=False, server_default="0"),
sa.Column("retryable_items", sa.Integer(), nullable=False, server_default="0"),
sa.Column("permanent_failed_items", sa.Integer(), nullable=False, server_default="0"),
sa.Column("item_fingerprint", sa.String(64), nullable=False),
sa.Column("result_fingerprint", sa.String(64)),
sa.Column("status", sa.String(32), nullable=False),
sa.Column("attempts", sa.Integer(), nullable=False, server_default="0"),
sa.Column("duration_ms", sa.Float()),
sa.Column("error_code", sa.String(64)),
sa.Column("bounded_errors", sa.JSON(), nullable=False),
sa.Column("started_at", sa.DateTime(timezone=True)),
sa.Column("completed_at", sa.DateTime(timezone=True)),
sa.UniqueConstraint(
"migration_plan_id", "batch_number", "generation", name="uq_migration_batch"
),
)
op.create_index(
"ix_migration_batch_checkpoints_migration_plan_id",
"migration_batch_checkpoints",
["migration_plan_id"],
)
op.create_index(
"ix_migration_batch_checkpoints_status", "migration_batch_checkpoints", ["status"]
)
op.create_table(
"migration_validation_snapshots",
_id(),
sa.Column(
"migration_plan_id",
sa.Uuid(),
sa.ForeignKey("migration_plans.id", ondelete="RESTRICT"),
nullable=False,
),
sa.Column(
"validation_policy_revision_id",
sa.Uuid(),
sa.ForeignKey("migration_validation_policy_revisions.id", ondelete="RESTRICT"),
nullable=False,
),
sa.Column("generation", sa.BigInteger(), nullable=False),
sa.Column("expected_count", sa.Integer(), nullable=False),
sa.Column("actual_count", sa.Integer(), nullable=False),
sa.Column("missing_count", sa.Integer(), nullable=False),
sa.Column("duplicate_count", sa.Integer(), nullable=False),
sa.Column("malformed_count", sa.Integer(), nullable=False),
sa.Column("non_finite_count", sa.Integer(), nullable=False),
sa.Column("wrong_dimension_count", sa.Integer(), nullable=False),
sa.Column("content_hash_mismatch_count", sa.Integer(), nullable=False),
sa.Column("wrong_space_count", sa.Integer(), nullable=False),
sa.Column("index_schema_matches", sa.Boolean(), nullable=False),
sa.Column("distance_metric_matches", sa.Boolean(), nullable=False),
sa.Column("payload_integrity", sa.Boolean(), nullable=False),
sa.Column("target_fingerprint", sa.String(64), nullable=False),
sa.Column("evaluation_run_ids", sa.JSON(), nullable=False),
sa.Column("comparable", sa.Boolean(), nullable=False),
sa.Column("critical_regressions", sa.Integer(), nullable=False),
sa.Column("latency_regression_ratio", sa.Float()),
sa.Column("project_fit_eligible", sa.Boolean(), nullable=False),
sa.Column("external_validation_satisfied", sa.Boolean(), nullable=False),
sa.Column("security_approved", sa.Boolean(), nullable=False),
sa.Column("evidence", sa.JSON(), nullable=False),
sa.Column("snapshot_fingerprint", sa.String(64), nullable=False),
sa.Column("passed", sa.Boolean(), nullable=False),
sa.Column("technical_cutover_eligible", sa.Boolean(), nullable=False),
sa.Column("project_promotion_eligible", sa.Boolean(), nullable=False),
sa.Column("blockers", sa.JSON(), nullable=False),
_created(),
sa.Column("immutable_at", sa.DateTime(timezone=True), server_default=sa.func.now()),
sa.UniqueConstraint("snapshot_fingerprint", name="uq_migration_validation_snapshot"),
)
op.create_index(
"ix_migration_validation_snapshots_migration_plan_id",
"migration_validation_snapshots",
["migration_plan_id"],
)
op.create_index(
"ix_migration_validation_snapshots_validation_policy_revision_id",
"migration_validation_snapshots",
["validation_policy_revision_id"],
)
op.create_index(
"ix_migration_validation_snapshots_passed", "migration_validation_snapshots", ["passed"]
)
op.create_table(
"migration_shadow_sessions",
_id(),
sa.Column(
"migration_plan_id",
sa.Uuid(),
sa.ForeignKey("migration_plans.id", ondelete="RESTRICT"),
nullable=False,
),
sa.Column("generation", sa.BigInteger(), nullable=False),
sa.Column("status", sa.String(32), nullable=False),
sa.Column("request_count", sa.Integer(), nullable=False),
sa.Column("source_error_count", sa.Integer(), nullable=False),
sa.Column("target_error_count", sa.Integer(), nullable=False),
sa.Column("source_latency_p95_ms", sa.Float(), nullable=False),
sa.Column("target_latency_p95_ms", sa.Float(), nullable=False),
sa.Column("critical_regressions", sa.Integer(), nullable=False),
sa.Column("metrics", sa.JSON(), nullable=False),
sa.Column("evidence_refs", sa.JSON(), nullable=False),
sa.Column("result_fingerprint", sa.String(64), nullable=False),
sa.Column("started_at", sa.DateTime(timezone=True), server_default=sa.func.now()),
sa.Column("completed_at", sa.DateTime(timezone=True)),
sa.UniqueConstraint("result_fingerprint", name="uq_migration_shadow_result_fingerprint"),
)
op.create_index(
"ix_migration_shadow_sessions_migration_plan_id",
"migration_shadow_sessions",
["migration_plan_id"],
)
op.create_index("ix_migration_shadow_sessions_status", "migration_shadow_sessions", ["status"])
op.create_table(
"migration_rollback_snapshots",
_id(),
sa.Column(
"migration_plan_id",
sa.Uuid(),
sa.ForeignKey("migration_plans.id", ondelete="RESTRICT"),
nullable=False,
),
sa.Column("snapshot", sa.JSON(), nullable=False),
sa.Column("snapshot_fingerprint", sa.String(64), nullable=False),
sa.Column("retain_until", sa.DateTime(timezone=True), nullable=False),
_created(),
sa.Column("immutable_at", sa.DateTime(timezone=True), server_default=sa.func.now()),
sa.UniqueConstraint("migration_plan_id", name="uq_migration_rollback_plan"),
sa.UniqueConstraint("snapshot_fingerprint", name="uq_migration_rollback_fingerprint"),
)
op.create_index(
"ix_migration_rollback_snapshots_migration_plan_id",
"migration_rollback_snapshots",
["migration_plan_id"],
)
op.create_index(
"ix_migration_rollback_snapshots_retain_until",
"migration_rollback_snapshots",
["retain_until"],
)
op.create_table(
"migration_cutover_operations",
_id(),
sa.Column(
"migration_plan_id",
sa.Uuid(),
sa.ForeignKey("migration_plans.id", ondelete="RESTRICT"),
nullable=False,
),
sa.Column("stage", sa.String(32), nullable=False),
sa.Column("idempotency_key", sa.String(128), nullable=False),
sa.Column("generation", sa.BigInteger(), nullable=False),
sa.Column("expected_plan_version", sa.Integer(), nullable=False),
sa.Column("source_before", sa.Text(), nullable=False),
sa.Column("target_after", sa.Text(), nullable=False),
sa.Column("external_state_fingerprint", sa.String(64), nullable=False),
sa.Column("health_evidence", sa.JSON(), nullable=False),
sa.Column("failure_code", sa.String(64)),
sa.Column("failure_details", sa.JSON(), nullable=False),
sa.Column("switch_duration_ms", sa.Float()),
sa.Column("rollback_duration_ms", sa.Float()),
sa.Column("started_at", sa.DateTime(timezone=True), server_default=sa.func.now()),
sa.Column("finished_at", sa.DateTime(timezone=True)),
sa.UniqueConstraint("idempotency_key", name="uq_migration_cutover_idempotency"),
)
op.create_index(
"ix_migration_cutover_operations_migration_plan_id",
"migration_cutover_operations",
["migration_plan_id"],
)
op.create_index(
"ix_migration_cutover_operations_stage", "migration_cutover_operations", ["stage"]
)
op.create_table(
"migration_events",
_id(),
sa.Column(
"migration_plan_id",
sa.Uuid(),
sa.ForeignKey("migration_plans.id", ondelete="RESTRICT"),
nullable=False,
),
sa.Column(
"operation_id",
sa.Uuid(),
sa.ForeignKey("migration_cutover_operations.id", ondelete="RESTRICT"),
),
sa.Column("event_type", sa.String(64), nullable=False),
sa.Column("before_state", sa.String(48)),
sa.Column("after_state", sa.String(48)),
sa.Column("actor", sa.String(255), nullable=False),
sa.Column("policy_revision", sa.String(128), nullable=False),
sa.Column("evidence_refs", sa.JSON(), nullable=False),
sa.Column("reason", sa.Text(), nullable=False),
sa.Column("source_identity", sa.JSON(), nullable=False),
sa.Column("target_identity", sa.JSON(), nullable=False),
sa.Column("generation", sa.BigInteger(), nullable=False),
sa.Column("change_id", sa.String(128), nullable=False),
sa.Column("details", sa.JSON(), nullable=False),
sa.Column("occurred_at", sa.DateTime(timezone=True), server_default=sa.func.now()),
)
for column in ("migration_plan_id", "operation_id", "event_type", "change_id", "occurred_at"):
op.create_index(f"ix_migration_events_{column}", "migration_events", [column])
def downgrade() -> None:
op.drop_table("migration_events")
op.drop_table("migration_cutover_operations")
op.drop_table("migration_rollback_snapshots")
op.drop_table("migration_shadow_sessions")
op.drop_table("migration_validation_snapshots")
op.drop_table("migration_batch_checkpoints")
op.drop_table("migration_plans")
op.drop_table("migration_validation_policy_revisions")
@@ -0,0 +1,257 @@
"""Add operational SLO, alert and bounded capacity history records.
Revision ID: 20260827_0020
Revises: 20260826_0019
"""
from collections.abc import Sequence
import sqlalchemy as sa
from alembic import op
revision: str = "20260827_0020"
down_revision: str | None = "20260826_0019"
branch_labels: str | Sequence[str] | None = None
depends_on: str | Sequence[str] | None = None
def _id() -> sa.Column[object]:
return sa.Column("id", sa.Uuid(), primary_key=True, nullable=False)
def _created() -> sa.Column[object]:
return sa.Column("created_at", sa.DateTime(timezone=True), server_default=sa.func.now())
def upgrade() -> None:
op.create_table(
"service_level_indicators",
_id(),
sa.Column("key", sa.String(128), nullable=False, unique=True),
sa.Column("name", sa.String(255), nullable=False),
sa.Column("service", sa.String(128), nullable=False),
sa.Column("capability", sa.String(128)),
sa.Column("measurement", sa.String(32), nullable=False),
sa.Column("valid_population", sa.JSON(), nullable=False),
sa.Column("success_condition", sa.JSON(), nullable=False),
sa.Column("default_window_seconds", sa.Integer(), nullable=False),
sa.Column("enabled", sa.Boolean(), nullable=False, server_default=sa.true()),
_created(),
)
for column in ("key", "service", "capability", "enabled"):
op.create_index(f"ix_service_level_indicators_{column}", "service_level_indicators", [column])
op.create_table(
"slo_policy_revisions",
_id(),
sa.Column("key", sa.String(128), nullable=False),
sa.Column("revision", sa.Integer(), nullable=False),
sa.Column("sli_definition_id", sa.Uuid(), sa.ForeignKey("service_level_indicators.id", ondelete="RESTRICT"), nullable=False),
sa.Column("objective", sa.Float(), nullable=False),
sa.Column("threshold_ms", sa.Float()),
sa.Column("rolling_window_seconds", sa.Integer(), nullable=False),
sa.Column("minimum_sample_count", sa.Integer(), nullable=False),
sa.Column("severity", sa.String(16), nullable=False),
sa.Column("environment", sa.String(16), nullable=False),
sa.Column("rationale", sa.Text(), nullable=False),
sa.Column("effective_from", sa.DateTime(timezone=True), nullable=False),
sa.Column("active", sa.Boolean(), nullable=False, server_default=sa.true()),
sa.Column("fingerprint", sa.String(64), nullable=False, unique=True),
sa.Column("created_by", sa.String(255), nullable=False),
_created(),
sa.Column("immutable_at", sa.DateTime(timezone=True), server_default=sa.func.now()),
sa.UniqueConstraint("key", "revision", name="uq_slo_policy_key_revision"),
)
for column in ("key", "sli_definition_id", "environment", "active"):
op.create_index(f"ix_slo_policy_revisions_{column}", "slo_policy_revisions", [column])
op.create_table(
"slo_evaluations",
_id(),
sa.Column("policy_id", sa.Uuid(), sa.ForeignKey("slo_policy_revisions.id", ondelete="RESTRICT"), nullable=False),
sa.Column("window_start", sa.DateTime(timezone=True), nullable=False),
sa.Column("window_end", sa.DateTime(timezone=True), nullable=False),
sa.Column("observed_value", sa.Float()),
sa.Column("sample_count", sa.Integer(), nullable=False),
sa.Column("good_count", sa.Integer(), nullable=False),
sa.Column("bad_count", sa.Integer(), nullable=False),
sa.Column("state", sa.String(32), nullable=False),
sa.Column("freshness_seconds", sa.Float()),
sa.Column("allowed_bad", sa.Float()),
sa.Column("consumed_bad", sa.Integer()),
sa.Column("remaining_bad", sa.Float()),
sa.Column("short_burn_rate", sa.Float()),
sa.Column("long_burn_rate", sa.Float()),
sa.Column("evidence", sa.JSON(), nullable=False),
sa.Column("observed_at", sa.DateTime(timezone=True), server_default=sa.func.now()),
sa.UniqueConstraint("policy_id", "window_end", name="uq_slo_evaluation_window"),
)
for column in ("policy_id", "window_start", "window_end", "state"):
op.create_index(f"ix_slo_evaluations_{column}", "slo_evaluations", [column])
op.create_table(
"alert_rule_revisions",
_id(),
sa.Column("key", sa.String(128), nullable=False),
sa.Column("revision", sa.Integer(), nullable=False),
sa.Column("alert_type", sa.String(64), nullable=False),
sa.Column("signal", sa.String(128), nullable=False),
sa.Column("slo_policy_id", sa.Uuid(), sa.ForeignKey("slo_policy_revisions.id", ondelete="RESTRICT")),
sa.Column("condition", sa.JSON(), nullable=False),
sa.Column("pending_seconds", sa.Integer(), nullable=False),
sa.Column("severity", sa.String(16), nullable=False),
sa.Column("labels", sa.JSON(), nullable=False),
sa.Column("cooldown_seconds", sa.Integer(), nullable=False),
sa.Column("recovery_condition", sa.JSON(), nullable=False),
sa.Column("active", sa.Boolean(), nullable=False, server_default=sa.true()),
sa.Column("fingerprint", sa.String(64), nullable=False, unique=True),
sa.Column("created_by", sa.String(255), nullable=False),
_created(),
sa.Column("immutable_at", sa.DateTime(timezone=True), server_default=sa.func.now()),
sa.UniqueConstraint("key", "revision", name="uq_alert_rule_key_revision"),
)
for column in ("key", "alert_type", "slo_policy_id", "severity", "active"):
op.create_index(f"ix_alert_rule_revisions_{column}", "alert_rule_revisions", [column])
op.create_table(
"maintenance_windows",
_id(),
sa.Column("name", sa.String(255), nullable=False),
sa.Column("starts_at", sa.DateTime(timezone=True), nullable=False),
sa.Column("ends_at", sa.DateTime(timezone=True), nullable=False),
sa.Column("matcher", sa.JSON(), nullable=False),
sa.Column("reason", sa.Text(), nullable=False),
sa.Column("active", sa.Boolean(), nullable=False, server_default=sa.true()),
sa.Column("created_by", sa.String(255), nullable=False),
_created(),
)
for column in ("starts_at", "ends_at", "active"):
op.create_index(f"ix_maintenance_windows_{column}", "maintenance_windows", [column])
op.create_table(
"operational_incidents",
_id(),
sa.Column("fingerprint", sa.String(64), nullable=False, unique=True),
sa.Column("title", sa.String(255), nullable=False),
sa.Column("state", sa.String(32), nullable=False),
sa.Column("severity", sa.String(16), nullable=False),
sa.Column("root_subject_type", sa.String(64), nullable=False),
sa.Column("root_subject_ref", sa.String(255), nullable=False),
sa.Column("correlation", sa.String(32), nullable=False),
sa.Column("first_seen_at", sa.DateTime(timezone=True), nullable=False),
sa.Column("last_seen_at", sa.DateTime(timezone=True), nullable=False),
sa.Column("resolved_at", sa.DateTime(timezone=True)),
)
op.create_index("ix_operational_incidents_state", "operational_incidents", ["state"])
op.create_table(
"operational_alerts",
_id(),
sa.Column("rule_id", sa.Uuid(), sa.ForeignKey("alert_rule_revisions.id", ondelete="RESTRICT"), nullable=False),
sa.Column("fingerprint", sa.String(64), nullable=False, unique=True),
sa.Column("alert_type", sa.String(64), nullable=False),
sa.Column("severity", sa.String(16), nullable=False),
sa.Column("state", sa.String(32), nullable=False),
sa.Column("source", sa.String(128), nullable=False),
sa.Column("subject_type", sa.String(64), nullable=False),
sa.Column("subject_ref", sa.String(255), nullable=False),
sa.Column("summary", sa.Text(), nullable=False),
sa.Column("details", sa.JSON(), nullable=False),
sa.Column("first_seen_at", sa.DateTime(timezone=True), nullable=False),
sa.Column("last_seen_at", sa.DateTime(timezone=True), nullable=False),
sa.Column("firing_at", sa.DateTime(timezone=True)),
sa.Column("acknowledged_at", sa.DateTime(timezone=True)),
sa.Column("acknowledged_by", sa.String(255)),
sa.Column("acknowledgement_reason", sa.Text()),
sa.Column("resolved_at", sa.DateTime(timezone=True)),
sa.Column("suppressed_until", sa.DateTime(timezone=True)),
sa.Column("occurrence_count", sa.Integer(), nullable=False, server_default="1"),
sa.Column("cooldown_until", sa.DateTime(timezone=True)),
sa.Column("incident_id", sa.Uuid(), sa.ForeignKey("operational_incidents.id", ondelete="SET NULL")),
)
for column in ("rule_id", "alert_type", "severity", "state", "subject_ref", "first_seen_at", "last_seen_at", "incident_id"):
op.create_index(f"ix_operational_alerts_{column}", "operational_alerts", [column])
op.create_table(
"alert_history_events",
_id(),
sa.Column("alert_id", sa.Uuid(), sa.ForeignKey("operational_alerts.id", ondelete="RESTRICT"), nullable=False),
sa.Column("from_state", sa.String(32)),
sa.Column("to_state", sa.String(32), nullable=False),
sa.Column("actor", sa.String(255), nullable=False),
sa.Column("reason", sa.Text(), nullable=False),
sa.Column("evidence", sa.JSON(), nullable=False),
sa.Column("occurred_at", sa.DateTime(timezone=True), server_default=sa.func.now()),
)
for column in ("alert_id", "to_state", "occurred_at"):
op.create_index(f"ix_alert_history_events_{column}", "alert_history_events", [column])
op.create_table(
"incident_timeline_events",
_id(),
sa.Column("incident_id", sa.Uuid(), sa.ForeignKey("operational_incidents.id", ondelete="RESTRICT"), nullable=False),
sa.Column("alert_id", sa.Uuid(), sa.ForeignKey("operational_alerts.id", ondelete="SET NULL")),
sa.Column("event_type", sa.String(64), nullable=False),
sa.Column("relation", sa.String(32), nullable=False),
sa.Column("summary", sa.Text(), nullable=False),
sa.Column("occurred_at", sa.DateTime(timezone=True), server_default=sa.func.now()),
)
for column in ("incident_id", "alert_id", "occurred_at"):
op.create_index(f"ix_incident_timeline_events_{column}", "incident_timeline_events", [column])
op.create_table(
"capacity_snapshots",
_id(),
sa.Column("observed_at", sa.DateTime(timezone=True), nullable=False),
sa.Column("received_at", sa.DateTime(timezone=True), nullable=False),
sa.Column("node_id", sa.Uuid(), sa.ForeignKey("compute_nodes.id", ondelete="RESTRICT"), nullable=False),
sa.Column("node_name", sa.String(255), nullable=False),
sa.Column("accelerator_id", sa.Uuid(), sa.ForeignKey("accelerators.id", ondelete="RESTRICT")),
sa.Column("gpu_total_bytes", sa.BigInteger()),
sa.Column("gpu_observed_bytes", sa.BigInteger()),
sa.Column("gpu_external_bytes", sa.BigInteger()),
sa.Column("gpu_managed_resident_bytes", sa.BigInteger()),
sa.Column("gpu_leased_bytes", sa.BigInteger()),
sa.Column("gpu_reserve_bytes", sa.BigInteger()),
sa.Column("gpu_schedulable_bytes", sa.BigInteger()),
sa.Column("pressure_state", sa.String(16), nullable=False),
sa.Column("system_ram_total_bytes", sa.BigInteger()),
sa.Column("system_ram_available_bytes", sa.BigInteger()),
sa.Column("storage_total_bytes", sa.BigInteger()),
sa.Column("storage_free_bytes", sa.BigInteger()),
sa.Column("availability", sa.String(32), nullable=False),
sa.Column("freshness_seconds", sa.Float(), nullable=False),
sa.UniqueConstraint("node_id", "accelerator_id", "observed_at", name="uq_capacity_observation"),
)
for column in ("observed_at", "received_at", "node_id", "accelerator_id"):
op.create_index(f"ix_capacity_snapshots_{column}", "capacity_snapshots", [column])
op.create_table(
"capacity_aggregates",
_id(),
sa.Column("node_id", sa.Uuid(), sa.ForeignKey("compute_nodes.id", ondelete="RESTRICT"), nullable=False),
sa.Column("accelerator_id", sa.Uuid(), sa.ForeignKey("accelerators.id", ondelete="RESTRICT")),
sa.Column("bucket_start", sa.DateTime(timezone=True), nullable=False),
sa.Column("bucket_seconds", sa.Integer(), nullable=False),
sa.Column("sample_count", sa.Integer(), nullable=False),
sa.Column("metrics", sa.JSON(), nullable=False),
_created(),
sa.UniqueConstraint("node_id", "accelerator_id", "bucket_start", name="uq_capacity_bucket"),
)
for column in ("node_id", "accelerator_id", "bucket_start"):
op.create_index(f"ix_capacity_aggregates_{column}", "capacity_aggregates", [column])
def downgrade() -> None:
op.drop_table("capacity_aggregates")
op.drop_table("capacity_snapshots")
op.drop_table("incident_timeline_events")
op.drop_table("alert_history_events")
op.drop_table("operational_alerts")
op.drop_table("operational_incidents")
op.drop_table("maintenance_windows")
op.drop_table("alert_rule_revisions")
op.drop_table("slo_evaluations")
op.drop_table("slo_policy_revisions")
op.drop_table("service_level_indicators")
@@ -0,0 +1,333 @@
"""Add versioned recovery policies, immutable backup sets, restore journals and artifact recovery.
Revision ID: 20260827_0021
Revises: 20260827_0020
"""
from collections.abc import Sequence
import sqlalchemy as sa
from alembic import op
revision: str = "20260827_0021"
down_revision: str | None = "20260827_0020"
branch_labels: str | Sequence[str] | None = None
depends_on: str | Sequence[str] | None = None
def _id() -> sa.Column[object]:
return sa.Column("id", sa.Uuid(), primary_key=True, nullable=False)
def _created() -> sa.Column[object]:
return sa.Column("created_at", sa.DateTime(timezone=True), server_default=sa.func.now())
def upgrade() -> None:
op.create_table(
"recovery_policy_revisions",
_id(),
sa.Column("key", sa.String(128), nullable=False),
sa.Column("revision", sa.Integer(), nullable=False),
sa.Column("name", sa.String(255), nullable=False),
sa.Column("asset_class", sa.String(32), nullable=False),
sa.Column("backup_method", sa.String(48), nullable=False),
sa.Column("retention_days", sa.Integer(), nullable=False),
sa.Column("minimum_verified_backups", sa.Integer(), nullable=False),
sa.Column("rpo_seconds", sa.Integer()),
sa.Column("rto_target_seconds", sa.Integer()),
sa.Column("restore_verification", sa.String(32), nullable=False),
sa.Column("encryption_required", sa.Boolean(), nullable=False, server_default=sa.false()),
sa.Column("external_dependency", sa.Boolean(), nullable=False, server_default=sa.false()),
sa.Column("rehydration_allowed", sa.Boolean(), nullable=False, server_default=sa.false()),
sa.Column("secret_class", sa.String(32)),
sa.Column("rationale", sa.Text(), nullable=False),
sa.Column("active", sa.Boolean(), nullable=False, server_default=sa.true()),
sa.Column("fingerprint", sa.String(64), nullable=False, unique=True),
sa.Column("created_by", sa.String(255), nullable=False),
_created(),
sa.Column("immutable_at", sa.DateTime(timezone=True), server_default=sa.func.now()),
sa.UniqueConstraint("key", "revision", name="uq_recovery_policy_key_revision"),
sa.CheckConstraint("retention_days >= 1", name="ck_recovery_policy_retention"),
sa.CheckConstraint(
"minimum_verified_backups >= 1", name="ck_recovery_policy_minimum_verified"
),
)
for column in ("key", "asset_class", "active"):
op.create_index(
f"ix_recovery_policy_revisions_{column}", "recovery_policy_revisions", [column]
)
op.create_table(
"recovery_asset_records",
_id(),
sa.Column("key", sa.String(128), nullable=False, unique=True),
sa.Column("name", sa.String(255), nullable=False),
sa.Column("asset_class", sa.String(32), nullable=False),
sa.Column("owner", sa.String(128), nullable=False),
sa.Column("location", sa.Text(), nullable=False),
sa.Column("backup_method", sa.String(48), nullable=False),
sa.Column("restore_method", sa.Text(), nullable=False),
sa.Column("rebuild_method", sa.Text()),
sa.Column("rpo_seconds", sa.Integer()),
sa.Column("readiness", sa.String(32), nullable=False),
sa.Column("dependencies", sa.JSON(), nullable=False),
sa.Column("notes", sa.Text(), nullable=False, server_default=""),
sa.Column(
"policy_revision_id",
sa.Uuid(),
sa.ForeignKey("recovery_policy_revisions.id", ondelete="RESTRICT"),
nullable=False,
),
_created(),
sa.Column("updated_at", sa.DateTime(timezone=True), server_default=sa.func.now()),
)
for column in ("key", "asset_class", "readiness", "policy_revision_id"):
op.create_index(f"ix_recovery_asset_records_{column}", "recovery_asset_records", [column])
op.create_table(
"backup_sets",
_id(),
sa.Column("backup_id", sa.String(64), nullable=False),
sa.Column("state", sa.String(32), nullable=False, server_default="PLANNED"),
sa.Column(
"policy_revision_id",
sa.Uuid(),
sa.ForeignKey("recovery_policy_revisions.id", ondelete="RESTRICT"),
nullable=False,
),
sa.Column("modelforge_version", sa.String(64), nullable=False),
sa.Column("modelforge_commit", sa.String(64)),
sa.Column("source_repository", sa.Text()),
sa.Column("source_reference", sa.String(255)),
sa.Column("schema_revision", sa.String(64)),
sa.Column("environment_fingerprint", sa.JSON(), nullable=False),
sa.Column("database_identity", sa.JSON(), nullable=False),
sa.Column("destination_root", sa.Text(), nullable=False),
sa.Column("payload_relative_path", sa.Text()),
sa.Column("payload_sha256", sa.String(64)),
sa.Column("manifest_relative_path", sa.Text()),
sa.Column("manifest_sha256", sa.String(64)),
sa.Column("included_asset_classes", sa.JSON(), nullable=False),
sa.Column("excluded_asset_classes", sa.JSON(), nullable=False),
sa.Column("payload_bytes", sa.BigInteger(), nullable=False, server_default="0"),
sa.Column("encrypted", sa.Boolean(), nullable=False, server_default=sa.false()),
sa.Column("encryption_algorithm", sa.String(64)),
sa.Column("encryption_key_id", sa.String(128)),
sa.Column("verification_details", sa.JSON(), nullable=False),
sa.Column("verified_at", sa.DateTime(timezone=True)),
sa.Column("failure_code", sa.String(64)),
sa.Column("failure_reason", sa.Text()),
sa.Column("milestone", sa.String(64)),
sa.Column("legal_hold", sa.Boolean(), nullable=False, server_default=sa.false()),
sa.Column("reason", sa.Text(), nullable=False),
sa.Column("created_by", sa.String(255), nullable=False),
sa.Column("started_at", sa.DateTime(timezone=True)),
sa.Column("completed_at", sa.DateTime(timezone=True)),
sa.Column("expires_at", sa.DateTime(timezone=True)),
_created(),
sa.Column("immutable_at", sa.DateTime(timezone=True)),
sa.UniqueConstraint("backup_id", name="uq_backup_set_backup_id"),
sa.CheckConstraint("payload_bytes >= 0", name="ck_backup_set_payload_bytes"),
)
for column in (
"backup_id",
"state",
"policy_revision_id",
"schema_revision",
"manifest_sha256",
"verified_at",
"milestone",
"legal_hold",
"expires_at",
):
op.create_index(f"ix_backup_sets_{column}", "backup_sets", [column])
op.create_table(
"backup_manifest_entries",
_id(),
sa.Column(
"backup_set_id",
sa.Uuid(),
sa.ForeignKey("backup_sets.id", ondelete="CASCADE"),
nullable=False,
),
sa.Column("logical_asset_type", sa.String(64), nullable=False),
sa.Column("object_name", sa.String(255), nullable=False),
sa.Column("relative_path", sa.Text(), nullable=False),
sa.Column("size_bytes", sa.BigInteger(), nullable=False),
sa.Column("sha256", sa.String(64), nullable=False),
sa.Column("source_generation", sa.String(128), nullable=False),
sa.Column("schema_version", sa.String(64)),
sa.Column("dependency_refs", sa.JSON(), nullable=False),
_created(),
sa.UniqueConstraint("backup_set_id", "object_name", name="uq_backup_manifest_object"),
sa.CheckConstraint("length(sha256) = 64", name="ck_backup_manifest_sha256_length"),
sa.CheckConstraint("size_bytes >= 0", name="ck_backup_manifest_size"),
)
for column in ("backup_set_id", "logical_asset_type"):
op.create_index(
f"ix_backup_manifest_entries_{column}", "backup_manifest_entries", [column]
)
op.create_table(
"restore_plans",
_id(),
sa.Column(
"backup_set_id",
sa.Uuid(),
sa.ForeignKey("backup_sets.id", ondelete="RESTRICT"),
nullable=False,
),
sa.Column("mode", sa.String(32), nullable=False),
sa.Column("state", sa.String(32), nullable=False, server_default="DRAFT"),
sa.Column("target_environment", sa.String(32), nullable=False),
sa.Column("target_label", sa.String(128), nullable=False),
sa.Column("database_destination", sa.Text(), nullable=False),
sa.Column("artifact_strategy", sa.String(32), nullable=False),
sa.Column("secret_strategy", sa.String(32), nullable=False),
sa.Column("node_strategy", sa.String(32), nullable=False),
sa.Column("expected_modelforge_version", sa.String(64)),
sa.Column("preflight", sa.JSON(), nullable=False),
sa.Column("validation_requirements", sa.JSON(), nullable=False),
sa.Column("fingerprint", sa.String(64), nullable=False),
sa.Column("reason", sa.Text(), nullable=False),
sa.Column("created_by", sa.String(255), nullable=False),
_created(),
sa.UniqueConstraint("fingerprint", name="uq_restore_plan_fingerprint"),
)
for column in ("backup_set_id", "mode", "state", "target_environment"):
op.create_index(f"ix_restore_plans_{column}", "restore_plans", [column])
op.create_table(
"restore_operations",
_id(),
sa.Column(
"plan_id",
sa.Uuid(),
sa.ForeignKey("restore_plans.id", ondelete="RESTRICT"),
nullable=False,
),
sa.Column(
"backup_set_id",
sa.Uuid(),
sa.ForeignKey("backup_sets.id", ondelete="RESTRICT"),
nullable=False,
),
sa.Column("state", sa.String(48), nullable=False, server_default="PLANNED"),
sa.Column("attempt", sa.Integer(), nullable=False, server_default="1"),
sa.Column("idempotency_key", sa.String(64), nullable=False),
sa.Column("preflight_result", sa.JSON(), nullable=False),
sa.Column("phase_durations", sa.JSON(), nullable=False),
sa.Column("source_fingerprint", sa.JSON(), nullable=False),
sa.Column("restored_fingerprint", sa.JSON(), nullable=False),
sa.Column("fingerprint_diff", sa.JSON(), nullable=False),
sa.Column("validation_result", sa.JSON(), nullable=False),
sa.Column("rpo_seconds", sa.Float()),
sa.Column("rto_seconds", sa.Float()),
sa.Column("failure_code", sa.String(64)),
sa.Column("failure_reason", sa.Text()),
sa.Column("started_at", sa.DateTime(timezone=True), server_default=sa.func.now()),
sa.Column("updated_at", sa.DateTime(timezone=True), server_default=sa.func.now()),
sa.Column("ready_at", sa.DateTime(timezone=True)),
sa.UniqueConstraint("idempotency_key", name="uq_restore_operation_idempotency"),
)
for column in ("plan_id", "backup_set_id", "state"):
op.create_index(f"ix_restore_operations_{column}", "restore_operations", [column])
op.create_table(
"restore_operation_events",
_id(),
sa.Column(
"restore_operation_id",
sa.Uuid(),
sa.ForeignKey("restore_operations.id", ondelete="CASCADE"),
nullable=False,
),
sa.Column("from_state", sa.String(48)),
sa.Column("to_state", sa.String(48), nullable=False),
sa.Column("phase", sa.String(48), nullable=False),
sa.Column("actor", sa.String(255), nullable=False),
sa.Column("reason", sa.Text(), nullable=False),
sa.Column("evidence", sa.JSON(), nullable=False),
sa.Column("occurred_at", sa.DateTime(timezone=True), server_default=sa.func.now()),
)
for column in ("restore_operation_id", "to_state", "occurred_at"):
op.create_index(
f"ix_restore_operation_events_{column}", "restore_operation_events", [column]
)
op.create_table(
"artifact_recovery_operations",
_id(),
sa.Column(
"restore_operation_id",
sa.Uuid(),
sa.ForeignKey("restore_operations.id", ondelete="SET NULL"),
),
sa.Column(
"artifact_set_id",
sa.Uuid(),
sa.ForeignKey("artifact_sets.id", ondelete="RESTRICT"),
nullable=False,
),
sa.Column(
"model_revision_id",
sa.Uuid(),
sa.ForeignKey("model_revisions.id", ondelete="RESTRICT"),
nullable=False,
),
sa.Column("recovery_class", sa.String(32), nullable=False),
sa.Column("state", sa.String(32), nullable=False, server_default="PLANNED"),
sa.Column("upstream_repository", sa.Text()),
sa.Column("upstream_commit_sha", sa.String(64)),
sa.Column(
"target_storage_root_id",
sa.Uuid(),
sa.ForeignKey("storage_roots.id", ondelete="RESTRICT"),
nullable=False,
),
sa.Column("expected_files", sa.JSON(), nullable=False),
sa.Column("verified_files", sa.JSON(), nullable=False),
sa.Column("bytes_total", sa.BigInteger(), nullable=False, server_default="0"),
sa.Column("bytes_recovered", sa.BigInteger(), nullable=False, server_default="0"),
sa.Column(
"download_plan_id", sa.Uuid(), sa.ForeignKey("download_plans.id", ondelete="SET NULL")
),
sa.Column(
"artifact_job_id", sa.Uuid(), sa.ForeignKey("artifact_jobs.id", ondelete="SET NULL")
),
sa.Column("lineage", sa.JSON(), nullable=False),
sa.Column("duration_seconds", sa.Float()),
sa.Column("failure_code", sa.String(64)),
sa.Column("failure_reason", sa.Text()),
sa.Column("reason", sa.Text(), nullable=False),
sa.Column("created_by", sa.String(255), nullable=False),
sa.Column("started_at", sa.DateTime(timezone=True), server_default=sa.func.now()),
sa.Column("completed_at", sa.DateTime(timezone=True)),
)
for column in (
"restore_operation_id",
"artifact_set_id",
"model_revision_id",
"recovery_class",
"state",
"target_storage_root_id",
"download_plan_id",
"artifact_job_id",
):
op.create_index(
f"ix_artifact_recovery_operations_{column}", "artifact_recovery_operations", [column]
)
def downgrade() -> None:
op.drop_table("artifact_recovery_operations")
op.drop_table("restore_operation_events")
op.drop_table("restore_operations")
op.drop_table("restore_plans")
op.drop_table("backup_manifest_entries")
op.drop_table("backup_sets")
op.drop_table("recovery_asset_records")
op.drop_table("recovery_policy_revisions")
@@ -0,0 +1,71 @@
"""Add audited compute-node decommission tombstones and operation records.
Revision ID: 20260828_0022
Revises: 20260827_0021
"""
from collections.abc import Sequence
import sqlalchemy as sa
from alembic import op
revision: str = "20260828_0022"
down_revision: str | None = "20260827_0021"
branch_labels: str | Sequence[str] | None = None
depends_on: str | Sequence[str] | None = None
def upgrade() -> None:
op.add_column(
"compute_nodes",
sa.Column("generation", sa.BigInteger(), nullable=False, server_default="1"),
)
op.add_column("compute_nodes", sa.Column("decommissioned_at", sa.DateTime(timezone=True)))
op.add_column("compute_nodes", sa.Column("decommission_reason", sa.Text()))
op.add_column("compute_nodes", sa.Column("decommissioned_by", sa.String(255)))
op.create_index("ix_compute_nodes_decommissioned_at", "compute_nodes", ["decommissioned_at"])
op.create_table(
"node_decommission_operations",
sa.Column("id", sa.Uuid(), primary_key=True, nullable=False),
sa.Column(
"compute_node_id",
sa.Uuid(),
sa.ForeignKey("compute_nodes.id", ondelete="RESTRICT"),
nullable=False,
),
sa.Column("persisted_identity", sa.String(128), nullable=False),
sa.Column("idempotency_key", sa.String(128), nullable=False),
sa.Column("expected_generation", sa.BigInteger(), nullable=False),
sa.Column("preview_digest", sa.String(64), nullable=False),
sa.Column("reason", sa.Text(), nullable=False),
sa.Column("operator", sa.String(255), nullable=False),
sa.Column("previous_state", sa.JSON(), nullable=False),
sa.Column("cleanup_summary", sa.JSON(), nullable=False),
sa.Column("status", sa.String(32), nullable=False, server_default="completed"),
sa.Column(
"created_at",
sa.DateTime(timezone=True),
nullable=False,
server_default=sa.func.now(),
),
sa.Column("decommissioned_at", sa.DateTime(timezone=True), nullable=False),
sa.UniqueConstraint("compute_node_id", name="uq_node_decommission_node"),
sa.UniqueConstraint("idempotency_key", name="uq_node_decommission_idempotency"),
sa.CheckConstraint("expected_generation >= 1", name="ck_node_decommission_generation"),
)
for column in ("compute_node_id", "persisted_identity", "status", "decommissioned_at"):
op.create_index(
f"ix_node_decommission_operations_{column}",
"node_decommission_operations",
[column],
)
def downgrade() -> None:
op.drop_table("node_decommission_operations")
op.drop_index("ix_compute_nodes_decommissioned_at", table_name="compute_nodes")
op.drop_column("compute_nodes", "decommissioned_by")
op.drop_column("compute_nodes", "decommission_reason")
op.drop_column("compute_nodes", "decommissioned_at")
op.drop_column("compute_nodes", "generation")
@@ -0,0 +1,56 @@
"""Enforce the two fixed Node Agent credential scopes.
Revision ID: 20260830_0023
Revises: 20260828_0022
"""
from collections.abc import Sequence
import sqlalchemy as sa
from alembic import op
revision: str = "20260830_0023"
down_revision: str | None = "20260828_0022"
branch_labels: str | Sequence[str] | None = None
depends_on: str | Sequence[str] | None = None
_SCOPE_CONSTRAINTS = (
("node_enrollments", "node.enroll", "ck_node_enrollment_scope"),
("node_credentials", "node.publish", "ck_node_credential_scope"),
)
def _validate_existing_scopes() -> None:
connection = op.get_bind()
for table_name, expected_scope, _constraint_name in _SCOPE_CONSTRAINTS:
table = sa.table(table_name, sa.column("scope", sa.String(64)))
invalid_rows = connection.scalar(
sa.select(sa.func.count())
.select_from(table)
.where(sa.or_(table.c.scope.is_(None), table.c.scope != expected_scope))
)
if invalid_rows:
raise RuntimeError(
f"refusing node-scope migration: {table_name} contains "
f"{invalid_rows} row(s) outside {expected_scope!r}"
)
def upgrade() -> None:
# Validate every table before changing either one. A malformed production row therefore aborts
# the migration without leaving a partially hardened schema.
_validate_existing_scopes()
for table_name, expected_scope, constraint_name in _SCOPE_CONSTRAINTS:
with op.batch_alter_table(table_name) as batch_op:
batch_op.create_check_constraint(
constraint_name,
f"scope = '{expected_scope}'",
)
def downgrade() -> None:
for table_name, _expected_scope, constraint_name in reversed(_SCOPE_CONSTRAINTS):
with op.batch_alter_table(table_name) as batch_op:
batch_op.drop_constraint(constraint_name, type_="check")
@@ -0,0 +1,717 @@
"""Version and checkpoint the tamper-evident audit chain.
Revision ID: 20260830_0024
Revises: 20260830_0023
"""
from __future__ import annotations
import hashlib
import json
import re
import uuid
from datetime import UTC, datetime
from typing import Any
import sqlalchemy as sa
from sqlalchemy.engine import Connection
from alembic import op
revision = "20260830_0024"
down_revision = "20260830_0023"
branch_labels = None
depends_on = None
_LEGACY_PREFIX_DOMAIN = b"modelforge:audit:legacy-prefix:v1\n"
_SHA256 = re.compile(r"^[0-9a-f]{64}$")
_AUDIT_CHAIN_LOCK_KEY = int.from_bytes(b"MF_AUDIT", byteorder="big", signed=False)
_AUDIT_OWNER_ROLE = "modelforge"
_AUDIT_RUNTIME_ROLE = "modelforge_runtime"
_POSTGRES_AUDIT_BOUNDARY_SQL = r"""
create schema if not exists modelforge_audit authorization modelforge;
alter schema modelforge_audit owner to modelforge;
revoke all on schema modelforge_audit from public;
revoke create on schema public from modelforge_runtime;
grant usage on schema public, modelforge_audit to modelforge_runtime;
create or replace function modelforge_audit.enforce_owner_mutation()
returns trigger
language plpgsql
security invoker
set search_path = pg_catalog
as $guard$
begin
if current_user <> 'modelforge' then
raise exception 'audit tables are writable only through the canonical append function'
using errcode = '42501';
end if;
if tg_op = 'DELETE' then
return old;
elsif tg_op = 'TRUNCATE' then
return null;
end if;
return new;
end
$guard$;
alter function modelforge_audit.enforce_owner_mutation() owner to modelforge;
revoke all on function modelforge_audit.enforce_owner_mutation() from public;
revoke all on function modelforge_audit.enforce_owner_mutation() from modelforge_runtime;
drop trigger if exists trg_modelforge_audit_events_owner on public.audit_events;
create trigger trg_modelforge_audit_events_owner
before insert or update or delete on public.audit_events
for each row execute function modelforge_audit.enforce_owner_mutation();
drop trigger if exists trg_modelforge_audit_events_truncate_owner on public.audit_events;
create trigger trg_modelforge_audit_events_truncate_owner
before truncate on public.audit_events
for each statement execute function modelforge_audit.enforce_owner_mutation();
drop trigger if exists trg_modelforge_audit_head_owner on public.audit_chain_heads;
create trigger trg_modelforge_audit_head_owner
before insert or update or delete on public.audit_chain_heads
for each row execute function modelforge_audit.enforce_owner_mutation();
drop trigger if exists trg_modelforge_audit_head_truncate_owner on public.audit_chain_heads;
create trigger trg_modelforge_audit_head_truncate_owner
before truncate on public.audit_chain_heads
for each statement execute function modelforge_audit.enforce_owner_mutation();
create or replace function modelforge_audit.append_event_v2(
p_event_id uuid,
p_occurred_at timestamptz,
p_correlation_id text,
p_actor_type text,
p_actor_id text,
p_action text,
p_resource_type text,
p_resource_id text,
p_outcome text,
p_details jsonb,
p_expected_event_count bigint,
p_expected_last_sequence bigint,
p_expected_last_event_hash text,
p_expected_hash_format text,
p_expected_v2_start_sequence bigint,
p_expected_legacy_prefix_count bigint,
p_expected_legacy_prefix_seal text
)
returns table(event_id uuid, sequence bigint, event_hash text, occurred_at timestamptz)
language plpgsql
security definer
set search_path = pg_catalog
as $append$
declare
v_head public.audit_chain_heads%rowtype;
v_tail public.audit_events%rowtype;
v_max_sequence bigint;
v_predecessor_hash text;
v_sequence bigint;
v_payload text;
v_hash text;
v_updated bigint;
begin
perform pg_catalog.pg_advisory_xact_lock(5568242723498248532);
if p_event_id is null or p_occurred_at is null or p_correlation_id is null
or p_actor_type is null or p_actor_id is null or p_action is null
or p_resource_type is null or p_outcome is null or p_details is null then
raise exception 'canonical audit append arguments must not be null'
using errcode = '23502';
end if;
if not pg_catalog.isfinite(p_occurred_at) then
raise exception 'canonical audit occurred_at must be finite' using errcode = '22008';
end if;
if pg_catalog.jsonb_typeof(p_details) <> 'object' then
raise exception 'canonical audit details must be a JSON object'
using errcode = '22023';
end if;
select head.* into v_head
from public.audit_chain_heads as head
where head.singleton_id = 1
for update;
if not found then
raise exception 'audit checkpoint is missing' using errcode = '23514';
end if;
if v_head.event_count is distinct from p_expected_event_count
or v_head.last_sequence is distinct from p_expected_last_sequence
or v_head.last_event_hash is distinct from p_expected_last_event_hash
or v_head.hash_format is distinct from p_expected_hash_format
or v_head.v2_start_sequence is distinct from p_expected_v2_start_sequence
or v_head.legacy_prefix_count is distinct from p_expected_legacy_prefix_count
or v_head.legacy_prefix_seal is distinct from p_expected_legacy_prefix_seal then
raise exception 'audit checkpoint changed before canonical append'
using errcode = '40001';
end if;
if v_head.hash_format <> 'v2'
or v_head.event_count <> v_head.last_sequence
or v_head.event_count < 0
or v_head.v2_start_sequence < 1
or v_head.legacy_prefix_count <> v_head.v2_start_sequence - 1
or v_head.legacy_prefix_count > v_head.event_count
or v_head.legacy_prefix_seal !~ '^[0-9a-f]{64}$' then
raise exception 'audit checkpoint invariants are invalid' using errcode = '23514';
end if;
select events.sequence into v_max_sequence
from public.audit_events as events
order by events.sequence desc, events.id desc
limit 1;
if v_head.event_count = 0 then
if v_max_sequence is not null or v_head.last_event_hash is not null then
raise exception 'empty audit checkpoint has retained events' using errcode = '23514';
end if;
else
if v_max_sequence is distinct from v_head.last_sequence
or v_head.last_event_hash is null then
raise exception 'audit checkpoint does not identify the retained tail'
using errcode = '23514';
end if;
select events.* into v_tail
from public.audit_events as events
where events.sequence = v_head.last_sequence;
if not found or v_tail.event_hash is distinct from v_head.last_event_hash
or v_tail.event_hash !~ '^[0-9a-f]{64}$' then
raise exception 'audit retained tail is missing or does not match the checkpoint'
using errcode = '23514';
end if;
if v_tail.hash_format = 'v2' then
if v_tail.canonical_payload is null
or pg_catalog.encode(
pg_catalog.sha256(pg_catalog.convert_to(v_tail.canonical_payload, 'UTF8')),
'hex'
) <> v_tail.event_hash
or v_tail.canonical_payload::jsonb is distinct from pg_catalog.jsonb_build_object(
'hash_format', 'v2',
'id', v_tail.id::text,
'occurred_at', pg_catalog.to_char(
v_tail.occurred_at at time zone 'UTC',
'YYYY-MM-DD"T"HH24:MI:SS.US"Z"'
),
'correlation_id', v_tail.correlation_id,
'actor_type', v_tail.actor_type,
'actor_id', v_tail.actor_id,
'action', v_tail.action,
'resource_type', v_tail.resource_type,
'resource_id', v_tail.resource_id,
'outcome', v_tail.outcome,
'details', v_tail.details::jsonb,
'previous_event_hash', v_tail.previous_event_hash
) then
raise exception 'v2 audit retained tail payload is invalid'
using errcode = '23514';
end if;
elsif v_tail.hash_format <> 'v1'
or v_tail.sequence <> v_head.v2_start_sequence - 1 then
raise exception 'audit retained tail hash format is invalid' using errcode = '23514';
end if;
if v_tail.sequence = 1 then
if v_tail.previous_event_hash is not null then
raise exception 'first audit event has a previous hash' using errcode = '23514';
end if;
else
select events.event_hash into v_predecessor_hash
from public.audit_events as events
where events.sequence = v_tail.sequence - 1;
if not found or v_tail.previous_event_hash is distinct from v_predecessor_hash then
raise exception 'audit retained tail link is invalid' using errcode = '23514';
end if;
end if;
end if;
v_sequence := v_head.last_sequence + 1;
v_payload := pg_catalog.jsonb_build_object(
'hash_format', 'v2',
'id', p_event_id::text,
'occurred_at', pg_catalog.to_char(
p_occurred_at at time zone 'UTC',
'YYYY-MM-DD"T"HH24:MI:SS.US"Z"'
),
'correlation_id', p_correlation_id,
'actor_type', p_actor_type,
'actor_id', p_actor_id,
'action', p_action,
'resource_type', p_resource_type,
'resource_id', p_resource_id,
'outcome', p_outcome,
'details', p_details,
'previous_event_hash', v_head.last_event_hash
)::text;
v_hash := pg_catalog.encode(
pg_catalog.sha256(pg_catalog.convert_to(v_payload, 'UTF8')), 'hex'
);
insert into public.audit_events (
id, sequence, occurred_at, correlation_id, actor_type, actor_id, action,
resource_type, resource_id, outcome, details, previous_event_hash, event_hash,
hash_format, canonical_payload
) values (
p_event_id, v_sequence, p_occurred_at, p_correlation_id, p_actor_type, p_actor_id,
p_action, p_resource_type, p_resource_id, p_outcome, p_details,
v_head.last_event_hash, v_hash, 'v2', v_payload
);
update public.audit_chain_heads as head
set event_count = v_head.event_count + 1,
last_sequence = v_sequence,
last_event_hash = v_hash,
updated_at = p_occurred_at
where head.singleton_id = 1
and head.event_count = v_head.event_count
and head.last_sequence = v_head.last_sequence
and head.last_event_hash is not distinct from v_head.last_event_hash
and head.hash_format = v_head.hash_format
and head.v2_start_sequence = v_head.v2_start_sequence
and head.legacy_prefix_count = v_head.legacy_prefix_count
and head.legacy_prefix_seal = v_head.legacy_prefix_seal;
get diagnostics v_updated = row_count;
if v_updated <> 1 then
raise exception 'audit checkpoint compare-and-set failed' using errcode = '40001';
end if;
return query select p_event_id, v_sequence, v_hash, p_occurred_at;
end
$append$;
alter function modelforge_audit.append_event_v2(
uuid, timestamptz, text, text, text, text, text, text, text, jsonb,
bigint, bigint, text, text, bigint, bigint, text
) owner to modelforge;
revoke all on function modelforge_audit.append_event_v2(
uuid, timestamptz, text, text, text, text, text, text, text, jsonb,
bigint, bigint, text, text, bigint, bigint, text
) from public;
grant execute on function modelforge_audit.append_event_v2(
uuid, timestamptz, text, text, text, text, text, text, text, jsonb,
bigint, bigint, text, text, bigint, bigint, text
) to modelforge_runtime;
grant select, insert, update, delete on all tables in schema public to modelforge_runtime;
grant usage, select on all sequences in schema public to modelforge_runtime;
revoke execute on all functions in schema public from public, modelforge_runtime;
revoke insert, update, delete, truncate, references, trigger
on public.audit_events, public.audit_chain_heads from modelforge_runtime;
grant select on public.audit_events, public.audit_chain_heads to modelforge_runtime;
alter default privileges for role modelforge in schema public
grant select, insert, update, delete on tables to modelforge_runtime;
alter default privileges for role modelforge in schema public
grant usage, select on sequences to modelforge_runtime;
alter default privileges for role modelforge in schema public
revoke execute on functions from public;
do $database_privileges$
begin
execute pg_catalog.format(
'revoke create, temporary on database %I from modelforge_runtime',
pg_catalog.current_database()
);
end
$database_privileges$;
"""
def _normalise_timestamp(value: datetime | str) -> str:
if isinstance(value, datetime):
moment = value
elif isinstance(value, str):
candidate = value.strip()
if candidate.endswith("Z"):
candidate = candidate[:-1] + "+00:00"
try:
moment = datetime.fromisoformat(candidate)
except ValueError as error:
raise RuntimeError("legacy audit event has an invalid occurred_at") from error
else:
raise RuntimeError("legacy audit event has an invalid occurred_at")
if moment.tzinfo is None:
moment = moment.replace(tzinfo=UTC)
return moment.astimezone(UTC).isoformat(timespec="microseconds").replace("+00:00", "Z")
def _details(value: Any) -> dict[str, Any]:
if isinstance(value, str):
try:
value = json.loads(value)
except json.JSONDecodeError as error:
raise RuntimeError("legacy audit event details are not valid JSON") from error
if not isinstance(value, dict):
raise RuntimeError("legacy audit event details must be a JSON object")
return value
def _legacy_hash(row: sa.RowMapping) -> str:
payload = {
"correlation_id": row["correlation_id"],
"actor_type": row["actor_type"],
"actor_id": row["actor_id"],
"action": row["action"],
"resource_type": row["resource_type"],
"resource_id": row["resource_id"],
"outcome": row["outcome"],
"details": _details(row["details"]),
"previous_event_hash": row["previous_event_hash"],
}
encoded = json.dumps(payload, sort_keys=True, separators=(",", ":"))
return hashlib.sha256(encoded.encode("utf-8")).hexdigest()
def _prefix_entry(row: sa.RowMapping) -> bytes:
try:
event_id = str(uuid.UUID(str(row["id"])))
except (AttributeError, TypeError, ValueError) as error:
raise RuntimeError("legacy audit event id is not a UUID") from error
payload = {
"sequence": int(row["sequence"]),
"id": event_id,
"occurred_at": _normalise_timestamp(row["occurred_at"]),
"event_hash": str(row["event_hash"]),
}
return json.dumps(payload, sort_keys=True, separators=(",", ":")).encode("utf-8") + b"\n"
def _validate_legacy_rows(rows: list[sa.RowMapping]) -> dict[str, Any]:
"""Validate the production-shaped v1 chain and return its immutable cutover state."""
previous_hash: str | None = None
prefix = hashlib.sha256()
prefix.update(_LEGACY_PREFIX_DOMAIN)
for expected_sequence, row in enumerate(rows, start=1):
try:
sequence = int(row["sequence"])
except (TypeError, ValueError) as error:
raise RuntimeError("legacy audit event sequence is not an integer") from error
if sequence != expected_sequence:
raise RuntimeError(
f"legacy audit chain has sequence {sequence}; expected {expected_sequence}"
)
event_hash = str(row["event_hash"])
if _SHA256.fullmatch(event_hash) is None:
raise RuntimeError(f"legacy audit event {sequence} has a malformed event hash")
if row["previous_event_hash"] != previous_hash:
raise RuntimeError(f"legacy audit event {sequence} has an invalid previous hash")
if _legacy_hash(row) != event_hash:
raise RuntimeError(f"legacy audit event {sequence} content hash is invalid")
prefix.update(_prefix_entry(row))
previous_hash = event_hash
count = len(rows)
return {
"event_count": count,
"last_sequence": count,
"last_event_hash": previous_hash,
"v2_start_sequence": count + 1,
"legacy_prefix_count": count,
"legacy_prefix_seal": prefix.hexdigest(),
}
def _read_and_validate_legacy_chain(connection: Connection) -> dict[str, Any]:
rows = list(
connection.execute(
sa.text(
"select id, sequence, occurred_at, correlation_id, actor_type, actor_id, "
"action, resource_type, resource_id, outcome, details, previous_event_hash, "
"event_hash from audit_events order by sequence, id"
)
).mappings()
)
return _validate_legacy_rows(rows)
def _lock_legacy_audit_chain(connection: Connection) -> None:
"""Serialize validation and checkpoint seed against every legacy writer.
The shared advisory key coordinates with 0024-aware writers. ``ACCESS EXCLUSIVE`` also blocks
pre-0024 applications, which do not know that key, until this migration transaction commits.
SQLite is test-only; a no-op write upgrades its deferred transaction to a writer before the
validation read so another test connection cannot append into the validation/seed window.
"""
dialect = connection.dialect.name
if dialect == "postgresql":
connection.execute(
sa.text("select pg_advisory_xact_lock(:lock_key)"),
{"lock_key": _AUDIT_CHAIN_LOCK_KEY},
)
connection.execute(sa.text("lock table audit_events in access exclusive mode"))
return
if dialect == "sqlite":
connection.execute(sa.text("update audit_events set event_hash = event_hash where 1 = 0"))
return
raise RuntimeError(f"audit-chain migration does not support the {dialect!r} dialect")
def _validate_postgres_role_preflight(connection: Connection) -> None:
"""Require the separately provisioned non-superuser owner/runtime roles before DDL.
Existing 1.2.1 installations commonly made ``modelforge`` the cluster bootstrap superuser.
That credential cannot be converted into the API boundary implicitly by an application
migration. Operators must first run the documented admin-owned provisioning step; failure is
deliberately before this migration changes a column or seeds a checkpoint.
"""
if connection.dialect.name != "postgresql":
return
roles = list(
connection.execute(
sa.text(
"select rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, "
"rolcanlogin, rolreplication, rolbypassrls from pg_catalog.pg_roles "
"where rolname in (:owner_role, :runtime_role) order by rolname"
),
{"owner_role": _AUDIT_OWNER_ROLE, "runtime_role": _AUDIT_RUNTIME_ROLE},
).mappings()
)
by_name = {str(row["rolname"]): row for row in roles}
if set(by_name) != {_AUDIT_OWNER_ROLE, _AUDIT_RUNTIME_ROLE}:
raise RuntimeError(
"audit migration preflight requires separately provisioned modelforge owner and "
"modelforge_runtime roles; run the v1.2.1-to-schema-0024 role provisioning step"
)
current_role = str(connection.scalar(sa.text("select current_user")))
if current_role != _AUDIT_OWNER_ROLE:
raise RuntimeError(
"audit migration must run with the non-superuser modelforge owner credential"
)
session_role = str(connection.scalar(sa.text("select session_user")))
if session_role != _AUDIT_OWNER_ROLE:
raise RuntimeError(
"audit migration must authenticate directly as modelforge, not SET ROLE from admin"
)
for role_name, require_noinherit in (
(_AUDIT_OWNER_ROLE, False),
(_AUDIT_RUNTIME_ROLE, True),
):
role = by_name[role_name]
forbidden = any(
bool(role[field])
for field in (
"rolsuper",
"rolcreaterole",
"rolcreatedb",
"rolreplication",
"rolbypassrls",
)
)
if forbidden or not bool(role["rolcanlogin"]):
raise RuntimeError(f"database role {role_name} has forbidden administrative powers")
if require_noinherit and bool(role["rolinherit"]):
raise RuntimeError("modelforge_runtime must be provisioned NOINHERIT")
app_role_membership_count = int(
connection.scalar(
sa.text(
"select count(*) from pg_catalog.pg_auth_members as membership "
"join pg_catalog.pg_roles as member on member.oid = membership.member "
"where member.rolname in (:runtime_role, :owner_role)"
),
{"runtime_role": _AUDIT_RUNTIME_ROLE, "owner_role": _AUDIT_OWNER_ROLE},
)
or 0
)
if app_role_membership_count:
raise RuntimeError(
"modelforge and modelforge_runtime must have no SET ROLE-capable memberships"
)
def _install_postgres_audit_boundary(connection: Connection) -> None:
if connection.dialect.name == "postgresql":
for statement in _postgres_sql_statements(_POSTGRES_AUDIT_BOUNDARY_SQL):
_exec_postgres_sql(connection, statement)
def _exec_postgres_sql(connection: Connection, statement: str) -> None:
"""Execute trusted static SQL without exposing PostgreSQL percent syntax to DBAPI parsing."""
paramstyle = getattr(connection.dialect, "paramstyle", None)
driver_statement = (
statement.replace("%", "%%")
if paramstyle in {"format", "pyformat"}
else statement
)
connection.exec_driver_sql(driver_statement)
def _postgres_sql_statements(script: str) -> list[str]:
"""Split this migration's trusted static SQL without splitting function bodies."""
statements: list[str] = []
start = 0
index = 0
quote: str | None = None
while index < len(script):
if quote is not None:
if quote == "'" and script.startswith("''", index):
index += 2
continue
if script.startswith(quote, index):
index += len(quote)
quote = None
continue
index += 1
continue
character = script[index]
if character == "'":
quote = "'"
index += 1
continue
if character == "$":
delimiter = re.match(r"\$[A-Za-z_][A-Za-z0-9_]*\$|\$\$", script[index:])
if delimiter is not None:
quote = delimiter.group(0)
index += len(quote)
continue
if character == ";":
statement = script[start:index].strip()
if statement:
statements.append(statement)
start = index + 1
index += 1
trailing = script[start:].strip()
if quote is not None:
raise RuntimeError("generated PostgreSQL audit boundary SQL has an unterminated literal")
if trailing:
statements.append(trailing)
return statements
def _remove_postgres_audit_boundary(connection: Connection) -> None:
if connection.dialect.name != "postgresql":
return
script = (
"drop trigger if exists trg_modelforge_audit_events_owner on public.audit_events; "
"drop trigger if exists trg_modelforge_audit_events_truncate_owner "
"on public.audit_events; "
"drop trigger if exists trg_modelforge_audit_head_owner on public.audit_chain_heads; "
"drop trigger if exists trg_modelforge_audit_head_truncate_owner "
"on public.audit_chain_heads; "
"drop function if exists modelforge_audit.append_event_v2("
"uuid, timestamptz, text, text, text, text, text, text, text, jsonb, "
"bigint, bigint, text, text, bigint, bigint, text); "
"drop function if exists modelforge_audit.enforce_owner_mutation(); "
"drop schema if exists modelforge_audit; "
"grant select, insert on public.audit_events to modelforge_runtime"
)
for statement in _postgres_sql_statements(script):
_exec_postgres_sql(connection, statement)
def upgrade() -> None:
connection = op.get_bind()
_validate_postgres_role_preflight(connection)
_lock_legacy_audit_chain(connection)
# Validation deliberately precedes every schema mutation. In particular, legacy recovery
# markers written with a random hash stop the migration instead of being blessed by a seal.
legacy = _read_and_validate_legacy_chain(connection)
op.add_column(
"audit_events",
sa.Column("hash_format", sa.String(length=16), nullable=True),
)
op.add_column(
"audit_events",
sa.Column("canonical_payload", sa.Text(), nullable=True),
)
connection.execute(sa.text("update audit_events set hash_format = 'v1'"))
with op.batch_alter_table("audit_events") as batch:
batch.alter_column(
"hash_format",
existing_type=sa.String(length=16),
nullable=False,
)
batch.create_check_constraint(
"ck_audit_event_hash_format", "hash_format IN ('v1', 'v2')"
)
batch.create_check_constraint(
"ck_audit_event_canonical_payload",
"((hash_format = 'v1' AND canonical_payload IS NULL) OR "
"(hash_format = 'v2' AND canonical_payload IS NOT NULL))",
)
op.create_table(
"audit_chain_heads",
sa.Column("singleton_id", sa.Integer(), nullable=False),
sa.Column("event_count", sa.BigInteger(), nullable=False),
sa.Column("last_sequence", sa.BigInteger(), nullable=False),
sa.Column("last_event_hash", sa.String(length=64), nullable=True),
sa.Column("hash_format", sa.String(length=16), nullable=False),
sa.Column("v2_start_sequence", sa.BigInteger(), nullable=False),
sa.Column("legacy_prefix_count", sa.BigInteger(), nullable=False),
sa.Column("legacy_prefix_seal", sa.String(length=64), nullable=False),
sa.Column(
"updated_at",
sa.DateTime(timezone=True),
server_default=sa.text("CURRENT_TIMESTAMP"),
nullable=False,
),
sa.CheckConstraint("singleton_id = 1", name="ck_audit_chain_head_singleton"),
sa.CheckConstraint("event_count >= 0", name="ck_audit_chain_head_count"),
sa.CheckConstraint("last_sequence >= 0", name="ck_audit_chain_head_sequence"),
sa.CheckConstraint(
"event_count = last_sequence",
name="ck_audit_chain_head_count_sequence",
),
sa.CheckConstraint("v2_start_sequence >= 1", name="ck_audit_chain_head_cutover"),
sa.CheckConstraint(
"legacy_prefix_count = v2_start_sequence - 1",
name="ck_audit_chain_head_prefix_count",
),
sa.CheckConstraint(
"legacy_prefix_count <= event_count",
name="ck_audit_chain_head_prefix_within_chain",
),
sa.CheckConstraint("hash_format = 'v2'", name="ck_audit_chain_head_hash_format"),
sa.CheckConstraint(
"length(legacy_prefix_seal) = 64",
name="ck_audit_chain_head_prefix_seal",
),
sa.CheckConstraint(
"((event_count = 0 AND last_sequence = 0 AND last_event_hash IS NULL) OR "
"(event_count > 0 AND last_sequence > 0 AND last_event_hash IS NOT NULL))",
name="ck_audit_chain_head_shape",
),
sa.PrimaryKeyConstraint("singleton_id"),
)
connection.execute(
sa.text(
"insert into audit_chain_heads (singleton_id, event_count, last_sequence, "
"last_event_hash, hash_format, v2_start_sequence, legacy_prefix_count, "
"legacy_prefix_seal) values (1, :event_count, :last_sequence, :last_event_hash, "
"'v2', :v2_start_sequence, :legacy_prefix_count, :legacy_prefix_seal)"
),
legacy,
)
_install_postgres_audit_boundary(connection)
def downgrade() -> None:
connection = op.get_bind()
_validate_postgres_role_preflight(connection)
_lock_legacy_audit_chain(connection)
non_legacy = int(
connection.scalar(
sa.text("select count(*) from audit_events where hash_format <> 'v1'")
)
or 0
)
if non_legacy:
raise RuntimeError(
"cannot downgrade audit hash format after v2 events exist without rewriting history"
)
legacy = _read_and_validate_legacy_chain(connection)
head = connection.execute(
sa.text(
"select event_count, last_sequence, last_event_hash, hash_format, "
"v2_start_sequence, legacy_prefix_count, legacy_prefix_seal "
"from audit_chain_heads where singleton_id = 1"
)
).mappings().one_or_none()
if head is None or head["hash_format"] != "v2":
raise RuntimeError("cannot downgrade a missing or malformed audit checkpoint")
for key, expected in legacy.items():
if head[key] != expected:
raise RuntimeError(f"cannot downgrade: audit checkpoint {key} is inconsistent")
_remove_postgres_audit_boundary(connection)
op.drop_table("audit_chain_heads")
with op.batch_alter_table("audit_events") as batch:
batch.drop_constraint("ck_audit_event_canonical_payload", type_="check")
batch.drop_constraint("ck_audit_event_hash_format", type_="check")
batch.drop_column("canonical_payload")
batch.drop_column("hash_format")
+65
View File
@@ -0,0 +1,65 @@
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"
[project]
name = "modelforge-api"
version = "1.2.2"
description = "ITWorx ModelForge control-plane API"
license = "AGPL-3.0-or-later"
requires-python = ">=3.12"
dependencies = [
"fastapi==0.141.1",
"uvicorn[standard]==0.52.4",
"pydantic==2.13.4",
"pydantic-settings==2.15.0",
"sqlalchemy==2.0.52",
"alembic==1.19.1",
"psycopg[binary]==3.3.4",
"cryptography==50.0.1",
"redis==6.4.0",
"httpx==0.28.1",
"huggingface-hub==1.29.0",
"nvidia-ml-py==13.610.43",
"psutil==7.2.2",
"pyyaml==6.0.3",
"structlog==25.5.0",
]
[project.optional-dependencies]
dev = [
"pytest==8.4.2",
"pytest-asyncio==1.4.0",
"ruff==0.16.5",
"mypy==1.20.2",
]
[tool.hatch.build.targets.wheel]
packages = ["src/modelforge_api"]
[tool.pytest.ini_options]
pythonpath = ["src"]
testpaths = ["tests"]
[tool.mypy]
# Strict, and analysed for the platform the images actually run on.
#
# Both of these were assumed rather than configured. There was no [tool.mypy] section at all, so
# `mypy src` ran with defaults while every milestone report described it as strict; and it analysed
# the developer's platform, so a Windows-only winreg branch type-checked cleanly here and failed in
# CI on identical code. Turning strict on cost three stale `type: ignore` comments.
strict = true
platform = "linux"
[tool.ruff]
line-length = 100
target-version = "py312"
[tool.ruff.lint]
extend-select = ["B", "BLE", "I", "S", "SIM", "UP"]
ignore = ["B008"]
[tool.ruff.lint.per-file-ignores]
"tests/*" = ["S101"]
"src/modelforge_api/settings.py" = ["S104"]
"src/modelforge_api/hardware/collectors.py" = ["B023"]
+11
View File
@@ -0,0 +1,11 @@
"""ITWorx ModelForge control plane.
The version is derived from the repository's VERSION file rather than declared again here. Four
independent copies of the product version is three chances to publish a release that misdescribes
itself, which is exactly the class of mistake a release gate is supposed to make impossible.
"""
from modelforge_api.domain.release import PRODUCT_VERSION
__all__ = ["__version__"]
__version__ = PRODUCT_VERSION
@@ -0,0 +1,208 @@
from __future__ import annotations
import hmac
import re
from dataclasses import dataclass
from enum import StrEnum
from typing import Annotated
from fastapi import Depends, Header, HTTPException, Request
from modelforge_api.settings import Settings, get_settings
class PrincipalRole(StrEnum):
"""Closed operator-console roles, ordered from read-only to security administration."""
VIEWER = "viewer"
OPERATOR = "operator"
ADMIN = "admin"
class AccessBoundary(StrEnum):
"""Mutually exclusive request authentication boundaries.
The control-plane boundary is deliberately the default. New endpoints therefore fail closed
behind the human operator credential until their narrower machine/public policy is explicitly
recorded here and in the route inventory tests.
"""
PUBLIC = "public"
SINGLE_USE_ENROLLMENT = "single_use_enrollment"
NODE = "node"
CAPABILITY_CLIENT = "capability_client"
CONTROL_PLANE = "control_plane"
_PUBLIC_REQUESTS = {
("GET", "/"),
("GET", "/api/v1/health/live"),
("GET", "/api/v1/health/ready"),
("GET", "/api/v1/version"),
}
_INTERACTIVE_API_PATHS = {"/docs", "/docs/oauth2-redirect", "/redoc", "/openapi.json"}
_NODE_REQUESTS = {
("POST", "/api/v1/agent/heartbeat"),
("PUT", "/api/v1/agent/inventory"),
("PUT", "/api/v1/agent/telemetry"),
("GET", "/api/v1/agent/artifact-jobs/next"),
("POST", "/api/v1/agent/artifact-jobs/{job_id}/progress"),
("POST", "/api/v1/agent/artifact-jobs/{job_id}/complete"),
("POST", "/api/v1/agent/artifact-jobs/{job_id}/fail"),
("GET", "/api/v1/agent/runtime-probes/next"),
("POST", "/api/v1/agent/runtime-probes/{probe_id}/progress"),
("POST", "/api/v1/agent/runtime-probes/{probe_id}/complete"),
("POST", "/api/v1/agent/runtime-probes/{probe_id}/fail"),
("GET", "/api/v1/agent/serving-jobs/next"),
("POST", "/api/v1/agent/serving-jobs/{job_id}/complete"),
("POST", "/api/v1/agent/serving-jobs/{job_id}/fail"),
("POST", "/api/v1/agent/serving-state"),
}
_NODE_PARAMETERIZED_REQUESTS = (
("POST", re.compile(r"^/api/v1/agent/artifact-jobs/[^/]+/(?:progress|complete|fail)$")),
("POST", re.compile(r"^/api/v1/agent/runtime-probes/[^/]+/(?:progress|complete|fail)$")),
("POST", re.compile(r"^/api/v1/agent/serving-jobs/[^/]+/(?:complete|fail)$")),
)
_CAPABILITY_REQUIREMENTS = {
("POST", "/api/v1/capabilities/rag.embedding@1/invoke"): "rag.embedding@1",
("POST", "/api/v1/capabilities/rag.reranking@1/invoke"): "rag.reranking@1",
("POST", "/api/v1/capabilities/document.ocr@1/invoke"): "document.ocr@1",
("POST", "/api/v1/capabilities/vision.embedding@1/invoke"): "vision.embedding@1",
("POST", "/api/v1/capabilities/speech.transcription@1/invoke"): "speech.transcription@1",
("POST", "/api/v1/capability-experiments/{route_key}/invoke"): "rag.embedding@1",
("POST", "/v1/embeddings"): "rag.embedding@1",
}
_CAPABILITY_CLIENT_REQUESTS = set(_CAPABILITY_REQUIREMENTS)
_CAPABILITY_EXPERIMENT_REQUEST = re.compile(r"^/api/v1/capability-experiments/[^/]+/invoke$")
def access_boundary_for_request(
method: str,
path: str,
) -> AccessBoundary:
"""Classify an HTTP request without reading its body or accepting credential aliases."""
normalized_method = method.upper()
if (normalized_method, path) in _PUBLIC_REQUESTS:
return AccessBoundary.PUBLIC
if path in _INTERACTIVE_API_PATHS:
# These routes exist only when the FastAPI app is explicitly constructed for development.
# In test/production they must reach routing unauthenticated so the disabled surface is a
# genuine 404 rather than an operator-auth challenge that reveals a hidden endpoint.
return AccessBoundary.PUBLIC
if normalized_method == "POST" and path == "/api/v1/agent/enroll":
return AccessBoundary.SINGLE_USE_ENROLLMENT
if (normalized_method, path) in _NODE_REQUESTS or any(
normalized_method == rule_method and pattern.fullmatch(path)
for rule_method, pattern in _NODE_PARAMETERIZED_REQUESTS
):
return AccessBoundary.NODE
if (normalized_method, path) in _CAPABILITY_CLIENT_REQUESTS or (
normalized_method == "POST" and _CAPABILITY_EXPERIMENT_REQUEST.fullmatch(path)
):
return AccessBoundary.CAPABILITY_CLIENT
return AccessBoundary.CONTROL_PLANE
def required_capability_for_request(method: str, path: str) -> str | None:
"""Return the exact capability scope for a registered project-facing route."""
normalized_method = method.upper()
requirement = _CAPABILITY_REQUIREMENTS.get((normalized_method, path))
if requirement is not None:
return requirement
if normalized_method == "POST" and _CAPABILITY_EXPERIMENT_REQUEST.fullmatch(path):
return "rag.embedding@1"
return None
def request_body_limit_bytes(settings: Settings, boundary: AccessBoundary) -> int:
"""Select an explicit pre-parser body limit for every request boundary."""
if boundary is AccessBoundary.CAPABILITY_CLIENT:
return settings.gateway_max_payload_bytes
if boundary in {AccessBoundary.NODE, AccessBoundary.SINGLE_USE_ENROLLMENT}:
return settings.node_agent_max_payload_bytes
return settings.control_plane_max_payload_bytes
_ROLE_RANK = {
PrincipalRole.VIEWER: 0,
PrincipalRole.OPERATOR: 1,
PrincipalRole.ADMIN: 2,
}
@dataclass(frozen=True, slots=True)
class Principal:
"""An authenticated human/control-plane principal.
Node and capability credentials deliberately never become a ``Principal``. Their separate
dependencies remain the only way into node-agent and inference surfaces, preventing credential
confusion at the type and dependency boundaries.
"""
subject: str
role: PrincipalRole
authentication_method: str
def permits(self, required: PrincipalRole) -> bool:
return _ROLE_RANK[self.role] >= _ROLE_RANK[required]
def authenticate_operator_token(settings: Settings, token: str | None) -> Principal:
"""Authenticate the backward-compatible operator key as an admin principal.
The legacy key is the sole human credential in this release. OIDC/session authentication can
add another principal producer later without changing route authorization policy.
"""
configured = settings.operator_api_key
if configured is None or not configured.get_secret_value():
raise HTTPException(status_code=503, detail="operator API authentication is not configured")
if token is None or not hmac.compare_digest(token, configured.get_secret_value()):
raise HTTPException(status_code=401, detail="invalid operator credential")
return Principal(
subject="legacy-operator",
role=PrincipalRole.ADMIN,
authentication_method="legacy_admin_token",
)
def authenticate_operator_principal(
request: Request,
settings: Annotated[Settings, Depends(get_settings)],
token: Annotated[str | None, Header(alias="X-ModelForge-Admin-Token")] = None,
) -> Principal:
"""Reuse the pre-body principal and retain a safe direct-dependency fallback."""
principal = getattr(request.state, "principal", None)
if isinstance(principal, Principal):
return principal
return authenticate_operator_token(settings, token)
AuthenticatedPrincipal = Annotated[Principal, Depends(authenticate_operator_principal)]
def _require_role(principal: Principal, required: PrincipalRole) -> Principal:
if not principal.permits(required):
raise HTTPException(status_code=403, detail="operator role is not authorized")
return principal
def require_viewer(principal: AuthenticatedPrincipal) -> Principal:
return _require_role(principal, PrincipalRole.VIEWER)
def require_operator(principal: AuthenticatedPrincipal) -> Principal:
return _require_role(principal, PrincipalRole.OPERATOR)
def require_admin(principal: AuthenticatedPrincipal) -> Principal:
return _require_role(principal, PrincipalRole.ADMIN)
Viewer = Annotated[Principal, Depends(require_viewer)]
Operator = Annotated[Principal, Depends(require_operator)]
Admin = Annotated[Principal, Depends(require_admin)]
@@ -0,0 +1,205 @@
from __future__ import annotations
from dataclasses import dataclass
from starlette.responses import JSONResponse
from starlette.types import ASGIApp, Message, Receive, Scope, Send
MAX_CONSECUTIVE_EMPTY_REQUEST_EVENTS = 32
MAX_TOTAL_EMPTY_REQUEST_EVENTS = 128
MAX_REQUEST_BODY_EVENTS = 4096
@dataclass(frozen=True, slots=True)
class RequestBodyTooLarge(Exception):
limit_bytes: int
@dataclass(frozen=True, slots=True)
class RequestBodyProgressExhausted(Exception):
received_events: int
empty_events: int
@dataclass(frozen=True, slots=True)
class InvalidContentLength(Exception):
message: str
class RequestBodyLimitMiddleware:
"""Reject declared or streamed oversized bodies before application parsing.
The receive wrapper forwards chunks only while the cumulative size is within the configured
boundary. It raises as soon as the next chunk crosses the limit and never drains or buffers the
remaining request body. It also rejects a body stream that exceeds the fixed request-event or
empty-progress budgets, preventing an immediately-ready sequence of empty ASGI frames from
spinning indefinitely without yielding useful input.
"""
def __init__(self, app: ASGIApp) -> None:
self.app = app
@staticmethod
def _declared_length(scope: Scope) -> int | None:
values = [
value for name, value in scope.get("headers", []) if name.lower() == b"content-length"
]
if not values:
return None
if len(values) != 1:
raise ValueError("multiple content-length headers are not accepted")
try:
rendered = values[0].decode("ascii")
if not rendered.isdecimal():
raise ValueError("content-length must be an unsigned decimal integer")
return int(rendered)
except UnicodeDecodeError as exc:
raise ValueError("content-length must be ASCII") from exc
@staticmethod
async def _error_response(
scope: Scope,
receive: Receive,
send: Send,
*,
status_code: int,
code: str,
message: str,
details: dict[str, int] | None = None,
) -> None:
state = scope.get("state", {})
correlation_id = state.get("correlation_id", "unknown")
response = JSONResponse(
status_code=status_code,
content={
"error": {
"code": code,
"message": message,
"correlation_id": correlation_id,
"details": details or {},
}
},
headers={"X-Correlation-ID": str(correlation_id)},
)
await response(scope, receive, send)
async def __call__(self, scope: Scope, receive: Receive, send: Send) -> None:
if scope["type"] != "http":
await self.app(scope, receive, send)
return
state = scope.setdefault("state", {})
configured_limit: int | None = None
headers_checked = False
received_bytes = 0
received_events = 0
empty_events = 0
consecutive_empty_events = 0
request_error: (
InvalidContentLength | RequestBodyTooLarge | RequestBodyProgressExhausted | None
) = None
async def limited_receive() -> Message:
nonlocal configured_limit, headers_checked
nonlocal consecutive_empty_events, empty_events, received_bytes
nonlocal received_events, request_error
if not headers_checked:
candidate_limit = state.get("request_body_limit_bytes")
if not isinstance(candidate_limit, int) or candidate_limit < 0:
raise RuntimeError("request body limit was not established before routing")
configured_limit = candidate_limit
headers_checked = True
try:
declared_length = self._declared_length(scope)
except ValueError as exc:
request_error = InvalidContentLength(str(exc))
state["request_body_error_status_code"] = 400
raise request_error from exc
if declared_length is not None and declared_length > configured_limit:
request_error = RequestBodyTooLarge(configured_limit)
state["request_body_error_status_code"] = 413
raise request_error
message = await receive()
if message["type"] == "http.request":
if configured_limit is None: # pragma: no cover - guarded before receive
raise RuntimeError("request body limit was not established before routing")
received_events += 1
body = message.get("body", b"")
received_bytes += len(body)
if received_bytes > configured_limit:
request_error = RequestBodyTooLarge(configured_limit)
state["request_body_error_status_code"] = 413
raise request_error
if body:
consecutive_empty_events = 0
elif message.get("more_body", False):
empty_events += 1
consecutive_empty_events += 1
else:
consecutive_empty_events = 0
if (
received_events > MAX_REQUEST_BODY_EVENTS
or empty_events > MAX_TOTAL_EMPTY_REQUEST_EVENTS
or consecutive_empty_events > MAX_CONSECUTIVE_EMPTY_REQUEST_EVENTS
):
request_error = RequestBodyProgressExhausted(
received_events=received_events,
empty_events=empty_events,
)
state["request_body_error_status_code"] = 400
raise request_error
return message
response_started = False
async def tracked_send(message: Message) -> None:
nonlocal response_started
# FastAPI deliberately converts arbitrary request-body receive failures to a generic
# 400. Once this middleware has observed a boundary violation, suppress that parser
# response and emit the boundary's typed response after the inner app unwinds.
if request_error is not None:
return
if message["type"] == "http.response.start":
response_started = True
await send(message)
try:
await self.app(scope, limited_receive, tracked_send)
except (InvalidContentLength, RequestBodyProgressExhausted, RequestBodyTooLarge) as exc:
request_error = exc
if request_error is not None:
if response_started:
raise request_error
if isinstance(request_error, InvalidContentLength):
await self._error_response(
scope,
receive,
send,
status_code=400,
code="invalid_content_length",
message=request_error.message,
)
elif isinstance(request_error, RequestBodyTooLarge):
await self._error_response(
scope,
receive,
send,
status_code=413,
code="request_body_too_large",
message="Request body exceeds the permitted boundary",
details={"limit_bytes": request_error.limit_bytes},
)
else:
await self._error_response(
scope,
receive,
send,
status_code=400,
code="request_body_progress_exhausted",
message="Request body made insufficient bounded progress",
details={
"max_consecutive_empty_events": MAX_CONSECUTIVE_EMPTY_REQUEST_EVENTS,
"max_empty_events": MAX_TOTAL_EMPTY_REQUEST_EVENTS,
"max_request_events": MAX_REQUEST_BODY_EVENTS,
},
)
@@ -0,0 +1,134 @@
from __future__ import annotations
import uuid
from typing import Annotated
from fastapi import APIRouter, Depends, Query
from sqlalchemy.orm import Session
from modelforge_api.api.authorization import require_admin, require_operator, require_viewer
from modelforge_api.db import get_session
from modelforge_api.domain.acquisition import (
ArtifactJobResponse,
ArtifactSetResponse,
DiscoveryCandidate,
DiscoverySearchRequest,
DownloadPlanCreate,
DownloadPlanResponse,
UpstreamRefreshRequest,
UpstreamSnapshotResponse,
)
from modelforge_api.providers.huggingface import OfficialHuggingFaceProvider
from modelforge_api.services.acquisition import AcquisitionService
from modelforge_api.settings import Settings, get_settings
router = APIRouter(
prefix="/api/v1",
tags=["artifact-acquisition"],
dependencies=[Depends(require_viewer)],
)
def get_acquisition_service(
session: Annotated[Session, Depends(get_session)],
settings: Annotated[Settings, Depends(get_settings)],
) -> AcquisitionService:
token = settings.hf_token.get_secret_value() if settings.hf_token else None
provider = OfficialHuggingFaceProvider(token=token, timeout=settings.hf_timeout_seconds)
return AcquisitionService(session, settings, provider)
Service = Annotated[AcquisitionService, Depends(get_acquisition_service)]
@router.post(
"/discovery/search",
response_model=list[DiscoveryCandidate],
dependencies=[Depends(require_operator)],
)
def search(request: DiscoverySearchRequest, service: Service) -> list[DiscoveryCandidate]:
return service.search(request)
@router.post(
"/models/{model_id}/refresh-upstream",
response_model=UpstreamSnapshotResponse,
dependencies=[Depends(require_operator)],
)
def refresh_upstream(
model_id: uuid.UUID, request: UpstreamRefreshRequest, service: Service
) -> UpstreamSnapshotResponse:
return service.refresh_model(model_id, request.revision)
@router.get("/models/{model_id}/upstream", response_model=UpstreamSnapshotResponse)
def model_upstream(model_id: uuid.UUID, service: Service) -> UpstreamSnapshotResponse:
return service.latest_snapshot(model_id)
@router.get("/revisions/{revision_id}/artifact-sets", response_model=list[ArtifactSetResponse])
def artifact_sets(revision_id: uuid.UUID, service: Service) -> list[ArtifactSetResponse]:
return service.artifact_sets(revision_id)
@router.post(
"/download-plans",
response_model=DownloadPlanResponse,
status_code=201,
dependencies=[Depends(require_operator)],
)
def create_download_plan(request: DownloadPlanCreate, service: Service) -> DownloadPlanResponse:
return service.create_plan(request)
@router.get("/download-plans/{plan_id}", response_model=DownloadPlanResponse)
def download_plan(plan_id: uuid.UUID, service: Service) -> DownloadPlanResponse:
return service.plan_response(plan_id)
@router.post(
"/download-plans/{plan_id}/approve",
response_model=DownloadPlanResponse,
dependencies=[Depends(require_admin)],
)
def approve_download_plan(plan_id: uuid.UUID, service: Service) -> DownloadPlanResponse:
return service.approve_plan(plan_id)
@router.post(
"/download-plans/{plan_id}/execute",
response_model=ArtifactJobResponse,
dependencies=[Depends(require_operator)],
)
def execute_download_plan(plan_id: uuid.UUID, service: Service) -> ArtifactJobResponse:
return service.execute_plan(plan_id)
@router.get("/artifact-jobs", response_model=list[ArtifactJobResponse])
def artifact_jobs(
service: Service, limit: Annotated[int, Query(ge=1, le=100)] = 100
) -> list[ArtifactJobResponse]:
return service.jobs()[:limit]
@router.get("/artifact-jobs/{job_id}", response_model=ArtifactJobResponse)
def artifact_job(job_id: uuid.UUID, service: Service) -> ArtifactJobResponse:
return service.job(job_id)
@router.post(
"/artifact-jobs/{job_id}/cancel",
response_model=ArtifactJobResponse,
dependencies=[Depends(require_operator)],
)
def cancel_artifact_job(job_id: uuid.UUID, service: Service) -> ArtifactJobResponse:
return service.cancel(job_id)
@router.post(
"/artifact-jobs/{job_id}/retry",
response_model=ArtifactJobResponse,
dependencies=[Depends(require_operator)],
)
def retry_artifact_job(job_id: uuid.UUID, service: Service) -> ArtifactJobResponse:
return service.retry(job_id)
@@ -0,0 +1,371 @@
from __future__ import annotations
import time
import uuid
from collections import defaultdict, deque
from typing import Annotated
from fastapi import APIRouter, Depends, Header, HTTPException, Request, Response, status
from pydantic import BaseModel
from sqlalchemy.orm import Session
from modelforge_api.api.authorization import Admin
from modelforge_api.db import get_session
from modelforge_api.domain.acquisition import (
AgentArtifactJobLease,
AgentJobComplete,
AgentJobControl,
AgentJobFailure,
AgentJobProgress,
ArtifactJobResponse,
)
from modelforge_api.domain.agent_protocol import (
EnrollmentRequest,
EnrollmentResponse,
EnrollmentTokenCreate,
EnrollmentTokenCreated,
EnrollmentTokenSummary,
HeartbeatRequest,
InventoryReport,
NodeCredentialCreated,
NodeManagementUpdate,
ObservationAck,
TelemetryReport,
)
from modelforge_api.domain.node_decommission import (
NodeDecommissionExecute,
NodeDecommissionPreview,
NodeDecommissionResult,
)
from modelforge_api.domain.runtime import (
AgentRuntimeProbeComplete,
AgentRuntimeProbeControl,
AgentRuntimeProbeFailure,
AgentRuntimeProbeLease,
AgentRuntimeProbeProgress,
RuntimeProbeResponse,
)
from modelforge_api.persistence.models import ComputeNode, NodeCredential
from modelforge_api.providers.huggingface import OfficialHuggingFaceProvider
from modelforge_api.services.acquisition import AcquisitionService
from modelforge_api.services.node_agent import NodeAgentService, NodeAuthenticationEvidence
from modelforge_api.services.node_decommission import NodeDecommissionService
from modelforge_api.services.runtime import RuntimeService
from modelforge_api.settings import Settings, get_settings
router = APIRouter(tags=["node-agent"])
class ActionResponse(BaseModel):
status: str = "ok"
class AttemptLimiter:
def __init__(self, limit: int = 10, window_seconds: int = 60) -> None:
self.limit = limit
self.window_seconds = window_seconds
self.attempts: dict[str, deque[float]] = defaultdict(deque)
def check(self, key: str) -> None:
now = time.monotonic()
bucket = self.attempts[key]
while bucket and bucket[0] <= now - self.window_seconds:
bucket.popleft()
if len(bucket) >= self.limit:
raise HTTPException(status_code=429, detail="enrollment rate limit exceeded")
bucket.append(now)
enrollment_limiter = AttemptLimiter()
def get_agent_service(
session: Annotated[Session, Depends(get_session)],
settings: Annotated[Settings, Depends(get_settings)],
) -> NodeAgentService:
return NodeAgentService(session, settings)
Service = Annotated[NodeAgentService, Depends(get_agent_service)]
def get_decommission_service(
session: Annotated[Session, Depends(get_session)],
) -> NodeDecommissionService:
return NodeDecommissionService(session)
DecommissionService = Annotated[NodeDecommissionService, Depends(get_decommission_service)]
@router.post(
"/api/v1/admin/node-enrollments",
response_model=EnrollmentTokenCreated,
status_code=status.HTTP_201_CREATED,
)
def create_enrollment(
request: EnrollmentTokenCreate, service: Service, _admin: Admin, response: Response
) -> EnrollmentTokenCreated:
response.headers["Cache-Control"] = "no-store"
return service.create_enrollment(request)
@router.get("/api/v1/admin/node-enrollments", response_model=list[EnrollmentTokenSummary])
def list_enrollments(service: Service, _admin: Admin) -> list[EnrollmentTokenSummary]:
return [
EnrollmentTokenSummary(
id=str(item.id),
created_at=item.created_at,
expires_at=item.expires_at,
used_at=item.used_at,
revoked_at=item.revoked_at,
)
for item in service.repository.enrollments()
]
@router.delete("/api/v1/admin/node-enrollments/{enrollment_id}", response_model=ActionResponse)
def revoke_enrollment(enrollment_id: uuid.UUID, service: Service, _admin: Admin) -> ActionResponse:
service.revoke_enrollment(enrollment_id)
return ActionResponse()
@router.patch("/api/v1/admin/hardware/nodes/{node_id}", response_model=ActionResponse)
def update_node(
node_id: uuid.UUID,
request: NodeManagementUpdate,
service: Service,
_admin: Admin,
) -> ActionResponse:
service.update_node(node_id, request)
return ActionResponse()
@router.post(
"/api/v1/admin/hardware/nodes/{node_id}/decommission/preview",
response_model=NodeDecommissionPreview,
)
def preview_node_decommission(
node_id: uuid.UUID, service: DecommissionService, _admin: Admin
) -> NodeDecommissionPreview:
return service.preview(node_id)
@router.post(
"/api/v1/admin/hardware/nodes/{node_id}/decommission",
response_model=NodeDecommissionResult,
)
def execute_node_decommission(
node_id: uuid.UUID,
request: NodeDecommissionExecute,
service: DecommissionService,
_admin: Admin,
) -> NodeDecommissionResult:
return service.execute(node_id, request)
@router.delete("/api/v1/admin/hardware/nodes/{node_id}/credential", response_model=ActionResponse)
def revoke_credential(node_id: uuid.UUID, service: Service, _admin: Admin) -> ActionResponse:
service.revoke_credential(node_id)
return ActionResponse()
@router.post(
"/api/v1/admin/hardware/nodes/{node_id}/credential/rotate",
response_model=NodeCredentialCreated,
)
def rotate_credential(
node_id: uuid.UUID, service: Service, _admin: Admin, response: Response
) -> NodeCredentialCreated:
response.headers["Cache-Control"] = "no-store"
return service.rotate_credential(node_id)
@router.post(
"/api/v1/agent/enroll",
response_model=EnrollmentResponse,
status_code=status.HTTP_201_CREATED,
)
def enroll(
request: EnrollmentRequest, http_request: Request, service: Service, response: Response
) -> EnrollmentResponse:
enrollment_limiter.check(http_request.client.host if http_request.client else "unknown")
response.headers["Cache-Control"] = "no-store"
return service.enroll(request)
def authenticated_node(
request: Request,
service: Service,
authorization: Annotated[str | None, Header(alias="Authorization")] = None,
) -> tuple[NodeCredential, ComputeNode]:
evidence = getattr(request.state, "node_authentication", None)
if isinstance(evidence, NodeAuthenticationEvidence):
return service.reuse_authentication(evidence, authorization)
return service.authenticate(authorization)
NodeIdentity = tuple[NodeCredential, ComputeNode]
AgentIdentity = Annotated[NodeIdentity, Depends(authenticated_node)]
def get_agent_acquisition_service(
session: Annotated[Session, Depends(get_session)],
settings: Annotated[Settings, Depends(get_settings)],
) -> AcquisitionService:
token = settings.hf_token.get_secret_value() if settings.hf_token else None
return AcquisitionService(
session,
settings,
OfficialHuggingFaceProvider(token=token, timeout=settings.hf_timeout_seconds),
actor_type="node_agent",
actor_id="authenticated-node",
)
AgentAcquisition = Annotated[AcquisitionService, Depends(get_agent_acquisition_service)]
def get_agent_runtime_service(
session: Annotated[Session, Depends(get_session)],
settings: Annotated[Settings, Depends(get_settings)],
) -> RuntimeService:
return RuntimeService(
session,
settings,
actor_type="runtime_worker",
actor_id="authenticated-node",
)
AgentRuntime = Annotated[RuntimeService, Depends(get_agent_runtime_service)]
@router.post("/api/v1/agent/heartbeat", response_model=ObservationAck)
def heartbeat(
request: HeartbeatRequest, service: Service, identity: AgentIdentity
) -> ObservationAck:
_credential, node = identity
return service.heartbeat(node, request)
@router.put("/api/v1/agent/inventory", response_model=ObservationAck)
def publish_inventory(
request: InventoryReport, service: Service, identity: AgentIdentity
) -> ObservationAck:
_credential, node = identity
return service.publish_inventory(node, request)
@router.put("/api/v1/agent/telemetry", response_model=ObservationAck)
def publish_telemetry(
request: TelemetryReport, service: Service, identity: AgentIdentity
) -> ObservationAck:
_credential, node = identity
return service.publish_telemetry(node, request)
@router.get(
"/api/v1/agent/artifact-jobs/next",
response_model=AgentArtifactJobLease | None,
)
def claim_artifact_job(
acquisition: AgentAcquisition, identity: AgentIdentity
) -> AgentArtifactJobLease | None:
_credential, node = identity
return acquisition.claim_next(node)
@router.post(
"/api/v1/agent/artifact-jobs/{job_id}/progress",
response_model=AgentJobControl,
)
def artifact_job_progress(
job_id: uuid.UUID,
request: AgentJobProgress,
acquisition: AgentAcquisition,
identity: AgentIdentity,
) -> AgentJobControl:
_credential, node = identity
return acquisition.progress(job_id, node, request)
@router.post(
"/api/v1/agent/artifact-jobs/{job_id}/complete",
response_model=ArtifactJobResponse,
)
def artifact_job_complete(
job_id: uuid.UUID,
request: AgentJobComplete,
acquisition: AgentAcquisition,
identity: AgentIdentity,
) -> ArtifactJobResponse:
_credential, node = identity
return acquisition.complete(job_id, node, request)
@router.post(
"/api/v1/agent/artifact-jobs/{job_id}/fail",
response_model=ArtifactJobResponse,
)
def artifact_job_fail(
job_id: uuid.UUID,
request: AgentJobFailure,
acquisition: AgentAcquisition,
identity: AgentIdentity,
) -> ArtifactJobResponse:
_credential, node = identity
return acquisition.fail(job_id, node, request)
@router.get(
"/api/v1/agent/runtime-probes/next",
response_model=AgentRuntimeProbeLease | None,
)
def claim_runtime_probe(
runtime: AgentRuntime, identity: AgentIdentity
) -> AgentRuntimeProbeLease | None:
_credential, node = identity
return runtime.claim_next(node)
@router.post(
"/api/v1/agent/runtime-probes/{probe_id}/progress",
response_model=AgentRuntimeProbeControl,
)
def runtime_probe_progress(
probe_id: uuid.UUID,
request: AgentRuntimeProbeProgress,
runtime: AgentRuntime,
identity: AgentIdentity,
) -> AgentRuntimeProbeControl:
_credential, node = identity
return runtime.progress(probe_id, node, request)
@router.post(
"/api/v1/agent/runtime-probes/{probe_id}/complete",
response_model=RuntimeProbeResponse,
)
def runtime_probe_complete(
probe_id: uuid.UUID,
request: AgentRuntimeProbeComplete,
runtime: AgentRuntime,
identity: AgentIdentity,
) -> RuntimeProbeResponse:
_credential, node = identity
return runtime.complete(probe_id, node, request)
@router.post(
"/api/v1/agent/runtime-probes/{probe_id}/fail",
response_model=RuntimeProbeResponse,
)
def runtime_probe_fail(
probe_id: uuid.UUID,
request: AgentRuntimeProbeFailure,
runtime: AgentRuntime,
identity: AgentIdentity,
) -> RuntimeProbeResponse:
_credential, node = identity
return runtime.fail(probe_id, node, request)
@@ -0,0 +1,273 @@
from fastapi import APIRouter, Depends
from sqlalchemy import select
from sqlalchemy.orm import Session
from modelforge_api.api.authorization import require_viewer
from modelforge_api.db import get_session
from modelforge_api.domain.schemas import (
CapabilityContractResponse,
CapabilityEstateResponse,
InstallationDependencyResponse,
ModelInstallationRationaleResponse,
ProjectBindingResponse,
ProjectResponse,
)
from modelforge_api.persistence.models import (
ArtifactSet,
Capability,
CapabilityContract,
CapabilityDeployment,
CapabilityEvaluationRun,
CapabilityResourceEnvelope,
Model,
ModelRevision,
Project,
ProjectBinding,
ProjectFitEvidence,
ResidencyAllocation,
RuntimeProfile,
ServiceClient,
)
from modelforge_api.services.manifest_registry import ManifestRegistry, get_manifest_registry
router = APIRouter(
prefix="/api/v1", tags=["registry"], dependencies=[Depends(require_viewer)]
)
@router.get("/capabilities", response_model=list[CapabilityContractResponse])
def list_capabilities(
registry: ManifestRegistry = Depends(get_manifest_registry),
session: Session = Depends(get_session),
) -> list[CapabilityContractResponse]:
responses = []
for contract in registry.capabilities():
stable = session.execute(
select(CapabilityDeployment)
.join(
CapabilityContract,
CapabilityContract.id == CapabilityDeployment.capability_contract_id,
)
.join(Capability, Capability.id == CapabilityContract.capability_id)
.where(
Capability.key == contract.capability,
CapabilityContract.version == contract.version,
CapabilityDeployment.status == "stable",
CapabilityDeployment.production.is_(True),
)
).scalar_one_or_none()
responses.append(
CapabilityContractResponse(
key=contract.capability,
version=contract.version,
description=contract.description,
contract=contract,
stable_deployment=(
{
"id": str(stable.id),
"status": stable.status,
"health": stable.health_status,
"production": stable.production,
}
if stable
else None
),
)
)
return responses
@router.get("/capability-estate", response_model=list[CapabilityEstateResponse])
def capability_estate(
registry: ManifestRegistry = Depends(get_manifest_registry),
session: Session = Depends(get_session),
) -> list[CapabilityEstateResponse]:
responses: list[CapabilityEstateResponse] = []
for manifest in registry.capabilities():
contract = session.scalar(
select(CapabilityContract)
.join(Capability, Capability.id == CapabilityContract.capability_id)
.where(Capability.key == manifest.capability, CapabilityContract.version == manifest.version)
)
deployment = None
model = None
revision = None
profile = None
envelope = None
latest_evaluation = None
if contract:
deployment = session.scalar(
select(CapabilityDeployment)
.where(CapabilityDeployment.capability_contract_id == contract.id)
.order_by(
CapabilityDeployment.production.desc(),
CapabilityDeployment.created_at.desc(),
)
.limit(1)
)
if deployment:
artifact_set = session.get(ArtifactSet, deployment.artifact_set_id)
revision = session.get(ModelRevision, artifact_set.revision_id) if artifact_set else None
model = session.get(Model, revision.model_id) if revision else None
profile = session.get(RuntimeProfile, deployment.runtime_profile_id)
envelope = session.scalar(
select(CapabilityResourceEnvelope).where(
CapabilityResourceEnvelope.capability_deployment_id == deployment.id
)
)
latest_evaluation = session.scalar(
select(CapabilityEvaluationRun)
.where(CapabilityEvaluationRun.capability_deployment_id == deployment.id)
.order_by(CapabilityEvaluationRun.created_at.desc())
.limit(1)
)
operational_state = (
deployment.status
if deployment
else ("blocked" if manifest.estate.stability == "blocked" else "not_deployed")
)
responses.append(
CapabilityEstateResponse(
capability=manifest.capability,
version=manifest.version,
category=manifest.estate.category,
purpose=manifest.estate.purpose,
declared_stability=manifest.estate.stability,
operational_state=operational_state,
current_deployment_id=deployment.id if deployment else None,
model=model.display_name if model else None,
revision=revision.resolved_commit_sha if revision else None,
runtime=profile.runtime_type if profile else None,
node=str(deployment.compute_node_id) if deployment else None,
resource_class=manifest.estate.resource_class,
measured_required_vram_bytes=envelope.required_vram_bytes if envelope else None,
consumers=manifest.estate.consumers,
privacy_class=manifest.privacy.classification,
evaluation_type=manifest.estate.evaluation_type,
evaluation_state=latest_evaluation.status if latest_evaluation else "not_evaluated",
)
)
return responses
@router.get(
"/models/installation-rationale",
response_model=list[ModelInstallationRationaleResponse],
)
def model_installation_rationale(
session: Session = Depends(get_session),
) -> list[ModelInstallationRationaleResponse]:
responses: list[ModelInstallationRationaleResponse] = []
for model in session.scalars(select(Model).order_by(Model.display_name)).all():
revisions = session.scalars(
select(ModelRevision).where(ModelRevision.model_id == model.id)
).all()
revision_ids = [item.id for item in revisions]
sets = (
session.scalars(select(ArtifactSet).where(ArtifactSet.revision_id.in_(revision_ids))).all()
if revision_ids
else []
)
installed_sets = [item for item in sets if item.availability == "local"]
dependencies: list[InstallationDependencyResponse] = []
for artifact_set in installed_sets:
deployments = session.scalars(
select(CapabilityDeployment).where(
CapabilityDeployment.artifact_set_id == artifact_set.id
)
).all()
for deployment in deployments:
contract = session.get(CapabilityContract, deployment.capability_contract_id)
capability = session.get(Capability, contract.capability_id) if contract else None
if not contract or not capability:
continue
projects = session.scalars(
select(Project)
.join(ProjectBinding, ProjectBinding.project_id == Project.id)
.where(
ProjectBinding.capability_contract_id == contract.id,
ProjectBinding.deprecated_at.is_(None),
)
.distinct()
).all()
active_projects = session.scalars(
select(Project)
.join(ProjectBinding, ProjectBinding.project_id == Project.id)
.join(ServiceClient, ServiceClient.project_binding_id == ProjectBinding.id)
.where(
ProjectBinding.capability_contract_id == contract.id,
ServiceClient.status == "active",
)
.distinct()
).all()
project_fit_ids = session.scalars(
select(ProjectFitEvidence.id)
.join(
ProjectBinding,
ProjectBinding.id == ProjectFitEvidence.project_binding_id,
)
.where(ProjectBinding.capability_contract_id == contract.id)
).all()
evaluations = session.scalars(
select(CapabilityEvaluationRun).where(
CapabilityEvaluationRun.capability_deployment_id == deployment.id
)
).all()
residency = session.scalar(
select(ResidencyAllocation).where(
ResidencyAllocation.capability_deployment_id == deployment.id
)
)
dependencies.append(
InstallationDependencyResponse(
capability=capability.key,
version=contract.version,
deployment_id=deployment.id,
channel=deployment.channel,
production=deployment.production,
project_consumers=[item.key for item in projects],
active_project_consumers=[item.key for item in active_projects],
project_fit_evidence_ids=list(project_fit_ids),
evaluation_run_ids=[item.id for item in evaluations],
last_used_at=residency.last_used_at if residency else None,
)
)
blockers = []
if dependencies:
blockers.append("capability_deployment_dependency")
if any(item.production for item in dependencies):
blockers.append("production_dependency")
responses.append(
ModelInstallationRationaleResponse(
model_id=model.id,
display_name=model.display_name,
upstream_source=model.upstream_source,
installed=bool(installed_sets),
installed_bytes=sum(item.total_size_bytes for item in installed_sets),
dependencies=dependencies,
can_delete=bool(installed_sets) and not blockers,
deletion_blockers=blockers,
)
)
return responses
@router.get("/projects", response_model=list[ProjectResponse])
def list_projects(
registry: ManifestRegistry = Depends(get_manifest_registry),
) -> list[ProjectResponse]:
return [
ProjectResponse(
**project.project.model_dump(),
notes=project.notes,
bindings=[
ProjectBindingResponse(
capability=capability,
contract_version=binding.contract_version,
binding=binding,
)
for capability, binding in project.bindings.items()
],
)
for project in registry.projects()
]
@@ -0,0 +1,387 @@
from __future__ import annotations
import uuid
from typing import Annotated
from fastapi import APIRouter, Depends, status
from sqlalchemy.orm import Session
from modelforge_api.api.authorization import Admin, require_viewer
from modelforge_api.db import get_session
from modelforge_api.domain.capability_evaluation import (
CapabilityAdvisorResponse,
CapabilityEvaluationRunCreate,
CapabilityEvaluationRunResponse,
CapabilityEvaluationSuiteCreate,
CapabilityEvaluationSuiteResponse,
)
from modelforge_api.domain.evaluation import (
AdvisorPolicyResponse,
AdvisorPolicyUpdate,
AdvisorRecommendationCreate,
AdvisorRecommendationDismiss,
AdvisorRecommendationResponse,
DiscoveryCandidateAssessmentCreate,
DiscoveryCandidateAssessmentResponse,
EmbeddingMigrationCreate,
EmbeddingMigrationResponse,
EvaluationCaseDefinitionResponse,
EvaluationCaseResultResponse,
EvaluationComparisonCreate,
EvaluationComparisonResponse,
EvaluationRunComplete,
EvaluationRunCreate,
EvaluationRunResponse,
EvaluationSuiteCreate,
EvaluationSuiteResponse,
MigrationUpdate,
ModelComparisonCreate,
ModelComparisonResponse,
RerankingCaseResultResponse,
RerankingRunComplete,
RerankingRunCreate,
RerankingRunResponse,
RetrievalCandidatePoolCreate,
RetrievalCandidatePoolResponse,
RetrievalPipelineIdentityCreate,
RetrievalPipelineIdentityResponse,
)
from modelforge_api.services.capability_evaluation import CapabilityEvaluationService
from modelforge_api.services.evaluation import EvaluationService
router = APIRouter(
prefix="/api/v1",
tags=["project-evaluation"],
dependencies=[Depends(require_viewer)],
)
def service(session: Annotated[Session, Depends(get_session)]) -> EvaluationService:
return EvaluationService(session)
Service = Annotated[EvaluationService, Depends(service)]
def capability_service(
session: Annotated[Session, Depends(get_session)],
) -> CapabilityEvaluationService:
return CapabilityEvaluationService(session)
CapabilityService = Annotated[CapabilityEvaluationService, Depends(capability_service)]
@router.get("/capability-advisor", response_model=list[CapabilityAdvisorResponse])
def capability_advisor(svc: CapabilityService) -> list[CapabilityAdvisorResponse]:
return svc.advisor()
@router.get(
"/capability-evaluation-suites",
response_model=list[CapabilityEvaluationSuiteResponse],
)
def capability_suites(svc: CapabilityService) -> list[CapabilityEvaluationSuiteResponse]:
return svc.suites()
@router.post(
"/capability-evaluation-suites",
response_model=CapabilityEvaluationSuiteResponse,
status_code=status.HTTP_201_CREATED,
)
def create_capability_suite(
request: CapabilityEvaluationSuiteCreate, svc: CapabilityService, _admin: Admin
) -> CapabilityEvaluationSuiteResponse:
return svc.create_suite(request)
@router.get(
"/capability-evaluation-runs",
response_model=list[CapabilityEvaluationRunResponse],
)
def capability_runs(svc: CapabilityService) -> list[CapabilityEvaluationRunResponse]:
return svc.runs()
@router.post(
"/capability-evaluation-runs",
response_model=CapabilityEvaluationRunResponse,
status_code=status.HTTP_201_CREATED,
)
def create_capability_run(
request: CapabilityEvaluationRunCreate, svc: CapabilityService, _admin: Admin
) -> CapabilityEvaluationRunResponse:
return svc.create_run(request)
@router.get("/evaluation-suites", response_model=list[EvaluationSuiteResponse])
def suites(svc: Service) -> list[EvaluationSuiteResponse]:
return svc.suites()
@router.post(
"/evaluation-suites",
response_model=EvaluationSuiteResponse,
status_code=status.HTTP_201_CREATED,
)
def create_suite(
request: EvaluationSuiteCreate, svc: Service, _admin: Admin
) -> EvaluationSuiteResponse:
return svc.create_suite(request)
@router.get(
"/evaluation-suites/{suite_id}/revisions/{revision_id}/cases",
response_model=list[EvaluationCaseDefinitionResponse],
)
def suite_cases(
suite_id: uuid.UUID, revision_id: uuid.UUID, svc: Service
) -> list[EvaluationCaseDefinitionResponse]:
return svc.suite_cases(suite_id, revision_id)
@router.get("/evaluation-runs", response_model=list[EvaluationRunResponse])
def runs(svc: Service) -> list[EvaluationRunResponse]:
return svc.runs()
@router.post(
"/evaluation-runs", response_model=EvaluationRunResponse, status_code=status.HTTP_201_CREATED
)
def create_run(request: EvaluationRunCreate, svc: Service, _admin: Admin) -> EvaluationRunResponse:
return svc.create_run(request)
@router.get("/evaluation-runs/{run_id}", response_model=EvaluationRunResponse)
def run(run_id: uuid.UUID, svc: Service) -> EvaluationRunResponse:
return svc.run(run_id)
@router.post("/evaluation-runs/{run_id}/complete", response_model=EvaluationRunResponse)
def complete_run(
run_id: uuid.UUID, request: EvaluationRunComplete, svc: Service, _admin: Admin
) -> EvaluationRunResponse:
return svc.complete_run(run_id, request)
@router.get("/evaluation-runs/{run_id}/cases", response_model=list[EvaluationCaseResultResponse])
def case_results(run_id: uuid.UUID, svc: Service) -> list[EvaluationCaseResultResponse]:
return svc.case_results(run_id)
@router.post(
"/retrieval-candidate-pools",
response_model=RetrievalCandidatePoolResponse,
status_code=status.HTTP_201_CREATED,
)
def create_candidate_pool(
request: RetrievalCandidatePoolCreate, svc: Service, _admin: Admin
) -> RetrievalCandidatePoolResponse:
return svc.create_candidate_pool(request)
@router.get("/retrieval-candidate-pools", response_model=list[RetrievalCandidatePoolResponse])
def candidate_pools(svc: Service) -> list[RetrievalCandidatePoolResponse]:
return svc.candidate_pools()
@router.post(
"/retrieval-pipeline-identities",
response_model=RetrievalPipelineIdentityResponse,
status_code=status.HTTP_201_CREATED,
)
def create_pipeline_identity(
request: RetrievalPipelineIdentityCreate, svc: Service, _admin: Admin
) -> RetrievalPipelineIdentityResponse:
return svc.create_pipeline_identity(request)
@router.get(
"/retrieval-pipeline-identities", response_model=list[RetrievalPipelineIdentityResponse]
)
def pipeline_identities(svc: Service) -> list[RetrievalPipelineIdentityResponse]:
return svc.pipeline_identities()
@router.post(
"/reranking-runs",
response_model=RerankingRunResponse,
status_code=status.HTTP_201_CREATED,
)
def create_reranking_run(
request: RerankingRunCreate, svc: Service, _admin: Admin
) -> RerankingRunResponse:
return svc.create_reranking_run(request)
@router.get("/reranking-runs", response_model=list[RerankingRunResponse])
def reranking_runs(svc: Service) -> list[RerankingRunResponse]:
return svc.reranking_runs()
@router.post("/reranking-runs/{run_id}/complete", response_model=RerankingRunResponse)
def complete_reranking_run(
run_id: uuid.UUID, request: RerankingRunComplete, svc: Service, _admin: Admin
) -> RerankingRunResponse:
return svc.complete_reranking_run(run_id, request)
@router.get("/reranking-runs/{run_id}/cases", response_model=list[RerankingCaseResultResponse])
def reranking_case_results(run_id: uuid.UUID, svc: Service) -> list[RerankingCaseResultResponse]:
return svc.reranking_case_results(run_id)
@router.post(
"/discovery-assessments",
response_model=DiscoveryCandidateAssessmentResponse,
status_code=status.HTTP_201_CREATED,
)
def create_discovery_assessment(
request: DiscoveryCandidateAssessmentCreate, svc: Service, _admin: Admin
) -> DiscoveryCandidateAssessmentResponse:
return svc.create_discovery_assessment(request)
@router.get("/discovery-assessments", response_model=list[DiscoveryCandidateAssessmentResponse])
def discovery_assessments(svc: Service) -> list[DiscoveryCandidateAssessmentResponse]:
return svc.discovery_assessments()
@router.post(
"/evaluation-comparisons",
response_model=EvaluationComparisonResponse,
status_code=status.HTTP_201_CREATED,
)
def compare(
request: EvaluationComparisonCreate, svc: Service, _admin: Admin
) -> EvaluationComparisonResponse:
return svc.compare(request)
@router.get("/evaluation-comparisons", response_model=list[EvaluationComparisonResponse])
def comparisons(svc: Service) -> list[EvaluationComparisonResponse]:
return svc.comparisons()
@router.get("/evaluation-comparisons/{comparison_id}", response_model=EvaluationComparisonResponse)
def comparison(comparison_id: uuid.UUID, svc: Service) -> EvaluationComparisonResponse:
return svc.comparison(comparison_id)
@router.post(
"/model-comparisons",
response_model=ModelComparisonResponse,
status_code=status.HTTP_201_CREATED,
)
def create_model_comparison(
request: ModelComparisonCreate, svc: Service, _admin: Admin
) -> ModelComparisonResponse:
return svc.create_model_comparison(request)
@router.get("/model-comparisons", response_model=list[ModelComparisonResponse])
def model_comparisons(svc: Service) -> list[ModelComparisonResponse]:
return svc.model_comparisons()
@router.get("/model-comparisons/{comparison_id}", response_model=ModelComparisonResponse)
def model_comparison(comparison_id: uuid.UUID, svc: Service) -> ModelComparisonResponse:
return svc.model_comparison(comparison_id)
@router.post(
"/model-comparisons/{comparison_id}/recommendations",
response_model=AdvisorRecommendationResponse,
status_code=status.HTTP_201_CREATED,
)
def create_recommendation(
comparison_id: uuid.UUID,
request: AdvisorRecommendationCreate,
svc: Service,
_admin: Admin,
) -> AdvisorRecommendationResponse:
return svc.recommend(comparison_id, request)
@router.get("/recommendations", response_model=list[AdvisorRecommendationResponse])
def recommendations(svc: Service) -> list[AdvisorRecommendationResponse]:
return svc.recommendations()
@router.get("/recommendations/{recommendation_id}", response_model=AdvisorRecommendationResponse)
def recommendation(recommendation_id: uuid.UUID, svc: Service) -> AdvisorRecommendationResponse:
return svc.recommendation(recommendation_id)
@router.post(
"/recommendations/{recommendation_id}/dismiss",
response_model=AdvisorRecommendationResponse,
)
def dismiss_recommendation(
recommendation_id: uuid.UUID,
request: AdvisorRecommendationDismiss,
svc: Service,
_admin: Admin,
) -> AdvisorRecommendationResponse:
return svc.dismiss_recommendation(recommendation_id, request)
@router.get("/admin/advisor-policies/current", response_model=AdvisorPolicyResponse)
def advisor_policy(svc: Service, _admin: Admin) -> AdvisorPolicyResponse:
return svc.advisor_policy()
@router.patch("/admin/advisor-policies/current", response_model=AdvisorPolicyResponse)
def update_advisor_policy(
request: AdvisorPolicyUpdate, svc: Service, _admin: Admin
) -> AdvisorPolicyResponse:
return svc.update_advisor_policy(request)
@router.get("/embedding-migrations", response_model=list[EmbeddingMigrationResponse])
def migrations(svc: Service) -> list[EmbeddingMigrationResponse]:
return svc.migrations()
@router.post(
"/projects/{project_id}/embedding-migrations",
response_model=EmbeddingMigrationResponse,
status_code=status.HTTP_201_CREATED,
)
def create_migration(
project_id: uuid.UUID, request: EmbeddingMigrationCreate, svc: Service, _admin: Admin
) -> EmbeddingMigrationResponse:
return svc.create_migration(project_id, request)
@router.get("/embedding-migrations/{migration_id}", response_model=EmbeddingMigrationResponse)
def migration(migration_id: uuid.UUID, svc: Service) -> EmbeddingMigrationResponse:
return svc.migration(migration_id)
@router.post(
"/embedding-migrations/{migration_id}/state", response_model=EmbeddingMigrationResponse
)
def update_migration(
migration_id: uuid.UUID, request: MigrationUpdate, svc: Service, _admin: Admin
) -> EmbeddingMigrationResponse:
return svc.update_migration(migration_id, request)
@router.post(
"/embedding-migrations/{migration_id}/start", response_model=EmbeddingMigrationResponse
)
def start_migration(
migration_id: uuid.UUID, svc: Service, _admin: Admin
) -> EmbeddingMigrationResponse:
return svc.start_migration(migration_id)
@router.post(
"/embedding-migrations/{migration_id}/cancel", response_model=EmbeddingMigrationResponse
)
def cancel_migration(
migration_id: uuid.UUID, svc: Service, _admin: Admin
) -> EmbeddingMigrationResponse:
return svc.cancel_migration(migration_id)
@@ -0,0 +1,82 @@
import uuid
from typing import Annotated
from fastapi import APIRouter, Depends, HTTPException, status
from sqlalchemy.orm import Session
from modelforge_api.api.authorization import require_operator, require_viewer
from modelforge_api.db import get_session
from modelforge_api.domain.hardware import AcceleratorState, HardwareState, NodeState
from modelforge_api.services.hardware_factory import build_hardware_service
from modelforge_api.services.hardware_inventory import HardwareInventoryService, HardwareRefreshBusy
from modelforge_api.settings import Settings, get_settings
router = APIRouter(
prefix="/api/v1/hardware", tags=["hardware"], dependencies=[Depends(require_viewer)]
)
def get_hardware_service(
session: Annotated[Session, Depends(get_session)],
settings: Annotated[Settings, Depends(get_settings)],
) -> HardwareInventoryService:
return build_hardware_service(session, settings)
Service = Annotated[HardwareInventoryService, Depends(get_hardware_service)]
@router.get("", response_model=HardwareState)
def hardware_overview(service: Service) -> HardwareState:
return service.state()
@router.get("/nodes", response_model=list[NodeState])
def list_nodes(service: Service) -> list[NodeState]:
return service.state().nodes
@router.get("/nodes/{node_id}", response_model=NodeState)
def get_node(node_id: uuid.UUID, service: Service) -> NodeState:
node = next((item for item in service.state().nodes if item.id == str(node_id)), None)
if node is None:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="compute node not found")
return node
@router.get("/accelerators", response_model=list[AcceleratorState])
def list_accelerators(service: Service) -> list[AcceleratorState]:
return [accelerator for node in service.state().nodes for accelerator in node.accelerators]
@router.get("/accelerators/{accelerator_id}", response_model=AcceleratorState)
def get_accelerator(accelerator_id: uuid.UUID, service: Service) -> AcceleratorState:
accelerator = next(
(
item
for node in service.state().nodes
for item in node.accelerators
if item.id == str(accelerator_id)
),
None,
)
if accelerator is None:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="accelerator not found")
return accelerator
@router.post(
"/refresh",
response_model=HardwareState,
dependencies=[Depends(require_operator)],
)
def refresh_hardware(service: Service) -> HardwareState:
try:
return service.refresh()
except HardwareRefreshBusy as exc:
raise HTTPException(status_code=status.HTTP_409_CONFLICT, detail=str(exc)) from exc
except Exception as exc:
raise HTTPException(
status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
detail=f"hardware inventory failed: {type(exc).__name__}",
) from exc
@@ -0,0 +1,109 @@
from fastapi import APIRouter, Depends
from sqlalchemy import func, select
from sqlalchemy.orm import Session
from modelforge_api import __version__
from modelforge_api.api.authorization import require_viewer
from modelforge_api.db import get_session
from modelforge_api.domain.enums import HealthStatus
from modelforge_api.domain.release import (
CURRENT_AGENT_PROTOCOL_VERSION,
MINIMUM_POSTGRES_MAJOR,
MINIMUM_UPGRADE_SOURCE,
PRODUCT_NAME,
RELEASE_CHANNEL,
SUPPORTED_AGENT_PROTOCOL_VERSIONS,
SUPPORTED_SCHEMA_REVISIONS,
TARGET_SCHEMA_REVISION,
build_identity,
)
from modelforge_api.domain.schemas import (
HealthResponse,
ReadinessResponse,
ReleaseCompatibility,
ReleaseInfo,
SystemMetadata,
)
from modelforge_api.persistence.models import CapabilityDeployment
from modelforge_api.services.manifest_registry import ManifestRegistry, get_manifest_registry
from modelforge_api.settings import Settings, get_settings
router = APIRouter(prefix="/api/v1", tags=["system"])
@router.get("/health/live", response_model=HealthResponse)
def liveness() -> HealthResponse:
return HealthResponse(version=__version__)
@router.get("/health/ready", response_model=ReadinessResponse)
def readiness(registry: ManifestRegistry = Depends(get_manifest_registry)) -> ReadinessResponse:
registry.capabilities()
registry.projects()
registry.candidates()
registry.benchmarks()
registry.policies()
return ReadinessResponse(
status=HealthStatus.HEALTHY,
checks={"manifests": HealthStatus.HEALTHY},
version=__version__,
)
@router.get(
"/system",
response_model=SystemMetadata,
dependencies=[Depends(require_viewer)],
)
def system_metadata(
settings: Settings = Depends(get_settings),
session: Session = Depends(get_session),
) -> SystemMetadata:
production_count = int(
session.scalar(
select(func.count(CapabilityDeployment.id)).where(
CapabilityDeployment.status == "stable",
CapabilityDeployment.production.is_(True),
)
)
or 0
)
return SystemMetadata(
name=PRODUCT_NAME,
version=__version__,
environment=settings.env,
release_channel=RELEASE_CHANNEL,
production_inference_available=production_count > 0,
)
@router.get("/version", response_model=ReleaseInfo)
def release_info(settings: Settings = Depends(get_settings)) -> ReleaseInfo:
"""Build identity and compatibility for the running process.
Unauthenticated on purpose: an operator diagnosing a deployment needs to know which build is
answering before they have credentials for it, and everything here is already implied by the
image they are running. Nothing configuration-derived or sensitive is exposed.
"""
identity = build_identity(
source_commit=settings.build_commit,
built_at=settings.build_timestamp,
image_digest=settings.build_image_digest,
)
return ReleaseInfo(
name=PRODUCT_NAME,
version=identity.version,
release_channel=identity.channel,
source_commit=identity.source_commit,
built_at=identity.built_at,
image_digest=identity.image_digest,
compatibility=ReleaseCompatibility(
schema_revision=TARGET_SCHEMA_REVISION,
supported_schema_revisions=list(SUPPORTED_SCHEMA_REVISIONS),
agent_protocol_version=CURRENT_AGENT_PROTOCOL_VERSION,
supported_agent_protocol_versions=list(SUPPORTED_AGENT_PROTOCOL_VERSIONS),
minimum_upgrade_source=MINIMUM_UPGRADE_SOURCE,
minimum_postgres_major=MINIMUM_POSTGRES_MAJOR,
),
)
@@ -0,0 +1,248 @@
from __future__ import annotations
import uuid
from typing import Annotated
from fastapi import APIRouter, Depends, Query, status
from sqlalchemy.orm import Session
from modelforge_api.api.authorization import Admin, require_viewer
from modelforge_api.db import get_session
from modelforge_api.domain.lifecycle_contracts import (
ApprovalDecision,
ApprovalPolicyCreate,
ApprovalPolicyResponse,
ApprovalRequestCreate,
ApprovalRequestResponse,
CanaryObservation,
CleanupExecutionCreate,
CleanupPlanCreate,
CleanupPlanResponse,
LifecycleEventResponse,
LifecycleOperationResponse,
LifecycleSubjectCreate,
LifecycleSubjectResponse,
PlanExecutionCreate,
PromotionPlanCreate,
PromotionPlanResponse,
RetentionPolicyCreate,
RetentionPolicyResponse,
)
from modelforge_api.services.lifecycle import LifecycleService
router = APIRouter(
prefix="/api/v1/admin/lifecycle",
tags=["lifecycle"],
dependencies=[Depends(require_viewer)],
)
def get_lifecycle_service(
session: Annotated[Session, Depends(get_session)],
) -> LifecycleService:
return LifecycleService(session)
Service = Annotated[LifecycleService, Depends(get_lifecycle_service)]
@router.get("/approval-policies", response_model=list[ApprovalPolicyResponse])
def approval_policies(service: Service, _admin: Admin) -> list[ApprovalPolicyResponse]:
return service.policies()
@router.post(
"/approval-policies",
response_model=ApprovalPolicyResponse,
status_code=status.HTTP_201_CREATED,
)
def create_approval_policy(
request: ApprovalPolicyCreate, service: Service, _admin: Admin
) -> ApprovalPolicyResponse:
return service.create_policy(request)
@router.get("/subjects", response_model=list[LifecycleSubjectResponse])
def subjects(service: Service, _admin: Admin) -> list[LifecycleSubjectResponse]:
return service.subjects()
@router.post(
"/subjects",
response_model=LifecycleSubjectResponse,
status_code=status.HTTP_201_CREATED,
)
def create_subject(
request: LifecycleSubjectCreate, service: Service, _admin: Admin
) -> LifecycleSubjectResponse:
return service.create_subject(request)
@router.get("/approval-requests", response_model=list[ApprovalRequestResponse])
def approval_requests(service: Service, _admin: Admin) -> list[ApprovalRequestResponse]:
return service.approval_requests()
@router.post(
"/approval-requests",
response_model=ApprovalRequestResponse,
status_code=status.HTTP_201_CREATED,
)
def request_approval(
request: ApprovalRequestCreate, service: Service, _admin: Admin
) -> ApprovalRequestResponse:
return service.request_approval(request)
@router.get("/approval-requests/{approval_id}", response_model=ApprovalRequestResponse)
def approval_request(
approval_id: uuid.UUID, service: Service, _admin: Admin
) -> ApprovalRequestResponse:
return service.approval(approval_id)
@router.post("/approval-requests/{approval_id}/approve", response_model=ApprovalRequestResponse)
def approve_request(
approval_id: uuid.UUID, request: ApprovalDecision, service: Service, _admin: Admin
) -> ApprovalRequestResponse:
return service.decide_approval(approval_id, request, "approve")
@router.post("/approval-requests/{approval_id}/reject", response_model=ApprovalRequestResponse)
def reject_request(
approval_id: uuid.UUID, request: ApprovalDecision, service: Service, _admin: Admin
) -> ApprovalRequestResponse:
return service.decide_approval(approval_id, request, "reject")
@router.post("/approval-requests/{approval_id}/revoke", response_model=ApprovalRequestResponse)
def revoke_request(
approval_id: uuid.UUID, request: ApprovalDecision, service: Service, _admin: Admin
) -> ApprovalRequestResponse:
return service.decide_approval(approval_id, request, "revoke")
@router.get("/promotion-plans", response_model=list[PromotionPlanResponse])
def promotion_plans(service: Service, _admin: Admin) -> list[PromotionPlanResponse]:
return service.plans()
@router.post(
"/promotion-plans",
response_model=PromotionPlanResponse,
status_code=status.HTTP_201_CREATED,
)
def create_promotion_plan(
request: PromotionPlanCreate, service: Service, _admin: Admin
) -> PromotionPlanResponse:
return service.create_plan(request)
@router.get("/promotion-plans/{plan_id}", response_model=PromotionPlanResponse)
def promotion_plan(plan_id: uuid.UUID, service: Service, _admin: Admin) -> PromotionPlanResponse:
return service.plan(plan_id)
@router.post("/promotion-plans/{plan_id}/approve", response_model=PromotionPlanResponse)
def approve_promotion_plan(
plan_id: uuid.UUID, request: ApprovalDecision, service: Service, _admin: Admin
) -> PromotionPlanResponse:
return service.approve_plan(plan_id, request)
@router.post("/promotion-plans/{plan_id}/execute", response_model=LifecycleOperationResponse)
def execute_promotion_plan(
plan_id: uuid.UUID, request: PlanExecutionCreate, service: Service, _admin: Admin
) -> LifecycleOperationResponse:
return service.execute_plan(plan_id, request)
@router.get("/operations", response_model=list[LifecycleOperationResponse])
def operations(service: Service, _admin: Admin) -> list[LifecycleOperationResponse]:
return service.operations()
@router.get("/operations/{operation_id}", response_model=LifecycleOperationResponse)
def operation(
operation_id: uuid.UUID, service: Service, _admin: Admin
) -> LifecycleOperationResponse:
return service.operation(operation_id)
@router.post("/operations/{operation_id}/canary", response_model=LifecycleOperationResponse)
def observe_canary(
operation_id: uuid.UUID, request: CanaryObservation, service: Service, _admin: Admin
) -> LifecycleOperationResponse:
return service.observe_canary(operation_id, request)
@router.post("/operations/{operation_id}/commit", response_model=LifecycleOperationResponse)
def commit_operation(
operation_id: uuid.UUID, request: ApprovalDecision, service: Service, _admin: Admin
) -> LifecycleOperationResponse:
return service.commit_operation(operation_id, request)
@router.post("/operations/{operation_id}/rollback", response_model=LifecycleOperationResponse)
def rollback_operation(
operation_id: uuid.UUID, request: ApprovalDecision, service: Service, _admin: Admin
) -> LifecycleOperationResponse:
return service.rollback_operation(operation_id, request)
@router.post("/reconcile", response_model=dict[str, int])
def reconcile(service: Service, _admin: Admin) -> dict[str, int]:
return {"reconciled_operations": service.reconcile_incomplete()}
@router.get("/retention-policies", response_model=list[RetentionPolicyResponse])
def retention_policies(service: Service, _admin: Admin) -> list[RetentionPolicyResponse]:
return service.retention_policies()
@router.post(
"/retention-policies",
response_model=RetentionPolicyResponse,
status_code=status.HTTP_201_CREATED,
)
def create_retention_policy(
request: RetentionPolicyCreate, service: Service, _admin: Admin
) -> RetentionPolicyResponse:
return service.create_retention_policy(request)
@router.get("/cleanup-plans", response_model=list[CleanupPlanResponse])
def cleanup_plans(service: Service, _admin: Admin) -> list[CleanupPlanResponse]:
return service.cleanup_plans()
@router.post(
"/cleanup-plans",
response_model=CleanupPlanResponse,
status_code=status.HTTP_201_CREATED,
)
def create_cleanup_plan(
request: CleanupPlanCreate, service: Service, _admin: Admin
) -> CleanupPlanResponse:
return service.create_cleanup_plan(request)
@router.get("/cleanup-plans/{plan_id}", response_model=CleanupPlanResponse)
def cleanup_plan(plan_id: uuid.UUID, service: Service, _admin: Admin) -> CleanupPlanResponse:
return service.cleanup_plan(plan_id)
@router.post("/cleanup-plans/{plan_id}/execute", response_model=CleanupPlanResponse)
def execute_cleanup_plan(
plan_id: uuid.UUID, request: CleanupExecutionCreate, service: Service, _admin: Admin
) -> CleanupPlanResponse:
return service.execute_cleanup(plan_id, request)
@router.get("/events", response_model=list[LifecycleEventResponse])
def lifecycle_events(
service: Service,
_admin: Admin,
limit: Annotated[int, Query(ge=1, le=1000)] = 200,
) -> list[LifecycleEventResponse]:
return service.events(limit)
@@ -0,0 +1,191 @@
from __future__ import annotations
import uuid
from typing import Annotated
from fastapi import APIRouter, Depends, Query, status
from sqlalchemy.orm import Session
from modelforge_api.api.authorization import Admin, require_viewer
from modelforge_api.db import get_session
from modelforge_api.domain.migration_contracts import (
BatchReport,
CutoverOperationResponse,
CutoverPrepare,
CutoverReport,
MigrationBatchResponse,
MigrationEventResponse,
MigrationPlanCreate,
MigrationPlanResponse,
MigrationValidationPolicyCreate,
MigrationValidationPolicyResponse,
PreflightReport,
ReconciliationReport,
RollbackReport,
ShadowReport,
StateAction,
ValidationReport,
ValidationSnapshotResponse,
)
from modelforge_api.services.migration_engine import MigrationEngineService
router = APIRouter(
prefix="/api/v1/admin/migrations",
tags=["migrations"],
dependencies=[Depends(require_viewer)],
)
def get_service(session: Annotated[Session, Depends(get_session)]) -> MigrationEngineService:
return MigrationEngineService(session)
Service = Annotated[MigrationEngineService, Depends(get_service)]
@router.get("/validation-policies", response_model=list[MigrationValidationPolicyResponse])
def validation_policies(service: Service, _admin: Admin) -> list[MigrationValidationPolicyResponse]:
return service.validation_policies()
@router.post(
"/validation-policies",
response_model=MigrationValidationPolicyResponse,
status_code=status.HTTP_201_CREATED,
)
def create_validation_policy(
request: MigrationValidationPolicyCreate, service: Service, _admin: Admin
) -> MigrationValidationPolicyResponse:
return service.create_validation_policy(request)
@router.get("/plans", response_model=list[MigrationPlanResponse])
def plans(service: Service, _admin: Admin) -> list[MigrationPlanResponse]:
return service.plans()
@router.post("/plans", response_model=MigrationPlanResponse, status_code=status.HTTP_201_CREATED)
def create_plan(
request: MigrationPlanCreate, service: Service, _admin: Admin
) -> MigrationPlanResponse:
return service.create_plan(request)
@router.get("/plans/{plan_id}", response_model=MigrationPlanResponse)
def plan(plan_id: uuid.UUID, service: Service, _admin: Admin) -> MigrationPlanResponse:
return service.plan(plan_id)
@router.post("/plans/{plan_id}/preflight", response_model=MigrationPlanResponse)
def preflight(
plan_id: uuid.UUID, request: PreflightReport, service: Service, _admin: Admin
) -> MigrationPlanResponse:
return service.preflight(plan_id, request)
@router.post("/plans/{plan_id}/backfill/start", response_model=MigrationPlanResponse)
def start_backfill(
plan_id: uuid.UUID, request: StateAction, service: Service, _admin: Admin
) -> MigrationPlanResponse:
return service.start_backfill(plan_id, request)
@router.post("/plans/{plan_id}/backfill/pause", response_model=MigrationPlanResponse)
def pause_backfill(
plan_id: uuid.UUID, request: StateAction, service: Service, _admin: Admin
) -> MigrationPlanResponse:
return service.pause_backfill(plan_id, request)
@router.post("/plans/{plan_id}/batches", response_model=MigrationBatchResponse)
def record_batch(
plan_id: uuid.UUID, request: BatchReport, service: Service, _admin: Admin
) -> MigrationBatchResponse:
return service.record_batch(plan_id, request)
@router.get("/plans/{plan_id}/batches", response_model=list[MigrationBatchResponse])
def batches(plan_id: uuid.UUID, service: Service, _admin: Admin) -> list[MigrationBatchResponse]:
return service.batches(plan_id)
@router.post("/plans/{plan_id}/validation", response_model=ValidationSnapshotResponse)
def validate_target(
plan_id: uuid.UUID, request: ValidationReport, service: Service, _admin: Admin
) -> ValidationSnapshotResponse:
return service.validate(plan_id, request)
@router.get("/plans/{plan_id}/validation", response_model=list[ValidationSnapshotResponse])
def validation_snapshots(
plan_id: uuid.UUID, service: Service, _admin: Admin
) -> list[ValidationSnapshotResponse]:
return service.validation_snapshots(plan_id)
@router.post("/plans/{plan_id}/shadow/start", response_model=MigrationPlanResponse)
def start_shadow(
plan_id: uuid.UUID, request: StateAction, service: Service, _admin: Admin
) -> MigrationPlanResponse:
return service.start_shadow(plan_id, request)
@router.post("/plans/{plan_id}/shadow/complete", response_model=MigrationPlanResponse)
def complete_shadow(
plan_id: uuid.UUID, request: ShadowReport, service: Service, _admin: Admin
) -> MigrationPlanResponse:
return service.complete_shadow(plan_id, request)
@router.post("/plans/{plan_id}/cutover/prepare", response_model=CutoverOperationResponse)
def prepare_cutover(
plan_id: uuid.UUID, request: CutoverPrepare, service: Service, _admin: Admin
) -> CutoverOperationResponse:
return service.prepare_cutover(plan_id, request)
@router.post("/plans/{plan_id}/cutover/report", response_model=CutoverOperationResponse)
def report_cutover(
plan_id: uuid.UUID, request: CutoverReport, service: Service, _admin: Admin
) -> CutoverOperationResponse:
return service.report_cutover(plan_id, request)
@router.post("/plans/{plan_id}/rollback", response_model=CutoverOperationResponse)
def rollback(
plan_id: uuid.UUID, request: RollbackReport, service: Service, _admin: Admin
) -> CutoverOperationResponse:
return service.rollback(plan_id, request)
@router.post("/reconcile", response_model=CutoverOperationResponse)
def reconcile(
request: ReconciliationReport, service: Service, _admin: Admin
) -> CutoverOperationResponse:
return service.reconcile(request)
@router.post("/plans/{plan_id}/cancel", response_model=MigrationPlanResponse)
def cancel(
plan_id: uuid.UUID, request: StateAction, service: Service, _admin: Admin
) -> MigrationPlanResponse:
return service.cancel(plan_id, request)
@router.get("/cutovers", response_model=list[CutoverOperationResponse])
def cutovers(
service: Service,
_admin: Admin,
plan_id: Annotated[uuid.UUID | None, Query()] = None,
) -> list[CutoverOperationResponse]:
return service.operations(plan_id)
@router.get("/events", response_model=list[MigrationEventResponse])
def events(
service: Service,
_admin: Admin,
plan_id: Annotated[uuid.UUID | None, Query()] = None,
limit: Annotated[int, Query(ge=1, le=1000)] = 200,
) -> list[MigrationEventResponse]:
return service.events(plan_id, limit)
@@ -0,0 +1,237 @@
from __future__ import annotations
import uuid
from typing import Annotated, Any
from fastapi import APIRouter, Depends, Query, Response, status
from sqlalchemy import select
from sqlalchemy.exc import SQLAlchemyError
from sqlalchemy.orm import Session
from modelforge_api.api.authorization import Admin, require_viewer
from modelforge_api.db import get_session
from modelforge_api.domain.observability import (
AlertAction,
AlertHistoryResponse,
AlertResponse,
AlertRuleCreate,
AlertRuleResponse,
CapacitySnapshotResponse,
IncidentResponse,
MaintenanceWindowCreate,
MaintenanceWindowResponse,
OperationsOverview,
SLIDefinitionResponse,
SLOEvaluationResponse,
SLOPolicyCreate,
SLOPolicyResponse,
TrendResponse,
metrics,
)
from modelforge_api.persistence.models import (
AlertHistoryEvent,
IncidentTimelineEvent,
OperationalIncident,
)
from modelforge_api.services.observability import ObservabilityService
router = APIRouter(tags=["operations"], dependencies=[Depends(require_viewer)])
def get_service(session: Annotated[Session, Depends(get_session)]) -> ObservabilityService:
return ObservabilityService(session)
Service = Annotated[ObservabilityService, Depends(get_service)]
@router.get("/metrics", response_class=Response)
def prometheus_metrics(service: Service, _admin: Admin) -> Response:
"""Admin-isolated Prometheus exposition; process metrics survive DB failure."""
try:
content = service.prometheus()
metrics.gauge("modelforge_observability_degraded", {}, 0)
except SQLAlchemyError:
service.session.rollback()
metrics.gauge("modelforge_observability_degraded", {}, 1)
content = metrics.render()
return Response(content=content, media_type="text/plain; version=0.0.4; charset=utf-8")
@router.get("/api/v1/admin/operations/overview", response_model=OperationsOverview)
def overview(service: Service, _admin: Admin) -> OperationsOverview:
return service.overview()
@router.get(
"/api/v1/admin/operations/slis", response_model=list[SLIDefinitionResponse]
)
def sli_definitions(service: Service, _admin: Admin) -> list[SLIDefinitionResponse]:
return service.definitions()
@router.get(
"/api/v1/admin/operations/slo-policies", response_model=list[SLOPolicyResponse]
)
def slo_policies(service: Service, _admin: Admin) -> list[SLOPolicyResponse]:
return service.policies()
@router.post(
"/api/v1/admin/operations/slo-policies",
response_model=SLOPolicyResponse,
status_code=status.HTTP_201_CREATED,
)
def create_slo_policy(
request: SLOPolicyCreate, service: Service, _admin: Admin
) -> SLOPolicyResponse:
return service.create_policy(request)
@router.get(
"/api/v1/admin/operations/slo-evaluations", response_model=list[SLOEvaluationResponse]
)
def slo_evaluations(
service: Service, _admin: Admin, limit: int = Query(default=100, ge=1, le=1000)
) -> list[SLOEvaluationResponse]:
return service.evaluations(limit)
@router.post(
"/api/v1/admin/operations/slo-evaluations/run",
response_model=list[SLOEvaluationResponse],
)
def evaluate_slos(service: Service, _admin: Admin) -> list[SLOEvaluationResponse]:
return service.evaluate_slos()
@router.get(
"/api/v1/admin/operations/alert-rules", response_model=list[AlertRuleResponse]
)
def alert_rules(service: Service, _admin: Admin) -> list[AlertRuleResponse]:
return service.rules()
@router.post(
"/api/v1/admin/operations/alert-rules",
response_model=AlertRuleResponse,
status_code=status.HTTP_201_CREATED,
)
def create_alert_rule(
request: AlertRuleCreate, service: Service, _admin: Admin
) -> AlertRuleResponse:
return service.create_rule(request)
@router.get("/api/v1/admin/operations/alerts", response_model=list[AlertResponse])
def alerts(
service: Service, _admin: Admin, limit: int = Query(default=200, ge=1, le=1000)
) -> list[AlertResponse]:
return service.alerts(limit)
@router.post("/api/v1/admin/operations/alerts/evaluate", response_model=list[AlertResponse])
def evaluate_alerts(service: Service, _admin: Admin) -> list[AlertResponse]:
return service.evaluate_alerts()
@router.post(
"/api/v1/admin/operations/alerts/{alert_id}/acknowledge", response_model=AlertResponse
)
def acknowledge_alert(
alert_id: uuid.UUID, request: AlertAction, service: Service, _admin: Admin
) -> AlertResponse:
return service.acknowledge(alert_id, request)
@router.get(
"/api/v1/admin/operations/alerts/{alert_id}/history",
response_model=list[AlertHistoryResponse],
)
def alert_history(
alert_id: uuid.UUID, service: Service, _admin: Admin
) -> list[AlertHistoryResponse]:
rows = service.session.scalars(
select(AlertHistoryEvent)
.where(AlertHistoryEvent.alert_id == alert_id)
.order_by(AlertHistoryEvent.occurred_at)
)
return [AlertHistoryResponse.model_validate(item) for item in rows]
@router.get(
"/api/v1/admin/operations/maintenance-windows",
response_model=list[MaintenanceWindowResponse],
)
def maintenance_windows(service: Service, _admin: Admin) -> list[MaintenanceWindowResponse]:
return service.maintenance_windows()
@router.post(
"/api/v1/admin/operations/maintenance-windows",
response_model=MaintenanceWindowResponse,
status_code=status.HTTP_201_CREATED,
)
def create_maintenance_window(
request: MaintenanceWindowCreate, service: Service, _admin: Admin
) -> MaintenanceWindowResponse:
return service.create_maintenance_window(request)
@router.get("/api/v1/admin/operations/capacity", response_model=list[CapacitySnapshotResponse])
def capacity(
service: Service, _admin: Admin, limit: int = Query(default=500, ge=1, le=5000)
) -> list[CapacitySnapshotResponse]:
return service.capacity(limit)
@router.post(
"/api/v1/admin/operations/capacity/collect", response_model=list[CapacitySnapshotResponse]
)
def collect_capacity(service: Service, _admin: Admin) -> list[CapacitySnapshotResponse]:
return service.collect_capacity()
@router.get("/api/v1/admin/operations/capacity/{node_id}/trend", response_model=TrendResponse)
def capacity_trend(
node_id: uuid.UUID,
service: Service,
_admin: Admin,
hours: int = Query(default=24, ge=1, le=2160),
) -> TrendResponse:
return service.capacity_trend(node_id, hours)
@router.post("/api/v1/admin/operations/retention/run", response_model=dict[str, int])
def apply_retention(service: Service, _admin: Admin) -> dict[str, int]:
return service.apply_retention()
@router.get("/api/v1/admin/operations/incidents", response_model=list[IncidentResponse])
def incidents(service: Service, _admin: Admin) -> list[IncidentResponse]:
rows = service.session.scalars(
select(OperationalIncident).order_by(OperationalIncident.last_seen_at.desc())
)
return [IncidentResponse.model_validate(item) for item in rows]
@router.get("/api/v1/admin/operations/incidents/{incident_id}/timeline")
def incident_timeline(
incident_id: uuid.UUID, service: Service, _admin: Admin
) -> list[dict[str, Any]]:
rows = service.session.scalars(
select(IncidentTimelineEvent)
.where(IncidentTimelineEvent.incident_id == incident_id)
.order_by(IncidentTimelineEvent.occurred_at)
)
return [
{
"id": str(item.id),
"alert_id": str(item.alert_id) if item.alert_id else None,
"event_type": item.event_type,
"relation": item.relation,
"summary": item.summary,
"occurred_at": item.occurred_at,
}
for item in rows
]
@@ -0,0 +1,215 @@
"""Operator-only recovery API.
Backup creation, verification and restore *planning* are safe control-plane operations. Restore
*execution* is deliberately restricted: it can only run against an isolated destination that is
not this control plane's own database, and replacing a production database stays an operator
runbook/CLI action rather than a remote call.
"""
from __future__ import annotations
import uuid
from typing import Annotated, Any
from fastapi import APIRouter, Depends, Query, status
from sqlalchemy.orm import Session
from modelforge_api.api.authorization import Admin, require_viewer
from modelforge_api.db import get_session
from modelforge_api.domain.recovery import (
ArtifactRecoveryCreate,
ArtifactRecoveryResponse,
BackupCapacityEstimate,
BackupSetCreate,
BackupSetResponse,
RecoveryAssetResponse,
RecoveryDashboard,
RecoveryPolicyCreate,
RecoveryPolicyResponse,
RestoreAdvanceRequest,
RestoreOperationEventResponse,
RestoreOperationResponse,
RestorePlanCreate,
RestorePlanResponse,
)
from modelforge_api.services.recovery import RecoveryService
from modelforge_api.settings import Settings, get_settings
router = APIRouter(
prefix="/api/v1/admin/recovery",
tags=["recovery"],
dependencies=[Depends(require_viewer)],
)
def get_service(
session: Annotated[Session, Depends(get_session)],
settings: Annotated[Settings, Depends(get_settings)],
) -> RecoveryService:
return RecoveryService(session, settings)
Service = Annotated[RecoveryService, Depends(get_service)]
@router.get("/dashboard", response_model=RecoveryDashboard)
def dashboard(service: Service, _admin: Admin) -> RecoveryDashboard:
return service.dashboard()
@router.get("/policies", response_model=list[RecoveryPolicyResponse])
def policies(service: Service, _admin: Admin) -> list[RecoveryPolicyResponse]:
return service.policies()
@router.post(
"/policies",
response_model=RecoveryPolicyResponse,
status_code=status.HTTP_201_CREATED,
)
def create_policy(
request: RecoveryPolicyCreate, service: Service, _admin: Admin
) -> RecoveryPolicyResponse:
return service.create_policy(request)
@router.get("/assets", response_model=list[RecoveryAssetResponse])
def assets(service: Service, _admin: Admin) -> list[RecoveryAssetResponse]:
return service.assets()
@router.get("/capacity", response_model=BackupCapacityEstimate)
def capacity(service: Service, _admin: Admin) -> BackupCapacityEstimate:
return service.estimate_capacity()
@router.get("/backups", response_model=list[BackupSetResponse])
def backups(
service: Service, _admin: Admin, limit: int = Query(default=100, ge=1, le=1000)
) -> list[BackupSetResponse]:
return service.backups(limit)
@router.post(
"/backups", response_model=BackupSetResponse, status_code=status.HTTP_201_CREATED
)
def create_backup(
request: BackupSetCreate, service: Service, _admin: Admin
) -> BackupSetResponse:
return service.create_backup(request)
@router.get("/backups/{backup_set_id}", response_model=BackupSetResponse)
def backup(backup_set_id: uuid.UUID, service: Service, _admin: Admin) -> BackupSetResponse:
return service.backup(backup_set_id)
@router.post("/backups/{backup_set_id}/verify", response_model=BackupSetResponse)
def verify_backup(
backup_set_id: uuid.UUID, service: Service, _admin: Admin
) -> BackupSetResponse:
return service.verify_backup(backup_set_id)
@router.post("/retention/run", response_model=dict[str, int])
def apply_retention(service: Service, _admin: Admin) -> dict[str, int]:
return service.apply_retention()
@router.get("/restore-plans", response_model=list[RestorePlanResponse])
def restore_plans(
service: Service, _admin: Admin, limit: int = Query(default=100, ge=1, le=1000)
) -> list[RestorePlanResponse]:
return service.restore_plans(limit)
@router.post(
"/restore-plans", response_model=RestorePlanResponse, status_code=status.HTTP_201_CREATED
)
def create_restore_plan(
request: RestorePlanCreate, service: Service, _admin: Admin
) -> RestorePlanResponse:
return service.create_restore_plan(request)
@router.get("/restore-plans/{plan_id}", response_model=RestorePlanResponse)
def restore_plan(plan_id: uuid.UUID, service: Service, _admin: Admin) -> RestorePlanResponse:
return service.restore_plan(plan_id)
@router.post("/restore-plans/{plan_id}/preflight", response_model=RestorePlanResponse)
def preflight(plan_id: uuid.UUID, service: Service, _admin: Admin) -> RestorePlanResponse:
return service.preflight(plan_id)
@router.post(
"/restore-plans/{plan_id}/start",
response_model=RestoreOperationResponse,
status_code=status.HTTP_201_CREATED,
)
def start_restore(
plan_id: uuid.UUID,
request: RestoreAdvanceRequest,
service: Service,
_admin: Admin,
) -> RestoreOperationResponse:
return service.start_restore(plan_id, request)
@router.get("/restore-operations", response_model=list[RestoreOperationResponse])
def restore_operations(
service: Service, _admin: Admin, limit: int = Query(default=100, ge=1, le=1000)
) -> list[RestoreOperationResponse]:
return service.restore_operations(limit)
@router.get("/restore-operations/{operation_id}", response_model=RestoreOperationResponse)
def restore_operation(
operation_id: uuid.UUID, service: Service, _admin: Admin
) -> RestoreOperationResponse:
return service.restore_operation(operation_id)
@router.post("/restore-operations/{operation_id}/advance", response_model=RestoreOperationResponse)
def advance_restore(
operation_id: uuid.UUID,
request: RestoreAdvanceRequest,
service: Service,
_admin: Admin,
) -> RestoreOperationResponse:
return service.advance_restore(operation_id, request)
@router.get(
"/restore-operations/{operation_id}/events",
response_model=list[RestoreOperationEventResponse],
)
def restore_events(
operation_id: uuid.UUID, service: Service, _admin: Admin
) -> list[RestoreOperationEventResponse]:
return service.restore_events(operation_id)
@router.get("/artifact-recoveries", response_model=list[ArtifactRecoveryResponse])
def artifact_recoveries(
service: Service, _admin: Admin, limit: int = Query(default=100, ge=1, le=1000)
) -> list[ArtifactRecoveryResponse]:
return service.artifact_recoveries(limit)
@router.post(
"/artifact-recoveries",
response_model=ArtifactRecoveryResponse,
status_code=status.HTTP_201_CREATED,
)
def plan_artifact_recovery(
request: ArtifactRecoveryCreate, service: Service, _admin: Admin
) -> ArtifactRecoveryResponse:
return service.plan_artifact_recovery(request)
@router.get("/fingerprint", response_model=dict[str, Any])
def fingerprint(service: Service, _admin: Admin) -> dict[str, Any]:
"""Bounded semantic fingerprint of the live control plane; contains no secret material."""
return service.fingerprint()
@@ -0,0 +1,291 @@
from __future__ import annotations
import math
import uuid
from typing import Annotated
from fastapi import APIRouter, Depends, Query, Response, status
from pydantic import BaseModel
from sqlalchemy.orm import Session
from modelforge_api.api.authorization import require_admin, require_operator, require_viewer
from modelforge_api.db import get_session
from modelforge_api.domain.registry import (
ArtifactCreate,
ArtifactResponse,
CapacityDecision,
DerivedArtifactCreate,
DerivedArtifactResponse,
ModelCreate,
ModelResponse,
ModelUpdate,
Page,
RevisionCreate,
RevisionResponse,
StorageRootCreate,
StorageRootObservation,
StorageRootResponse,
StorageRootUpdate,
VerifyResponse,
)
from modelforge_api.services.registry import RegistryService
router = APIRouter(
prefix="/api/v1", tags=["model-registry"], dependencies=[Depends(require_viewer)]
)
def get_registry_service(session: Annotated[Session, Depends(get_session)]) -> RegistryService:
return RegistryService(session)
Service = Annotated[RegistryService, Depends(get_registry_service)]
def page_values[T: BaseModel](items: list[T], page: int, page_size: int) -> Page[T]:
total = len(items)
start = (page - 1) * page_size
return Page(
items=items[start : start + page_size],
page=page,
page_size=page_size,
total=total,
pages=math.ceil(total / page_size) if total else 0,
)
@router.get("/models", response_model=Page[ModelResponse])
def list_models(
service: Service,
page: Annotated[int, Query(ge=1)] = 1,
page_size: Annotated[int, Query(ge=1, le=100)] = 20,
search: str | None = None,
lifecycle: str | None = None,
source_type: str | None = None,
) -> Page[ModelResponse]:
return service.list_models(
page=page,
page_size=page_size,
search=search,
lifecycle=lifecycle,
source_type=source_type,
)
@router.post(
"/models",
response_model=ModelResponse,
status_code=status.HTTP_201_CREATED,
dependencies=[Depends(require_operator)],
)
def create_model(request: ModelCreate, service: Service) -> ModelResponse:
return service.create_model(request)
@router.get("/models/{model_id}", response_model=ModelResponse)
def get_model(model_id: uuid.UUID, service: Service) -> ModelResponse:
return service.get_model(model_id)
@router.patch(
"/models/{model_id}",
response_model=ModelResponse,
dependencies=[Depends(require_operator)],
)
def update_model(model_id: uuid.UUID, request: ModelUpdate, service: Service) -> ModelResponse:
return service.update_model(model_id, request)
@router.post(
"/models/{model_id}/deprecate",
response_model=ModelResponse,
dependencies=[Depends(require_admin)],
)
def deprecate_model(model_id: uuid.UUID, service: Service) -> ModelResponse:
return service.deprecate_model(model_id)
@router.post(
"/models/{model_id}/archive",
response_model=ModelResponse,
dependencies=[Depends(require_admin)],
)
def archive_model(model_id: uuid.UUID, service: Service) -> ModelResponse:
return service.archive_model(model_id)
@router.delete(
"/models/{model_id}",
status_code=status.HTTP_204_NO_CONTENT,
dependencies=[Depends(require_admin)],
)
def delete_model(model_id: uuid.UUID, service: Service) -> Response:
service.delete("model", model_id)
return Response(status_code=status.HTTP_204_NO_CONTENT)
@router.get("/models/{model_id}/revisions", response_model=Page[RevisionResponse])
def list_revisions(
model_id: uuid.UUID,
service: Service,
page: Annotated[int, Query(ge=1)] = 1,
page_size: Annotated[int, Query(ge=1, le=100)] = 20,
) -> Page[RevisionResponse]:
return page_values(service.revisions(model_id), page, page_size)
@router.post(
"/models/{model_id}/revisions",
response_model=RevisionResponse,
status_code=status.HTTP_201_CREATED,
dependencies=[Depends(require_operator)],
)
def create_revision(
model_id: uuid.UUID, request: RevisionCreate, service: Service
) -> RevisionResponse:
return service.create_revision(model_id, request)
@router.delete(
"/revisions/{revision_id}",
status_code=status.HTTP_204_NO_CONTENT,
dependencies=[Depends(require_admin)],
)
def delete_revision(revision_id: uuid.UUID, service: Service) -> Response:
service.delete("model_revision", revision_id)
return Response(status_code=status.HTTP_204_NO_CONTENT)
@router.get("/revisions/{revision_id}/artifacts", response_model=Page[ArtifactResponse])
def list_artifacts(
revision_id: uuid.UUID,
service: Service,
page: Annotated[int, Query(ge=1)] = 1,
page_size: Annotated[int, Query(ge=1, le=100)] = 20,
) -> Page[ArtifactResponse]:
return page_values(service.artifacts(revision_id), page, page_size)
@router.post(
"/revisions/{revision_id}/artifacts",
response_model=ArtifactResponse,
status_code=status.HTTP_201_CREATED,
dependencies=[Depends(require_operator)],
)
def create_artifact(
revision_id: uuid.UUID, request: ArtifactCreate, service: Service
) -> ArtifactResponse:
return service.create_artifact(revision_id, request)
@router.delete(
"/artifacts/{artifact_id}",
status_code=status.HTTP_204_NO_CONTENT,
dependencies=[Depends(require_admin)],
)
def delete_artifact(artifact_id: uuid.UUID, service: Service) -> Response:
service.delete("model_artifact", artifact_id)
return Response(status_code=status.HTTP_204_NO_CONTENT)
@router.post(
"/artifacts/{artifact_id}/verify",
response_model=VerifyResponse,
dependencies=[Depends(require_operator)],
)
def verify_artifact(
artifact_id: uuid.UUID, location_id: uuid.UUID, service: Service
) -> VerifyResponse:
return service.verify_artifact(artifact_id, location_id)
@router.get(
"/revisions/{revision_id}/derived-artifacts", response_model=Page[DerivedArtifactResponse]
)
def list_derived_artifacts(
revision_id: uuid.UUID,
service: Service,
page: Annotated[int, Query(ge=1)] = 1,
page_size: Annotated[int, Query(ge=1, le=100)] = 20,
) -> Page[DerivedArtifactResponse]:
return page_values(service.derived(revision_id), page, page_size)
@router.post(
"/derived-artifacts",
response_model=DerivedArtifactResponse,
status_code=status.HTTP_201_CREATED,
dependencies=[Depends(require_operator)],
)
def create_derived_artifact(
request: DerivedArtifactCreate, service: Service
) -> DerivedArtifactResponse:
return service.create_derived(request)
@router.delete(
"/derived-artifacts/{artifact_id}",
status_code=status.HTTP_204_NO_CONTENT,
dependencies=[Depends(require_admin)],
)
def delete_derived_artifact(artifact_id: uuid.UUID, service: Service) -> Response:
service.delete("derived_artifact", artifact_id)
return Response(status_code=status.HTTP_204_NO_CONTENT)
@router.get("/storage-roots", response_model=list[StorageRootResponse])
def list_storage_roots(
service: Service, node_id: uuid.UUID | None = None
) -> list[StorageRootResponse]:
return service.storage_roots(node_id)
@router.post(
"/storage-roots",
response_model=StorageRootResponse,
status_code=status.HTTP_201_CREATED,
dependencies=[Depends(require_admin)],
)
def create_storage_root(request: StorageRootCreate, service: Service) -> StorageRootResponse:
return service.create_storage_root(request)
@router.patch(
"/storage-roots/{root_id}",
response_model=StorageRootResponse,
dependencies=[Depends(require_admin)],
)
def update_storage_root(
root_id: uuid.UUID, request: StorageRootUpdate, service: Service
) -> StorageRootResponse:
return service.update_storage_root(root_id, request)
@router.post(
"/storage-roots/{root_id}/observations",
response_model=StorageRootResponse,
dependencies=[Depends(require_operator)],
)
def observe_storage_root(
root_id: uuid.UUID, request: StorageRootObservation, service: Service
) -> StorageRootResponse:
return service.observe_storage_root(root_id, request)
@router.get(
"/storage-roots/{root_id}/capacity",
response_model=CapacityDecision,
dependencies=[Depends(require_operator)],
)
def check_capacity(root_id: uuid.UUID, requested_bytes: int, service: Service) -> CapacityDecision:
return service.check_capacity(root_id, requested_bytes)
@router.delete(
"/storage-roots/{root_id}",
status_code=status.HTTP_204_NO_CONTENT,
dependencies=[Depends(require_admin)],
)
def delete_storage_root(root_id: uuid.UUID, service: Service) -> Response:
service.delete("storage_root", root_id)
return Response(status_code=status.HTTP_204_NO_CONTENT)
@@ -0,0 +1,187 @@
from __future__ import annotations
import uuid
from typing import Annotated
from fastapi import APIRouter, Depends, Query
from sqlalchemy.orm import Session
from modelforge_api.api.authorization import require_admin, require_operator, require_viewer
from modelforge_api.db import get_session
from modelforge_api.domain.runtime import (
CompatibilityAssessmentCreate,
CompatibilityAssessmentResponse,
DeploymentCandidateResponse,
ExecutionApprovalCreate,
ExecutionApprovalResponse,
RuntimeEnvironmentCreate,
RuntimeEnvironmentResponse,
RuntimeProbeCreate,
RuntimeProbeResponse,
RuntimeProfileCreate,
RuntimeProfileResponse,
)
from modelforge_api.services.runtime import RuntimeService
from modelforge_api.settings import Settings, get_settings
router = APIRouter(
prefix="/api/v1", tags=["runtime-plane"], dependencies=[Depends(require_viewer)]
)
def get_runtime_service(
session: Annotated[Session, Depends(get_session)],
settings: Annotated[Settings, Depends(get_settings)],
) -> RuntimeService:
return RuntimeService(session, settings)
Service = Annotated[RuntimeService, Depends(get_runtime_service)]
@router.post(
"/runtime-environments",
response_model=RuntimeEnvironmentResponse,
status_code=201,
dependencies=[Depends(require_operator)],
)
def create_environment(
request: RuntimeEnvironmentCreate, service: Service
) -> RuntimeEnvironmentResponse:
return service.create_environment(request)
@router.get("/runtime-environments", response_model=list[RuntimeEnvironmentResponse])
def environments(service: Service) -> list[RuntimeEnvironmentResponse]:
return service.environments()
@router.get("/runtime-environments/{environment_id}", response_model=RuntimeEnvironmentResponse)
def environment(environment_id: uuid.UUID, service: Service) -> RuntimeEnvironmentResponse:
return service.environment(environment_id)
@router.post(
"/runtime-profiles",
response_model=RuntimeProfileResponse,
status_code=201,
dependencies=[Depends(require_operator)],
)
def create_profile(request: RuntimeProfileCreate, service: Service) -> RuntimeProfileResponse:
return service.create_profile(request)
@router.get("/runtime-profiles", response_model=list[RuntimeProfileResponse])
def profiles(service: Service) -> list[RuntimeProfileResponse]:
return service.profiles()
@router.get("/runtime-profiles/{profile_id}", response_model=RuntimeProfileResponse)
def profile(profile_id: uuid.UUID, service: Service) -> RuntimeProfileResponse:
return service.profile(profile_id)
@router.post(
"/artifact-sets/{artifact_set_id}/compatibility-assessments",
response_model=CompatibilityAssessmentResponse,
status_code=201,
dependencies=[Depends(require_operator)],
)
def assess(
artifact_set_id: uuid.UUID,
request: CompatibilityAssessmentCreate,
service: Service,
) -> CompatibilityAssessmentResponse:
profile = service.repo.profile(request.runtime_profile_id)
if not profile or profile.artifact_set_id != artifact_set_id:
from modelforge_api.services.registry import RegistryConflict
raise RegistryConflict("runtime profile belongs to another artifact set")
return service.assess(request)
@router.get("/compatibility-assessments", response_model=list[CompatibilityAssessmentResponse])
def assessments(
service: Service,
artifact_set_id: Annotated[uuid.UUID | None, Query()] = None,
model_id: Annotated[uuid.UUID | None, Query()] = None,
compute_node_id: Annotated[uuid.UUID | None, Query()] = None,
runtime_profile_id: Annotated[uuid.UUID | None, Query()] = None,
status: Annotated[str | None, Query()] = None,
) -> list[CompatibilityAssessmentResponse]:
return service.assessments(
artifact_set_id=artifact_set_id,
model_id=model_id,
compute_node_id=compute_node_id,
runtime_profile_id=runtime_profile_id,
status=status,
)
@router.get(
"/compatibility-assessments/{assessment_id}",
response_model=CompatibilityAssessmentResponse,
)
def assessment(assessment_id: uuid.UUID, service: Service) -> CompatibilityAssessmentResponse:
return service.assessment(assessment_id)
@router.post(
"/artifact-sets/{artifact_set_id}/execution-approvals",
response_model=ExecutionApprovalResponse,
status_code=201,
dependencies=[Depends(require_admin)],
)
def approve_execution(
artifact_set_id: uuid.UUID,
request: ExecutionApprovalCreate,
service: Service,
) -> ExecutionApprovalResponse:
return service.approve(artifact_set_id, request)
@router.get("/execution-approvals", response_model=list[ExecutionApprovalResponse])
def approvals(
service: Service,
artifact_set_id: Annotated[uuid.UUID | None, Query()] = None,
) -> list[ExecutionApprovalResponse]:
return service.approvals(artifact_set_id)
@router.post(
"/runtime-probes",
response_model=RuntimeProbeResponse,
status_code=201,
dependencies=[Depends(require_operator)],
)
def create_probe(request: RuntimeProbeCreate, service: Service) -> RuntimeProbeResponse:
return service.create_probe(request)
@router.get("/runtime-probes", response_model=list[RuntimeProbeResponse])
def probes(service: Service) -> list[RuntimeProbeResponse]:
return service.probes()
@router.get("/runtime-probes/{probe_id}", response_model=RuntimeProbeResponse)
def probe(probe_id: uuid.UUID, service: Service) -> RuntimeProbeResponse:
return service.probe(probe_id)
@router.post(
"/runtime-probes/{probe_id}/cancel",
response_model=RuntimeProbeResponse,
dependencies=[Depends(require_operator)],
)
def cancel_probe(probe_id: uuid.UUID, service: Service) -> RuntimeProbeResponse:
return service.cancel(probe_id)
@router.get("/deployment-candidates", response_model=list[DeploymentCandidateResponse])
def deployment_candidates(service: Service) -> list[DeploymentCandidateResponse]:
return service.candidates()
@router.get("/deployment-candidates/{candidate_id}", response_model=DeploymentCandidateResponse)
def deployment_candidate(candidate_id: uuid.UUID, service: Service) -> DeploymentCandidateResponse:
return service.candidate(candidate_id)
@@ -0,0 +1,582 @@
from __future__ import annotations
import uuid
from functools import lru_cache
from typing import Annotated
from fastapi import APIRouter, Depends, Header, Query, Request, Response, status
from sqlalchemy.orm import Session
from modelforge_api.api.authorization import Admin, require_viewer
from modelforge_api.api.routes.agent import AgentIdentity
from modelforge_api.db import get_session
from modelforge_api.domain.serving import (
AgentServingJobComplete,
AgentServingJobFailure,
AgentServingJobLease,
AgentServingStateAck,
AgentServingStateReport,
CapabilityDeploymentResponse,
CapabilityExperimentCreate,
CapabilityExperimentResponse,
CapabilityPromotionCreate,
CoResidencyEvidenceCreate,
CoResidencyEvidenceResponse,
EmbeddingInvokeRequest,
EmbeddingInvokeResponse,
GatewayRequestResponse,
OCRInvokeRequest,
OCRInvokeResponse,
OpenAIEmbeddingItem,
OpenAIEmbeddingRequest,
OpenAIEmbeddingResponse,
OpenAIUsage,
PlacementPlanRequest,
PlacementPlanResponse,
ProductionApprovalCreate,
ProductionApprovalResponse,
ProjectFitEvidenceCreate,
ProjectFitEvidenceResponse,
ProjectIntegrationResponse,
RerankingInvokeRequest,
RerankingInvokeResponse,
ResidencyPolicyUpdate,
SchedulerBudgetResponse,
SchedulerMetricsResponse,
SchedulerPolicyResponse,
SchedulerPolicyUpdate,
ServiceClientCreate,
ServiceClientCreated,
ServiceClientResponse,
SpeechTranscriptionInvokeRequest,
SpeechTranscriptionInvokeResponse,
StableEmbeddingInvokeResponse,
VisionEmbeddingInvokeRequest,
VisionEmbeddingInvokeResponse,
)
from modelforge_api.persistence.models import ServiceClient
from modelforge_api.services.manifest_registry import ManifestRegistry, get_manifest_registry
from modelforge_api.services.serving import (
CapabilityAuthenticationEvidence,
ServingError,
ServingService,
)
from modelforge_api.services.transient_payloads import RedisPayloadStore
from modelforge_api.settings import Settings, get_settings
router = APIRouter(tags=["capability-serving"])
@lru_cache
def payload_store() -> RedisPayloadStore:
return RedisPayloadStore(get_settings().redis_url)
def get_serving_service(
session: Annotated[Session, Depends(get_session)],
settings: Annotated[Settings, Depends(get_settings)],
manifests: Annotated[ManifestRegistry, Depends(get_manifest_registry)],
) -> ServingService:
return ServingService(session, settings, manifests, payload_store())
Service = Annotated[ServingService, Depends(get_serving_service)]
def _authenticate_capability_client(
request: Request,
service: ServingService,
authorization: str | None,
capability: str,
) -> ServiceClient:
evidence = getattr(request.state, "capability_authentication", None)
if isinstance(evidence, CapabilityAuthenticationEvidence):
return service.reuse_authentication(evidence, authorization, capability)
return service.authenticate(authorization, capability)
def authenticate_rag_embedding_client(
request: Request,
service: Service,
authorization: Annotated[str | None, Header(alias="Authorization")] = None,
) -> ServiceClient:
return _authenticate_capability_client(request, service, authorization, "rag.embedding@1")
def authenticate_rag_reranking_client(
request: Request,
service: Service,
authorization: Annotated[str | None, Header(alias="Authorization")] = None,
) -> ServiceClient:
return _authenticate_capability_client(request, service, authorization, "rag.reranking@1")
def authenticate_document_ocr_client(
request: Request,
service: Service,
authorization: Annotated[str | None, Header(alias="Authorization")] = None,
) -> ServiceClient:
return _authenticate_capability_client(request, service, authorization, "document.ocr@1")
def authenticate_vision_embedding_client(
request: Request,
service: Service,
authorization: Annotated[str | None, Header(alias="Authorization")] = None,
) -> ServiceClient:
return _authenticate_capability_client(request, service, authorization, "vision.embedding@1")
def authenticate_speech_transcription_client(
request: Request,
service: Service,
authorization: Annotated[str | None, Header(alias="Authorization")] = None,
) -> ServiceClient:
return _authenticate_capability_client(
request, service, authorization, "speech.transcription@1"
)
RagEmbeddingClient = Annotated[ServiceClient, Depends(authenticate_rag_embedding_client)]
RagRerankingClient = Annotated[ServiceClient, Depends(authenticate_rag_reranking_client)]
DocumentOcrClient = Annotated[ServiceClient, Depends(authenticate_document_ocr_client)]
VisionEmbeddingClient = Annotated[ServiceClient, Depends(authenticate_vision_embedding_client)]
SpeechTranscriptionClient = Annotated[
ServiceClient, Depends(authenticate_speech_transcription_client)
]
@router.post(
"/api/v1/admin/deployment-candidates/{candidate_id}/production-approvals",
response_model=ProductionApprovalResponse,
status_code=status.HTTP_201_CREATED,
)
def approve_production(
candidate_id: uuid.UUID,
request: ProductionApprovalCreate,
service: Service,
_admin: Admin,
) -> ProductionApprovalResponse:
return service.approve_production(candidate_id, request)
@router.get(
"/api/v1/admin/production-approvals",
response_model=list[ProductionApprovalResponse],
)
def production_approvals(service: Service, _admin: Admin) -> list[ProductionApprovalResponse]:
return service.approvals()
@router.post(
"/api/v1/admin/deployment-candidates/{candidate_id}/promote",
response_model=CapabilityDeploymentResponse,
status_code=status.HTTP_201_CREATED,
)
def promote_candidate(
candidate_id: uuid.UUID,
request: CapabilityPromotionCreate,
service: Service,
_admin: Admin,
) -> CapabilityDeploymentResponse:
return service.promote(candidate_id, request)
@router.get(
"/api/v1/capability-deployments",
response_model=list[CapabilityDeploymentResponse],
dependencies=[Depends(require_viewer)],
)
def capability_deployments(service: Service) -> list[CapabilityDeploymentResponse]:
return service.deployments()
@router.post(
"/api/v1/admin/deployment-candidates/{candidate_id}/experiments",
response_model=CapabilityExperimentResponse,
status_code=status.HTTP_201_CREATED,
)
def create_capability_experiment(
candidate_id: uuid.UUID,
request: CapabilityExperimentCreate,
service: Service,
_admin: Admin,
) -> CapabilityExperimentResponse:
return service.create_experiment(candidate_id, request)
@router.get(
"/api/v1/capability-experiments",
response_model=list[CapabilityExperimentResponse],
dependencies=[Depends(require_viewer)],
)
def capability_experiments(service: Service) -> list[CapabilityExperimentResponse]:
return service.experiments()
@router.post(
"/api/v1/admin/capability-experiments/{experiment_id}/deactivate",
response_model=CapabilityExperimentResponse,
)
def deactivate_capability_experiment(
experiment_id: uuid.UUID, service: Service, _admin: Admin
) -> CapabilityExperimentResponse:
return service.deactivate_experiment(experiment_id)
@router.post(
"/api/v1/admin/capability-deployments/{deployment_id}/unload",
response_model=CapabilityDeploymentResponse,
)
def unload_deployment(
deployment_id: uuid.UUID, service: Service, _admin: Admin
) -> CapabilityDeploymentResponse:
return service.request_unload(deployment_id)
@router.post(
"/api/v1/admin/capability-deployments/{deployment_id}/drain",
response_model=CapabilityDeploymentResponse,
)
def drain_deployment(
deployment_id: uuid.UUID, service: Service, _admin: Admin
) -> CapabilityDeploymentResponse:
return service.request_unload(deployment_id, drain=True)
@router.post(
"/api/v1/admin/service-clients",
response_model=ServiceClientCreated,
status_code=status.HTTP_201_CREATED,
)
def create_service_client(
request: ServiceClientCreate,
service: Service,
_admin: Admin,
response: Response,
) -> ServiceClientCreated:
response.headers["Cache-Control"] = "no-store"
return service.create_client(request)
@router.get("/api/v1/admin/service-clients", response_model=list[ServiceClientResponse])
def service_clients(service: Service, _admin: Admin) -> list[ServiceClientResponse]:
return service.clients()
@router.delete(
"/api/v1/admin/service-clients/{client_id}/credential",
response_model=ServiceClientResponse,
)
def revoke_service_credential(
client_id: uuid.UUID, service: Service, _admin: Admin
) -> ServiceClientResponse:
return service.revoke_client_credential(client_id)
@router.post(
"/api/v1/admin/service-clients/{client_id}/credential/rotate",
response_model=ServiceClientCreated,
)
def rotate_service_credential(
client_id: uuid.UUID, service: Service, _admin: Admin, response: Response
) -> ServiceClientCreated:
response.headers["Cache-Control"] = "no-store"
return service.rotate_client_credential(client_id)
@router.get(
"/api/v1/project-integrations",
response_model=list[ProjectIntegrationResponse],
dependencies=[Depends(require_viewer)],
)
def project_integrations(service: Service) -> list[ProjectIntegrationResponse]:
return service.project_integrations()
@router.post(
"/api/v1/admin/project-fit-evidence",
response_model=ProjectFitEvidenceResponse,
status_code=status.HTTP_201_CREATED,
)
def record_project_fit_evidence(
request: ProjectFitEvidenceCreate, service: Service, _admin: Admin
) -> ProjectFitEvidenceResponse:
return service.record_project_fit(request)
@router.get(
"/api/v1/scheduler",
response_model=list[SchedulerBudgetResponse],
dependencies=[Depends(require_viewer)],
)
def scheduler_overview(service: Service) -> list[SchedulerBudgetResponse]:
return service.scheduler_overview()
@router.get(
"/api/v1/scheduler/co-residency",
response_model=list[CoResidencyEvidenceResponse],
dependencies=[Depends(require_viewer)],
)
def co_residency_matrix(service: Service) -> list[CoResidencyEvidenceResponse]:
return service.co_residency_matrix()
@router.post(
"/api/v1/admin/scheduler/co-residency-evidence",
response_model=CoResidencyEvidenceResponse,
status_code=status.HTTP_201_CREATED,
)
def record_co_residency_evidence(
request: CoResidencyEvidenceCreate, service: Service, _admin: Admin
) -> CoResidencyEvidenceResponse:
return service.record_co_residency_evidence(request)
@router.get("/api/v1/admin/scheduler/policy", response_model=SchedulerPolicyResponse)
def scheduler_policy(service: Service, _admin: Admin) -> SchedulerPolicyResponse:
return service.scheduler_policy()
@router.get("/api/v1/admin/scheduler/metrics", response_model=SchedulerMetricsResponse)
def scheduler_metrics(service: Service, _admin: Admin) -> SchedulerMetricsResponse:
return service.scheduler_metrics()
@router.put("/api/v1/admin/scheduler/policy", response_model=SchedulerPolicyResponse)
def update_scheduler_policy(
request: SchedulerPolicyUpdate, service: Service, _admin: Admin
) -> SchedulerPolicyResponse:
return service.update_scheduler_policy(request)
@router.post(
"/api/v1/admin/scheduler/placements/{deployment_id}/dry-run",
response_model=PlacementPlanResponse,
)
def dry_run_placement(
deployment_id: uuid.UUID,
request: PlacementPlanRequest,
service: Service,
_admin: Admin,
) -> PlacementPlanResponse:
return service.dry_run_placement(deployment_id, request)
@router.get(
"/api/v1/admin/scheduler/placements",
response_model=list[PlacementPlanResponse],
)
def placement_history(
service: Service,
_admin: Admin,
limit: Annotated[int, Query(ge=1, le=500)] = 100,
) -> list[PlacementPlanResponse]:
return service.placement_history(limit)
@router.put(
"/api/v1/admin/capability-deployments/{deployment_id}/residency-policy",
response_model=CapabilityDeploymentResponse,
)
def update_residency_policy(
deployment_id: uuid.UUID,
request: ResidencyPolicyUpdate,
service: Service,
_admin: Admin,
) -> CapabilityDeploymentResponse:
return service.update_residency_policy(deployment_id, request)
@router.get("/api/v1/gateway/requests", response_model=list[GatewayRequestResponse])
def gateway_requests(
service: Service,
_admin: Admin,
limit: Annotated[int, Query(ge=1, le=500)] = 100,
) -> list[GatewayRequestResponse]:
return service.request_history(limit)
@router.get("/api/v1/admin/latency-traces", response_model=list[GatewayRequestResponse])
def latency_traces(
service: Service,
_admin: Admin,
limit: Annotated[int, Query(ge=1, le=500)] = 100,
) -> list[GatewayRequestResponse]:
"""Return bounded span summaries; request content and input digests are excluded."""
return service.request_history(limit)
@router.post(
"/api/v1/capabilities/rag.embedding@1/invoke",
response_model=StableEmbeddingInvokeResponse,
)
def invoke_embedding(
request: EmbeddingInvokeRequest,
service: Service,
client: RagEmbeddingClient,
) -> StableEmbeddingInvokeResponse:
return StableEmbeddingInvokeResponse.model_validate(service.invoke(request, client))
@router.post(
"/api/v1/capabilities/rag.reranking@1/invoke",
response_model=RerankingInvokeResponse,
)
def invoke_reranking(
request: RerankingInvokeRequest,
service: Service,
client: RagRerankingClient,
) -> RerankingInvokeResponse:
return service.invoke_reranking(request, client)
@router.post(
"/api/v1/capabilities/document.ocr@1/invoke",
response_model=OCRInvokeResponse,
)
def invoke_ocr(
request: OCRInvokeRequest,
service: Service,
client: DocumentOcrClient,
) -> OCRInvokeResponse:
result, request_id, execution = service.invoke_modality(
"document.ocr", request.model_dump(mode="json"), client
)
return OCRInvokeResponse(request_id=request_id, execution=execution, **result)
@router.post(
"/api/v1/capabilities/vision.embedding@1/invoke",
response_model=VisionEmbeddingInvokeResponse,
)
def invoke_vision_embedding(
request: VisionEmbeddingInvokeRequest,
service: Service,
client: VisionEmbeddingClient,
) -> VisionEmbeddingInvokeResponse:
result, request_id, execution = service.invoke_modality(
"vision.embedding", request.model_dump(mode="json"), client
)
return VisionEmbeddingInvokeResponse(
request_id=request_id,
execution=execution,
dimension=int(result["dimension"]),
embedding_space_id=result["embedding_space_id"],
data=result["vectors"],
)
@router.post(
"/api/v1/capabilities/speech.transcription@1/invoke",
response_model=SpeechTranscriptionInvokeResponse,
)
def invoke_speech_transcription(
request: SpeechTranscriptionInvokeRequest,
service: Service,
client: SpeechTranscriptionClient,
) -> SpeechTranscriptionInvokeResponse:
result, request_id, execution = service.invoke_modality(
"speech.transcription", request.model_dump(mode="json"), client
)
return SpeechTranscriptionInvokeResponse(
request_id=request_id,
execution=execution,
**result,
)
@router.post(
"/api/v1/capability-experiments/{route_key}/invoke",
response_model=EmbeddingInvokeResponse,
)
def invoke_embedding_experiment(
route_key: str,
request: EmbeddingInvokeRequest,
service: Service,
client: RagEmbeddingClient,
) -> EmbeddingInvokeResponse:
route = service.repo.experiment_route(route_key)
deployment = (
service.repo.deployment(route.capability_deployment_id)
if route and route.status == "active"
else None
)
if not deployment:
raise ServingError(404, "EXPERIMENT_NOT_FOUND", "capability experiment is unavailable")
return service.invoke(
request,
client,
deployment=deployment,
experiment_route=route_key,
)
@router.post("/v1/embeddings", response_model=OpenAIEmbeddingResponse)
def openai_embeddings(
request: OpenAIEmbeddingRequest,
service: Service,
client: RagEmbeddingClient,
) -> OpenAIEmbeddingResponse:
native = service.invoke(EmbeddingInvokeRequest(input=request.input), client)
return OpenAIEmbeddingResponse(
data=[
OpenAIEmbeddingItem(index=index, embedding=embedding)
for index, embedding in enumerate(native.data)
],
usage=OpenAIUsage(
prompt_tokens=native.usage.input_tokens,
total_tokens=native.usage.input_tokens,
),
)
@router.get(
"/api/v1/agent/serving-jobs/next",
response_model=AgentServingJobLease | None,
)
def claim_serving_job(
service: Service,
identity: AgentIdentity,
wait_seconds: Annotated[float, Query(ge=0.0, le=5.0)] = 0.0,
) -> AgentServingJobLease | None:
_credential, node = identity
return service.claim_next(node, wait_seconds=wait_seconds)
@router.post("/api/v1/agent/serving-jobs/{job_id}/complete")
def complete_serving_job(
job_id: uuid.UUID,
request: AgentServingJobComplete,
service: Service,
identity: AgentIdentity,
) -> dict[str, str]:
_credential, node = identity
service.complete_job(job_id, node, request)
return {"status": "accepted"}
@router.post("/api/v1/agent/serving-jobs/{job_id}/fail")
def fail_serving_job(
job_id: uuid.UUID,
request: AgentServingJobFailure,
service: Service,
identity: AgentIdentity,
) -> dict[str, str]:
_credential, node = identity
service.fail_job(job_id, node, request)
return {"status": "accepted"}
@router.post(
"/api/v1/agent/serving-state",
response_model=AgentServingStateAck,
)
def report_serving_state(
request: AgentServingStateReport,
service: Service,
identity: AgentIdentity,
) -> AgentServingStateAck:
_credential, node = identity
return service.report_state(node, request)
@@ -0,0 +1 @@
"""ModelForge operator command-line surfaces."""
+334
View File
@@ -0,0 +1,334 @@
"""Operator disaster-recovery CLI.
Restoring over a live database is an operator action with a runbook behind it, not a remote API
call, so the destructive half of recovery lives here. Every subcommand takes typed arguments and
runs fixed operations: there is no pass-through shell, and no argument reaches a shell.
python -m modelforge_api.cli.dr create-backup --backup-id m15-rehearsal-a --reason "..."
python -m modelforge_api.cli.dr verify-backup --backup-id m15-rehearsal-a
python -m modelforge_api.cli.dr plan-restore --backup-id m15-rehearsal-a --target-url ...
python -m modelforge_api.cli.dr validate-restore --plan-id <uuid>
python -m modelforge_api.cli.dr run-restore --plan-id <uuid> --reason "..."
python -m modelforge_api.cli.dr readiness
python -m modelforge_api.cli.dr bundle --backup-id m15-rehearsal-a
"""
from __future__ import annotations
import argparse
import json
import sys
import uuid
from typing import Any
from sqlalchemy import select
from sqlalchemy.orm import Session
from modelforge_api.db import build_engine
from modelforge_api.domain.recovery import (
POINT_IN_TIME_SUPPORT,
BackupSetCreate,
RestoreAdvanceRequest,
RestoreMode,
RestorePlanCreate,
redact_database_url,
)
from modelforge_api.persistence.models import BackupSet
from modelforge_api.services.recovery import RecoveryError, RecoveryService
from modelforge_api.settings import get_settings
def _service(session: Session) -> RecoveryService:
return RecoveryService(session, get_settings(), "operator", "dr-cli")
def _emit(payload: Any) -> None:
print(json.dumps(payload, indent=2, default=str))
def _backup_by_id(session: Session, backup_id: str) -> BackupSet:
record = session.scalar(select(BackupSet).where(BackupSet.backup_id == backup_id))
if record is None:
raise RecoveryError(404, "backup_not_found", f"no backup set named {backup_id}")
return record
def _cmd_create_backup(session: Session, args: argparse.Namespace) -> int:
service = _service(session)
service.ensure_defaults()
response = service.create_backup(
BackupSetCreate(
backup_id=args.backup_id,
reason=args.reason,
milestone=args.milestone,
legal_hold=args.legal_hold,
created_by=args.actor,
)
)
_emit(json.loads(response.model_dump_json()))
return 0 if response.state.value in {"CREATED", "VERIFIED"} else 1
def _cmd_verify_backup(session: Session, args: argparse.Namespace) -> int:
service = _service(session)
record = _backup_by_id(session, args.backup_id)
response = service.verify_backup(record.id)
_emit(
{
"backup_id": response.backup_id,
"state": response.state.value,
"restore_eligible": response.restore_eligible,
"manifest_sha256": response.manifest_sha256,
"failure_code": response.failure_code,
"failure_reason": response.failure_reason,
"verification": response.verification_details.get("verification", {}),
}
)
return 0 if response.restore_eligible else 1
def _cmd_list_backups(session: Session, args: argparse.Namespace) -> int:
service = _service(session)
_emit(
[
{
"backup_id": item.backup_id,
"state": item.state.value,
"restore_eligible": item.restore_eligible,
"schema_revision": item.schema_revision,
"payload_bytes": item.payload_bytes,
"encrypted": item.encrypted,
"verified_at": item.verified_at,
"expires_at": item.expires_at,
}
for item in service.backups(args.limit)
]
)
return 0
def _cmd_plan_restore(session: Session, args: argparse.Namespace) -> int:
service = _service(session)
record = _backup_by_id(session, args.backup_id)
response = service.create_restore_plan(
RestorePlanCreate(
backup_set_id=record.id,
mode=RestoreMode(args.mode),
target_environment=args.target_environment,
target_label=args.target_label,
database_destination=args.target_url,
artifact_strategy=args.artifact_strategy,
secret_strategy=args.secret_strategy,
node_strategy=args.node_strategy,
reason=args.reason,
created_by=args.actor,
)
)
_emit(json.loads(response.model_dump_json()))
return 0
def _cmd_validate_restore(session: Session, args: argparse.Namespace) -> int:
service = _service(session)
response = service.preflight(uuid.UUID(args.plan_id))
_emit(
{
"plan_id": str(response.id),
"backup_id": response.backup_id,
"state": response.state.value,
"destination": response.database_destination_redacted,
"preflight": response.preflight,
}
)
return 0 if response.preflight.get("status") == "PASS" else 1
def _cmd_run_restore(session: Session, args: argparse.Namespace) -> int:
service = _service(session)
plan_id = uuid.UUID(args.plan_id)
request = RestoreAdvanceRequest(actor=args.actor, reason=args.reason)
operation = service.start_restore(plan_id, request)
operation = service.advance_restore(operation.id, request)
_emit(
{
"operation_id": str(operation.id),
"backup_id": operation.backup_id,
"state": operation.state.value,
"attempt": operation.attempt,
"phase_durations": operation.phase_durations,
"rto_seconds": operation.rto_seconds,
"rpo_seconds": operation.rpo_seconds,
"validation": operation.validation_result,
"fingerprint_diff": operation.fingerprint_diff,
"failure_code": operation.failure_code,
"failure_reason": operation.failure_reason,
}
)
return 0 if operation.state.value == "READY" else 1
def _cmd_readiness(session: Session, _args: argparse.Namespace) -> int:
service = _service(session)
dashboard = service.dashboard()
_emit(json.loads(dashboard.model_dump_json()))
return 0 if not dashboard.unprotected_assets and not dashboard.stale_backup else 1
def _cmd_bundle(session: Session, args: argparse.Namespace) -> int:
"""Print the bounded DR bundle manifest: what an operator needs to rebuild from nothing."""
service = _service(session)
record = _backup_by_id(session, args.backup_id)
backup = service.backup(record.id)
settings = get_settings()
_emit(
{
"modelforge": {
"version": backup.modelforge_version,
"commit": backup.modelforge_commit,
"reference": backup.environment_fingerprint.get("source_reference"),
"repository": record.source_repository,
},
"database_backup": {
"backup_id": backup.backup_id,
"state": backup.state.value,
"restore_eligible": backup.restore_eligible,
"destination_root": backup.destination_root,
"manifest": backup.manifest_relative_path,
"manifest_sha256": backup.manifest_sha256,
"payload_bytes": backup.payload_bytes,
"schema_revision": backup.schema_revision,
"database_identity": backup.database_identity,
"point_in_time_support": POINT_IN_TIME_SUPPORT,
},
"encryption": {
"encrypted": backup.encrypted,
"algorithm": backup.encryption_algorithm,
"key_id": backup.encryption_key_id,
"key_requirement": (
"MODELFORGE_BACKUP_ENCRYPTION_KEY must be supplied by the operator; it is "
"never written into a backup"
),
},
"host_configuration": {
"backup_root": str(settings.backup_root),
"artifact_root": settings.artifact_root,
"quarantine_root": settings.quarantine_root,
"config_root": str(settings.config_root),
"database_url": redact_database_url(settings.database_url),
},
"artifact_recovery_plan": [
{
"object": entry.object_name,
"type": entry.logical_asset_type,
"sha256": entry.sha256,
"size_bytes": entry.size_bytes,
}
for entry in backup.entries
],
"node_enrollment": (
"Issue a fresh enrollment token through /api/v1/admin/node-enrollments and "
"revoke any credential believed lost; hardware identity is persisted so a "
"recovered node keeps its node id."
),
"external_dependencies": [
item.asset_key
for item in service.readiness()
if item.readiness.value == "EXTERNAL_DEPENDENCY"
],
"runbooks": [
"docs/operations/RUNBOOK_FULL_DR.md",
"docs/operations/RUNBOOK_DATABASE_RESTORE.md",
"docs/operations/RUNBOOK_CONTROL_PLANE_LOSS.md",
"docs/operations/RUNBOOK_NODE_LOSS.md",
"docs/operations/RUNBOOK_ARTIFACT_LOSS.md",
"docs/operations/RUNBOOK_BACKUP_FAILURE.md",
],
}
)
return 0
def build_parser() -> argparse.ArgumentParser:
parser = argparse.ArgumentParser(
prog="modelforge-dr", description="ModelForge disaster-recovery operator commands"
)
parser.add_argument("--database-url", default=None, help="override the control-plane database")
subparsers = parser.add_subparsers(dest="command", required=True)
create = subparsers.add_parser("create-backup", help="create a new backup set")
create.add_argument("--backup-id", required=True)
create.add_argument("--reason", required=True)
create.add_argument("--milestone", default=None)
create.add_argument("--legal-hold", action="store_true")
create.add_argument("--actor", default="operator")
create.set_defaults(handler=_cmd_create_backup)
verify = subparsers.add_parser("verify-backup", help="verify a backup set end to end")
verify.add_argument("--backup-id", required=True)
verify.set_defaults(handler=_cmd_verify_backup)
listing = subparsers.add_parser("list-backups", help="list journaled backup sets")
listing.add_argument("--limit", type=int, default=25)
listing.set_defaults(handler=_cmd_list_backups)
plan = subparsers.add_parser("plan-restore", help="create a restore plan")
plan.add_argument("--backup-id", required=True)
plan.add_argument("--target-url", required=True)
plan.add_argument("--target-label", required=True)
plan.add_argument(
"--mode", choices=[item.value for item in RestoreMode], default=RestoreMode.VALIDATION.value
)
plan.add_argument(
"--target-environment", choices=["ISOLATED", "STAGING", "PRODUCTION"], default="ISOLATED"
)
plan.add_argument(
"--artifact-strategy",
choices=["NONE", "MANIFEST_ONLY", "REHYDRATE_MISSING", "RESTORE_LOCAL"],
default="MANIFEST_ONLY",
)
plan.add_argument(
"--secret-strategy", choices=["ROTATE", "RESTORE_HASHES", "MANUAL"], default="RESTORE_HASHES"
)
plan.add_argument(
"--node-strategy", choices=["REUSE_CREDENTIAL", "RE_ENROLL", "NONE"], default="NONE"
)
plan.add_argument("--reason", required=True)
plan.add_argument("--actor", default="operator")
plan.set_defaults(handler=_cmd_plan_restore)
validate = subparsers.add_parser("validate-restore", help="run the restore preflight")
validate.add_argument("--plan-id", required=True)
validate.set_defaults(handler=_cmd_validate_restore)
run = subparsers.add_parser("run-restore", help="execute a restore plan to completion")
run.add_argument("--plan-id", required=True)
run.add_argument("--reason", required=True)
run.add_argument("--actor", default="operator")
run.set_defaults(handler=_cmd_run_restore)
readiness = subparsers.add_parser("readiness", help="print recovery readiness")
readiness.set_defaults(handler=_cmd_readiness)
bundle = subparsers.add_parser("bundle", help="print the operator DR bundle manifest")
bundle.add_argument("--backup-id", required=True)
bundle.set_defaults(handler=_cmd_bundle)
return parser
def main(argv: list[str] | None = None) -> int:
args = build_parser().parse_args(argv)
engine = build_engine(args.database_url)
try:
with Session(engine) as session:
handler = args.handler
return int(handler(session, args))
except RecoveryError as error:
_emit({"error": {"code": error.code, "message": error.message, "details": error.details}})
return 2
finally:
engine.dispose()
if __name__ == "__main__": # pragma: no cover - operator entry point
sys.exit(main())
+38
View File
@@ -0,0 +1,38 @@
from collections.abc import Generator
from typing import Any, cast
from sqlalchemy import CursorResult, Engine, create_engine
from sqlalchemy.engine import Result
from sqlalchemy.orm import Session
from modelforge_api.persistence.runtime_engine import register_application_engine
from modelforge_api.settings import get_settings
def rows_affected(result: Result[Any]) -> int:
"""How many rows a DML statement actually changed.
`Session.execute` is typed as returning `Result`, which carries no `rowcount`; a DML statement
always returns a `CursorResult`, which does. SQLAlchemy 2.0.52 narrowed that return type, so
reading `.rowcount` directly stopped type-checking — while continuing to work at runtime.
The cast is where that knowledge lives, once, rather than at ten call sites. It matters more
than it looks: every single-use claim in the platform is a conditional UPDATE whose row count
decides the winner, and that is what makes enrolment tokens and lifecycle claims atomic instead
of merely usually-correct.
"""
return cast("CursorResult[Any]", result).rowcount
def build_engine(database_url: str | None = None) -> Engine:
url = database_url or get_settings().database_url
return register_application_engine(create_engine(url, pool_pre_ping=True))
engine = build_engine()
def get_session() -> Generator[Session, None, None]:
with Session(engine) as session:
yield session
@@ -0,0 +1,217 @@
from __future__ import annotations
import uuid
from datetime import datetime
from typing import Any, Literal
from pydantic import BaseModel, ConfigDict, Field, field_validator
class AcquisitionModel(BaseModel):
model_config = ConfigDict(extra="forbid", from_attributes=True)
class DiscoverySearchRequest(AcquisitionModel):
query: str = Field(min_length=1, max_length=255)
limit: int = Field(default=20, ge=1, le=100)
sort: Literal["downloads", "likes", "last_modified"] = "downloads"
pipeline_tag: str | None = Field(default=None, max_length=128)
class DiscoveryCandidate(AcquisitionModel):
repository_id: str
resolved_commit_sha: str | None = None
access_state: str
pipeline_tag: str | None = None
library_name: str | None = None
tags: list[str] = Field(default_factory=list)
downloads: int | None = None
likes: int | None = None
last_modified: datetime | None = None
matched_model_id: uuid.UUID | None = None
upstream_facts: dict[str, Any] = Field(default_factory=dict)
local_interpretation: dict[str, Any] = Field(default_factory=dict)
class UpstreamRefreshRequest(AcquisitionModel):
revision: str = Field(default="main", min_length=1, max_length=255)
class UpstreamFileResponse(AcquisitionModel):
id: uuid.UUID
path: str
size_bytes: int | None
blob_id: str | None
upstream_sha256: str | None
file_format: str
role: str
risk_flags: list[str]
metadata_snapshot: dict[str, Any]
class UpstreamSnapshotResponse(AcquisitionModel):
id: uuid.UUID
model_id: uuid.UUID | None
provider: str
repository_id: str
requested_revision: str
resolved_commit_sha: str
access_state: str
metadata_snapshot: dict[str, Any]
card_metadata: dict[str, Any]
security_metadata: dict[str, Any]
source_updated_at: datetime | None
observed_at: datetime
stale_after: datetime
stale: bool
files: list[UpstreamFileResponse]
class ArtifactSetResponse(AcquisitionModel):
id: uuid.UUID
revision_id: uuid.UUID
snapshot_id: uuid.UUID
variant_key: str
label: str
selection_reason: str
selected_paths: list[str]
total_size_bytes: int
file_count: int
availability: str
status: str
completeness: str
security_status: str
license_status: str
immutable_at: datetime
created_at: datetime
updated_at: datetime
class DownloadPlanCreate(AcquisitionModel):
artifact_set_id: uuid.UUID
compute_node_id: uuid.UUID
storage_root_id: uuid.UUID
expires_in_seconds: int = Field(default=3600, ge=300, le=86400)
class DownloadPlanFileResponse(AcquisitionModel):
ordinal: int
path: str
size_bytes: int
upstream_sha256: str | None
file_format: str
role: str
risk_flags: list[str]
class DownloadPlanResponse(AcquisitionModel):
id: uuid.UUID
artifact_set_id: uuid.UUID
compute_node_id: uuid.UUID
storage_root_id: uuid.UUID
repository_id: str
resolved_commit_sha: str
total_size_bytes: int
file_count: int
status: str
idempotency_key: str
preflight: dict[str, Any]
immutable_payload: dict[str, Any]
planned_at: datetime
expires_at: datetime
immutable_at: datetime
created_at: datetime
updated_at: datetime
stale: bool
files: list[DownloadPlanFileResponse]
class ArtifactJobResponse(AcquisitionModel):
id: uuid.UUID
plan_id: uuid.UUID
compute_node_id: uuid.UUID
storage_root_id: uuid.UUID
status: str
attempt_count: int
progress_bytes: int
total_bytes: int
current_file: str | None
cancel_requested: bool
quarantine_relative_path: str | None
promoted_relative_path: str | None
error_code: str | None
error_message: str | None
result: dict[str, Any]
started_at: datetime | None
completed_at: datetime | None
created_at: datetime
updated_at: datetime
class AgentArtifactJobFile(AcquisitionModel):
ordinal: int
path: str
size_bytes: int
upstream_sha256: str | None
file_format: str
role: str
risk_flags: list[str]
class AgentArtifactJobLease(AcquisitionModel):
job_id: uuid.UUID
lease_token: str
lease_expires_at: datetime
repository_id: str
resolved_commit_sha: str
storage_root_id: uuid.UUID
target_root: str
total_size_bytes: int
reserve_bytes: int
reserve_percent: int
files: list[AgentArtifactJobFile]
class AgentJobProgress(AcquisitionModel):
lease_token: str = Field(min_length=32, max_length=512)
status: Literal["claimed", "downloading", "verifying", "promoting"]
progress_bytes: int = Field(ge=0)
current_file: str | None = Field(default=None, max_length=2048)
quarantine_relative_path: str | None = Field(default=None, max_length=2048)
class AgentJobControl(AcquisitionModel):
accepted: bool
cancel_requested: bool
lease_expires_at: datetime
class CompletedFile(AcquisitionModel):
path: str
relative_path: str
size_bytes: int = Field(ge=0)
sha256: str = Field(pattern=r"^[0-9a-f]{64}$")
inspections: list[dict[str, Any]] = Field(default_factory=list)
@field_validator("path", "relative_path")
@classmethod
def safe_path(cls, value: str) -> str:
normalized = value.replace("\\", "/")
if normalized.startswith("/") or ".." in normalized.split("/"):
raise ValueError("path must be relative and confined")
return normalized
class AgentJobComplete(AcquisitionModel):
lease_token: str = Field(min_length=32, max_length=512)
promoted_relative_path: str
capacity_observation: dict[str, Any]
files: list[CompletedFile] = Field(min_length=1)
class AgentJobFailure(AcquisitionModel):
lease_token: str = Field(min_length=32, max_length=512)
error_code: str = Field(min_length=1, max_length=64)
error_message: str = Field(min_length=1, max_length=2000)
retryable: bool = False
details: dict[str, Any] = Field(default_factory=dict)
@@ -0,0 +1,135 @@
from __future__ import annotations
from datetime import datetime
from pydantic import BaseModel, ConfigDict, Field
from modelforge_api.domain.enums import Availability
from modelforge_api.domain.hardware import (
AcceleratorInventory,
AcceleratorTelemetry,
HostInventory,
ObservedValue,
StorageObservation,
)
AGENT_PROTOCOL_VERSION = 1
AGENT_PROTOCOL_CAPABILITIES = [
"hardware.inventory",
"hardware.telemetry",
"artifact.acquire.v1",
"runtime.probe.v1",
"runtime.health.v1",
"runtime.unload.v1",
"deployment.load.v1",
"deployment.invoke.v1",
"deployment.health.v1",
"deployment.drain.v1",
"deployment.unload.v1",
]
class AgentProtocolModel(BaseModel):
model_config = ConfigDict(extra="forbid")
class AgentMetadata(AgentProtocolModel):
agent_version: str
protocol_version: int
supported_capabilities: list[str] = Field(default_factory=list)
started_at: datetime
class EnrollmentRequest(AgentProtocolModel):
enrollment_token: str = Field(min_length=32, max_length=512)
identity_key: str = Field(min_length=1, max_length=128)
identity_source: str = Field(min_length=1, max_length=32)
hostname: str = Field(min_length=1, max_length=255)
display_name: str = Field(min_length=1, max_length=255)
metadata: AgentMetadata
class EnrollmentResponse(AgentProtocolModel):
node_id: str
credential_id: str
node_credential: str
protocol_version: int = AGENT_PROTOCOL_VERSION
class NodeCredentialCreated(AgentProtocolModel):
node_id: str
credential_id: str
node_credential: str
class HeartbeatRequest(AgentProtocolModel):
identity_key: str
metadata: AgentMetadata
observed_at: datetime
last_error: str | None = Field(default=None, max_length=1000)
class InventoryNvidiaPayload(AgentProtocolModel):
availability: Availability
reason: str | None = None
inventory: list[AcceleratorInventory] = Field(default_factory=list)
class InventoryReport(AgentProtocolModel):
identity_key: str
protocol_version: int
sequence: int = Field(ge=1, le=9_223_372_036_854_775_807)
observed_at: datetime
host: HostInventory
nvidia: InventoryNvidiaPayload
class TelemetryReport(AgentProtocolModel):
identity_key: str
protocol_version: int
sequence: int = Field(ge=1, le=9_223_372_036_854_775_807)
observed_at: datetime
available_ram_bytes: ObservedValue[int]
storage: list[StorageObservation] = Field(default_factory=list)
accelerators: list[AcceleratorTelemetry] = Field(default_factory=list)
class ObservationAck(AgentProtocolModel):
accepted: bool
reason: str | None = None
received_at: datetime
class EnrollmentTokenCreate(AgentProtocolModel):
expires_in_seconds: int = Field(default=900, ge=60, le=86400)
display_name: str | None = Field(default=None, max_length=255)
role: str | None = Field(default=None, max_length=64)
labels: dict[str, str | bool] = Field(default_factory=dict)
production_eligible: bool = False
lab_eligible: bool = True
benchmark_eligible: bool = False
class EnrollmentTokenCreated(AgentProtocolModel):
id: str
enrollment_token: str
expires_at: datetime
setup_environment: dict[str, str]
class EnrollmentTokenSummary(AgentProtocolModel):
id: str
created_at: datetime
expires_at: datetime
used_at: datetime | None
revoked_at: datetime | None
class NodeManagementUpdate(AgentProtocolModel):
display_name: str | None = Field(default=None, min_length=1, max_length=255)
role: str | None = Field(default=None, max_length=64)
labels: dict[str, str | bool] | None = None
enabled: bool | None = None
production_eligible: bool | None = None
lab_eligible: bool | None = None
benchmark_eligible: bool | None = None
+140
View File
@@ -0,0 +1,140 @@
"""Stable audit-chain format identifiers shared by persistence and services."""
from __future__ import annotations
import hashlib
import json
import uuid
from datetime import UTC, datetime
from typing import Any
AUDIT_CHAIN_SINGLETON_ID = 1
AUDIT_HASH_FORMAT_V1 = "v1"
AUDIT_HASH_FORMAT_V2 = "v2"
AUDIT_CURRENT_HASH_FORMAT = AUDIT_HASH_FORMAT_V2
AUDIT_LEGACY_PREFIX_DOMAIN = b"modelforge:audit:legacy-prefix:v1\n"
AUDIT_EMPTY_LEGACY_PREFIX_SEAL = hashlib.sha256(AUDIT_LEGACY_PREFIX_DOMAIN).hexdigest()
def normalise_audit_timestamp(value: datetime | str) -> str:
"""Return the stable UTC/microsecond representation used by v2 audit hashes."""
moment: datetime
if isinstance(value, datetime):
moment = value
elif isinstance(value, str):
candidate = value.strip()
if candidate.endswith("Z"):
candidate = candidate[:-1] + "+00:00"
try:
moment = datetime.fromisoformat(candidate)
except ValueError as error:
raise ValueError("audit occurred_at is not an ISO-8601 timestamp") from error
else:
raise TypeError("audit occurred_at must be a datetime or ISO-8601 string")
if moment.tzinfo is None:
moment = moment.replace(tzinfo=UTC)
return moment.astimezone(UTC).isoformat(timespec="microseconds").replace("+00:00", "Z")
def normalise_audit_event_id(value: Any) -> str:
try:
return str(uuid.UUID(str(value)))
except (AttributeError, TypeError, ValueError) as error:
raise ValueError("audit event id is not a UUID") from error
def canonical_audit_payload_and_hash(
*,
correlation_id: str,
actor_type: str,
actor_id: str,
action: str,
resource_type: str,
resource_id: str | None,
outcome: str,
details: dict[str, Any],
previous_event_hash: str | None,
hash_format: str = AUDIT_HASH_FORMAT_V1,
event_id: Any | None = None,
occurred_at: datetime | str | None = None,
) -> tuple[dict[str, Any], str]:
"""Return detached stored fields and their versioned SHA-256 identity."""
stored_payload, _encoded_hash, event_hash = canonical_audit_payload_text_and_hash(
correlation_id=correlation_id,
actor_type=actor_type,
actor_id=actor_id,
action=action,
resource_type=resource_type,
resource_id=resource_id,
outcome=outcome,
details=details,
previous_event_hash=previous_event_hash,
hash_format=hash_format,
event_id=event_id,
occurred_at=occurred_at,
)
return stored_payload, event_hash
def canonical_audit_payload_text_and_hash(
*,
correlation_id: str,
actor_type: str,
actor_id: str,
action: str,
resource_type: str,
resource_id: str | None,
outcome: str,
details: dict[str, Any],
previous_event_hash: str | None,
hash_format: str = AUDIT_HASH_FORMAT_V1,
event_id: Any | None = None,
occurred_at: datetime | str | None = None,
) -> tuple[dict[str, Any], str, str]:
"""Return stored fields, the exact hashed text, and its SHA-256 digest.
The exact text is persisted for v2 events. That makes PostgreSQL's SECURITY DEFINER writer
authoritative for its own canonical encoding without asking Python and PostgreSQL to reproduce
each other's JSON lexical representation. Strict verification hashes the stored bytes and then
independently checks that the decoded object is exactly the event's semantic payload.
"""
if not isinstance(details, dict):
raise TypeError("audit details must be an object")
stored_payload: dict[str, Any] = {
"correlation_id": correlation_id,
"actor_type": actor_type,
"actor_id": actor_id,
"action": action,
"resource_type": resource_type,
"resource_id": resource_id,
"outcome": outcome,
"details": details,
"previous_event_hash": previous_event_hash,
}
encoded_stored = json.dumps(stored_payload, sort_keys=True, separators=(",", ":"))
canonical_stored = json.loads(encoded_stored)
if not isinstance(canonical_stored, dict): # pragma: no cover - constructed above
raise TypeError("canonical audit payload must be an object")
if hash_format == AUDIT_HASH_FORMAT_V1:
hash_payload = canonical_stored
elif hash_format == AUDIT_HASH_FORMAT_V2:
if event_id is None or occurred_at is None:
raise ValueError("v2 audit hashes require an event id and occurred_at")
hash_payload = {
"hash_format": AUDIT_HASH_FORMAT_V2,
"id": normalise_audit_event_id(event_id),
"occurred_at": normalise_audit_timestamp(occurred_at),
**canonical_stored,
}
else:
raise ValueError(f"unsupported audit hash format {hash_format!r}")
encoded_hash = json.dumps(hash_payload, sort_keys=True, separators=(",", ":"))
return (
canonical_stored,
encoded_hash,
hashlib.sha256(encoded_hash.encode("utf-8")).hexdigest(),
)
@@ -0,0 +1,171 @@
from __future__ import annotations
import math
import re
import uuid
from datetime import datetime
from typing import Any, Literal
from pydantic import BaseModel, ConfigDict, Field, field_validator, model_validator
EvaluationType = Literal[
"retrieval", "ocr", "visual-retrieval", "asr", "tts", "generation", "llm"
]
CapabilityRecommendationState = Literal[
"KEEP_CURRENT",
"LAB_READY",
"PROMOTION_ELIGIBLE",
"REQUIRES_MORE_EVIDENCE",
"BLOCKED",
]
MetricDirection = Literal["higher_is_better", "lower_is_better", "informational"]
_KEY = re.compile(r"^[a-z0-9][a-z0-9_-]{0,127}$")
ALLOWED_METRICS: dict[str, frozenset[str]] = {
"retrieval": frozenset({"recall_at_5", "recall_at_10", "mrr", "ndcg_at_10"}),
"ocr": frozenset({"cer", "wer", "text_accuracy", "field_accuracy", "layout_accuracy", "latency_ms", "peak_vram_bytes"}),
"visual-retrieval": frozenset({"recall_at_1", "recall_at_5", "recall_at_10", "mrr", "latency_ms", "peak_vram_bytes"}),
"asr": frozenset({"wer", "real_time_factor", "latency_ms", "peak_vram_bytes"}),
"tts": frozenset({"latency_ms", "real_time_factor", "peak_vram_bytes"}),
"generation": frozenset({"latency_ms", "peak_vram_bytes"}),
"llm": frozenset({"task_success", "structured_output_validity", "latency_ms", "peak_vram_bytes"}),
}
class EvaluationModel(BaseModel):
model_config = ConfigDict(extra="forbid", from_attributes=True)
class MetricDefinition(EvaluationModel):
name: str
direction: MetricDirection
unit: str = Field(min_length=1, max_length=64)
minimum: float | None = None
maximum: float | None = None
@model_validator(mode="after")
def valid_range(self) -> MetricDefinition:
if self.minimum is not None and self.maximum is not None and self.minimum > self.maximum:
raise ValueError("metric minimum cannot exceed maximum")
return self
class CapabilityEvaluationCase(EvaluationModel):
key: str
fixture_ref: str = Field(min_length=1, max_length=512)
fixture_sha256: str = Field(pattern=r"^[a-f0-9]{64}$")
ground_truth: dict[str, Any]
labels: list[str] = Field(default_factory=list)
critical: bool = False
@field_validator("key")
@classmethod
def valid_key(cls, value: str) -> str:
if not _KEY.fullmatch(value):
raise ValueError("case key must be a safe identifier")
return value
class CapabilityEvaluationSuiteCreate(EvaluationModel):
capability: str = Field(pattern=r"^[a-z][a-z0-9_]*(?:\.[a-z][a-z0-9_]*)+$")
contract_version: int = Field(default=1, ge=1)
key: str
evaluation_type: EvaluationType
revision: str
dataset_revision: str
metrics: list[MetricDefinition] = Field(min_length=1)
cases: list[CapabilityEvaluationCase] = Field(min_length=1, max_length=500)
thresholds: dict[str, float] = Field(default_factory=dict)
@model_validator(mode="after")
def validate_definition(self) -> CapabilityEvaluationSuiteCreate:
for key in (self.key, self.revision, self.dataset_revision):
if not _KEY.fullmatch(key):
raise ValueError("suite identifiers must be safe")
metric_names = [item.name for item in self.metrics]
if len(metric_names) != len(set(metric_names)):
raise ValueError("metric names must be unique")
unsupported = set(metric_names) - ALLOWED_METRICS[self.evaluation_type]
if unsupported:
raise ValueError(f"metrics are invalid for {self.evaluation_type}: {sorted(unsupported)}")
if set(self.thresholds) - set(metric_names):
raise ValueError("thresholds must refer to declared metrics")
if len({item.key for item in self.cases}) != len(self.cases):
raise ValueError("case keys must be unique")
return self
class CapabilityEvaluationSuiteResponse(EvaluationModel):
id: uuid.UUID
capability: str
contract_version: int
key: str
evaluation_type: EvaluationType
revision: str
dataset_revision: str
definition_digest: str
metrics: list[MetricDefinition]
cases: list[CapabilityEvaluationCase]
thresholds: dict[str, float]
created_at: datetime
class CapabilityCaseResult(EvaluationModel):
case_key: str
metrics: dict[str, float]
output_sha256: str = Field(pattern=r"^[a-f0-9]{64}$")
status: Literal["passed", "failed", "error"]
error_code: str | None = Field(default=None, max_length=64)
class CapabilityEvaluationRunCreate(EvaluationModel):
suite_id: uuid.UUID
capability_deployment_id: uuid.UUID
status: Literal["completed", "failed"]
metrics: dict[str, float]
cases: list[CapabilityCaseResult] = Field(min_length=1, max_length=500)
resource_metrics: dict[str, float]
environment_fingerprint: str = Field(pattern=r"^[a-f0-9]{64}$")
evidence: dict[str, Any] = Field(default_factory=dict)
started_at: datetime
completed_at: datetime
@field_validator("metrics", "resource_metrics")
@classmethod
def finite_values(cls, value: dict[str, float]) -> dict[str, float]:
if any(not math.isfinite(item) for item in value.values()):
raise ValueError("metric values must be finite")
return value
@model_validator(mode="after")
def chronological(self) -> CapabilityEvaluationRunCreate:
if self.completed_at < self.started_at:
raise ValueError("evaluation completion precedes its start")
return self
class CapabilityEvaluationRunResponse(EvaluationModel):
id: uuid.UUID
suite_id: uuid.UUID
capability_deployment_id: uuid.UUID
evaluation_type: EvaluationType
status: str
metrics: dict[str, float]
cases: list[CapabilityCaseResult]
resource_metrics: dict[str, float]
environment_fingerprint: str
evidence_digest: str
evidence: dict[str, Any]
started_at: datetime
completed_at: datetime
created_at: datetime
class CapabilityAdvisorResponse(EvaluationModel):
capability: str
contract_version: int
state: CapabilityRecommendationState
deployment_id: uuid.UUID | None = None
evaluation_run_id: uuid.UUID | None = None
reasons: list[str]
automatic_promotion: Literal[False] = False
@@ -0,0 +1,282 @@
"""Documentation for every setting, held next to the settings themselves.
`.env.example` and `docs/CONFIGURATION.md` are generated from this table joined with the typed
defaults, and a test fails when a setting exists without an entry here. Hand-maintained
configuration documentation drifts silently — the operator finds out when a production deployment
does something the manual said it would not.
`required_in_production` means startup validation refuses to run production without it, not merely
that it is recommended.
"""
from __future__ import annotations
from dataclasses import dataclass
from enum import StrEnum
class Sensitivity(StrEnum):
PUBLIC = "public"
#: Reveals deployment topology. Not a credential, but not for a public issue tracker either.
INTERNAL = "internal"
#: A credential or key. Never logged, never packaged, never echoed in an error.
SECRET = "secret" # noqa: S105 - a classification label, not a credential
@dataclass(frozen=True, slots=True)
class SettingDoc:
description: str
sensitivity: Sensitivity = Sensitivity.PUBLIC
required_in_production: bool = False
#: False when the value is re-read per request or per poll rather than only at startup.
restart_required: bool = True
def _p(description: str, **kwargs: object) -> SettingDoc:
return SettingDoc(description, **kwargs) # type: ignore[arg-type]
def _secret(description: str, *, required: bool = False) -> SettingDoc:
return SettingDoc(description, Sensitivity.SECRET, required_in_production=required)
def _internal(description: str, *, required: bool = False) -> SettingDoc:
return SettingDoc(description, Sensitivity.INTERNAL, required_in_production=required)
SETTING_DOCS: dict[str, SettingDoc] = {
# ---------------------------------------------------------------- process
"env": _p(
"Deployment profile. 'production' turns on every fail-closed startup rule; "
"'development' and 'test' report the same problems without refusing to start."
),
"api_host": _p("Interface the API binds inside its container. Leave at 0.0.0.0."),
"api_port": _p("Port the API listens on inside its container."),
"control_plane_max_payload_bytes": _p(
"Pre-parser request-body limit for public and operator control-plane routes."
),
"service_name": _p("Name this process reports in logs and audit events."),
"log_level": _p("Structured log level: DEBUG, INFO, WARNING or ERROR.", restart_required=True),
# ---------------------------------------------------------------- dependencies
"database_url": _secret(
"SQLAlchemy URL for the API's non-owner modelforge_runtime role. It may read the audit "
"trail and execute the canonical append function, but cannot mutate audit tables directly.",
required=True,
),
"migration_database_url": _secret(
"SQLAlchemy URL for the non-superuser modelforge schema-owner role. Set only in the "
"one-shot migration process; production API startup refuses when this secret is present."
),
"redis_url": _internal("Redis URL for transient request payloads and queues.", required=True),
"cors_origins": _p(
"Comma-separated exact origins allowed to call the API from a browser. "
"A wildcard is refused in production because requests are credentialed."
),
# ---------------------------------------------------------------- credentials
"operator_api_key": _secret(
"Operator API key guarding every admin route. Generate at least 32 random characters; "
"ModelForge never mints one for you.",
required=True,
),
"hf_token": _secret(
"Optional Hugging Face token, used only for acquiring gated repositories. "
"It is never passed to a runtime and never leaves the control plane."
),
"backup_encryption_key": _secret(
"Base64 AES-256 key for backup encryption. Without it no backup can be produced, and "
"without the same key no backup can be restored — store it outside this deployment.",
required=True,
),
"backup_encryption_key_id": _p(
"Identifier recorded in each backup manifest so a restore can name the key it needs."
),
# ---------------------------------------------------------------- storage
"hf_home": _p("Hugging Face cache root inside the container."),
"artifact_root": _p("Verified model artifact root. Must exist and be writable."),
"quarantine_root": _p("Where acquired artifacts are held until their checks pass."),
"runtime_artifact_root": _p("Artifact root as a runtime worker sees it on a compute node."),
"config_root": _p("Directory holding the capability, project and policy manifests."),
"backup_root": _p("Backup destination. Must exist and be writable, or backups fail closed."),
"backup_restore_root": _p("Working directory a restore stages into before it commits."),
"alembic_directory": _p("Override for the migration directory. Leave empty in a container."),
# ---------------------------------------------------------------- acquisition
"hf_timeout_seconds": _p("Per-request timeout for Hugging Face metadata calls."),
"hf_snapshot_ttl_seconds": _p("How long a resolved upstream snapshot stays cached."),
"allow_remote_code": _p(
"Whether model repositories may execute their own Python. Always false in production; "
"startup refuses any other value there."
),
# ---------------------------------------------------------------- hardware and nodes
"enable_gpu_telemetry": _p("Collect GPU telemetry on this host."),
"hardware_refresh_on_startup": _p("Run a hardware inventory pass when the process starts."),
"hardware_poll_interval_seconds": _p("Interval between hardware inventory passes."),
"node_identity": _internal("Explicit node identity. Leave empty to use the persisted file."),
"node_identity_mode": _p("'persisted' keeps a node's identity across restarts; 'auto' derives it."),
"node_identity_file": _p("Where a persisted node identity is stored."),
"node_stale_after_seconds": _p("Silence after which a node is considered stale."),
"node_offline_after_seconds": _p(
"Silence after which a node is considered offline. Must exceed the stale threshold."
),
"liveness_poll_interval_seconds": _p("How often node liveness is re-evaluated."),
"agent_max_clock_skew_seconds": _p("Clock skew tolerated on an agent report before refusal."),
"node_agent_max_payload_bytes": _p(
"Pre-parser request-body limit for enrollment and authenticated Node Agent reports."
),
"node_liveness_monitor_enabled": _p("Run the node liveness monitor in this process."),
"agent_protocol_version": _p("Agent protocol version this control plane speaks."),
# ---------------------------------------------------------------- gateway
"gateway_max_batch_size": _p("Maximum inputs accepted in a single capability invocation."),
"gateway_max_input_characters": _p("Maximum characters per input item."),
"gateway_max_payload_bytes": _p("Maximum accepted request body size."),
"gateway_request_timeout_seconds": _p(
"Total time a capability invocation may take. Must exceed the queue timeout."
),
"gateway_queue_timeout_seconds": _p("How long a request may wait for capacity before rejection."),
"serving_job_lease_seconds": _p("Lease held by a serving job before it is reclaimed."),
"serving_payload_ttl_seconds": _p("How long a request payload survives in Redis."),
# ---------------------------------------------------------------- scheduler
"scheduler_safety_reserve_bytes": _p("VRAM never offered to a placement, as an absolute floor."),
"scheduler_safety_reserve_percentage": _p(
"VRAM never offered to a placement, as a fraction. Half a device leaves nothing schedulable."
),
"scheduler_runtime_margin_bytes": _p("Headroom reserved for runtime overhead per node."),
"scheduler_deployment_margin_bytes": _p("Absolute headroom added to each deployment estimate."),
"scheduler_deployment_margin_percentage": _p("Proportional headroom added to each estimate."),
"scheduler_global_queue_limit": _p("Queued requests accepted before capacity rejection begins."),
"scheduler_telemetry_stale_seconds": _p(
"Telemetry age past which admission is blocked rather than extrapolated."
),
"scheduler_pressure_stable_seconds": _p("How long pressure must hold before the state changes."),
"scheduler_eviction_cooldown_seconds": _p("Minimum interval between evictions on a node."),
"scheduler_placement_history_limit": _p("Placement decisions retained for inspection."),
# ---------------------------------------------------------------- background work
"registry_seed_on_startup": _p("Seed the candidate and project registries from manifests."),
"serving_reconciliation_enabled": _p("Reconcile abandoned serving work in this process."),
"serving_reconciliation_interval_seconds": _p("Interval between serving reconciliation passes."),
"lifecycle_reconciliation_enabled": _p("Roll back incomplete lifecycle operations at startup."),
"migration_reconciliation_enabled": _p(
"Report interrupted migration cutovers at startup. They are never auto-resolved: external "
"alias truth cannot be inferred after a crash."
),
"observability_monitor_enabled": _p("Run SLO and alert evaluation in this process."),
"observability_poll_interval_seconds": _p("Interval between observability evaluation passes."),
"recovery_reconciliation_enabled": _p("Reconcile interrupted backups and restores at startup."),
# ---------------------------------------------------------------- recovery
"backup_pg_dump_path": _p("pg_dump executable. Must match the server major version."),
"backup_pg_restore_path": _p("pg_restore executable."),
"backup_psql_path": _p("psql executable."),
"backup_command_timeout_seconds": _p("Timeout for a dump or restore command."),
"backup_stale_after_seconds": _p("Age past which the newest verified backup raises BACKUP_STALE."),
"backup_minimum_free_bytes": _p("Free space below which a backup refuses to start."),
"backup_capacity_headroom_ratio": _p("Required free space as a multiple of the estimated size."),
"restore_allow_production_target": _p(
"Whether a restore may overwrite the live database. Keep false outside a rehearsal."
),
# ---------------------------------------------------------------- build identity
"build_commit": _p("Source commit stamped into the image at build time. Never set by hand."),
"build_timestamp": _p("Build time stamped into the image. Never set by hand."),
"build_image_digest": _p("Image digest recorded at deployment. Never set by hand."),
}
# --------------------------------------------------------------------------- deployment variables
#
# Variables the deployment reads rather than the control-plane process: Compose interpolation, the
# Node Agent and the Runtime Worker. They are not `Settings` fields, but an operator still has to
# set them, so leaving them out of the reference would recreate exactly the undocumented-variable
# problem this table exists to prevent.
DEPLOYMENT_DOCS: dict[str, SettingDoc] = {
# ---------------------------------------------------------------- network bindings
"MODELFORGE_POSTGRES_BIND": _p(
"Host address the control-plane database is published on. Defaults to 127.0.0.1; "
"publishing it more widely exposes provenance, credential hashes and the audit trail."
),
"MODELFORGE_REDIS_BIND": _p("Host address Redis is published on. Defaults to 127.0.0.1."),
"MODELFORGE_API_BIND": _p(
"Host address the API is published on. Defaults to 0.0.0.0 deliberately: the console and "
"compute nodes need it, and every admin route is operator-authenticated."
),
"MODELFORGE_WEB_BIND": _p("Host address the console is published on. Defaults to 127.0.0.1."),
"MODELFORGE_POSTGRES_PORT": _p("Host port the database is published on. Defaults to 5432."),
"MODELFORGE_REDIS_PORT": _p("Host port Redis is published on. Defaults to 6379."),
"MODELFORGE_API_PUBLISHED_PORT": _p("Host port the API is published on. Defaults to 8000."),
"MODELFORGE_WEB_PORT": _p("Host port the console is published on. Defaults to 3000."),
"MODELFORGE_DR_POSTGRES_BIND": _p("Host address for the DR rehearsal database. Loopback only."),
"MODELFORGE_DR_API_BIND": _p("Host address for the DR rehearsal API. Loopback only."),
"VITE_API_BASE_URL": _p(
"API base URL compiled into the console. Vite inlines it at build time, so changing it "
"requires rebuilding the console image, not restarting it."
),
# ---------------------------------------------------------------- production identity
"MODELFORGE_POSTGRES_DB": _p("Production database name. Required by the production overlay."),
"MODELFORGE_POSTGRES_ADMIN_USER": _internal(
"Bootstrap/admin role used only by PostgreSQL provisioning; defaults to postgres."
),
"MODELFORGE_POSTGRES_ADMIN_PASSWORD": _secret(
"Bootstrap/admin password; never passed to the migration or API container.", required=True
),
"MODELFORGE_MIGRATION_DB_PASSWORD": _secret(
"Raw password supplied to provisioning for the non-superuser modelforge owner role.",
required=True,
),
"MODELFORGE_RUNTIME_DB_PASSWORD": _secret(
"Raw password supplied to provisioning for the non-owner modelforge_runtime role.",
required=True,
),
"MODELFORGE_RUNTIME_DATABASE_URL": _secret(
"Non-owner runtime-role URL passed only to the API container.", required=True
),
"MODELFORGE_VERSION": _p(
"Exact version tag applied to built images and required when the production overlay is "
"not given explicit API and web image references."
),
"MODELFORGE_COMMIT": _p("Source commit stamped into images at build time."),
"MODELFORGE_BUILT_AT": _p("Build timestamp stamped into images."),
"MODELFORGE_API_IMAGE": _p(
"Exact tag or digest the production overlay runs for the API; never use latest."
),
"MODELFORGE_WEB_IMAGE": _p(
"Exact tag or digest the production overlay runs for the console; never use latest."
),
"MODELFORGE_NODE_AGENT_IMAGE": _p(
"Exact release tag or digest for the standalone Node Agent. The local-build fallback is "
"named local and never resolves to latest."
),
"MODELFORGE_API_IMAGE_DIGEST": _p("Digest recorded as the running API build identity."),
# ---------------------------------------------------------------- volumes
"MODELFORGE_BACKUP_VOLUME": _p("Volume or bind path backing the backup root."),
"MODELFORGE_RESTORE_VOLUME": _p("Volume or bind path backing the restore staging root."),
"MODELFORGE_AGENT_STATE_VOLUME": _p("Volume or bind path holding the agent's persisted identity."),
"MODELFORGE_AGENT_HF_CACHE_VOLUME": _p("Volume or bind path for the agent's Hugging Face cache."),
"MODELFORGE_AGENT_ARTIFACT_VOLUME": _p("Volume or bind path for verified artifacts on a node."),
"MODELFORGE_AGENT_QUARANTINE_VOLUME": _p("Volume or bind path for the node's quarantine area."),
# ---------------------------------------------------------------- node agent
"MODELFORGE_AGENT_CONTROL_PLANE_URL": _p(
"URL the agent reports to. Outbound only; the control plane never dials a node."
),
"MODELFORGE_AGENT_ENROLLMENT_TOKEN": _secret(
"Single-use enrolment token. Consumed atomically: a storm against one token produces "
"exactly one identity."
),
"MODELFORGE_AGENT_HOSTNAME": _p("Hostname the agent enrols under."),
"MODELFORGE_AGENT_ACCELERATOR_MODE": _p(
"Accelerator contract: nvidia fails closed unless NVML inventory and telemetry are valid; "
"cpu permits a legitimate CPU-only node; auto requires NVIDIA when injected devices are "
"observed. Canonical GPU Compose deployments set nvidia explicitly."
),
"MODELFORGE_AGENT_TLS_VERIFY": _p(
"Whether the agent verifies the control plane's certificate. True wherever TLS is real."
),
"MODELFORGE_AGENT_CONTROL_PLANE_HOST_ADDRESS": _internal(
"Host address mapped for a private-CA deployment."
),
"MODELFORGE_AGENT_CA_CERT_PATH": _p("Path to the private CA certificate the agent trusts."),
# ---------------------------------------------------------------- runtime worker
"MODELFORGE_RUNTIME_WORKER_ARTIFACT_ROOT": _p("Artifact root as the runtime worker sees it."),
"MODELFORGE_RUNTIME_WORKER_POLL_INTERVAL_SECONDS": _p("Worker poll interval, in seconds."),
# ---------------------------------------------------------------- provenance passthrough
"MODELFORGE_SOURCE_COMMIT": _p("Source commit reported by the deployment."),
"MODELFORGE_SOURCE_REFERENCE": _p("Git reference reported by the deployment."),
"MODELFORGE_SOURCE_REPOSITORY": _p("Repository URL reported by the deployment."),
}
@@ -0,0 +1,432 @@
from __future__ import annotations
import hashlib
import json
import re
from datetime import UTC, datetime
from typing import Any, Literal, Protocol
from pydantic import BaseModel, ConfigDict, Field, field_validator, model_validator
from .enums import (
DeploymentChannel,
FailureCode,
FailureOwner,
HealthStatus,
MigrationStatus,
ResidencyPolicy,
UpgradeClass,
VerificationStatus,
WorkloadPriority,
)
CAPABILITY_KEY = re.compile(r"^[a-z][a-z0-9_]*(?:\.[a-z][a-z0-9_]*)+$")
SHA256 = re.compile(r"^[a-f0-9]{64}$")
COMMIT_SHA = re.compile(r"^[a-f0-9]{40,64}$")
class StrictModel(BaseModel):
model_config = ConfigDict(extra="forbid")
class SchemaDocument(StrictModel):
schema_: dict[str, Any] = Field(alias="schema")
class ModalityContract(StrictModel):
input: list[str] = Field(min_length=1)
output: list[str] = Field(min_length=1)
class VectorContract(StrictModel):
dimensionality: int | None = Field(default=None, gt=0)
normalized: bool | None = None
cross_deployment_compatible: bool = False
class SLOContract(StrictModel):
latency_p95_ms: int | None = Field(default=None, gt=0)
availability_percent: float | None = Field(default=None, ge=0, le=100)
max_concurrency: int | None = Field(default=None, gt=0)
class PrivacyContract(StrictModel):
classification: Literal["public", "internal", "confidential", "restricted"] = "internal"
allow_persistence: bool = False
allow_logging_payloads: bool = False
allow_network_egress: bool = False
class ResourceRequirement(StrictModel):
accelerator_required: bool = False
minimum_vram_mb: int | None = Field(default=None, ge=0)
cpu_fallback_allowed: bool = False
CapabilityCategory = Literal[
"TEXT", "RAG", "DOCUMENT", "VISION", "AUDIO", "GENERATION", "ASSISTANTS"
]
CapabilityStability = Literal["stable", "experimental", "blocked", "planned"]
EvaluationType = Literal[
"retrieval", "ocr", "visual-retrieval", "asr", "tts", "generation", "llm"
]
ResourceClass = Literal["LIGHT", "MEDIUM", "HEAVY", "EXCLUSIVE_GPU"]
class PayloadLimits(StrictModel):
max_bytes: int = Field(ge=1, le=67_108_864)
max_batch_count: int = Field(default=1, ge=1, le=64)
max_width: int | None = Field(default=None, ge=1, le=16_384)
max_height: int | None = Field(default=None, ge=1, le=16_384)
max_pages: int | None = Field(default=None, ge=1, le=128)
max_duration_seconds: float | None = Field(default=None, gt=0, le=3600)
class CapabilityEstateMetadata(StrictModel):
category: CapabilityCategory
purpose: str = Field(min_length=1, max_length=1000)
stability: CapabilityStability
resource_class: ResourceClass
evaluation_type: EvaluationType
consumers: list[str] = Field(default_factory=list)
payload_limits: PayloadLimits
class FallbackContract(StrictModel):
allowed: bool = False
mode: Literal["none", "compatible_deployment", "degrade", "hard_fail"] = "hard_fail"
capability: str | None = None
@model_validator(mode="after")
def validate_fallback(self) -> FallbackContract:
if not self.allowed and self.mode not in {"none", "hard_fail"}:
raise ValueError("disabled fallback must use none or hard_fail")
return self
class CapabilityContractManifest(StrictModel):
capability: str
version: int = Field(ge=1)
description: str = Field(min_length=1)
input_schema: dict[str, Any]
output_schema: dict[str, Any]
modalities: ModalityContract
languages: list[str] = Field(default_factory=list)
streaming: bool = False
structured_output: bool = False
vector: VectorContract | None = None
slo: SLOContract = Field(default_factory=SLOContract)
quality_metrics: list[str] = Field(min_length=1)
upgrade_class: UpgradeClass
fallback: FallbackContract = Field(default_factory=FallbackContract)
privacy: PrivacyContract = Field(default_factory=PrivacyContract)
resources: ResourceRequirement = Field(default_factory=ResourceRequirement)
production_priority: WorkloadPriority
default_residency: ResidencyPolicy
estate: CapabilityEstateMetadata
@field_validator("capability")
@classmethod
def validate_key(cls, value: str) -> str:
if not CAPABILITY_KEY.fullmatch(value):
raise ValueError("capability must be a dotted, lowercase logical key")
return value
@model_validator(mode="after")
def vector_safety(self) -> CapabilityContractManifest:
vector_output = self.vector is not None or "embedding" in self.capability
if vector_output and not self.vector:
raise ValueError("vector-producing capabilities require a vector contract")
if (
vector_output
and self.vector is not None
and not self.vector.cross_deployment_compatible
and self.upgrade_class is not UpgradeClass.REQUIRES_REINDEX
):
raise ValueError("incompatible vector spaces require requires_reindex")
return self
class ProjectDefinition(StrictModel):
id: str = Field(pattern=r"^[a-z][a-z0-9-]*$")
name: str = Field(min_length=1)
description: str = Field(min_length=1)
class ProjectBindingManifest(StrictModel):
contract_version: int = Field(ge=1)
channel: DeploymentChannel
priority: WorkloadPriority
optional: bool = False
fallback: FallbackContract = Field(default_factory=FallbackContract)
migration_support: Literal["none", "reindex", "schema"] = "none"
slo: SLOContract = Field(default_factory=SLOContract)
benchmark_suites: list[str] = Field(default_factory=list)
class ProjectManifest(StrictModel):
project: ProjectDefinition
bindings: dict[str, ProjectBindingManifest]
notes: list[str] = Field(default_factory=list)
@field_validator("bindings")
@classmethod
def validate_bindings(
cls, value: dict[str, ProjectBindingManifest]
) -> dict[str, ProjectBindingManifest]:
for key in value:
if not CAPABILITY_KEY.fullmatch(key):
raise ValueError(f"invalid capability binding key: {key}")
return value
class CandidateManifest(StrictModel):
id: str
display_name: str
source: str
intended_capabilities: list[str] = Field(min_length=1)
proposed_role: str
preferred_runtime: str | None = None
verification_status: VerificationStatus = VerificationStatus.UNVERIFIED
deployment_status: Literal["not_deployed"] = "not_deployed"
class CandidateRegistryManifest(StrictModel):
candidates: list[CandidateManifest]
class BenchmarkSuiteDefinition(StrictModel):
id: str
capability: str
version: int = Field(ge=1)
purpose: str
dataset_revision: str
runnable: bool = False
class BenchmarkSuiteManifest(StrictModel):
suite: BenchmarkSuiteDefinition
metrics: dict[str, list[str]]
performance: dict[str, list[str]]
release_policy: dict[str, Any]
@model_validator(mode="after")
def require_pinned_dataset_for_runnable_suite(self) -> BenchmarkSuiteManifest:
if self.suite.runnable and self.suite.dataset_revision.startswith("pending"):
raise ValueError("runnable benchmark suites require a pinned dataset revision")
return self
class SecurityDefaults(StrictModel):
trust_remote_code: Literal[False] = False
prefer_safetensors: Literal[True] = True
require_exact_revision_for_approval: Literal[True] = True
require_artifact_digest: Literal[True] = True
inference_network_egress: Literal[False] = False
expose_runtime_workers: Literal[False] = False
class LifecycleDefaults(StrictModel):
automatic_production_promotion: Literal[False] = False
require_local_benchmark_for_stable: Literal[True] = True
retain_previous_stable_as_rollback_target: Literal[True] = True
destructive_cleanup_requires_explicit_approval: Literal[True] = True
class SchedulerDefaults(StrictModel):
priority_order: list[WorkloadPriority]
reserve_vram_mb: int = Field(ge=0)
default_warm_ttl_seconds: int = Field(ge=0)
benchmark_may_disrupt_production: Literal[False] = False
class UpgradeDefaults(StrictModel):
default_behavioral_change_class: Literal[UpgradeClass.BEHAVIORAL]
vector_producer_default_change_class: Literal[UpgradeClass.REQUIRES_REINDEX]
schema_change_requires_contract_version_bump: Literal[True] = True
class PolicyDefaults(StrictModel):
security: SecurityDefaults
lifecycle: LifecycleDefaults
scheduler: SchedulerDefaults
upgrades: UpgradeDefaults
class ArtifactProvenance(StrictModel):
upstream_repository: str
resolved_commit_sha: str
filename: str
artifact_type: str
sha256: str
source_artifact_sha256: str | None = None
derivation_tool: str | None = None
derivation_tool_version: str | None = None
derivation_arguments: dict[str, Any] = Field(default_factory=dict)
imported_at: datetime = Field(default_factory=lambda: datetime.now(UTC))
@field_validator("resolved_commit_sha")
@classmethod
def validate_commit(cls, value: str) -> str:
value = value.lower()
if not COMMIT_SHA.fullmatch(value):
raise ValueError("resolved commit must be a 40-64 character hexadecimal digest")
return value
@field_validator("sha256", "source_artifact_sha256")
@classmethod
def validate_digest(cls, value: str | None) -> str | None:
if value is not None and not SHA256.fullmatch(value.lower()):
raise ValueError("artifact digest must be lowercase SHA-256")
return value.lower() if value else None
@model_validator(mode="after")
def validate_lineage(self) -> ArtifactProvenance:
derived = self.source_artifact_sha256 is not None
if derived and (not self.derivation_tool or not self.derivation_tool_version):
raise ValueError("derived artifacts require derivation tool and version")
return self
class RuntimeProfileContract(StrictModel):
runtime_type: Literal["vllm", "transformers", "diffusers", "llama_cpp", "custom"]
runtime_version: str
runtime_image_digest: str | None = Field(default=None, pattern=r"^sha256:[a-f0-9]{64}$")
artifact_sha256: str = Field(pattern=r"^[a-f0-9]{64}$")
quantization: str | None = None
context_length: int | None = Field(default=None, gt=0)
max_concurrency: int = Field(default=1, gt=0)
launch_arguments: dict[str, Any] = Field(default_factory=dict)
environment_constraints: dict[str, Any] = Field(default_factory=dict)
trust_remote_code: bool = False
network_egress: bool = False
@property
def fingerprint(self) -> str:
payload = self.model_dump(mode="json", exclude_none=True)
return hashlib.sha256(json.dumps(payload, sort_keys=True).encode()).hexdigest()
class ResourceEnvelopeContract(StrictModel):
deployment_id: str
accelerator_kind: str
context_length: int | None = Field(default=None, gt=0)
concurrency: int = Field(ge=1)
batch_size: int = Field(ge=1)
idle_vram_mb: int = Field(ge=0)
peak_vram_mb: int = Field(ge=0)
safety_margin_mb: int = Field(default=1024, ge=0)
@model_validator(mode="after")
def peak_not_below_idle(self) -> ResourceEnvelopeContract:
if self.peak_vram_mb < self.idle_vram_mb:
raise ValueError("peak VRAM cannot be below idle VRAM")
return self
class LeaseRequest(StrictModel):
deployment_id: str
priority: WorkloadPriority
residency: ResidencyPolicy
envelope: ResourceEnvelopeContract
warm_ttl_seconds: int = Field(default=900, ge=0)
exclusive: bool = False
allow_cpu_fallback: bool = False
class BenchmarkEnvironmentFingerprint(StrictModel):
runtime_type: str
runtime_version: str
runtime_image_digest: str | None = None
launch_arguments: dict[str, Any]
cuda_version: str | None
driver_version: str | None
accelerator_name: str
accelerator_uuid: str | None
context_length: int | None
concurrency: int = Field(ge=1)
seed: int | None
operating_system: str
python_version: str | None = None
@property
def digest(self) -> str:
raw = json.dumps(self.model_dump(mode="json"), sort_keys=True, separators=(",", ":"))
return hashlib.sha256(raw.encode()).hexdigest()
def comparable_with(self, other: BenchmarkEnvironmentFingerprint) -> bool:
relevant = (
"runtime_type",
"runtime_version",
"runtime_image_digest",
"launch_arguments",
"cuda_version",
"driver_version",
"accelerator_name",
"context_length",
"concurrency",
)
return all(getattr(self, field) == getattr(other, field) for field in relevant)
class BenchmarkRunContract(StrictModel):
deployment_id: str
model_revision_sha: str = Field(pattern=r"^[a-f0-9]{40,64}$")
artifact_sha256: str = Field(pattern=r"^[a-f0-9]{64}$")
suite_key: str
suite_revision: str
dataset_revision: str
environment: BenchmarkEnvironmentFingerprint
started_at: datetime
completed_at: datetime | None = None
class MigrationContract(StrictModel):
project_id: str
capability: str
source_deployment_id: str
target_deployment_id: str
upgrade_class: UpgradeClass
current_index_ref: str
shadow_index_ref: str
status: MigrationStatus = MigrationStatus.PLANNED
rollback_retain_until: datetime
@model_validator(mode="after")
def require_reindex(self) -> MigrationContract:
if (
"embedding" in self.capability
and self.upgrade_class is not UpgradeClass.REQUIRES_REINDEX
):
raise ValueError("embedding migrations must be classified requires_reindex")
if self.current_index_ref == self.shadow_index_ref:
raise ValueError("shadow index must be distinct from current production index")
return self
class LayerHealth(StrictModel):
process: HealthStatus
runtime: HealthStatus
model: HealthStatus
capability: HealthStatus
project: HealthStatus
reasons: list[str] = Field(default_factory=list)
class FailurePolicy(StrictModel):
code: FailureCode
owner: FailureOwner
retryable: bool
fallback_allowed: bool
hard_failure: bool
class RuntimeAdapter(Protocol):
"""Stable control-plane boundary; concrete adapters arrive in M5."""
runtime_type: str
def validate_profile(self, profile: RuntimeProfileContract) -> None: ...
def probe(self, profile: RuntimeProfileContract) -> LayerHealth: ...
def load(self, profile: RuntimeProfileContract, lease_id: str) -> str: ...
def unload(self, deployment_id: str) -> None: ...
+200
View File
@@ -0,0 +1,200 @@
from enum import StrEnum
class ModelLifecycle(StrEnum):
DISCOVERED = "discovered"
CANDIDATE = "candidate"
DOWNLOADING = "downloading"
QUARANTINED = "quarantined"
VERIFIED = "verified"
TESTING = "testing"
APPROVED = "approved"
STANDBY = "standby"
ACTIVE = "active"
DEPRECATED = "deprecated"
ARCHIVED = "archived"
REJECTED = "rejected"
INCOMPATIBLE = "incompatible"
SECURITY_BLOCKED = "security_blocked"
LICENSE_BLOCKED = "license_blocked"
class UpgradeClass(StrEnum):
TRANSPARENT = "transparent"
BEHAVIORAL = "behavioral"
REQUIRES_REINDEX = "requires_reindex"
SCHEMA_BREAKING = "schema_breaking"
class DeploymentChannel(StrEnum):
STABLE = "stable"
CANDIDATE = "candidate"
EXPERIMENTAL = "experimental"
ARCHIVE = "archive"
class ResidencyPolicy(StrEnum):
ALWAYS_WARM = "always_warm"
KEEP_WARM = "keep_warm"
LOAD_ON_DEMAND = "load_on_demand"
EXCLUSIVE = "exclusive"
LAB_ONLY = "lab_only"
class WorkloadPriority(StrEnum):
PRODUCTION = "production"
INTERACTIVE = "interactive"
BACKGROUND = "background"
BENCHMARK = "benchmark"
MAINTENANCE = "maintenance"
class VerificationStatus(StrEnum):
UNKNOWN = "unknown"
UNVERIFIED = "unverified"
QUARANTINED = "quarantined"
VERIFIED = "verified"
BLOCKED = "blocked"
class LicenseStatus(StrEnum):
UNKNOWN = "unknown"
REVIEW_REQUIRED = "review_required"
APPROVED = "approved"
BLOCKED = "blocked"
class ArtifactStatus(StrEnum):
REMOTE = "remote"
LOCAL = "local"
VERIFYING = "verifying"
VERIFIED = "verified"
MISSING = "missing"
CORRUPT = "corrupt"
QUARANTINED = "quarantined"
ARCHIVED = "archived"
UNREACHABLE = "unreachable"
class StorageRootStatus(StrEnum):
UNKNOWN = "unknown"
READY = "ready"
READ_ONLY = "read_only"
CAPACITY_BLOCKED = "capacity_blocked"
UNAVAILABLE = "unavailable"
DEPRECATED = "deprecated"
class DeploymentStatus(StrEnum):
DRAFT = "draft"
VALIDATING = "validating"
READY = "ready"
ACTIVE = "active"
UNHEALTHY = "unhealthy"
RETIRED = "retired"
class MigrationStatus(StrEnum):
PLANNED = "planned"
BACKFILLING = "backfilling"
VALIDATING = "validating"
SHADOWING = "shadowing"
READY = "ready"
PROMOTED = "promoted"
ROLLED_BACK = "rolled_back"
FAILED = "failed"
class HealthStatus(StrEnum):
HEALTHY = "healthy"
DEGRADED = "degraded"
UNHEALTHY = "unhealthy"
UNAVAILABLE = "unavailable"
UNKNOWN = "unknown"
class FailureCode(StrEnum):
MODEL_LOAD_FAILED = "MODEL_LOAD_FAILED"
GPU_OOM = "GPU_OOM"
RUNTIME_CRASH = "RUNTIME_CRASH"
TIMEOUT = "TIMEOUT"
INVALID_OUTPUT = "INVALID_OUTPUT"
HEALTHCHECK_FAILED = "HEALTHCHECK_FAILED"
ARTIFACT_CORRUPT = "ARTIFACT_CORRUPT"
DRIVER_ERROR = "DRIVER_ERROR"
CAPABILITY_UNAVAILABLE = "CAPABILITY_UNAVAILABLE"
RUNTIME_INCOMPATIBLE = "RUNTIME_INCOMPATIBLE"
ARTIFACT_INCOMPLETE = "ARTIFACT_INCOMPLETE"
EXECUTION_NOT_APPROVED = "EXECUTION_NOT_APPROVED"
RUNTIME_DEPENDENCY_MISSING = "RUNTIME_DEPENDENCY_MISSING"
OFFLINE_LOAD_VIOLATION = "OFFLINE_LOAD_VIOLATION"
UNLOAD_FAILED = "UNLOAD_FAILED"
GPU_MEMORY_NOT_RECLAIMED = "GPU_MEMORY_NOT_RECLAIMED"
NO_ELIGIBLE_NODE = "NO_ELIGIBLE_NODE"
INSUFFICIENT_SCHEDULABLE_VRAM = "INSUFFICIENT_SCHEDULABLE_VRAM"
QUEUE_FULL = "QUEUE_FULL"
LEASE_TIMEOUT = "LEASE_TIMEOUT"
RESIDENCY_LOAD_FAILED = "RESIDENCY_LOAD_FAILED"
RESIDENCY_UNLOAD_FAILED = "RESIDENCY_UNLOAD_FAILED"
AUTHORIZATION_DENIED = "AUTHORIZATION_DENIED"
RATE_LIMITED = "RATE_LIMITED"
EXTERNAL_GPU_PRESSURE = "EXTERNAL_GPU_PRESSURE"
STALE_RESOURCE_ENVELOPE = "STALE_RESOURCE_ENVELOPE"
class FailureOwner(StrEnum):
RUNTIME_ADAPTER = "runtime_adapter"
SCHEDULER = "scheduler"
GATEWAY = "gateway"
CONTROL_PLANE = "control_plane"
OPERATOR = "operator"
class Availability(StrEnum):
KNOWN = "known"
UNKNOWN = "unknown"
UNSUPPORTED = "unsupported"
UNAVAILABLE = "unavailable"
TEMPORARILY_FAILED = "temporarily_failed"
class HardwareStatus(StrEnum):
UNKNOWN = "unknown"
ACTIVE = "active"
MISSING = "missing"
UNAVAILABLE = "unavailable"
DEGRADED = "degraded"
PENDING = "pending"
DECOMMISSIONED = "decommissioned"
class InventorySource(StrEnum):
HOST = "host"
NVIDIA_NVML = "nvidia_nvml"
class InventoryRunStatus(StrEnum):
RUNNING = "running"
SUCCEEDED = "succeeded"
DEGRADED = "degraded"
FAILED = "failed"
class NodeLiveness(StrEnum):
ONLINE = "online"
STALE = "stale"
OFFLINE = "offline"
DISABLED = "disabled"
DECOMMISSIONED = "decommissioned"
class AgentHealth(StrEnum):
HEALTHY = "healthy"
INCOMPATIBLE = "incompatible"
REVOKED = "revoked"
UNKNOWN = "unknown"
class ObservationSource(StrEnum):
LOCAL_CONTROL_PLANE = "local_control_plane"
REMOTE_AGENT = "remote_agent"
@@ -0,0 +1,593 @@
from __future__ import annotations
import math
import re
import uuid
from datetime import datetime
from typing import Any, Literal
from pydantic import BaseModel, ConfigDict, Field, field_validator, model_validator
MetricName = Literal["recall_at_5", "recall_at_10", "mrr", "ndcg_at_10"]
TargetKind = Literal["current", "shadow"]
_KEY = re.compile(r"^[a-z0-9][a-z0-9_-]{0,127}$")
class EvaluationModel(BaseModel):
model_config = ConfigDict(extra="forbid", from_attributes=True)
class EvaluationCaseCreate(EvaluationModel):
case_key: str
query: str = Field(min_length=1, max_length=4000)
relevant_chunk_ids: list[uuid.UUID] = Field(min_length=1, max_length=100)
relevant_document_ids: list[uuid.UUID] = Field(default_factory=list, max_length=100)
relevance_grades: dict[str, int] = Field(default_factory=dict)
label_provenance: dict[str, Any]
critical: bool = False
review_status: Literal["reviewed", "approved"]
@field_validator("case_key")
@classmethod
def valid_key(cls, value: str) -> str:
if not _KEY.fullmatch(value):
raise ValueError("case_key must be a safe identifier")
return value
@field_validator("relevant_chunk_ids")
@classmethod
def unique_relevance(cls, value: list[uuid.UUID]) -> list[uuid.UUID]:
if len(set(value)) != len(value):
raise ValueError("relevant chunk ids must be unique")
return value
class EvaluationRevisionCreate(EvaluationModel):
revision: str
dataset_revision: str
metrics: list[MetricName] = ["recall_at_5", "recall_at_10", "mrr", "ndcg_at_10"]
top_k: int = Field(default=10, ge=10, le=100)
retrieval_settings: dict[str, Any]
thresholds: dict[str, float] = Field(default_factory=dict)
cases: list[EvaluationCaseCreate] = Field(min_length=1, max_length=500)
@model_validator(mode="after")
def validate_revision(self) -> EvaluationRevisionCreate:
if not _KEY.fullmatch(self.revision) or not _KEY.fullmatch(self.dataset_revision):
raise ValueError("revision identifiers must be safe")
if len(set(self.metrics)) != len(self.metrics):
raise ValueError("metrics must be unique")
if len({case.case_key for case in self.cases}) != len(self.cases):
raise ValueError("case keys must be unique")
return self
class EvaluationSuiteCreate(EvaluationModel):
project_id: uuid.UUID
key: str
name: str = Field(min_length=1, max_length=255)
description: str = Field(min_length=1, max_length=4000)
revision: EvaluationRevisionCreate
@field_validator("key")
@classmethod
def valid_key(cls, value: str) -> str:
if not _KEY.fullmatch(value):
raise ValueError("suite key must be a safe identifier")
return value
class EvaluationSuiteResponse(EvaluationModel):
id: uuid.UUID
project_id: uuid.UUID
key: str
name: str
description: str
latest_revision_id: uuid.UUID
latest_revision: str
case_count: int
critical_case_count: int
created_at: datetime
class EvaluationCaseDefinitionResponse(EvaluationModel):
id: uuid.UUID
suite_revision_id: uuid.UUID
case_key: str
query: str
relevant_chunk_ids: list[str]
relevant_document_ids: list[str]
relevance_grades: dict[str, int]
label_provenance: dict[str, Any]
critical: bool
review_status: str
created_at: datetime
class EvaluationRunCreate(EvaluationModel):
project_id: uuid.UUID
suite_revision_id: uuid.UUID
target_kind: TargetKind
target_index_ref: str = Field(min_length=1, max_length=512)
embedding_space_ref: str = Field(min_length=1, max_length=255)
capability_deployment_id: uuid.UUID | None = None
corpus_revision: str = Field(min_length=1, max_length=128)
retrieval_config_digest: str = Field(pattern=r"^[0-9a-f]{64}$")
environment_fingerprint: dict[str, Any]
class RankedResult(EvaluationModel):
chunk_id: uuid.UUID
document_id: uuid.UUID | None = None
score: float
@field_validator("score")
@classmethod
def finite_score(cls, value: float) -> float:
if not math.isfinite(value):
raise ValueError("score must be finite")
return value
class CandidatePoolEntry(EvaluationModel):
id: str = Field(min_length=1, max_length=255)
document_id: str | None = Field(default=None, max_length=255)
score: float
content_sha256: str = Field(pattern=r"^[0-9a-f]{64}$")
@field_validator("score")
@classmethod
def finite_score(cls, value: float) -> float:
if not math.isfinite(value):
raise ValueError("score must be finite")
return value
class RetrievalCandidatePoolCreate(EvaluationModel):
project_id: uuid.UUID
suite_revision_id: uuid.UUID
evaluation_case_id: uuid.UUID
source_embedding_space: str = Field(min_length=1, max_length=255)
source_index_ref: str = Field(min_length=1, max_length=512)
corpus_revision: str = Field(min_length=1, max_length=128)
retrieval_config_digest: str = Field(pattern=r"^[0-9a-f]{64}$")
ordered_candidates: list[CandidatePoolEntry] = Field(min_length=1, max_length=40)
@model_validator(mode="after")
def unique_ordered_ids(self) -> RetrievalCandidatePoolCreate:
ids = [item.id for item in self.ordered_candidates]
if len(set(ids)) != len(ids):
raise ValueError("candidate pool ids must be unique")
return self
class RetrievalCandidatePoolResponse(EvaluationModel):
id: uuid.UUID
project_id: uuid.UUID
suite_revision_id: uuid.UUID
evaluation_case_id: uuid.UUID
source_embedding_space: str
source_index_ref: str
corpus_revision: str
retrieval_config_digest: str
candidate_count: int
ordered_candidates: list[dict[str, Any]]
fingerprint: str
created_at: datetime
immutable_at: datetime
class RetrievalPipelineIdentityCreate(EvaluationModel):
project_id: uuid.UUID
embedding_space_ref: str = Field(min_length=1, max_length=255)
sparse_config_digest: str = Field(pattern=r"^[0-9a-f]{64}$")
fusion_config_digest: str = Field(pattern=r"^[0-9a-f]{64}$")
reranker_deployment_id: uuid.UUID | None = None
reranker_config_digest: str | None = Field(default=None, pattern=r"^[0-9a-f]{64}$")
candidate_k: Literal[40] = 40
output_k: int = Field(default=10, ge=1, le=10)
configuration: dict[str, Any] = Field(default_factory=dict)
@model_validator(mode="after")
def reranker_identity_is_complete(self) -> RetrievalPipelineIdentityCreate:
if (self.reranker_deployment_id is None) != (self.reranker_config_digest is None):
raise ValueError("reranker deployment and config digest must be supplied together")
return self
class RetrievalPipelineIdentityResponse(EvaluationModel):
id: uuid.UUID
project_id: uuid.UUID
embedding_space_ref: str
sparse_config_digest: str
fusion_config_digest: str
reranker_deployment_id: uuid.UUID | None
reranker_config_digest: str | None
candidate_k: int
output_k: int
identity_digest: str
configuration: dict[str, Any]
migration_class: str
created_at: datetime
immutable_at: datetime
class RerankingRunCreate(EvaluationModel):
project_id: uuid.UUID
suite_revision_id: uuid.UUID
pipeline_identity_id: uuid.UUID
control_pipeline_identity_id: uuid.UUID
candidate_pool_ids: list[uuid.UUID] = Field(min_length=1, max_length=500)
corpus_revision: str = Field(min_length=1, max_length=128)
environment_fingerprint: dict[str, Any]
class RerankingCaseResultCreate(EvaluationModel):
case_id: uuid.UUID
candidate_pool_id: uuid.UUID
ranked_results: list[RankedResult] = Field(min_length=1, max_length=10)
retrieval_latency_ms: float = Field(ge=0, le=3_600_000)
rerank_latency_ms: float = Field(ge=0, le=3_600_000)
total_latency_ms: float = Field(ge=0, le=3_600_000)
error_code: str | None = Field(default=None, max_length=64)
class RerankingRunComplete(EvaluationModel):
results: list[RerankingCaseResultCreate] = Field(min_length=1, max_length=500)
class RerankingRunResponse(EvaluationModel):
id: uuid.UUID
project_id: uuid.UUID
suite_revision_id: uuid.UUID
pipeline_identity_id: uuid.UUID
control_pipeline_identity_id: uuid.UUID
reranker_deployment_id: uuid.UUID | None
status: str
corpus_revision: str
candidate_pool_set_fingerprint: str
environment_fingerprint: dict[str, Any]
environment_digest: str
expected_cases: int
completed_cases: int
error_count: int
aggregate_metrics: dict[str, float]
latency_metrics: dict[str, float]
started_at: datetime | None
completed_at: datetime | None
created_at: datetime
class RerankingCaseResultResponse(EvaluationModel):
id: uuid.UUID
run_id: uuid.UUID
case_id: uuid.UUID
case_key: str
critical: bool
candidate_pool_id: uuid.UUID
ranked_results: list[dict[str, Any]]
relevant_results: list[str]
first_relevant_rank: int | None
metrics: dict[str, float]
retrieval_latency_ms: float
rerank_latency_ms: float
total_latency_ms: float
error_code: str | None
class DiscoveryCandidateAssessmentCreate(EvaluationModel):
model_id: uuid.UUID | None = None
upstream_snapshot_id: uuid.UUID
candidate_key: str
repository_id: str = Field(min_length=3, max_length=255)
resolved_commit_sha: str = Field(pattern=r"^[0-9a-f]{40,64}$")
artifact_evidence: dict[str, Any]
security_state: dict[str, Any]
license_state: dict[str, Any]
gpu_fit: dict[str, Any]
status: Literal[
"shortlisted",
"preflight_passed",
"evaluated",
"discovery_security_blocked",
"discovery_gpu_fit_blocked",
"license_blocked",
"not_selected",
]
rationale: str = Field(min_length=10, max_length=4000)
@field_validator("candidate_key")
@classmethod
def valid_candidate_key(cls, value: str) -> str:
if not _KEY.fullmatch(value):
raise ValueError("candidate_key must be a safe identifier")
return value
class DiscoveryCandidateAssessmentResponse(DiscoveryCandidateAssessmentCreate):
id: uuid.UUID
evidence_fingerprint: str
created_at: datetime
immutable_at: datetime
class EvaluationCaseResultCreate(EvaluationModel):
case_id: uuid.UUID
ranked_results: list[RankedResult] = Field(max_length=100)
latency_ms: float = Field(ge=0, le=3_600_000)
error_code: str | None = Field(default=None, max_length=64)
class EvaluationRunComplete(EvaluationModel):
results: list[EvaluationCaseResultCreate] = Field(min_length=1, max_length=500)
class EvaluationRunResponse(EvaluationModel):
id: uuid.UUID
project_id: uuid.UUID
suite_revision_id: uuid.UUID
target_kind: str
target_index_ref: str
embedding_space_ref: str
capability_deployment_id: uuid.UUID | None
status: str
corpus_revision: str
retrieval_config_digest: str
environment_fingerprint: dict[str, Any]
environment_digest: str
expected_cases: int
completed_cases: int
error_count: int
aggregate_metrics: dict[str, float]
started_at: datetime | None
completed_at: datetime | None
created_at: datetime
class EvaluationCaseResultResponse(EvaluationModel):
id: uuid.UUID
run_id: uuid.UUID
case_id: uuid.UUID
case_key: str
critical: bool
ranked_results: list[dict[str, Any]]
relevant_results: list[str]
first_relevant_rank: int | None
metrics: dict[str, float]
latency_ms: float
error_code: str | None
class EvaluationComparisonCreate(EvaluationModel):
baseline_run_id: uuid.UUID
candidate_run_id: uuid.UUID
require_no_critical_regressions: bool = True
maximum_error_rate: float = Field(default=0.0, ge=0, le=1)
minimum_recall_at_10_delta: float = Field(default=0.0, ge=-1, le=1)
class EvaluationComparisonResponse(EvaluationModel):
id: uuid.UUID
project_id: uuid.UUID
baseline_run_id: uuid.UUID
candidate_run_id: uuid.UUID
comparability: str
comparability_evidence: dict[str, Any]
metric_deltas: dict[str, float]
improved_cases: int
unchanged_cases: int
regressed_cases: int
critical_regressions: int
case_comparisons: list[dict[str, Any]]
promotion_eligibility: str
eligibility_evidence: dict[str, Any]
created_at: datetime
class ModelComparisonCandidateCreate(EvaluationModel):
candidate_key: str
label: str = Field(min_length=1, max_length=255)
status: Literal["evaluated", "blocked", "unknown"]
evaluation_run_id: uuid.UUID | None = None
candidate_deployment_id: uuid.UUID | None = None
embedding_space: str | None = Field(default=None, max_length=255)
artifact_size_bytes: int | None = Field(default=None, ge=0)
latency_ms: dict[str, float] = Field(default_factory=dict)
resource_evidence: dict[str, Any] = Field(default_factory=dict)
migration_impact: dict[str, Any] = Field(default_factory=dict)
security_state: dict[str, Any] = Field(default_factory=dict)
provenance: dict[str, Any] = Field(default_factory=dict)
blockers: list[str] = Field(default_factory=list, max_length=32)
candidate_kind: Literal["embedding_deployment", "retrieval_pipeline"] = "embedding_deployment"
pipeline_identity_id: uuid.UUID | None = None
@field_validator("candidate_key")
@classmethod
def valid_candidate_key(cls, value: str) -> str:
if not _KEY.fullmatch(value):
raise ValueError("candidate_key must be a safe identifier")
return value
@model_validator(mode="after")
def evaluated_run_required(self) -> ModelComparisonCandidateCreate:
if (
self.status == "evaluated"
and self.candidate_kind == "embedding_deployment"
and self.evaluation_run_id is None
):
raise ValueError("evaluated embedding candidate requires evaluation_run_id")
if self.candidate_kind == "retrieval_pipeline" and self.evaluation_run_id is not None:
raise ValueError("retrieval pipeline evidence must come from a fixed-pool run")
if self.status != "evaluated" and self.evaluation_run_id is not None:
raise ValueError("blocked or unknown candidate cannot claim an evaluation run")
if self.candidate_kind == "retrieval_pipeline" and self.pipeline_identity_id is None:
raise ValueError("retrieval pipeline candidate requires pipeline_identity_id")
return self
class ModelComparisonCreate(EvaluationModel):
project_id: uuid.UUID
capability_contract_id: uuid.UUID
suite_revision_id: uuid.UUID
current_run_id: uuid.UUID
title: str = Field(min_length=1, max_length=255)
candidates: list[ModelComparisonCandidateCreate] = Field(min_length=1, max_length=20)
@model_validator(mode="after")
def unique_candidates(self) -> ModelComparisonCreate:
if len({item.candidate_key for item in self.candidates}) != len(self.candidates):
raise ValueError("candidate keys must be unique")
return self
class ModelComparisonResponse(EvaluationModel):
id: uuid.UUID
project_id: uuid.UUID
capability_contract_id: uuid.UUID
suite_revision_id: uuid.UUID
current_run_id: uuid.UUID
title: str
candidates: list[dict[str, Any]]
comparability: str
evidence_fingerprint: str
created_at: datetime
class AdvisorPolicyUpdate(EvaluationModel):
maximum_latency_p95_regression_ratio: float = Field(ge=0, le=10)
minimum_metric_deltas: dict[MetricName, float]
rationale: str = Field(min_length=20, max_length=4000)
class AdvisorPolicyResponse(EvaluationModel):
id: uuid.UUID
key: str
required_evidence_level: str
critical_regression_hard_block: bool
maximum_latency_p95_regression_ratio: float
minimum_metric_deltas: dict[str, float]
require_verified_supply_chain: bool
require_runtime_fit: bool
rationale: str
version: int
created_at: datetime
updated_at: datetime
class AdvisorRecommendationCreate(EvaluationModel):
candidate_key: str
@field_validator("candidate_key")
@classmethod
def valid_candidate_key(cls, value: str) -> str:
if not _KEY.fullmatch(value):
raise ValueError("candidate_key must be a safe identifier")
return value
class AdvisorRecommendationDismiss(EvaluationModel):
dismissed_by: str = Field(min_length=1, max_length=255)
reason: str = Field(min_length=10, max_length=2000)
class AdvisorRecommendationResponse(EvaluationModel):
id: uuid.UUID
comparison_id: uuid.UUID
project_id: uuid.UUID
capability_contract_id: uuid.UUID
policy_id: uuid.UUID
candidate_key: str
current_deployment_id: uuid.UUID | None
candidate_deployment_id: uuid.UUID | None
current_embedding_space: str
candidate_embedding_space: str | None
target_kind: Literal["embedding_deployment", "retrieval_pipeline"]
current_pipeline_identity_id: uuid.UUID | None
candidate_pipeline_identity_id: uuid.UUID | None
verdict: Literal[
"KEEP_CURRENT",
"KEEP_CURRENT_EMBEDDING_ADD_RERANKER_CANDIDATE",
"PROMOTION_ELIGIBLE",
"PROMOTION_NOT_RECOMMENDED",
"REQUIRES_MORE_EVIDENCE",
]
confidence: str
evidence_level: str
quality_deltas: dict[str, float]
latency_deltas: dict[str, Any]
resource_deltas: dict[str, Any]
migration_impact: dict[str, Any]
security_state: dict[str, Any]
key_improvements: list[str]
blockers: list[str]
policy_snapshot: dict[str, Any]
evidence_fingerprint: str
status: str
generated_at: datetime
dismissed_at: datetime | None
dismissed_by: str | None
dismissal_reason: str | None
class EmbeddingMigrationCreate(EvaluationModel):
source_embedding_space: str = Field(min_length=1, max_length=255)
target_embedding_space_id: uuid.UUID
source_index_ref: str = Field(min_length=1, max_length=512)
target_index_ref: str = Field(min_length=1, max_length=512)
corpus_revision: str = Field(min_length=1, max_length=128)
total_chunks: int = Field(ge=1, le=10_000_000)
batch_size: int = Field(default=32, ge=1, le=256)
concurrency: int = Field(default=1, ge=1, le=8)
class MigrationUpdate(EvaluationModel):
status: Literal[
"preflight",
"backfilling",
"validating",
"ready_for_evaluation",
"evaluated",
"promotion_eligible",
"not_eligible",
"failed",
"cancelled",
]
completed_chunks: int = Field(ge=0)
failed_chunks: int = Field(ge=0)
retried_chunks: int = Field(ge=0)
preflight_evidence: dict[str, Any] = Field(default_factory=dict)
progress_evidence: dict[str, Any] = Field(default_factory=dict)
validation_evidence: dict[str, Any] = Field(default_factory=dict)
operational_metrics: dict[str, Any] = Field(default_factory=dict)
failure_code: str | None = Field(default=None, max_length=64)
failure_message: str | None = Field(default=None, max_length=2000)
class EmbeddingMigrationResponse(EvaluationModel):
id: uuid.UUID
project_id: uuid.UUID
source_embedding_space: str
target_embedding_space_id: uuid.UUID
source_index_ref: str
target_index_ref: str
corpus_revision: str
status: str
total_chunks: int
completed_chunks: int
failed_chunks: int
retried_chunks: int
batch_size: int
concurrency: int
priority: str
preflight_evidence: dict[str, Any]
progress_evidence: dict[str, Any]
validation_evidence: dict[str, Any]
operational_metrics: dict[str, Any]
evaluation_eligibility: bool
cancel_requested: bool
failure_code: str | None
failure_message: str | None
started_at: datetime | None
finished_at: datetime | None
created_at: datetime
updated_at: datetime
@@ -0,0 +1,246 @@
from __future__ import annotations
import hashlib
import json
from datetime import UTC, datetime
from typing import Protocol
from pydantic import BaseModel, ConfigDict, Field, model_validator
from modelforge_api.domain.enums import (
AgentHealth,
Availability,
HardwareStatus,
InventorySource,
NodeLiveness,
ObservationSource,
)
class HardwareModel(BaseModel):
model_config = ConfigDict(extra="forbid")
class ObservedValue[T](HardwareModel):
value: T | None = None
availability: Availability
reason: str | None = None
@model_validator(mode="after")
def value_matches_availability(self) -> ObservedValue[T]:
if self.availability is Availability.KNOWN and self.value is None:
raise ValueError("known observations require a value")
if self.availability is not Availability.KNOWN and self.value is not None:
raise ValueError("non-known observations cannot carry a value")
return self
@classmethod
def known(cls, value: T) -> ObservedValue[T]:
return cls(value=value, availability=Availability.KNOWN)
@classmethod
def absent(cls, availability: Availability, reason: str | None = None) -> ObservedValue[T]:
return cls(availability=availability, reason=reason)
class StorageObservation(HardwareModel):
purpose: str
path: str
total_bytes: ObservedValue[int]
used_bytes: ObservedValue[int]
free_bytes: ObservedValue[int]
class HostInventory(HardwareModel):
identity_key: str
identity_source: str
hostname: str
display_name: str
os_name: str
os_version: ObservedValue[str]
architecture: str
kernel_version: ObservedValue[str]
cpu_model: ObservedValue[str]
logical_cpu_count: ObservedValue[int]
physical_core_count: ObservedValue[int]
total_ram_bytes: ObservedValue[int]
available_ram_bytes: ObservedValue[int]
agent_version: str
inventory_at: datetime = Field(default_factory=lambda: datetime.now(UTC))
storage: list[StorageObservation] = Field(default_factory=list)
metadata: dict[str, str] = Field(default_factory=dict)
class AcceleratorInventory(HardwareModel):
device_index: int
device_uuid: str
pci_bus_id: ObservedValue[str]
name: str
vendor: str = "NVIDIA"
architecture: ObservedValue[str]
compute_capability_major: ObservedValue[int]
compute_capability_minor: ObservedValue[int]
total_vram_bytes: ObservedValue[int]
driver_version: ObservedValue[str]
cuda_driver_version: ObservedValue[str]
mig_mode_current: ObservedValue[bool]
inventory_at: datetime = Field(default_factory=lambda: datetime.now(UTC))
source: InventorySource = InventorySource.NVIDIA_NVML
class AcceleratorTelemetry(HardwareModel):
device_uuid: str
observed_at: datetime = Field(default_factory=lambda: datetime.now(UTC))
used_vram_bytes: ObservedValue[int]
free_vram_bytes: ObservedValue[int]
gpu_utilization_percent: ObservedValue[int]
memory_utilization_percent: ObservedValue[int]
temperature_c: ObservedValue[int]
power_draw_w: ObservedValue[float]
power_limit_w: ObservedValue[float]
graphics_clock_mhz: ObservedValue[int]
memory_clock_mhz: ObservedValue[int]
fan_speed_percent: ObservedValue[int]
performance_state: ObservedValue[str]
class NvidiaCollection(HardwareModel):
availability: Availability
reason: str | None = None
inventory: list[AcceleratorInventory] = Field(default_factory=list)
telemetry: list[AcceleratorTelemetry] = Field(default_factory=list)
observed_at: datetime = Field(default_factory=lambda: datetime.now(UTC))
class HardwareSnapshot(HardwareModel):
host: HostInventory
nvidia: NvidiaCollection
@property
def fingerprint(self) -> str:
facts = {
"node_identity": self.host.identity_key,
"os": self.host.os_name,
"os_version": self.host.os_version.model_dump(mode="json"),
"architecture": self.host.architecture,
"kernel": self.host.kernel_version.model_dump(mode="json"),
"accelerators": [
{
"uuid": item.device_uuid,
"name": item.name,
"pci": item.pci_bus_id.model_dump(mode="json"),
"vram": item.total_vram_bytes.model_dump(mode="json"),
"compute_major": item.compute_capability_major.model_dump(mode="json"),
"compute_minor": item.compute_capability_minor.model_dump(mode="json"),
"driver": item.driver_version.model_dump(mode="json"),
"cuda_driver": item.cuda_driver_version.model_dump(mode="json"),
}
for item in sorted(self.nvidia.inventory, key=lambda value: value.device_uuid)
],
}
canonical = json.dumps(facts, sort_keys=True, separators=(",", ":"))
return hashlib.sha256(canonical.encode()).hexdigest()
class HostCollector(Protocol):
def collect(self) -> HostInventory: ...
class AcceleratorCollector(Protocol):
def collect(self) -> NvidiaCollection: ...
class HardwareOverview(HardwareModel):
status: HardwareStatus
inventory_state: HardwareStatus
node_count: int
accelerator_count: int
last_inventory_at: datetime | None
reason: str | None = None
class StorageState(HardwareModel):
id: str
purpose: str
path: str
total_bytes: ObservedValue[int]
used_bytes: ObservedValue[int]
free_bytes: ObservedValue[int]
observed_at: datetime
class AcceleratorState(HardwareModel):
id: str
node_id: str
status: HardwareStatus
status_reason: str | None
device_index: int
device_uuid: str
pci_bus_id: ObservedValue[str]
name: str
vendor: str
architecture: ObservedValue[str]
compute_capability_major: ObservedValue[int]
compute_capability_minor: ObservedValue[int]
total_vram_bytes: ObservedValue[int]
driver_version: ObservedValue[str]
cuda_driver_version: ObservedValue[str]
mig_mode_current: ObservedValue[bool]
first_seen_at: datetime
last_seen_at: datetime | None
inventory_at: datetime | None
telemetry: AcceleratorTelemetry | None
class NodeState(HardwareModel):
id: str
identity_key: str
identity_source: str
hostname: str
display_name: str
status: HardwareStatus
status_reason: str | None
os_name: str | None
os_version: ObservedValue[str]
architecture: str | None
kernel_version: ObservedValue[str]
cpu_model: ObservedValue[str]
logical_cpu_count: ObservedValue[int]
physical_core_count: ObservedValue[int]
total_ram_bytes: ObservedValue[int]
available_ram_bytes: ObservedValue[int]
agent_version: str | None
first_seen_at: datetime
last_seen_at: datetime | None
inventory_at: datetime | None
hardware_fingerprint: str | None
enabled: bool = True
liveness: NodeLiveness = NodeLiveness.OFFLINE
agent_health: AgentHealth = AgentHealth.UNKNOWN
observation_source: ObservationSource = ObservationSource.LOCAL_CONTROL_PLANE
protocol_version: int | None = None
supported_capabilities: list[str] = Field(default_factory=list)
agent_started_at: datetime | None = None
last_heartbeat_at: datetime | None = None
last_inventory_received_at: datetime | None = None
last_telemetry_received_at: datetime | None = None
inventory_age_seconds: int | None = None
telemetry_age_seconds: int | None = None
last_connection_error: str | None = None
role: str | None = None
labels: dict[str, str | bool] = Field(default_factory=dict)
production_eligible: bool = False
lab_eligible: bool = True
benchmark_eligible: bool = False
generation: int = 1
decommissioned_at: datetime | None = None
decommission_reason: str | None = None
decommissioned_by: str | None = None
environment: str | None = None
storage: list[StorageState]
accelerators: list[AcceleratorState]
class HardwareState(HardwareModel):
overview: HardwareOverview
nodes: list[NodeState]
@@ -0,0 +1,75 @@
from __future__ import annotations
from .enums import MigrationStatus, ModelLifecycle, UpgradeClass, VerificationStatus
class InvalidTransition(ValueError):
pass
MODEL_TRANSITIONS: dict[ModelLifecycle, frozenset[ModelLifecycle]] = {
ModelLifecycle.DISCOVERED: frozenset(
{ModelLifecycle.CANDIDATE, ModelLifecycle.REJECTED, ModelLifecycle.DEPRECATED}
),
ModelLifecycle.CANDIDATE: frozenset(
{ModelLifecycle.DOWNLOADING, ModelLifecycle.REJECTED, ModelLifecycle.DEPRECATED}
),
ModelLifecycle.DOWNLOADING: frozenset({ModelLifecycle.QUARANTINED, ModelLifecycle.REJECTED}),
ModelLifecycle.QUARANTINED: frozenset(
{ModelLifecycle.VERIFIED, ModelLifecycle.SECURITY_BLOCKED, ModelLifecycle.LICENSE_BLOCKED}
),
ModelLifecycle.VERIFIED: frozenset({ModelLifecycle.TESTING, ModelLifecycle.INCOMPATIBLE}),
ModelLifecycle.TESTING: frozenset(
{ModelLifecycle.APPROVED, ModelLifecycle.INCOMPATIBLE, ModelLifecycle.REJECTED}
),
ModelLifecycle.APPROVED: frozenset(
{ModelLifecycle.STANDBY, ModelLifecycle.ACTIVE, ModelLifecycle.DEPRECATED}
),
ModelLifecycle.STANDBY: frozenset({ModelLifecycle.ACTIVE, ModelLifecycle.DEPRECATED}),
ModelLifecycle.ACTIVE: frozenset({ModelLifecycle.STANDBY, ModelLifecycle.DEPRECATED}),
ModelLifecycle.DEPRECATED: frozenset({ModelLifecycle.ARCHIVED, ModelLifecycle.ACTIVE}),
ModelLifecycle.ARCHIVED: frozenset(),
ModelLifecycle.REJECTED: frozenset(),
ModelLifecycle.INCOMPATIBLE: frozenset({ModelLifecycle.CANDIDATE}),
ModelLifecycle.SECURITY_BLOCKED: frozenset(
{ModelLifecycle.QUARANTINED, ModelLifecycle.REJECTED}
),
ModelLifecycle.LICENSE_BLOCKED: frozenset(
{ModelLifecycle.QUARANTINED, ModelLifecycle.REJECTED}
),
}
def assert_model_transition(current: ModelLifecycle, target: ModelLifecycle) -> None:
if target not in MODEL_TRANSITIONS[current]:
raise InvalidTransition(f"model lifecycle transition {current} -> {target} is not allowed")
def assert_promotion_allowed(
*,
upgrade_class: UpgradeClass,
verification_status: VerificationStatus,
local_benchmark_ids: list[str],
project_benchmark_ids: list[str],
operator_approval_id: str | None,
rollback_deployment_id: str | None,
migration_status: MigrationStatus | None = None,
) -> None:
blockers: list[str] = []
if verification_status is not VerificationStatus.VERIFIED:
blockers.append("artifact is not verified")
if not local_benchmark_ids:
blockers.append("local benchmark evidence is missing")
if not project_benchmark_ids:
blockers.append("project benchmark evidence is missing")
if not operator_approval_id:
blockers.append("operator approval is missing")
if not rollback_deployment_id:
blockers.append("rollback target is missing")
if (
upgrade_class is UpgradeClass.REQUIRES_REINDEX
and migration_status is not MigrationStatus.READY
):
blockers.append("ready migration is required for reindexing change")
if blockers:
raise InvalidTransition("promotion blocked: " + "; ".join(blockers))
@@ -0,0 +1,412 @@
from __future__ import annotations
import uuid
from datetime import datetime
from enum import StrEnum
from typing import Any, Literal
from pydantic import BaseModel, ConfigDict, Field, model_validator
class LifecycleModel(BaseModel):
model_config = ConfigDict(extra="forbid", from_attributes=True)
class LifecycleEnvironment(StrEnum):
LAB = "LAB"
PRODUCTION = "PRODUCTION"
class DeploymentLifecycleState(StrEnum):
CANDIDATE = "CANDIDATE"
LAB_READY = "LAB_READY"
PROMOTION_ELIGIBLE = "PROMOTION_ELIGIBLE"
CANARY = "CANARY"
LAB_STABLE = "LAB_STABLE"
STABLE = "STABLE"
DRAINING = "DRAINING"
DEPRECATED = "DEPRECATED"
ARCHIVED = "ARCHIVED"
MANUAL_INTERVENTION_REQUIRED = "MANUAL_INTERVENTION_REQUIRED"
class ApprovalStatus(StrEnum):
PENDING = "PENDING"
APPROVED = "APPROVED"
REJECTED = "REJECTED"
BLOCKED = "BLOCKED"
EXPIRED = "EXPIRED"
STALE = "STALE"
REVOKED = "REVOKED"
class PromotionPlanStatus(StrEnum):
DRAFT = "DRAFT"
APPROVED = "APPROVED"
EXECUTING = "EXECUTING"
COMMITTED = "COMMITTED"
ROLLED_BACK = "ROLLED_BACK"
FAILED = "FAILED"
class OperationStage(StrEnum):
PLANNED = "PLANNED"
APPROVED = "APPROVED"
PREPARING = "PREPARING"
ACTIVATING = "ACTIVATING"
VERIFYING = "VERIFYING"
COMMITTED = "COMMITTED"
ROLLING_BACK = "ROLLING_BACK"
ROLLED_BACK = "ROLLED_BACK"
FAILED = "FAILED"
class CanaryStatus(StrEnum):
PLANNED = "PLANNED"
RUNNING = "RUNNING"
READY_FOR_PROMOTION_REVIEW = "READY_FOR_PROMOTION_REVIEW"
ABORTED = "ABORTED"
class RetentionState(StrEnum):
ACTIVE = "ACTIVE"
ROLLBACK_RETAINED = "ROLLBACK_RETAINED"
DEPRECATED = "DEPRECATED"
ARCHIVABLE = "ARCHIVABLE"
ARCHIVED = "ARCHIVED"
class CleanupStatus(StrEnum):
READY = "READY"
BLOCKED = "BLOCKED"
STALE = "STALE"
EXECUTED = "EXECUTED"
ALLOWED_DEPLOYMENT_TRANSITIONS: dict[
DeploymentLifecycleState, frozenset[DeploymentLifecycleState]
] = {
DeploymentLifecycleState.CANDIDATE: frozenset(
{DeploymentLifecycleState.LAB_READY, DeploymentLifecycleState.DEPRECATED}
),
DeploymentLifecycleState.LAB_READY: frozenset(
{
DeploymentLifecycleState.PROMOTION_ELIGIBLE,
DeploymentLifecycleState.CANARY,
DeploymentLifecycleState.DEPRECATED,
}
),
DeploymentLifecycleState.PROMOTION_ELIGIBLE: frozenset(
{DeploymentLifecycleState.CANARY, DeploymentLifecycleState.DEPRECATED}
),
DeploymentLifecycleState.CANARY: frozenset(
{
DeploymentLifecycleState.LAB_READY,
DeploymentLifecycleState.LAB_STABLE,
DeploymentLifecycleState.STABLE,
}
),
DeploymentLifecycleState.LAB_STABLE: frozenset(
{
DeploymentLifecycleState.CANARY,
DeploymentLifecycleState.LAB_READY,
DeploymentLifecycleState.DEPRECATED,
}
),
DeploymentLifecycleState.STABLE: frozenset(
{DeploymentLifecycleState.CANARY, DeploymentLifecycleState.DRAINING}
),
DeploymentLifecycleState.DRAINING: frozenset(
{DeploymentLifecycleState.STABLE, DeploymentLifecycleState.DEPRECATED}
),
DeploymentLifecycleState.DEPRECATED: frozenset({DeploymentLifecycleState.ARCHIVED}),
DeploymentLifecycleState.ARCHIVED: frozenset(),
DeploymentLifecycleState.MANUAL_INTERVENTION_REQUIRED: frozenset(),
}
def assert_deployment_transition(
current: DeploymentLifecycleState,
target: DeploymentLifecycleState,
) -> None:
if target not in ALLOWED_DEPLOYMENT_TRANSITIONS[current]:
raise ValueError(f"lifecycle transition {current} -> {target} is not allowed")
class LifecycleEvidenceBundle(LifecycleModel):
artifact_set_id: uuid.UUID | None = None
model_revision_id: uuid.UUID | None = None
runtime_profile_id: uuid.UUID | None = None
runtime_probe_id: uuid.UUID | None = None
production_execution_approval_id: uuid.UUID | None = None
runtime_image_digest: str | None = Field(default=None, max_length=128)
artifact_digests: list[str] = Field(default_factory=list, max_length=1000)
capability_deployment_id: uuid.UUID | None = None
project_binding_id: uuid.UUID | None = None
project_fit_evidence_id: uuid.UUID | None = None
evaluation_run_ids: list[uuid.UUID] = Field(default_factory=list, max_length=1000)
resource_envelope_id: uuid.UUID | None = None
embedding_space_id: uuid.UUID | None = None
retrieval_pipeline_id: uuid.UUID | None = None
migration_id: uuid.UUID | None = None
rollback_target_ref: str | None = Field(default=None, max_length=255)
integrity: Literal["UNKNOWN", "VERIFIED", "CORRUPT"] = "UNKNOWN"
security: Literal["UNREVIEWED", "APPROVED", "BLOCKED"] = "UNREVIEWED"
license: Literal["UNKNOWN", "APPROVED", "BLOCKED"] = "UNKNOWN"
runtime: Literal["UNPROBED", "PROVEN", "INCOMPATIBLE"] = "UNPROBED"
evaluation: Literal["NOT_EVALUATED", "EVALUATED", "REGRESSED", "PROMOTION_ELIGIBLE"] = (
"NOT_EVALUATED"
)
project_fit: Literal[
"UNKNOWN",
"REQUIRES_MORE_EVIDENCE",
"ELIGIBLE",
"BLOCKED",
"DEFERRED_EXTERNAL_VALIDATION",
"KEEP_LAB",
] = "UNKNOWN"
engineering_integration: Literal["UNKNOWN", "PASS", "INCOMPLETE", "BLOCKED"] = "UNKNOWN"
platform_readiness: Literal["UNKNOWN", "LAB_READY", "PROMOTION_ELIGIBLE", "STABLE"] = (
"UNKNOWN"
)
production_validation: Literal[
"NOT_REQUIRED", "REQUIRED", "DEFERRED_EXTERNAL_VALIDATION", "SATISFIED"
] = "REQUIRED"
scheduler_readiness: Literal["UNKNOWN", "READY", "BLOCKED"] = "UNKNOWN"
critical_regressions: int = Field(default=0, ge=0)
evidence_revisions: dict[str, str] = Field(default_factory=dict)
class ApprovalPolicyCreate(LifecycleModel):
key: str = Field(pattern=r"^[a-z][a-z0-9-]+$", max_length=128)
scope: Literal["LAB_PROMOTION", "CAPABILITY_PRODUCTION", "PROJECT_PRODUCTION"]
requirements: dict[str, Any]
created_by: str = Field(min_length=1, max_length=255)
class ApprovalPolicyResponse(LifecycleModel):
id: uuid.UUID
key: str
revision: int
scope: str
requirements: dict[str, Any]
fingerprint: str
active: bool
created_by: str
created_at: datetime
class LifecycleSubjectCreate(LifecycleModel):
target_type: Literal[
"CAPABILITY_DEPLOYMENT", "PROJECT_BINDING", "ARTIFACT_SET", "LAB_REHEARSAL"
]
target_ref: str = Field(min_length=1, max_length=255)
environment: LifecycleEnvironment
state: DeploymentLifecycleState
class LifecycleSubjectResponse(LifecycleModel):
id: uuid.UUID
target_type: str
target_ref: str
environment: str
state: str
version: int
superseded_by_ref: str | None
created_at: datetime
updated_at: datetime
class ApprovalRequestCreate(LifecycleModel):
policy_key: str = Field(pattern=r"^[a-z][a-z0-9-]+$", max_length=128)
subject_id: uuid.UUID | None = None
target_type: Literal[
"CAPABILITY_DEPLOYMENT", "PROJECT_BINDING", "CAPABILITY", "ARTIFACT_SET", "LAB_REHEARSAL"
]
target_ref: str = Field(min_length=1, max_length=255)
environment: LifecycleEnvironment
requested_transition: DeploymentLifecycleState
evidence: LifecycleEvidenceBundle
requested_by: str = Field(min_length=1, max_length=255)
reason: str = Field(min_length=8, max_length=4000)
expires_at: datetime | None = None
class ApprovalDecision(LifecycleModel):
actor: str = Field(min_length=1, max_length=255)
reason: str = Field(min_length=8, max_length=4000)
class ApprovalRequestResponse(LifecycleModel):
id: uuid.UUID
policy_revision_id: uuid.UUID
policy_key: str
policy_revision: int
version: int
subject_id: uuid.UUID | None
target_type: str
target_ref: str
environment: str
requested_transition: str
evidence_snapshot: dict[str, Any]
evidence_fingerprint: str
status: str
blockers: list[str]
warnings: list[str]
requested_by: str
approved_by: str | None
reason: str
expires_at: datetime | None
stale_at: datetime | None
decided_at: datetime | None
created_at: datetime
class PromotionPlanCreate(LifecycleModel):
approval_request_id: uuid.UUID
subject_id: uuid.UUID
desired_state: DeploymentLifecycleState
migration_class: Literal["transparent", "behavioral", "requires_reindex", "schema_breaking"]
candidate_deployment_id: uuid.UUID | None = None
rollback_target_ref: str = Field(min_length=1, max_length=255)
project_consumers: list[str] = Field(default_factory=list, max_length=1000)
affected_identities: dict[str, Any] = Field(default_factory=dict)
migration_id: uuid.UUID | None = None
canary_strategy: dict[str, Any] = Field(default_factory=dict)
drain_strategy: dict[str, Any] = Field(default_factory=dict)
health_gates: dict[str, Any] = Field(default_factory=dict)
automatic_abort_conditions: list[str] = Field(default_factory=list)
created_by: str = Field(min_length=1, max_length=255)
class PromotionPlanResponse(LifecycleModel):
id: uuid.UUID
approval_request_id: uuid.UUID
subject_id: uuid.UUID
current_state: str
desired_state: str
migration_class: str
candidate_deployment_id: uuid.UUID | None
rollback_target_ref: str
project_consumers: list[str]
affected_identities: dict[str, Any]
impact_analysis: dict[str, Any]
migration_id: uuid.UUID | None
canary_strategy: dict[str, Any]
drain_strategy: dict[str, Any]
health_gates: dict[str, Any]
automatic_abort_conditions: list[str]
plan_fingerprint: str
status: str
version: int
created_by: str
approved_by: str | None
immutable_at: datetime | None
created_at: datetime
class PlanExecutionCreate(LifecycleModel):
executor: str = Field(min_length=1, max_length=255)
idempotency_key: str = Field(min_length=8, max_length=128)
expected_subject_version: int = Field(ge=1)
rehearsal_pause_stage: Literal["PREPARING"] | None = None
class CanaryObservation(LifecycleModel):
request_count: int = Field(ge=0)
error_count: int = Field(ge=0)
latency_p95_ms: float = Field(ge=0)
capability_health: Literal["HEALTHY", "UNHEALTHY"]
scheduler_ready: bool
critical_project_failures: int = Field(default=0, ge=0)
external_pressure: bool = False
worker_healthy: bool = True
@model_validator(mode="after")
def validate_counts(self) -> CanaryObservation:
if self.error_count > self.request_count:
raise ValueError("error_count cannot exceed request_count")
return self
class LifecycleOperationResponse(LifecycleModel):
id: uuid.UUID
promotion_plan_id: uuid.UUID
version: int
stage: str
idempotency_key: str
expected_subject_version: int
requester: str
approver: str
executor: str
failure_code: str | None
failure_details: dict[str, Any]
rollback_duration_ms: float | None
started_at: datetime
finished_at: datetime | None
canary: dict[str, Any] | None = None
class RetentionPolicyCreate(LifecycleModel):
key: str = Field(pattern=r"^[a-z][a-z0-9-]+$", max_length=128)
minimum_rollback_days: int = Field(ge=1, le=3650)
requirements: dict[str, Any] = Field(default_factory=dict)
created_by: str = Field(min_length=1, max_length=255)
class RetentionPolicyResponse(LifecycleModel):
id: uuid.UUID
key: str
revision: int
minimum_rollback_days: int
requirements: dict[str, Any]
fingerprint: str
active: bool
created_by: str
created_at: datetime
class CleanupPlanCreate(LifecycleModel):
target_type: Literal["ARTIFACT_SET", "ARTIFACT_LOCATION"]
target_ref: uuid.UUID
action: Literal["ARCHIVE_METADATA", "RECORD_LOCATION_REMOVAL"]
created_by: str = Field(min_length=1, max_length=255)
class CleanupPlanResponse(LifecycleModel):
id: uuid.UUID
target_type: str
target_ref: str
action: str
dependencies: list[dict[str, Any]]
dependency_digest: str
reclaimable_bytes: int
retention_state: str
blockers: list[str]
status: str
created_by: str
created_at: datetime
executed_at: datetime | None
class CleanupExecutionCreate(LifecycleModel):
executor: str = Field(min_length=1, max_length=255)
confirm: Literal[True]
physical_removal_confirmed: bool = False
class LifecycleEventResponse(LifecycleModel):
id: uuid.UUID
event_type: str
object_type: str
object_ref: str
from_state: str | None
to_state: str | None
actor: str
actor_role: str
policy_revision_id: uuid.UUID | None
evidence_ids: list[str]
reason: str
change_id: str
details: dict[str, Any]
occurred_at: datetime
@@ -0,0 +1,543 @@
from __future__ import annotations
import re
import uuid
from datetime import datetime
from enum import StrEnum
from typing import Any, Literal
from pydantic import BaseModel, ConfigDict, Field, field_validator, model_validator
class MigrationModel(BaseModel):
model_config = ConfigDict(extra="forbid", from_attributes=True)
class MigrationClass(StrEnum):
TRANSPARENT = "TRANSPARENT"
BEHAVIORAL = "BEHAVIORAL"
REQUIRES_REINDEX = "REQUIRES_REINDEX"
SCHEMA_BREAKING = "SCHEMA_BREAKING"
class MigrationState(StrEnum):
PLANNED = "PLANNED"
PREFLIGHT = "PREFLIGHT"
READY = "READY"
BACKFILLING = "BACKFILLING"
BACKFILL_PAUSED = "BACKFILL_PAUSED"
BACKFILL_COMPLETE = "BACKFILL_COMPLETE"
VALIDATING = "VALIDATING"
VALIDATION_FAILED = "VALIDATION_FAILED"
READY_FOR_SHADOW = "READY_FOR_SHADOW"
SHADOWING = "SHADOWING"
READY_FOR_CUTOVER = "READY_FOR_CUTOVER"
CUTOVER_PREPARING = "CUTOVER_PREPARING"
CUTTING_OVER = "CUTTING_OVER"
VERIFYING_CUTOVER = "VERIFYING_CUTOVER"
CUTOVER_COMMITTED = "CUTOVER_COMMITTED"
ROLLING_BACK = "ROLLING_BACK"
ROLLED_BACK = "ROLLED_BACK"
FAILED = "FAILED"
CANCELLED = "CANCELLED"
MANUAL_INTERVENTION_REQUIRED = "MANUAL_INTERVENTION_REQUIRED"
class CutoverStage(StrEnum):
PREPARING = "PREPARING"
LOCKING = "LOCKING"
SWITCHING = "SWITCHING"
VERIFYING = "VERIFYING"
COMMITTING = "COMMITTING"
COMMITTED = "COMMITTED"
ROLLING_BACK = "ROLLING_BACK"
ROLLED_BACK = "ROLLED_BACK"
FAILED = "FAILED"
class BatchState(StrEnum):
PLANNED = "PLANNED"
RUNNING = "RUNNING"
COMPLETED = "COMPLETED"
RETRYABLE_FAILED = "RETRYABLE_FAILED"
PERMANENTLY_FAILED = "PERMANENTLY_FAILED"
class MigrationFailureCode(StrEnum):
SOURCE_CHANGED = "SOURCE_CHANGED"
STALE_SOURCE = "STALE_SOURCE"
TARGET_CONFLICT = "TARGET_CONFLICT"
CAPABILITY_UNAVAILABLE = "CAPABILITY_UNAVAILABLE"
BATCH_FAILED = "BATCH_FAILED"
VECTOR_INVALID = "VECTOR_INVALID"
WRITE_FAILED = "WRITE_FAILED"
CHECKPOINT_FAILED = "CHECKPOINT_FAILED"
CONTENT_HASH_MISMATCH = "CONTENT_HASH_MISMATCH"
TARGET_SCHEMA_MISMATCH = "TARGET_SCHEMA_MISMATCH"
VALIDATION_FAILED = "VALIDATION_FAILED"
HEALTH_CHECK_FAILED = "HEALTH_CHECK_FAILED"
ROLLBACK_FAILED = "ROLLBACK_FAILED"
STALE_APPROVAL = "STALE_APPROVAL"
ALLOWED_TRANSITIONS: dict[MigrationState, frozenset[MigrationState]] = {
MigrationState.PLANNED: frozenset(
{MigrationState.PREFLIGHT, MigrationState.CANCELLED, MigrationState.FAILED}
),
MigrationState.PREFLIGHT: frozenset(
{MigrationState.READY, MigrationState.FAILED, MigrationState.CANCELLED}
),
MigrationState.READY: frozenset(
{MigrationState.BACKFILLING, MigrationState.CANCELLED, MigrationState.FAILED}
),
MigrationState.BACKFILLING: frozenset(
{
MigrationState.BACKFILL_PAUSED,
MigrationState.BACKFILL_COMPLETE,
MigrationState.CANCELLED,
MigrationState.FAILED,
}
),
MigrationState.BACKFILL_PAUSED: frozenset(
{MigrationState.BACKFILLING, MigrationState.CANCELLED, MigrationState.FAILED}
),
MigrationState.BACKFILL_COMPLETE: frozenset(
{MigrationState.VALIDATING, MigrationState.CANCELLED, MigrationState.FAILED}
),
MigrationState.VALIDATING: frozenset(
{
MigrationState.VALIDATION_FAILED,
MigrationState.READY_FOR_SHADOW,
MigrationState.FAILED,
MigrationState.CANCELLED,
}
),
MigrationState.VALIDATION_FAILED: frozenset(
{MigrationState.VALIDATING, MigrationState.CANCELLED, MigrationState.FAILED}
),
MigrationState.READY_FOR_SHADOW: frozenset(
{MigrationState.SHADOWING, MigrationState.CANCELLED, MigrationState.FAILED}
),
MigrationState.SHADOWING: frozenset(
{MigrationState.READY_FOR_CUTOVER, MigrationState.CANCELLED, MigrationState.FAILED}
),
MigrationState.READY_FOR_CUTOVER: frozenset(
{MigrationState.CUTOVER_PREPARING, MigrationState.CANCELLED, MigrationState.FAILED}
),
MigrationState.CUTOVER_PREPARING: frozenset(
{MigrationState.CUTTING_OVER, MigrationState.FAILED, MigrationState.CANCELLED}
),
MigrationState.CUTTING_OVER: frozenset(
{
MigrationState.VERIFYING_CUTOVER,
MigrationState.ROLLING_BACK,
MigrationState.MANUAL_INTERVENTION_REQUIRED,
}
),
MigrationState.VERIFYING_CUTOVER: frozenset(
{
MigrationState.CUTOVER_COMMITTED,
MigrationState.ROLLING_BACK,
MigrationState.MANUAL_INTERVENTION_REQUIRED,
}
),
MigrationState.CUTOVER_COMMITTED: frozenset({MigrationState.ROLLING_BACK}),
MigrationState.ROLLING_BACK: frozenset(
{MigrationState.ROLLED_BACK, MigrationState.MANUAL_INTERVENTION_REQUIRED}
),
MigrationState.ROLLED_BACK: frozenset(),
MigrationState.FAILED: frozenset(),
MigrationState.CANCELLED: frozenset(),
MigrationState.MANUAL_INTERVENTION_REQUIRED: frozenset(),
}
def assert_migration_transition(before: str, after: str) -> None:
current = MigrationState(before)
target = MigrationState(after)
if target not in ALLOWED_TRANSITIONS[current]:
raise ValueError(f"invalid migration transition {current.value} -> {target.value}")
class AdapterContract(MigrationModel):
key: str = Field(pattern=r"^[a-z0-9][a-z0-9._-]{1,127}$")
version: str = Field(min_length=1, max_length=64)
operations: frozenset[
Literal[
"preflight",
"create_shadow_target",
"enumerate_source_items",
"transform_batch",
"write_batch",
"validate_batch",
"finalize_backfill",
"validate_target",
"shadow_compare",
"cutover",
"rollback",
"inspect_external_state",
]
]
fingerprint: str = Field(pattern=r"^[0-9a-f]{64}$")
schema_operations: frozenset[str] = Field(default_factory=frozenset, max_length=32)
@field_validator("schema_operations")
@classmethod
def validate_schema_operations(cls, value: frozenset[str]) -> frozenset[str]:
if any(not re.fullmatch(r"[a-z0-9][a-z0-9._-]{1,127}", item) for item in value):
raise ValueError("schema operations must be typed adapter keys")
return value
class SchemaMigrationStep(MigrationModel):
operation: str = Field(min_length=1, max_length=64)
adapter_step: str = Field(default="manual-boundary", min_length=1, max_length=128)
preconditions: list[str] = Field(default_factory=list, max_length=32)
required_application_versions: dict[str, str] = Field(default_factory=dict)
compatibility_window: str = Field(default="unspecified", min_length=1, max_length=255)
rollback_feasible: bool = False
irreversible: bool = False
class MigrationPlanCreate(MigrationModel):
project_id: uuid.UUID
project_binding_id: uuid.UUID
capability_contract_id: uuid.UUID
migration_class: MigrationClass
environment: Literal["LAB", "PRODUCTION"]
adapter: AdapterContract
source_identity: dict[str, Any]
target_identity: dict[str, Any]
source_data_target: str = Field(min_length=1, max_length=512)
target_shadow_target: str = Field(min_length=1, max_length=512)
source_space_ref: str = Field(min_length=1, max_length=255)
target_space_id: uuid.UUID
corpus_revision: str = Field(min_length=1, max_length=128)
migration_policy_revision: str = Field(min_length=1, max_length=128)
validation_policy_revision_id: uuid.UUID
lifecycle_approval_id: uuid.UUID
promotion_plan_id: uuid.UUID | None = None
rollback_target_ref: str = Field(min_length=1, max_length=512)
total_expected_items: int = Field(ge=1, le=10_000_000)
batch_size: int = Field(default=32, ge=1, le=256)
max_in_flight_batches: int = Field(default=1, ge=1, le=8)
concurrency: int = Field(default=1, ge=1, le=8)
priority: Literal["BACKGROUND"] = "BACKGROUND"
target_storage: dict[str, Any]
shadow_policy: dict[str, Any]
cutover_policy: dict[str, Any]
rollback_retention_days: int = Field(default=30, ge=30, le=3650)
environment_fingerprint: str = Field(pattern=r"^[0-9a-f]{64}$")
idempotency_key: str = Field(min_length=8, max_length=128)
created_by: str = Field(min_length=1, max_length=255)
irreversible: bool = False
schema_steps: list[SchemaMigrationStep] = Field(default_factory=list, max_length=32)
@model_validator(mode="after")
def identities_are_isolated(self) -> MigrationPlanCreate:
if self.migration_class is MigrationClass.REQUIRES_REINDEX:
if self.source_data_target == self.target_shadow_target:
raise ValueError("reindex target must be separate from source")
if self.source_space_ref == str(self.target_space_id):
raise ValueError("reindex requires a distinct target embedding space")
if self.migration_class is MigrationClass.SCHEMA_BREAKING and not self.schema_steps:
raise ValueError("schema-breaking plans require typed adapter steps")
return self
class MigrationPlanResponse(MigrationPlanCreate):
id: uuid.UUID
state: MigrationState
version: int
generation: int
plan_fingerprint: str
approval_fingerprint: str
completed_items: int
failed_items: int
retryable_items: int
permanent_failed_items: int
last_cursor: str | None
cancel_requested: bool
failure_code: str | None
failure_details: dict[str, Any]
started_at: datetime | None
completed_at: datetime | None
immutable_at: datetime
created_at: datetime
updated_at: datetime
class MigrationValidationPolicyCreate(MigrationModel):
key: str = Field(min_length=1, max_length=128)
revision: int = Field(ge=1)
required_completeness: float = Field(default=1.0, ge=0, le=1)
allowed_failures: int = Field(default=0, ge=0)
required_evaluation: bool = True
critical_regressions_allowed: int = Field(default=0, ge=0)
maximum_latency_regression_ratio: float | None = Field(default=None, ge=0)
require_project_fit: bool = True
require_external_validation: bool = True
require_security_approved: bool = True
allow_isolated_lab_cutover: bool = False
created_by: str = Field(min_length=1, max_length=255)
class MigrationValidationPolicyResponse(MigrationValidationPolicyCreate):
id: uuid.UUID
fingerprint: str
active: bool
created_at: datetime
immutable_at: datetime
class PreflightReport(MigrationModel):
expected_version: int = Field(ge=1)
adapter_fingerprint: str = Field(pattern=r"^[0-9a-f]{64}$")
source_fingerprint: str = Field(pattern=r"^[0-9a-f]{64}$")
source_exists: bool
source_healthy: bool
source_count: int = Field(ge=0)
target_conflict_free: bool
target_space_valid: bool
capability_healthy: bool
project_credential_valid: bool
storage_sufficient: bool
scheduler_capacity: bool
adapter_available: bool
rollback_source_retained: bool
evaluation_suite_available: bool
lifecycle_approval_current: bool
evidence: dict[str, Any] = Field(default_factory=dict)
@property
def passed(self) -> bool:
return all(
(
self.source_exists,
self.source_healthy,
self.target_conflict_free,
self.target_space_valid,
self.capability_healthy,
self.project_credential_valid,
self.storage_sufficient,
self.scheduler_capacity,
self.adapter_available,
self.rollback_source_retained,
self.evaluation_suite_available,
self.lifecycle_approval_current,
)
)
class BatchReport(MigrationModel):
expected_version: int = Field(ge=1)
generation: int = Field(ge=1)
batch_number: int = Field(ge=0)
cursor_start: str = Field(min_length=1, max_length=255)
cursor_end: str = Field(min_length=1, max_length=255)
item_count: int = Field(ge=1, le=256)
completed_items: int = Field(ge=0, le=256)
failed_items: int = Field(ge=0, le=256)
retryable_items: int = Field(ge=0, le=256)
permanent_failed_items: int = Field(ge=0, le=256)
item_fingerprint: str = Field(pattern=r"^[0-9a-f]{64}$")
result_fingerprint: str = Field(pattern=r"^[0-9a-f]{64}$")
output_shape_valid: bool
finite: bool
target_space_matches: bool
destination_committed: bool
content_hashes_match: bool
duration_ms: float = Field(ge=0)
retries: int = Field(default=0, ge=0, le=20)
error_code: MigrationFailureCode | None = None
bounded_errors: list[dict[str, Any]] = Field(default_factory=list, max_length=20)
@model_validator(mode="after")
def totals_are_consistent(self) -> BatchReport:
if self.completed_items + self.failed_items != self.item_count:
raise ValueError("batch outcome count must equal item count")
if self.retryable_items + self.permanent_failed_items != self.failed_items:
raise ValueError("failed item taxonomy is incomplete")
return self
class StateAction(MigrationModel):
expected_version: int = Field(ge=1)
actor: str = Field(min_length=1, max_length=255)
reason: str = Field(min_length=1, max_length=2000)
class ValidationReport(MigrationModel):
expected_version: int = Field(ge=1)
generation: int = Field(ge=1)
expected_count: int = Field(ge=0)
actual_count: int = Field(ge=0)
missing_count: int = Field(ge=0)
duplicate_count: int = Field(ge=0)
malformed_count: int = Field(ge=0)
non_finite_count: int = Field(ge=0)
wrong_dimension_count: int = Field(ge=0)
content_hash_mismatch_count: int = Field(ge=0)
wrong_space_count: int = Field(ge=0)
index_schema_matches: bool
distance_metric_matches: bool
payload_integrity: bool
target_fingerprint: str = Field(pattern=r"^[0-9a-f]{64}$")
evaluation_run_ids: list[uuid.UUID] = Field(default_factory=list, max_length=32)
comparable: bool
critical_regressions: int = Field(ge=0)
latency_regression_ratio: float | None = Field(default=None, ge=0)
project_fit_eligible: bool
external_validation_satisfied: bool
security_approved: bool
evidence: dict[str, Any] = Field(default_factory=dict)
class ValidationSnapshotResponse(ValidationReport):
id: uuid.UUID
migration_plan_id: uuid.UUID
validation_policy_revision_id: uuid.UUID
snapshot_fingerprint: str
passed: bool
technical_cutover_eligible: bool
project_promotion_eligible: bool
blockers: list[str]
created_at: datetime
immutable_at: datetime
class ShadowReport(MigrationModel):
expected_version: int = Field(ge=1)
generation: int = Field(ge=1)
request_count: int = Field(ge=1)
source_error_count: int = Field(ge=0)
target_error_count: int = Field(ge=0)
source_latency_p95_ms: float = Field(ge=0)
target_latency_p95_ms: float = Field(ge=0)
critical_regressions: int = Field(ge=0)
metrics: dict[str, float]
evidence_refs: list[str] = Field(default_factory=list, max_length=64)
result_fingerprint: str = Field(pattern=r"^[0-9a-f]{64}$")
class CutoverPrepare(MigrationModel):
expected_version: int = Field(ge=1)
idempotency_key: str = Field(min_length=8, max_length=128)
actor: str = Field(min_length=1, max_length=255)
expected_external_source: str = Field(min_length=1, max_length=512)
observed_external_source: str = Field(min_length=1, max_length=512)
external_state_fingerprint: str = Field(pattern=r"^[0-9a-f]{64}$")
configuration_version: str = Field(min_length=1, max_length=128)
class CutoverReport(MigrationModel):
expected_version: int = Field(ge=1)
operation_id: uuid.UUID
generation: int = Field(ge=1)
external_source_before: str = Field(min_length=1, max_length=512)
external_target_after: str = Field(min_length=1, max_length=512)
external_state_fingerprint: str = Field(pattern=r"^[0-9a-f]{64}$")
switch_duration_ms: float = Field(ge=0)
target_reachable: bool
expected_identity: bool
capability_healthy: bool
project_read_path_healthy: bool
error_rate: float = Field(ge=0, le=1)
smoke_query_count: int = Field(ge=0, le=100)
smoke_error_count: int = Field(ge=0, le=100)
evidence: dict[str, Any] = Field(default_factory=dict)
class RollbackReport(MigrationModel):
expected_version: int = Field(ge=1)
operation_id: uuid.UUID
generation: int = Field(ge=1)
restored_external_target: str = Field(min_length=1, max_length=512)
external_state_fingerprint: str = Field(pattern=r"^[0-9a-f]{64}$")
elapsed_ms: float = Field(ge=0)
source_reachable: bool
exact_identity_restored: bool
capability_healthy: bool
project_read_path_healthy: bool
evidence: dict[str, Any] = Field(default_factory=dict)
class ReconciliationReport(MigrationModel):
operation_id: uuid.UUID
generation: int = Field(ge=1)
observed_external_target: str = Field(min_length=1, max_length=512)
external_state_fingerprint: str = Field(pattern=r"^[0-9a-f]{64}$")
target_healthy: bool
source_healthy: bool
switch_duration_ms: float | None = Field(default=None, ge=0)
smoke_query_count: int = Field(default=0, ge=0, le=100)
smoke_error_count: int = Field(default=0, ge=0, le=100)
evidence: dict[str, Any] = Field(default_factory=dict)
actor: Literal["migration-reconciler"] = "migration-reconciler"
@model_validator(mode="after")
def smoke_totals_are_consistent(self) -> ReconciliationReport:
if self.smoke_error_count > self.smoke_query_count:
raise ValueError("smoke errors cannot exceed smoke queries")
return self
class MigrationBatchResponse(MigrationModel):
id: uuid.UUID
migration_plan_id: uuid.UUID
batch_number: int
generation: int
cursor_start: str
cursor_end: str
item_count: int
completed_items: int
failed_items: int
retryable_items: int
permanent_failed_items: int
item_fingerprint: str
result_fingerprint: str | None
status: BatchState
attempts: int
duration_ms: float | None
error_code: str | None
bounded_errors: list[dict[str, Any]]
started_at: datetime | None
completed_at: datetime | None
class CutoverOperationResponse(MigrationModel):
id: uuid.UUID
migration_plan_id: uuid.UUID
stage: CutoverStage
idempotency_key: str
generation: int
expected_plan_version: int
source_before: str
target_after: str
external_state_fingerprint: str
health_evidence: dict[str, Any]
failure_code: str | None
failure_details: dict[str, Any]
switch_duration_ms: float | None
rollback_duration_ms: float | None
started_at: datetime
finished_at: datetime | None
class MigrationEventResponse(MigrationModel):
id: uuid.UUID
migration_plan_id: uuid.UUID
operation_id: uuid.UUID | None
event_type: str
before_state: str | None
after_state: str | None
actor: str
policy_revision: str
evidence_refs: list[str]
reason: str
source_identity: dict[str, Any]
target_identity: dict[str, Any]
generation: int
change_id: str
details: dict[str, Any]
occurred_at: datetime
@@ -0,0 +1,60 @@
from __future__ import annotations
import uuid
from datetime import datetime
from pydantic import BaseModel, ConfigDict, Field
class NodeDecommissionModel(BaseModel):
model_config = ConfigDict(extra="forbid")
class DecommissionBlocker(NodeDecommissionModel):
code: str
message: str
record_type: str
count: int = Field(ge=1)
resource_ids: list[str] = Field(default_factory=list)
class DecommissionRecordCount(NodeDecommissionModel):
record_type: str
count: int = Field(ge=0)
action: str
class NodeDecommissionPreview(NodeDecommissionModel):
node_id: uuid.UUID
persisted_identity: str
hostname: str
display_name: str
current_state: dict[str, object]
node_generation: int = Field(ge=1)
dependency_digest: str = Field(pattern=r"^[0-9a-f]{64}$")
safe: bool
blockers: list[DecommissionBlocker]
cleanup: list[DecommissionRecordCount]
preserved: list[DecommissionRecordCount]
dependent_records: list[DecommissionRecordCount]
class NodeDecommissionExecute(NodeDecommissionModel):
expected_generation: int = Field(ge=1)
preview_digest: str = Field(pattern=r"^[0-9a-f]{64}$")
idempotency_key: str = Field(min_length=8, max_length=128)
operator: str = Field(min_length=1, max_length=255)
reason: str = Field(min_length=10, max_length=2000)
confirmation: str = Field(min_length=1, max_length=255)
class NodeDecommissionResult(NodeDecommissionModel):
operation_id: uuid.UUID
node_id: uuid.UUID
persisted_identity: str
status: str
decommissioned_at: datetime
cleanup_summary: dict[str, int]
previous_state: dict[str, object]
credential_revocations: int = Field(ge=0)
idempotent_replay: bool = False
@@ -0,0 +1,399 @@
from __future__ import annotations
import re
import threading
import time
import uuid
from collections import defaultdict
from datetime import datetime
from enum import StrEnum
from typing import Any, Literal
from pydantic import BaseModel, ConfigDict, Field, field_validator, model_validator
class TelemetryType(StrEnum):
COUNTER = "COUNTER"
GAUGE = "GAUGE"
HISTOGRAM = "HISTOGRAM"
EVENT = "EVENT"
STATE = "STATE"
class SLOState(StrEnum):
HEALTHY = "HEALTHY"
AT_RISK = "AT_RISK"
BREACHED = "BREACHED"
INSUFFICIENT_DATA = "INSUFFICIENT_DATA"
STALE = "STALE"
DISABLED = "DISABLED"
class AlertState(StrEnum):
PENDING = "PENDING"
FIRING = "FIRING"
ACKNOWLEDGED = "ACKNOWLEDGED"
RESOLVED = "RESOLVED"
SUPPRESSED = "SUPPRESSED"
class AlertSeverity(StrEnum):
INFO = "INFO"
WARNING = "WARNING"
CRITICAL = "CRITICAL"
class OperationalModel(BaseModel):
model_config = ConfigDict(extra="forbid", from_attributes=True)
class MetricDefinition(OperationalModel):
name: str = Field(pattern=r"^modelforge_[a-z][a-z0-9_]*$")
type: TelemetryType
help: str = Field(min_length=3, max_length=500)
labels: tuple[str, ...] = ()
@field_validator("labels")
@classmethod
def bounded_labels(cls, value: tuple[str, ...]) -> tuple[str, ...]:
forbidden = {
"request_id",
"artifact_sha",
"query",
"project_uuid",
"filename",
"error_text",
}
if len(value) > 6 or len(set(value)) != len(value) or forbidden.intersection(value):
raise ValueError("metric labels must be unique, bounded and non-sensitive")
if any(not re.fullmatch(r"[a-z][a-z0-9_]*", item) for item in value):
raise ValueError("metric label names must use snake_case")
return value
class MetricRegistry:
"""Process-local Prometheus registry; DB history is deliberately a separate concern."""
def __init__(self) -> None:
self._definitions: dict[str, MetricDefinition] = {}
self._values: dict[tuple[str, tuple[tuple[str, str], ...]], float] = defaultdict(float)
self._histograms: dict[tuple[str, tuple[tuple[str, str], ...]], list[float]] = defaultdict(
list
)
self._lock = threading.Lock()
self.started_at = time.time()
def define(self, definition: MetricDefinition) -> None:
current = self._definitions.get(definition.name)
if current and current != definition:
raise ValueError(f"metric {definition.name} is already defined differently")
self._definitions[definition.name] = definition
def _key(self, name: str, labels: dict[str, str]) -> tuple[str, tuple[tuple[str, str], ...]]:
definition = self._definitions[name]
if set(labels) != set(definition.labels):
raise ValueError(f"metric {name} requires labels {definition.labels}")
if any(len(value) > 128 or "\n" in value for value in labels.values()):
raise ValueError("metric label values must be bounded single-line values")
return name, tuple(sorted(labels.items()))
def increment(self, name: str, labels: dict[str, str], value: float = 1.0) -> None:
if self._definitions[name].type is not TelemetryType.COUNTER or value < 0:
raise ValueError("only counters accept non-negative increments")
with self._lock:
self._values[self._key(name, labels)] += value
def gauge(self, name: str, labels: dict[str, str], value: float) -> None:
if self._definitions[name].type is not TelemetryType.GAUGE:
raise ValueError("only gauges accept current values")
with self._lock:
self._values[self._key(name, labels)] = value
def observe(self, name: str, labels: dict[str, str], value: float) -> None:
if self._definitions[name].type is not TelemetryType.HISTOGRAM or value < 0:
raise ValueError("only histograms accept non-negative observations")
with self._lock:
samples = self._histograms[self._key(name, labels)]
samples.append(value)
if len(samples) > 10_000:
del samples[: len(samples) - 10_000]
def histogram(self, name: str, labels: dict[str, str]) -> list[float]:
with self._lock:
return list(self._histograms.get(self._key(name, labels), []))
def samples(self, name: str) -> list[tuple[dict[str, str], float]]:
with self._lock:
return [
(dict(labels), value)
for (metric, labels), value in self._values.items()
if metric == name
]
@staticmethod
def _label_text(labels: tuple[tuple[str, str], ...]) -> str:
if not labels:
return ""
escaped = [f'{key}="{value.replace(chr(92), chr(92) * 2).replace(chr(34), chr(92) + chr(34))}"' for key, value in labels]
return "{" + ",".join(escaped) + "}"
def render(self) -> str:
buckets = (0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0)
lines: list[str] = []
with self._lock:
for name, definition in sorted(self._definitions.items()):
lines.extend((f"# HELP {name} {definition.help}", f"# TYPE {name} {definition.type.value.lower()}"))
for (metric, labels), value in sorted(self._values.items()):
if metric == name:
lines.append(f"{name}{self._label_text(labels)} {value}")
for (metric, labels), values in sorted(self._histograms.items()):
if metric != name:
continue
label_text = self._label_text(labels)
for upper_bound in buckets:
bucket_labels = tuple(sorted((*labels, ("le", str(upper_bound)))))
count = sum(value <= upper_bound for value in values)
lines.append(f"{name}_bucket{self._label_text(bucket_labels)} {count}")
infinite_labels = tuple(sorted((*labels, ("le", "+Inf"))))
lines.append(
f"{name}_bucket{self._label_text(infinite_labels)} {len(values)}"
)
lines.append(f"{name}_count{label_text} {len(values)}")
lines.append(f"{name}_sum{label_text} {sum(values)}")
lines.append(f"modelforge_process_uptime_seconds {max(0.0, time.time() - self.started_at)}")
return "\n".join(lines) + "\n"
metrics = MetricRegistry()
for _definition in (
MetricDefinition(name="modelforge_api_requests_total", type=TelemetryType.COUNTER, help="Bounded API request outcomes", labels=("method", "route_class", "status_class")),
MetricDefinition(name="modelforge_api_request_duration_seconds", type=TelemetryType.HISTOGRAM, help="API request duration", labels=("method", "route_class")),
MetricDefinition(name="modelforge_observability_degraded", type=TelemetryType.GAUGE, help="Historical observability persistence is degraded", labels=()),
):
metrics.define(_definition)
class SLIDefinitionCreate(OperationalModel):
key: str = Field(pattern=r"^[a-z][a-z0-9_.-]{2,127}$")
name: str = Field(min_length=3, max_length=255)
service: str = Field(min_length=2, max_length=128)
capability: str | None = Field(default=None, max_length=128)
measurement: Literal["SUCCESS_RATIO", "LATENCY_P95", "FRESHNESS", "CORRECTNESS_RATIO"]
valid_population: dict[str, Any]
success_condition: dict[str, Any]
default_window_seconds: int = Field(ge=60, le=2_592_000)
class SLOPolicyCreate(OperationalModel):
key: str = Field(pattern=r"^[a-z][a-z0-9_.-]{2,127}$")
sli_definition_id: uuid.UUID
objective: float = Field(gt=0, le=1)
threshold_ms: float | None = Field(default=None, gt=0)
rolling_window_seconds: int = Field(ge=60, le=2_592_000)
minimum_sample_count: int = Field(ge=1, le=1_000_000)
severity: AlertSeverity
environment: Literal["PRODUCTION", "LAB", "BACKGROUND"]
effective_from: datetime
rationale: str = Field(min_length=10, max_length=4000)
created_by: str = Field(default="operator", min_length=1, max_length=255)
class AlertRuleCreate(OperationalModel):
key: str = Field(pattern=r"^[a-z][a-z0-9_.-]{2,127}$")
alert_type: str = Field(pattern=r"^[A-Z][A-Z0-9_]{2,63}$")
signal: str = Field(min_length=3, max_length=128)
slo_policy_id: uuid.UUID | None = None
condition: dict[str, Any]
pending_seconds: int = Field(ge=0, le=86_400)
severity: AlertSeverity
labels: dict[str, str] = Field(default_factory=dict)
cooldown_seconds: int = Field(ge=0, le=604_800)
recovery_condition: dict[str, Any]
created_by: str = Field(default="operator", min_length=1, max_length=255)
@field_validator("labels")
@classmethod
def validate_labels(cls, value: dict[str, str]) -> dict[str, str]:
if len(value) > 8 or any(len(key) > 64 or len(item) > 128 for key, item in value.items()):
raise ValueError("alert labels must be bounded")
return value
class MaintenanceWindowCreate(OperationalModel):
name: str = Field(min_length=3, max_length=255)
starts_at: datetime
ends_at: datetime
matcher: dict[str, str]
reason: str = Field(min_length=5, max_length=2000)
created_by: str = Field(default="operator", min_length=1, max_length=255)
@model_validator(mode="after")
def ordered(self) -> MaintenanceWindowCreate:
if self.ends_at <= self.starts_at:
raise ValueError("maintenance window end must follow start")
return self
class AlertAction(OperationalModel):
actor: str = Field(min_length=1, max_length=255)
reason: str = Field(min_length=3, max_length=2000)
class SLIDefinitionResponse(SLIDefinitionCreate):
id: uuid.UUID
enabled: bool
created_at: datetime
class SLOPolicyResponse(SLOPolicyCreate):
id: uuid.UUID
revision: int
active: bool
fingerprint: str
created_at: datetime
class AlertRuleResponse(AlertRuleCreate):
id: uuid.UUID
revision: int
active: bool
fingerprint: str
created_at: datetime
class MaintenanceWindowResponse(MaintenanceWindowCreate):
id: uuid.UUID
active: bool
created_at: datetime
class AlertHistoryResponse(OperationalModel):
id: uuid.UUID
alert_id: uuid.UUID
from_state: str | None
to_state: str
actor: str
reason: str
evidence: dict[str, Any]
occurred_at: datetime
class IncidentResponse(OperationalModel):
id: uuid.UUID
fingerprint: str
title: str
state: str
severity: str
root_subject_type: str
root_subject_ref: str
correlation: Literal["RELATED", "LIKELY_ROOT", "DOWNSTREAM", "UNKNOWN"]
first_seen_at: datetime
last_seen_at: datetime
resolved_at: datetime | None
class SLOEvaluationResponse(OperationalModel):
id: uuid.UUID
policy_id: uuid.UUID
policy_key: str
policy_revision: int
sli_key: str
environment: str
objective: float
observed_value: float | None
threshold_ms: float | None
sample_count: int
good_count: int
bad_count: int
state: SLOState
window_start: datetime
window_end: datetime
observed_at: datetime
freshness_seconds: float | None
allowed_bad: float | None
consumed_bad: int | None
remaining_bad: float | None
short_burn_rate: float | None
long_burn_rate: float | None
evidence: dict[str, Any]
class AlertResponse(OperationalModel):
id: uuid.UUID
rule_id: uuid.UUID
fingerprint: str
alert_type: str
severity: str
state: AlertState
source: str
subject_type: str
subject_ref: str
summary: str
details: dict[str, Any]
first_seen_at: datetime
last_seen_at: datetime
firing_at: datetime | None
acknowledged_at: datetime | None
acknowledged_by: str | None
resolved_at: datetime | None
suppressed_until: datetime | None
occurrence_count: int
class CapacitySnapshotResponse(OperationalModel):
id: uuid.UUID
observed_at: datetime
received_at: datetime
node_id: uuid.UUID
node_name: str
accelerator_id: uuid.UUID | None
gpu_total_bytes: int | None
gpu_observed_bytes: int | None
gpu_external_bytes: int | None
gpu_managed_resident_bytes: int | None
gpu_leased_bytes: int | None
gpu_reserve_bytes: int | None
gpu_schedulable_bytes: int | None
pressure_state: str
system_ram_total_bytes: int | None
system_ram_available_bytes: int | None
storage_total_bytes: int | None
storage_free_bytes: int | None
availability: str
freshness_seconds: float
class TrendResponse(OperationalModel):
subject: str
sample_count: int
period_start: datetime | None
period_end: datetime | None
status: Literal["AVAILABLE", "INSUFFICIENT_DATA", "STALE"]
metrics: dict[str, float | int | None]
forecast: dict[str, Any]
class OperationsOverview(OperationalModel):
status: Literal["HEALTHY", "DEGRADED", "OBSERVABILITY_DEGRADED"]
observed_at: datetime
active_alerts: list[AlertResponse]
slo_evaluations: list[SLOEvaluationResponse]
capacity: list[CapacitySnapshotResponse]
capability_health: list[dict[str, Any]]
project_health: list[dict[str, Any]]
recent_failures: list[dict[str, Any]]
history_available: bool
def route_class(path: str) -> str:
if path.startswith("/api/v1/capabilities/") or path == "/v1/embeddings":
return "gateway"
if path.startswith("/api/v1/agent/"):
return "agent"
if path.startswith("/api/v1/admin/"):
return "admin"
if path.startswith("/api/v1/health/"):
return "health"
if path == "/metrics":
return "metrics"
return "control_plane"
@@ -0,0 +1,471 @@
"""M15 backup, restore and disaster-recovery contracts.
Recovery is a first-class control-plane concern: what ModelForge owns authoritatively,
what it can rebuild from an exact upstream identity, what is deliberately discarded and
what is never exportable are separate classifications with separate guarantees.
"""
from __future__ import annotations
import re
import uuid
from datetime import datetime
from enum import StrEnum
from typing import Any, Literal
from pydantic import BaseModel, ConfigDict, Field, field_validator, model_validator
BACKUP_ID_PATTERN = r"^[a-z0-9][a-z0-9-]{6,62}$"
SHA256_PATTERN = r"^[0-9a-f]{64}$"
# ModelForge stores verified logical snapshots. WAL archiving and continuous point-in-time
# recovery are deliberately out of scope for v1; the value is exported so operators and the
# recovery dashboard never have to infer it.
POINT_IN_TIME_SUPPORT: Literal["SUPPORTED", "NOT_SUPPORTED"] = "NOT_SUPPORTED"
class RecoveryAssetClass(StrEnum):
"""Top-level classification that decides whether state must be copied at all."""
AUTHORITATIVE = "AUTHORITATIVE"
REBUILDABLE = "REBUILDABLE"
EPHEMERAL = "EPHEMERAL"
EXTERNAL = "EXTERNAL"
SECRET = "SECRET" # noqa: S105 - asset classification, not a credential
class SecretRecoveryClass(StrEnum):
RESTORABLE_SECRET = "RESTORABLE_SECRET" # noqa: S105 - asset classification, not a credential
ROTATABLE_SECRET = "ROTATABLE_SECRET" # noqa: S105 - asset classification, not a credential
NON_EXPORTABLE_SECRET = "NON_EXPORTABLE_SECRET" # noqa: S105 - asset classification, not a credential
class ArtifactRecoveryClass(StrEnum):
REHYDRATABLE = "REHYDRATABLE"
NON_REHYDRATABLE = "NON_REHYDRATABLE"
DERIVED = "DERIVED"
LOCAL_ONLY = "LOCAL_ONLY"
class ConfigurationClass(StrEnum):
SOURCE_CONTROLLED = "SOURCE_CONTROLLED"
SECRET = "SECRET" # noqa: S105 - asset classification, not a credential
GENERATED = "GENERATED"
HOST_LOCAL = "HOST_LOCAL"
class BackupState(StrEnum):
PLANNED = "PLANNED"
CREATING = "CREATING"
CREATED = "CREATED"
VERIFYING = "VERIFYING"
VERIFIED = "VERIFIED"
FAILED = "FAILED"
EXPIRED = "EXPIRED"
DELETED = "DELETED"
RESTORE_ELIGIBLE_STATES = frozenset({BackupState.VERIFIED})
BACKUP_TERMINAL_STATES = frozenset({BackupState.FAILED, BackupState.EXPIRED, BackupState.DELETED})
class BackupMethod(StrEnum):
POSTGRES_LOGICAL_CUSTOM = "POSTGRES_LOGICAL_CUSTOM"
MANIFEST_ONLY = "MANIFEST_ONLY"
FILE_COPY = "FILE_COPY"
SOURCE_CONTROL_REFERENCE = "SOURCE_CONTROL_REFERENCE"
NOT_BACKED_UP = "NOT_BACKED_UP"
class RestoreMode(StrEnum):
"""VALIDATION never touches an active target; the destructive modes are operator-gated."""
VALIDATION = "VALIDATION"
REPLACEMENT = "REPLACEMENT"
DISASTER_RECOVERY = "DISASTER_RECOVERY"
class RestorePlanState(StrEnum):
DRAFT = "DRAFT"
PREFLIGHT_PASSED = "PREFLIGHT_PASSED"
PREFLIGHT_FAILED = "PREFLIGHT_FAILED"
CONSUMED = "CONSUMED"
class RestoreState(StrEnum):
PLANNED = "PLANNED"
PREFLIGHT = "PREFLIGHT"
RESTORING_DATABASE = "RESTORING_DATABASE"
RESTORING_CONFIGURATION = "RESTORING_CONFIGURATION"
REHYDRATING_ARTIFACTS = "REHYDRATING_ARTIFACTS"
RECONCILING = "RECONCILING"
VALIDATING = "VALIDATING"
READY = "READY"
FAILED = "FAILED"
MANUAL_INTERVENTION_REQUIRED = "MANUAL_INTERVENTION_REQUIRED"
RESTORE_TERMINAL_STATES = frozenset(
{RestoreState.READY, RestoreState.FAILED, RestoreState.MANUAL_INTERVENTION_REQUIRED}
)
RESTORE_PHASE_ORDER: tuple[RestoreState, ...] = (
RestoreState.PREFLIGHT,
RestoreState.RESTORING_DATABASE,
RestoreState.RESTORING_CONFIGURATION,
RestoreState.REHYDRATING_ARTIFACTS,
RestoreState.RECONCILING,
RestoreState.VALIDATING,
RestoreState.READY,
)
class ArtifactRecoveryState(StrEnum):
PLANNED = "PLANNED"
REHYDRATING = "REHYDRATING"
VERIFYING = "VERIFYING"
RECOVERED = "RECOVERED"
BLOCKED = "BLOCKED"
FAILED = "FAILED"
class RecoveryReadiness(StrEnum):
PROTECTED = "PROTECTED"
REHYDRATABLE = "REHYDRATABLE"
ROTATION_REQUIRED = "ROTATION_REQUIRED"
EXTERNAL_DEPENDENCY = "EXTERNAL_DEPENDENCY"
UNPROTECTED = "UNPROTECTED"
class RecoveryFailureCode(StrEnum):
HASH_MISMATCH = "HASH_MISMATCH"
MANIFEST_HASH_MISMATCH = "MANIFEST_HASH_MISMATCH"
MANIFEST_INCOMPLETE = "MANIFEST_INCOMPLETE"
PAYLOAD_MISSING = "PAYLOAD_MISSING"
BACKUP_NOT_RESTORE_ELIGIBLE = "BACKUP_NOT_RESTORE_ELIGIBLE"
ENCRYPTION_KEY_UNAVAILABLE = "ENCRYPTION_KEY_UNAVAILABLE"
DECRYPTION_FAILED = "DECRYPTION_FAILED"
SCHEMA_TOO_NEW = "SCHEMA_TOO_NEW"
SCHEMA_UNKNOWN = "SCHEMA_UNKNOWN"
POSTGRES_VERSION_INCOMPATIBLE = "POSTGRES_VERSION_INCOMPATIBLE"
DESTINATION_NOT_ISOLATED = "DESTINATION_NOT_ISOLATED"
DESTINATION_NOT_EMPTY = "DESTINATION_NOT_EMPTY"
INSUFFICIENT_CAPACITY = "INSUFFICIENT_CAPACITY"
DESTINATION_UNAVAILABLE = "DESTINATION_UNAVAILABLE"
BACKUP_TOOL_UNAVAILABLE = "BACKUP_TOOL_UNAVAILABLE"
BACKUP_TOOL_FAILED = "BACKUP_TOOL_FAILED"
CONCURRENT_OPERATION = "CONCURRENT_OPERATION"
PATH_NOT_ALLOWED = "PATH_NOT_ALLOWED"
ARCHIVE_UNSAFE = "ARCHIVE_UNSAFE"
ARTIFACT_REHYDRATION_BLOCKED = "ARTIFACT_REHYDRATION_BLOCKED"
ARTIFACT_HASH_MISMATCH = "ARTIFACT_HASH_MISMATCH"
ARTIFACT_NOT_REHYDRATABLE = "ARTIFACT_NOT_REHYDRATABLE"
AUDIT_CHAIN_CORRUPT = "AUDIT_CHAIN_CORRUPT"
RECOVERY_RECONCILIATION_REQUIRED = "RECOVERY_RECONCILIATION_REQUIRED"
SOURCE_CONTROL_REVISION_UNAVAILABLE = "SOURCE_CONTROL_REVISION_UNAVAILABLE"
class RecoveryModel(BaseModel):
model_config = ConfigDict(extra="forbid", from_attributes=True)
class RecoveryPolicyCreate(RecoveryModel):
"""A versioned recovery contract per asset class; never scattered inline rules."""
key: str = Field(pattern=r"^[a-z][a-z0-9_.-]{2,127}$")
name: str = Field(min_length=3, max_length=255)
asset_class: RecoveryAssetClass
backup_method: BackupMethod
retention_days: int = Field(ge=1, le=3650)
minimum_verified_backups: int = Field(ge=1, le=100)
rpo_seconds: int | None = Field(default=None, ge=0, le=2_592_000)
rto_target_seconds: int | None = Field(default=None, ge=0, le=2_592_000)
restore_verification: Literal["FULL_RESTORE", "HASH_ONLY", "MANIFEST_ONLY", "NOT_APPLICABLE"]
encryption_required: bool
external_dependency: bool = False
rehydration_allowed: bool = False
secret_class: SecretRecoveryClass | None = None
rationale: str = Field(min_length=10, max_length=4000)
created_by: str = Field(default="operator", min_length=1, max_length=255)
@model_validator(mode="after")
def coherent(self) -> RecoveryPolicyCreate:
if self.asset_class is RecoveryAssetClass.AUTHORITATIVE:
if self.backup_method in {BackupMethod.NOT_BACKED_UP, BackupMethod.MANIFEST_ONLY}:
raise ValueError("authoritative state requires a payload-bearing backup method")
if self.rpo_seconds is None:
raise ValueError("authoritative state requires an explicit RPO target")
if self.asset_class is RecoveryAssetClass.EPHEMERAL and self.backup_method not in {
BackupMethod.NOT_BACKED_UP,
BackupMethod.MANIFEST_ONLY,
}:
raise ValueError("ephemeral state must not claim a payload backup")
if self.asset_class is RecoveryAssetClass.EXTERNAL and not self.external_dependency:
raise ValueError("external state must be marked as an external dependency")
if self.asset_class is RecoveryAssetClass.SECRET and self.secret_class is None:
raise ValueError("secret state requires an explicit secret recovery class")
if self.rehydration_allowed and self.asset_class is not RecoveryAssetClass.REBUILDABLE:
raise ValueError("only rebuildable state may be rehydrated instead of copied")
return self
class RecoveryPolicyResponse(RecoveryPolicyCreate):
id: uuid.UUID
revision: int
active: bool
fingerprint: str
created_at: datetime
class RecoveryAssetResponse(RecoveryModel):
id: uuid.UUID
key: str
name: str
asset_class: RecoveryAssetClass
owner: str
location: str
backup_method: BackupMethod
restore_method: str
rebuild_method: str | None
rpo_seconds: int | None
readiness: RecoveryReadiness
dependencies: list[str]
notes: str
policy_key: str
updated_at: datetime
class BackupSetCreate(RecoveryModel):
backup_id: str = Field(pattern=BACKUP_ID_PATTERN)
reason: str = Field(min_length=5, max_length=2000)
milestone: str | None = Field(default=None, max_length=64)
legal_hold: bool = False
include_artifact_manifest: bool = True
created_by: str = Field(default="operator", min_length=1, max_length=255)
class BackupManifestEntryResponse(RecoveryModel):
id: uuid.UUID
logical_asset_type: str
object_name: str
relative_path: str
size_bytes: int
sha256: str
source_generation: str
schema_version: str | None
dependency_refs: dict[str, Any]
class BackupSetResponse(RecoveryModel):
id: uuid.UUID
backup_id: str
state: BackupState
policy_key: str
policy_revision: int
modelforge_version: str
modelforge_commit: str | None
schema_revision: str | None
environment_fingerprint: dict[str, Any]
database_identity: dict[str, Any]
destination_root: str
manifest_relative_path: str | None
manifest_sha256: str | None
included_asset_classes: list[str]
excluded_asset_classes: list[str]
payload_bytes: int
encrypted: bool
encryption_algorithm: str | None
encryption_key_id: str | None
verification_details: dict[str, Any]
verified_at: datetime | None
failure_code: str | None
failure_reason: str | None
milestone: str | None
legal_hold: bool
restore_eligible: bool
reason: str
created_by: str
started_at: datetime | None
completed_at: datetime | None
expires_at: datetime | None
created_at: datetime
entries: list[BackupManifestEntryResponse] = Field(default_factory=list)
class RestorePlanCreate(RecoveryModel):
backup_set_id: uuid.UUID
mode: RestoreMode
target_environment: Literal["ISOLATED", "STAGING", "PRODUCTION"]
target_label: str = Field(min_length=3, max_length=128)
database_destination: str = Field(min_length=8, max_length=1024)
artifact_strategy: Literal["NONE", "MANIFEST_ONLY", "REHYDRATE_MISSING", "RESTORE_LOCAL"]
secret_strategy: Literal["ROTATE", "RESTORE_HASHES", "MANUAL"]
node_strategy: Literal["REUSE_CREDENTIAL", "RE_ENROLL", "NONE"]
expected_modelforge_version: str | None = Field(default=None, max_length=64)
validation_requirements: dict[str, Any] = Field(default_factory=dict)
reason: str = Field(min_length=10, max_length=4000)
created_by: str = Field(default="operator", min_length=1, max_length=255)
@field_validator("database_destination")
@classmethod
def supported_destination(cls, value: str) -> str:
if not value.startswith("postgresql+psycopg://"):
raise ValueError("restore destinations must be PostgreSQL SQLAlchemy URLs")
return value
@model_validator(mode="after")
def guarded(self) -> RestorePlanCreate:
if self.mode is RestoreMode.VALIDATION and self.target_environment == "PRODUCTION":
raise ValueError("validation restores must never target a production environment")
if self.target_environment == "PRODUCTION" and self.mode is not RestoreMode.DISASTER_RECOVERY:
raise ValueError("production targets require an explicit disaster-recovery restore")
return self
class RestorePlanResponse(RecoveryModel):
id: uuid.UUID
backup_set_id: uuid.UUID
backup_id: str
mode: RestoreMode
state: RestorePlanState
target_environment: str
target_label: str
database_destination_redacted: str
artifact_strategy: str
secret_strategy: str
node_strategy: str
expected_modelforge_version: str | None
preflight: dict[str, Any]
validation_requirements: dict[str, Any]
fingerprint: str
reason: str
created_by: str
created_at: datetime
class RestoreOperationResponse(RecoveryModel):
id: uuid.UUID
plan_id: uuid.UUID
backup_set_id: uuid.UUID
backup_id: str
state: RestoreState
mode: RestoreMode
attempt: int
idempotency_key: str
preflight_result: dict[str, Any]
phase_durations: dict[str, float]
source_fingerprint: dict[str, Any]
restored_fingerprint: dict[str, Any]
fingerprint_diff: dict[str, Any]
validation_result: dict[str, Any]
rpo_seconds: float | None
rto_seconds: float | None
failure_code: str | None
failure_reason: str | None
started_at: datetime
updated_at: datetime
ready_at: datetime | None
class RestoreOperationEventResponse(RecoveryModel):
id: uuid.UUID
restore_operation_id: uuid.UUID
from_state: str | None
to_state: str
phase: str
actor: str
reason: str
evidence: dict[str, Any]
occurred_at: datetime
class RestoreAdvanceRequest(RecoveryModel):
actor: str = Field(default="operator", min_length=1, max_length=255)
reason: str = Field(min_length=5, max_length=2000)
stop_after: RestoreState | None = None
simulate_interruption_after: RestoreState | None = None
class ArtifactRecoveryCreate(RecoveryModel):
artifact_set_id: uuid.UUID
target_storage_root_id: uuid.UUID
reason: str = Field(min_length=10, max_length=2000)
restore_operation_id: uuid.UUID | None = None
created_by: str = Field(default="operator", min_length=1, max_length=255)
class ArtifactRecoveryResponse(RecoveryModel):
id: uuid.UUID
restore_operation_id: uuid.UUID | None
artifact_set_id: uuid.UUID
model_revision_id: uuid.UUID
recovery_class: ArtifactRecoveryClass
state: ArtifactRecoveryState
upstream_repository: str | None
upstream_commit_sha: str | None
target_storage_root_id: uuid.UUID
expected_files: list[dict[str, Any]]
verified_files: list[dict[str, Any]]
bytes_total: int
bytes_recovered: int
download_plan_id: uuid.UUID | None
artifact_job_id: uuid.UUID | None
lineage: dict[str, Any]
duration_seconds: float | None
failure_code: str | None
failure_reason: str | None
started_at: datetime
completed_at: datetime | None
class RecoveryReadinessEntry(RecoveryModel):
asset_key: str
asset_name: str
asset_class: RecoveryAssetClass
readiness: RecoveryReadiness
policy_key: str
rpo_seconds: int | None
detail: str
class RecoveryDashboard(RecoveryModel):
"""Every field is measured; unknown values stay null instead of becoming a fiction."""
observed_at: datetime
point_in_time_support: Literal["SUPPORTED", "NOT_SUPPORTED"]
latest_verified_backup_id: str | None
latest_verified_backup_at: datetime | None
latest_verified_backup_age_seconds: float | None
latest_verified_schema_revision: str | None
backup_states: dict[str, int]
verified_backup_count: int
stale_backup: bool
backup_staleness_threshold_seconds: int
last_restore_rehearsal_at: datetime | None
last_restore_rehearsal_state: str | None
observed_restore_seconds: float | None
observed_rpo_seconds: float | None
protected_asset_count: int
unprotected_assets: list[str]
readiness: list[RecoveryReadinessEntry]
coverage_ratio: float
estimated_protected_bytes: int
estimated_rehydratable_bytes: int
destination_capacity_bytes: int | None
destination_free_bytes: int | None
class BackupCapacityEstimate(RecoveryModel):
bytes_to_copy: int
bytes_manifest_only: int
estimated_protected_bytes: int
available_bytes: int | None
capacity_bytes: int | None
sufficient: bool
detail: str
def redact_database_url(url: str) -> str:
"""Never journal or return a restore DSN that still carries its password."""
return re.sub(r"://([^:/@]+):[^@]*@", r"://\1:***@", url)
@@ -0,0 +1,239 @@
from __future__ import annotations
import uuid
from datetime import datetime
from typing import Any, Literal
from pydantic import BaseModel, ConfigDict, Field, field_validator
from modelforge_api.domain.enums import ArtifactStatus, ModelLifecycle, StorageRootStatus
class RegistryModel(BaseModel):
model_config = ConfigDict(extra="forbid", from_attributes=True)
class Page[Item](RegistryModel):
items: list[Item]
page: int
page_size: int
total: int
pages: int
class ModelCreate(RegistryModel):
key: str = Field(min_length=1, max_length=128, pattern=r"^[a-z0-9][a-z0-9._-]*$")
display_name: str = Field(min_length=1, max_length=255)
description: str | None = None
source_type: Literal["huggingface", "local", "custom"] = "huggingface"
upstream_provider: str = Field(min_length=1, max_length=64)
upstream_source: str = Field(min_length=1, max_length=255)
upstream_metadata: dict[str, Any] = Field(default_factory=dict)
local_metadata: dict[str, Any] = Field(default_factory=dict)
interpretation_metadata: dict[str, Any] = Field(default_factory=dict)
family: str | None = None
modalities: list[str] = Field(default_factory=list)
parameter_metadata: dict[str, Any] = Field(default_factory=dict)
license_metadata: dict[str, Any] = Field(default_factory=lambda: {"status": "unknown"})
lifecycle: ModelLifecycle = ModelLifecycle.CANDIDATE
class ModelUpdate(RegistryModel):
display_name: str | None = Field(default=None, min_length=1, max_length=255)
description: str | None = None
local_metadata: dict[str, Any] | None = None
interpretation_metadata: dict[str, Any] | None = None
lifecycle: ModelLifecycle | None = None
class ModelResponse(ModelCreate):
id: uuid.UUID
created_at: datetime
updated_at: datetime
deprecated_at: datetime | None = None
revision_count: int = 0
artifact_count: int = 0
verification_status: str = "unverified"
deployment_status: str = "not_deployed"
class RevisionCreate(RegistryModel):
upstream_revision: str = Field(min_length=1, max_length=255)
resolved_commit_sha: str = Field(pattern=r"^[0-9a-f]{40,64}$")
metadata_snapshot: dict[str, Any] = Field(default_factory=dict)
class RevisionResponse(RevisionCreate):
id: uuid.UUID
model_id: uuid.UUID
discovered_at: datetime
approved_at: datetime | None = None
immutable_at: datetime
deprecated_at: datetime | None = None
archived_at: datetime | None = None
created_at: datetime
updated_at: datetime
class ArtifactLocationCreate(RegistryModel):
storage_root_id: uuid.UUID
relative_path: str = Field(min_length=1)
status: ArtifactStatus = ArtifactStatus.LOCAL
@field_validator("relative_path")
@classmethod
def safe_relative_path(cls, value: str) -> str:
normalized = value.replace("\\", "/")
if normalized.startswith("/") or ".." in normalized.split("/"):
raise ValueError("relative_path must remain within the storage root")
return normalized
class ArtifactLocationResponse(ArtifactLocationCreate):
id: uuid.UUID
artifact_id: uuid.UUID | None = None
derived_artifact_id: uuid.UUID | None = None
size_bytes: int | None = None
observed_sha256: str | None = None
last_checked_at: datetime | None = None
created_at: datetime
updated_at: datetime
class ArtifactCreate(RegistryModel):
filename: str = Field(min_length=1, max_length=512)
artifact_type: str = Field(min_length=1, max_length=64)
serialization_format: str = Field(min_length=1, max_length=64)
sha256: str = Field(pattern=r"^[0-9a-f]{64}$")
size_bytes: int = Field(ge=0)
status: ArtifactStatus = ArtifactStatus.REMOTE
security_status: str = "unverified"
license_status: str = "unknown"
locations: list[ArtifactLocationCreate] = Field(default_factory=list)
class ArtifactResponse(RegistryModel):
id: uuid.UUID
revision_id: uuid.UUID
filename: str
artifact_type: str
serialization_format: str
sha256: str
size_bytes: int
status: ArtifactStatus
security_status: str
license_status: str
quarantined: bool
verification_details: dict[str, Any]
verified_at: datetime | None = None
immutable_at: datetime | None = None
deprecated_at: datetime | None = None
archived_at: datetime | None = None
created_at: datetime
updated_at: datetime
locations: list[ArtifactLocationResponse] = Field(default_factory=list)
class DerivedArtifactCreate(RegistryModel):
revision_id: uuid.UUID
source_artifact_ids: list[uuid.UUID] = Field(min_length=1)
filename: str = Field(min_length=1, max_length=512)
artifact_type: str = Field(min_length=1, max_length=64)
sha256: str = Field(pattern=r"^[0-9a-f]{64}$")
size_bytes: int = Field(ge=0)
transformation_type: str = Field(min_length=1, max_length=64)
tool: str = Field(min_length=1, max_length=128)
tool_version: str = Field(min_length=1, max_length=128)
configuration: dict[str, Any] = Field(default_factory=dict)
environment_snapshot: dict[str, Any] = Field(default_factory=dict)
status: ArtifactStatus = ArtifactStatus.REMOTE
locations: list[ArtifactLocationCreate] = Field(default_factory=list)
class DerivedSourceResponse(RegistryModel):
artifact_id: uuid.UUID
sha256: str
ordinal: int
class DerivedArtifactResponse(RegistryModel):
id: uuid.UUID
revision_id: uuid.UUID
filename: str
artifact_type: str
sha256: str
size_bytes: int
transformation_type: str
tool: str
tool_version: str
configuration: dict[str, Any]
environment_snapshot: dict[str, Any]
status: ArtifactStatus
immutable_at: datetime | None = None
created_at: datetime
updated_at: datetime
sources: list[DerivedSourceResponse]
locations: list[ArtifactLocationResponse]
class StorageRootCreate(RegistryModel):
compute_node_id: uuid.UUID
name: str = Field(min_length=1, max_length=128)
purpose: str = Field(default="model_artifacts", min_length=1, max_length=64)
path: str = Field(min_length=1)
agent_path: str | None = Field(default=None, min_length=1)
reserve_bytes: int = Field(default=0, ge=0)
reserve_percent: int = Field(default=10, ge=0, le=100)
class StorageRootObservation(RegistryModel):
writable: bool
capacity_bytes: int | None = Field(default=None, ge=0)
free_bytes: int | None = Field(default=None, ge=0)
details: dict[str, Any] = Field(default_factory=dict)
class StorageRootUpdate(RegistryModel):
agent_path: str = Field(min_length=1)
class StorageRootResponse(StorageRootCreate):
id: uuid.UUID
status: StorageRootStatus
writable: bool
capacity_bytes: int | None = None
free_bytes: int | None = None
capacity_observed_at: datetime | None = None
validation_details: dict[str, Any]
deprecated_at: datetime | None = None
created_at: datetime
updated_at: datetime
class CapacityDecision(RegistryModel):
allowed: bool
status: StorageRootStatus
requested_bytes: int
usable_bytes: int | None
reason: str
class VerifyResponse(RegistryModel):
artifact_id: uuid.UUID
status: ArtifactStatus
expected_sha256: str
observed_sha256: str | None
observed_size_bytes: int | None
checked_at: datetime
location_id: uuid.UUID
class DependencyReference(RegistryModel):
resource_type: str
resource_id: uuid.UUID
relation: str
class DependencyConflict(RegistryModel):
message: str
dependencies: list[DependencyReference]
@@ -0,0 +1,223 @@
"""The v1 release contract.
One authoritative product version, and the compatibility ranges that version promises. Before M17
the version existed four times — backend, Node Agent, Runtime Worker and the console each declared
``0.1.0`` independently — which is three opportunities for a release to describe itself wrongly.
The version lives in the repository's ``VERSION`` file. Every packaged manifest is checked against
it rather than trusted to agree, because a manifest that has drifted looks exactly like one that has
not.
Compatibility is stated, not implied. An application refusing to start against a schema it does not
support is a far better outcome than one that starts and writes rows the next version cannot read.
"""
from __future__ import annotations
from dataclasses import dataclass
from datetime import UTC, datetime
from enum import StrEnum
from pathlib import Path
# --------------------------------------------------------------------------- version
#: Schema revisions this application version can run against, oldest first. The application refuses
#: to serve against anything outside this list rather than guessing.
SUPPORTED_SCHEMA_REVISIONS: tuple[str, ...] = ("20260830_0024",)
#: The revision a clean installation and a completed upgrade must both arrive at.
TARGET_SCHEMA_REVISION = SUPPORTED_SCHEMA_REVISIONS[-1]
#: Schema revisions from which the upgrade tool may migrate to the target. These are deliberately
#: separate from runtime compatibility: the node-decommission code reads columns that do not exist
#: at 0021, so serving on 0021 would be unsafe even though migrating from it is supported.
SUPPORTED_UPGRADE_SOURCE_SCHEMA_REVISIONS: tuple[str, ...] = (
"20260827_0021",
"20260828_0022",
"20260830_0023",
TARGET_SCHEMA_REVISION,
)
#: The oldest release an in-place upgrade to this version is supported from.
MINIMUM_UPGRADE_SOURCE = "v1.0.0"
#: Agent protocol versions this control plane accepts.
SUPPORTED_AGENT_PROTOCOL_VERSIONS: tuple[int, ...] = (1,)
#: The protocol version this control plane itself speaks.
CURRENT_AGENT_PROTOCOL_VERSION = SUPPORTED_AGENT_PROTOCOL_VERSIONS[-1]
#: Release channels. v1 ships one; the enum exists so adding another is a typed change.
RELEASE_CHANNEL = "stable"
#: Minimum PostgreSQL major version. The schema uses partial unique indexes and generated columns
#: that older majors either lack or plan differently.
MINIMUM_POSTGRES_MAJOR = 16
def _read_version_file() -> str:
"""Read the repository's VERSION file, falling back to the packaged distribution metadata."""
here = Path(__file__).resolve()
for parent in here.parents:
candidate = parent / "VERSION"
if candidate.is_file():
return candidate.read_text("utf-8").strip()
# An installed wheel has no VERSION file beside it; the distribution metadata is authoritative
# there, and the packaging test proves the two agree at build time.
from importlib.metadata import PackageNotFoundError, version
try:
return version("modelforge-api")
except PackageNotFoundError: # pragma: no cover - only in a broken install
return "0.0.0"
PRODUCT_VERSION = _read_version_file()
PRODUCT_NAME = "ITWorx ModelForge"
# --------------------------------------------------------------------------- semver
@dataclass(frozen=True, slots=True)
class SemanticVersion:
major: int
minor: int
patch: int
prerelease: str | None = None
@classmethod
def parse(cls, value: str) -> SemanticVersion:
core, _, prerelease = value.partition("-")
parts = core.split(".")
if len(parts) != 3 or not all(part.isdigit() for part in parts):
raise ValueError(f"{value!r} is not a MAJOR.MINOR.PATCH version")
major, minor, patch = (int(part) for part in parts)
return cls(major, minor, patch, prerelease or None)
def __str__(self) -> str:
core = f"{self.major}.{self.minor}.{self.patch}"
return f"{core}-{self.prerelease}" if self.prerelease else core
@property
def is_prerelease(self) -> bool:
return self.prerelease is not None
# --------------------------------------------------------------------------- compatibility
class Compatibility(StrEnum):
"""The four answers a compatibility question can have. There is no fifth, and no silent pass."""
COMPATIBLE = "COMPATIBLE"
TOO_OLD = "TOO_OLD"
TOO_NEW = "TOO_NEW"
UNKNOWN = "UNKNOWN"
def schema_compatibility(revision: str | None) -> Compatibility:
"""Is this application version able to run against that schema revision?"""
if revision is None:
return Compatibility.UNKNOWN
if revision in SUPPORTED_SCHEMA_REVISIONS:
return Compatibility.COMPATIBLE
# Revisions are date-ordered identifiers, so a straight comparison against the oldest and newest
# supported revision tells old from new without a migration graph walk.
if revision < SUPPORTED_SCHEMA_REVISIONS[0]:
return Compatibility.TOO_OLD
if revision > SUPPORTED_SCHEMA_REVISIONS[-1]:
return Compatibility.TOO_NEW
return Compatibility.UNKNOWN
def agent_protocol_compatibility(protocol_version: int | None) -> Compatibility:
"""Can an agent speaking that protocol version talk to this control plane?"""
if protocol_version is None:
return Compatibility.UNKNOWN
if protocol_version in SUPPORTED_AGENT_PROTOCOL_VERSIONS:
return Compatibility.COMPATIBLE
if protocol_version < SUPPORTED_AGENT_PROTOCOL_VERSIONS[0]:
return Compatibility.TOO_OLD
return Compatibility.TOO_NEW
def upgrade_required(compatibility: Compatibility) -> str | None:
"""What the operator has to do, in one sentence, or None when nothing is required."""
match compatibility:
case Compatibility.COMPATIBLE:
return None
case Compatibility.TOO_OLD:
return "upgrade the component to a release that speaks the current protocol"
case Compatibility.TOO_NEW:
return "upgrade the control plane, which is older than the component reporting to it"
case Compatibility.UNKNOWN:
return "the version could not be determined and is refused rather than assumed"
# --------------------------------------------------------------------------- build identity
@dataclass(frozen=True, slots=True)
class BuildIdentity:
"""What a running binary can say about where it came from.
Everything here is either compiled in at build time or read from the environment the image was
built with. Nothing is inferred at runtime, because a build identity that a running process can
talk itself into is worth nothing during an incident.
"""
version: str
source_commit: str | None
built_at: str | None
image_digest: str | None
channel: str
schema_revision: str
agent_protocol_version: int
def as_dict(self) -> dict[str, object]:
return {
"version": self.version,
"source_commit": self.source_commit,
"built_at": self.built_at,
"image_digest": self.image_digest,
"channel": self.channel,
"schema_revision": self.schema_revision,
"agent_protocol_version": self.agent_protocol_version,
}
def _clean(value: str | None) -> str | None:
"""Treat an unsubstituted build argument as absent rather than reporting it as a fact."""
if value is None:
return None
stripped = value.strip()
if not stripped or stripped.lower() in {"unknown", "none", "null"}:
return None
return stripped
def build_identity(
*,
source_commit: str | None = None,
built_at: str | None = None,
image_digest: str | None = None,
) -> BuildIdentity:
return BuildIdentity(
version=PRODUCT_VERSION,
source_commit=_clean(source_commit),
built_at=_clean(built_at),
image_digest=_clean(image_digest),
channel=RELEASE_CHANNEL,
schema_revision=TARGET_SCHEMA_REVISION,
agent_protocol_version=CURRENT_AGENT_PROTOCOL_VERSION,
)
def utc_now_iso() -> str:
return datetime.now(UTC).isoformat()
@@ -0,0 +1,249 @@
from __future__ import annotations
import re
import uuid
from datetime import datetime
from typing import Any, Literal
from pydantic import BaseModel, ConfigDict, Field, field_validator, model_validator
RuntimeAdapterName = Literal[
"sentence_transformers",
"qwen3_reranker",
"transformers_trocr",
"transformers_siglip2",
"transformers_whisper",
"transformers",
"vllm",
"llama_cpp",
"diffusers",
"custom",
]
CompatibilityStatus = Literal["compatible", "incompatible", "unknown", "requires_probe", "blocked"]
ProbeStatus = Literal[
"queued",
"preparing",
"loading",
"healthchecking",
"ready",
"unloading",
"completed",
"failed",
"cancelled",
]
class RuntimeModel(BaseModel):
model_config = ConfigDict(extra="forbid", from_attributes=True)
class RuntimeEnvironmentCreate(RuntimeModel):
name: str = Field(min_length=1, max_length=255)
adapter: RuntimeAdapterName
runtime_version: str = Field(min_length=1, max_length=128)
image_repository: str = Field(min_length=1, max_length=255)
image_digest: str = Field(pattern=r"^sha256:[a-f0-9]{64}$")
python_version: str = Field(min_length=1, max_length=64)
cuda_runtime_version: str | None = Field(default=None, max_length=64)
package_versions: dict[str, str] = Field(default_factory=dict)
supported_model_types: list[str] = Field(default_factory=list)
supported_formats: list[str] = Field(default_factory=list)
supported_modalities: list[str] = Field(default_factory=list)
network_policy: Literal["offline_control_plane_only"] = "offline_control_plane_only"
class RuntimeEnvironmentResponse(RuntimeEnvironmentCreate):
id: uuid.UUID
fingerprint: str
immutable_at: datetime
created_at: datetime
class RuntimeProfileCreate(RuntimeModel):
name: str = Field(min_length=1, max_length=255)
runtime_environment_id: uuid.UUID
artifact_set_id: uuid.UUID
dtype: Literal["float32", "float16", "bfloat16"] = "bfloat16"
quantization: str | None = Field(default=None, max_length=64)
modality: Literal[
"embedding", "reranking", "text_generation", "vision", "document", "audio", "diffusion"
]
max_sequence_length: int = Field(default=128, ge=1, le=131072)
batch_size: int = Field(default=1, ge=1, le=128)
concurrency: int = Field(default=1, ge=1, le=128)
device_policy: Literal["cuda_required", "cuda_preferred", "cpu_only"] = "cuda_required"
gpu_memory_policy: dict[str, Any] = Field(default_factory=dict)
launch_parameters: dict[str, Any] = Field(default_factory=dict)
environment_variables: dict[str, str] = Field(default_factory=dict)
trust_remote_code: Literal[False] = False
network_egress: Literal[False] = False
@field_validator("environment_variables")
@classmethod
def reject_secrets(cls, value: dict[str, str]) -> dict[str, str]:
forbidden = {"TOKEN", "SECRET", "PASSWORD", "KEY", "CREDENTIAL"}
if any(
forbidden.intersection(filter(None, re.split(r"[^A-Z0-9]+", key.upper())))
for key in value
):
raise ValueError("runtime profile environment cannot contain secrets")
return value
@model_validator(mode="after")
def fixed_probe_shape(self) -> RuntimeProfileCreate:
if self.modality == "embedding" and self.batch_size != 1:
raise ValueError("M4 embedding probes require batch_size=1")
return self
class RuntimeProfileResponse(RuntimeProfileCreate):
id: uuid.UUID
adapter: str
runtime_version: str
image_digest: str
version: int
fingerprint: str
health_contract: dict[str, Any]
immutable_at: datetime
created_at: datetime
class CompatibilityAssessmentCreate(RuntimeModel):
runtime_profile_id: uuid.UUID
compute_node_id: uuid.UUID
class CompatibilityAssessmentResponse(RuntimeModel):
id: uuid.UUID
artifact_set_id: uuid.UUID
runtime_profile_id: uuid.UUID
compute_node_id: uuid.UUID
adapter: str
runtime_version: str
status: CompatibilityStatus
static_result: dict[str, Any]
evidence: dict[str, Any]
blockers: list[str]
warnings: list[str]
required_approvals: list[str]
hardware_facts: dict[str, Any]
artifact_facts: dict[str, Any]
environment_fingerprint: str
stale: bool
stale_reason: str | None
created_at: datetime
class ExecutionApprovalCreate(RuntimeModel):
scope: Literal["lab_execution"] = "lab_execution"
reason: str = Field(min_length=8, max_length=2000)
approved_by: str = Field(min_length=1, max_length=255)
expires_at: datetime | None = None
class ExecutionApprovalResponse(RuntimeModel):
id: uuid.UUID
artifact_set_id: uuid.UUID
scope: str
status: str
evidence_fingerprint: str
reason: str
approved_by: str
approved_at: datetime
expires_at: datetime | None
revoked_at: datetime | None
stale: bool
class RuntimeProbeCreate(RuntimeModel):
compatibility_assessment_id: uuid.UUID
execution_approval_id: uuid.UUID
input_text: Literal["ModelForge runtime compatibility probe"] = (
"ModelForge runtime compatibility probe"
)
class RuntimeProbeResponse(RuntimeModel):
id: uuid.UUID
artifact_set_id: uuid.UUID
runtime_profile_id: uuid.UUID
compute_node_id: uuid.UUID
compatibility_assessment_id: uuid.UUID
execution_approval_id: uuid.UUID
status: ProbeStatus
phase: str | None
attempt_count: int
cancel_requested: bool
load_result: dict[str, Any]
health_result: dict[str, Any]
inference_result: dict[str, Any]
unload_result: dict[str, Any]
measured_resources: dict[str, Any]
runtime_facts: dict[str, Any]
environment_fingerprint: str
failure_code: str | None
failure_message: str | None
logs_reference: str | None
started_at: datetime | None
finished_at: datetime | None
created_at: datetime
updated_at: datetime
class DeploymentCandidateResponse(RuntimeModel):
id: uuid.UUID
artifact_set_id: uuid.UUID
runtime_profile_id: uuid.UUID
compute_node_id: uuid.UUID
compatibility_assessment_id: uuid.UUID
runtime_probe_id: uuid.UUID
channel: Literal["lab"]
status: Literal["lab_ready"]
production: Literal[False]
health_contract: dict[str, Any]
measured_resources: dict[str, Any]
created_at: datetime
class AgentRuntimeProbeLease(RuntimeModel):
probe_id: uuid.UUID
lease_token: str
lease_expires_at: datetime
artifact_set_id: uuid.UUID
revision_sha: str = Field(pattern=r"^[a-f0-9]{40,64}$")
artifact_root: str
artifact_relative_path: str
expected_manifest: dict[str, Any]
runtime_profile: dict[str, Any]
runtime_environment: dict[str, Any]
probe_input: Literal["ModelForge runtime compatibility probe"]
class AgentRuntimeProbeProgress(RuntimeModel):
lease_token: str = Field(min_length=32, max_length=512)
status: Literal["preparing", "loading", "healthchecking", "ready", "unloading"]
details: dict[str, Any] = Field(default_factory=dict)
class AgentRuntimeProbeControl(RuntimeModel):
accepted: bool
cancel_requested: bool
lease_expires_at: datetime
class AgentRuntimeProbeComplete(RuntimeModel):
lease_token: str = Field(min_length=32, max_length=512)
load_result: dict[str, Any]
health_result: dict[str, Any]
inference_result: dict[str, Any]
unload_result: dict[str, Any]
measured_resources: dict[str, Any]
runtime_facts: dict[str, Any]
environment_fingerprint: str = Field(pattern=r"^[a-f0-9]{64}$")
class AgentRuntimeProbeFailure(RuntimeModel):
lease_token: str = Field(min_length=32, max_length=512)
failure_code: str = Field(min_length=1, max_length=64)
failure_message: str = Field(min_length=1, max_length=2000)
details: dict[str, Any] = Field(default_factory=dict)
@@ -0,0 +1,172 @@
import uuid
from datetime import UTC, datetime
from typing import Any
from pydantic import BaseModel, Field
from .contracts import (
CapabilityCategory,
CapabilityContractManifest,
CapabilityStability,
EvaluationType,
ProjectBindingManifest,
ResourceClass,
)
from .enums import (
HealthStatus,
ModelLifecycle,
VerificationStatus,
)
class HealthResponse(BaseModel):
status: str = "ok"
service: str = "modelforge-api"
version: str
timestamp: datetime = Field(default_factory=lambda: datetime.now(UTC))
class ReadinessResponse(BaseModel):
status: HealthStatus
checks: dict[str, HealthStatus]
version: str
timestamp: datetime = Field(default_factory=lambda: datetime.now(UTC))
class SystemMetadata(BaseModel):
name: str
version: str
environment: str
api_version: str = "v1"
release_channel: str = "stable"
production_inference_available: bool = False
class ReleaseCompatibility(BaseModel):
"""What this build will and will not talk to, stated rather than implied."""
schema_revision: str
supported_schema_revisions: list[str]
agent_protocol_version: int
supported_agent_protocol_versions: list[int]
minimum_upgrade_source: str
minimum_postgres_major: int
class ReleaseInfo(BaseModel):
"""Build identity for the running process.
Deliberately free of anything sensitive: no paths, no configuration values, no credentials —
only what an operator needs to answer "which build is this, and what does it support?".
"""
name: str
version: str
release_channel: str
source_commit: str | None = None
built_at: str | None = None
image_digest: str | None = None
compatibility: ReleaseCompatibility
class ErrorDetail(BaseModel):
code: str
message: str
correlation_id: str
details: dict[str, Any] = Field(default_factory=dict)
class ErrorEnvelope(BaseModel):
error: ErrorDetail
class GPUInfo(BaseModel):
index: int
name: str
uuid: str | None = None
memory_total_mb: int | None = None
memory_used_mb: int | None = None
utilization_gpu_percent: int | None = None
utilization_memory_percent: int | None = None
temperature_c: int | None = None
power_draw_w: float | None = None
telemetry_available: bool = True
error: str | None = None
class CapabilityContractResponse(BaseModel):
key: str
version: int
description: str
contract: CapabilityContractManifest
stable_deployment: dict[str, Any] | None = None
class CapabilityEstateResponse(BaseModel):
capability: str
version: int
category: CapabilityCategory
purpose: str
declared_stability: CapabilityStability
operational_state: str
current_deployment_id: uuid.UUID | None = None
model: str | None = None
revision: str | None = None
runtime: str | None = None
node: str | None = None
resource_class: ResourceClass
measured_required_vram_bytes: int | None = None
consumers: list[str]
privacy_class: str
evaluation_type: EvaluationType
evaluation_state: str
class InstallationDependencyResponse(BaseModel):
capability: str
version: int
deployment_id: uuid.UUID
channel: str
production: bool
project_consumers: list[str]
active_project_consumers: list[str]
project_fit_evidence_ids: list[uuid.UUID]
evaluation_run_ids: list[uuid.UUID]
last_used_at: datetime | None
class ModelInstallationRationaleResponse(BaseModel):
model_id: uuid.UUID
display_name: str
upstream_source: str
installed: bool
installed_bytes: int
dependencies: list[InstallationDependencyResponse]
can_delete: bool
deletion_blockers: list[str]
class CandidateSummary(BaseModel):
id: str
display_name: str
source: str
intended_capabilities: list[str]
proposed_role: str
preferred_runtime: str | None = None
lifecycle: ModelLifecycle = ModelLifecycle.CANDIDATE
verification_status: VerificationStatus = VerificationStatus.UNVERIFIED
deployment_status: str = "not_deployed"
class ProjectBindingResponse(BaseModel):
capability: str
contract_version: int
binding: ProjectBindingManifest
class ProjectResponse(BaseModel):
id: str
name: str
description: str
bindings: list[ProjectBindingResponse]
notes: list[str] = Field(default_factory=list)
@@ -0,0 +1,764 @@
from __future__ import annotations
import base64
import binascii
import math
import re
import uuid
from datetime import datetime
from typing import Any, Literal
from pydantic import BaseModel, ConfigDict, Field, field_validator, model_validator
CAPABILITY_VERSION_KEY = re.compile(r"^[a-z][a-z0-9_]*(?:\.[a-z][a-z0-9_]*)+@[1-9][0-9]*$")
ServingOperation = Literal["load", "invoke", "health", "drain", "unload"]
class ServingModel(BaseModel):
model_config = ConfigDict(extra="forbid", from_attributes=True)
class SupplyChainReview(ServingModel):
exact_revision_reviewed: Literal[True]
artifact_hashes_reviewed: Literal[True]
safetensors_only: Literal[True]
remote_code_required: Literal[False]
pickle_present: Literal[False]
scanner_evidence_reviewed: Literal[True]
provenance_complete: Literal[True]
runtime_offline_reviewed: Literal[True]
dependency_provenance_reviewed: Literal[True]
license_reviewed: Literal[True]
license_identifier: str = Field(min_length=1, max_length=255)
class ProductionApprovalCreate(ServingModel):
capability: Literal["rag.embedding"] = "rag.embedding"
contract_version: Literal[1] = 1
approved_by: str = Field(min_length=1, max_length=255)
reason: str = Field(min_length=16, max_length=4000)
supply_chain_review: SupplyChainReview
deployment_config: dict[str, Any] = Field(default_factory=dict)
class ProductionApprovalResponse(ServingModel):
id: uuid.UUID
deployment_candidate_id: uuid.UUID
capability_contract_id: uuid.UUID
artifact_set_id: uuid.UUID
runtime_profile_id: uuid.UUID
compute_node_id: uuid.UUID
deployment_config: dict[str, Any]
supply_chain_evidence: dict[str, Any]
evidence_fingerprint: str
status: str
approved_by: str
reason: str
approved_at: datetime
revoked_at: datetime | None
stale: bool = False
class CapabilityPromotionCreate(ServingModel):
production_approval_id: uuid.UUID
residency_policy: Literal["always_warm", "keep_warm", "load_on_demand"] = "keep_warm"
keep_warm_seconds: int = Field(default=900, ge=5, le=86400)
max_concurrency: Literal[1] = 1
max_queue_depth: int = Field(default=16, ge=1, le=1024)
routing_weight: int = Field(default=100, ge=0, le=100)
rollback_policy: dict[str, Any] = Field(
default_factory=lambda: {"mode": "drain_to_unavailable", "previous_deployment_id": None}
)
class EmbeddingSpaceResponse(ServingModel):
id: uuid.UUID
identity_digest: str
dimension: int
normalized: bool
migration_class: Literal["requires_reindex"]
identity_facts: dict[str, Any]
created_at: datetime
class ResourceEnvelopeResponse(ServingModel):
id: uuid.UUID
runtime_probe_id: uuid.UUID
accelerator_kind: str
accelerator_uuid: str
environment_fingerprint: str
concurrency: int
batch_size: int
max_sequence_length: int
baseline_vram_bytes: int
resident_vram_bytes: int
peak_vram_bytes: int
required_vram_bytes: int
cold_load_time_ms: float
inference_latency_ms: float
stale: bool
stale_reason: str | None
class ResidencyResponse(ServingModel):
id: uuid.UUID
state: str
worker_instance_id: str | None
load_count: int
active_requests: int
measured_resident_vram_bytes: int
external_baseline_vram_bytes: int
health: dict[str, Any]
resident_since: datetime | None
last_used_at: datetime | None
failure_code: str | None
failure_message: str | None
generation: int = 1
transition_reason: str | None = None
class CapabilityDeploymentResponse(ServingModel):
id: uuid.UUID
capability: str
contract_version: int
deployment_candidate_id: uuid.UUID
production_approval_id: uuid.UUID | None
execution_approval_id: uuid.UUID | None
artifact_set_id: uuid.UUID
runtime_profile_id: uuid.UUID
compute_node_id: uuid.UUID
accelerator_id: uuid.UUID
embedding_space: EmbeddingSpaceResponse | None
resource_envelope: ResourceEnvelopeResponse
residency: ResidencyResponse
channel: str
status: str
production: bool
health_status: str
routing_weight: int
fallback_policy: dict[str, Any]
residency_policy: str
keep_warm_seconds: int
max_concurrency: int
max_queue_depth: int
config_fingerprint: str
provenance: dict[str, Any]
rollback_policy: dict[str, Any]
promoted_at: datetime | None
created_at: datetime
class ServiceClientCreate(ServingModel):
name: str = Field(min_length=1, max_length=255, pattern=r"^[a-z][a-z0-9-]*$")
allowed_capabilities: list[str] = Field(min_length=1, max_length=32)
requests_per_minute: int = Field(default=60, ge=1, le=10000)
max_concurrent_requests: int = Field(default=1, ge=1, le=64)
workload_priority: Literal["production", "interactive", "background", "benchmark"] = (
"production"
)
project_key: str | None = Field(default=None, pattern=r"^[a-z][a-z0-9-]*$")
integration_environment: Literal["production", "shadow", "evaluation", "lab"] | None = None
purpose: str | None = Field(default=None, min_length=3, max_length=500)
credential_expires_at: datetime | None = None
@field_validator("allowed_capabilities")
@classmethod
def validate_capability_scopes(cls, value: list[str]) -> list[str]:
if len(set(value)) != len(value) or any(
not CAPABILITY_VERSION_KEY.fullmatch(v) for v in value
):
raise ValueError("capability scopes must be unique versioned capability keys")
return value
@model_validator(mode="after")
def validate_project_scope(self) -> ServiceClientCreate:
project_fields = (self.project_key, self.integration_environment, self.purpose)
if any(value is not None for value in project_fields) and not all(
value is not None for value in project_fields
):
raise ValueError("project_key, integration_environment and purpose are required together")
if self.project_key is not None and len(self.allowed_capabilities) != 1:
raise ValueError("a project service identity must bind exactly one capability")
return self
class ServiceClientResponse(ServingModel):
id: uuid.UUID
name: str
status: str
allowed_capabilities: list[str]
requests_per_minute: int
max_concurrent_requests: int
workload_priority: str
project_key: str | None
project_binding_id: uuid.UUID | None
integration_environment: str | None
purpose: str | None
credential_prefix: str | None
credential_created_at: datetime | None
credential_expires_at: datetime | None
credential_revoked_at: datetime | None
last_used_at: datetime | None
created_at: datetime
class ServiceClientCreated(ServiceClientResponse):
credential: str
class ProjectFitEvidenceCreate(ServingModel):
project_key: str = Field(pattern=r"^[a-z][a-z0-9-]*$")
capability: str = Field(pattern=r"^[a-z][a-z0-9.]*@[1-9][0-9]*$")
environment: Literal["production", "shadow", "evaluation", "lab"]
recommendation: Literal[
"PROMOTION_ELIGIBLE", "KEEP_LAB", "BLOCKED", "REQUIRES_MORE_EVIDENCE"
]
evidence_class: Literal[
"OWNER_PHOTO", "PUBLIC_PHYSICAL_CAPTURE", "CATALOG_REFERENCE"
]
engineering_integration: Literal["PASS", "INCOMPLETE", "BLOCKED"]
production_validation: Literal[
"DEFERRED_EXTERNAL_VALIDATION", "REQUIRED", "SATISFIED"
]
production_action: Literal["NONE"] = "NONE"
case_count: int = Field(ge=0, le=1_000_000)
metric_values: dict[str, float | int | str | bool | None]
critical_errors: int = Field(ge=0)
blockers: list[str] = Field(default_factory=list, max_length=100)
evidence: dict[str, Any] = Field(default_factory=dict)
capability_deployment_id: uuid.UUID | None = None
class ProjectFitEvidenceResponse(ServingModel):
id: uuid.UUID
project_key: str
capability: str
environment: str
recommendation: str
evidence_class: str
engineering_integration: str
production_validation: str
production_action: str
case_count: int
metric_values: dict[str, Any]
critical_errors: int
blockers: list[str]
evidence_digest: str
evidence: dict[str, Any]
capability_deployment_id: uuid.UUID | None
created_at: datetime
class ProjectIntegrationUsage(ServingModel):
request_volume: int
successful_requests: int
error_count: int
last_used_at: datetime | None
latency_p50_ms: float | None
latency_p95_ms: float | None
class ProjectIntegrationResponse(ServingModel):
project_key: str
project_name: str
capability: str
environment: str
state: str
purpose: str
client_id: uuid.UUID
client_name: str
deployment_id: uuid.UUID | None
resource_impact_bytes: int | None
usage: ProjectIntegrationUsage
project_fit: ProjectFitEvidenceResponse | None
class EmbeddingInvokeRequest(ServingModel):
input: str | list[str]
input_type: Literal["raw", "query", "document"] = "raw"
@model_validator(mode="after")
def validate_input(self) -> EmbeddingInvokeRequest:
values = [self.input] if isinstance(self.input, str) else self.input
if not values or any(not item.strip() for item in values):
raise ValueError("input must contain at least one non-empty string")
return self
def inputs(self) -> list[str]:
return [self.input] if isinstance(self.input, str) else self.input
class CapabilityExperimentCreate(ServingModel):
route_key: str = Field(min_length=3, max_length=128, pattern=r"^[a-z][a-z0-9-]+$")
execution_approval_id: uuid.UUID
capability: Literal[
"rag.embedding",
"rag.reranking",
"document.ocr",
"vision.embedding",
"speech.transcription",
] = "rag.embedding"
purpose: str = Field(min_length=16, max_length=2000)
expected_dimension: int = Field(default=1024, ge=32, le=8192)
residency_policy: Literal["keep_warm", "load_on_demand"] = "keep_warm"
keep_warm_seconds: int = Field(default=300, ge=5, le=86400)
max_queue_depth: int = Field(default=16, ge=1, le=1024)
class CapabilityExperimentResponse(ServingModel):
id: uuid.UUID
route_key: str
capability: str
contract_version: int
capability_deployment_id: uuid.UUID
status: str
purpose: str
evidence: dict[str, Any]
deployment: CapabilityDeploymentResponse
created_at: datetime
class GatewayTiming(ServingModel):
validation_ms: float = 0.0
resolution_ms: float = 0.0
scheduling_ms: float = 0.0
payload_ms: float = 0.0
dispatch_ms: float = 0.0
worker_preprocess_ms: float = 0.0
worker_serialize_ms: float = 0.0
worker_total_ms: float = 0.0
completion_transport_ms: float = 0.0
result_ms: float = 0.0
gateway_ms: float
# Backwards-compatible alias for the scheduler/lease wait used by M5/M6.
queue_ms: float
load_ms: float
inference_ms: float
total_ms: float
class GatewayExecution(ServingModel):
cold: bool
node: str
residency: str
load_count: int
timings: GatewayTiming
class GatewayUsage(ServingModel):
input_count: int
input_tokens: int
class EmbeddingInvokeResponse(ServingModel):
capability: Literal["rag.embedding@1"] = "rag.embedding@1"
dimension: int = Field(ge=1, le=65_536)
normalized: Literal[True] = True
embedding_space_id: uuid.UUID
data: list[list[float]]
request_id: uuid.UUID
execution: GatewayExecution
usage: GatewayUsage
class StableEmbeddingInvokeResponse(EmbeddingInvokeResponse):
"""Public stable contract; lab experiment routes may use another dimension."""
dimension: Literal[1024] = 1024
class RerankDocument(ServingModel):
id: str = Field(min_length=1, max_length=255)
text: str = Field(min_length=1, max_length=16_384)
class RerankingInvokeRequest(ServingModel):
query: str = Field(min_length=1, max_length=4_000)
documents: list[RerankDocument] = Field(min_length=1, max_length=40)
top_n: int = Field(default=10, ge=1, le=10)
@model_validator(mode="after")
def validate_reranking_input(self) -> RerankingInvokeRequest:
if not self.query.strip() or any(not document.text.strip() for document in self.documents):
raise ValueError("query and document text must be non-empty")
if len({document.id for document in self.documents}) != len(self.documents):
raise ValueError("document ids must be unique")
if self.top_n > len(self.documents):
raise ValueError("top_n cannot exceed the document count")
if len(self.query) + sum(len(document.text) for document in self.documents) > 262_144:
raise ValueError("rerank request is too large")
return self
class RerankingResult(ServingModel):
id: str
score: float
rank: int = Field(ge=1, le=10)
@field_validator("score")
@classmethod
def finite_score(cls, value: float) -> float:
if not math.isfinite(value):
raise ValueError("reranking score must be finite")
return value
class RerankingInvokeResponse(ServingModel):
capability: Literal["rag.reranking@1"] = "rag.reranking@1"
results: list[RerankingResult]
request_id: uuid.UUID
execution: GatewayExecution
usage: GatewayUsage
def _bounded_base64(value: str, maximum_bytes: int) -> str:
try:
decoded = base64.b64decode(value, validate=True)
except (binascii.Error, ValueError) as exc:
raise ValueError("content must be canonical base64") from exc
if not decoded or len(decoded) > maximum_bytes:
raise ValueError(f"decoded content must be between 1 and {maximum_bytes} bytes")
return value
class OCRInvokeRequest(ServingModel):
content_base64: str
media_type: Literal["image/png", "image/jpeg"]
language_hint: Literal["nl", "en", "auto"] = "auto"
@field_validator("content_base64")
@classmethod
def bounded_content(cls, value: str) -> str:
return _bounded_base64(value, 8 * 1024 * 1024)
class OCRBlock(ServingModel):
text: str
order: int = Field(ge=0)
bbox: list[int] | None = Field(default=None, min_length=4, max_length=4)
confidence: float | None = Field(default=None, ge=0, le=1)
class OCRPage(ServingModel):
page: Literal[1] = 1
width: int = Field(ge=1, le=4096)
height: int = Field(ge=1, le=4096)
blocks: list[OCRBlock]
class OCRInvokeResponse(ServingModel):
capability: Literal["document.ocr@1"] = "document.ocr@1"
text: str
pages: list[OCRPage] = Field(min_length=1, max_length=1)
confidence: float | None = Field(default=None, ge=0, le=1)
request_id: uuid.UUID
execution: GatewayExecution
class VisionEmbeddingItem(ServingModel):
image_base64: str | None = None
media_type: Literal["image/png", "image/jpeg"] | None = None
text: str | None = Field(default=None, min_length=1, max_length=4096)
@model_validator(mode="after")
def exactly_one_modality(self) -> VisionEmbeddingItem:
image = self.image_base64 is not None
text = self.text is not None
if image == text or image != (self.media_type is not None):
raise ValueError("each item must contain exactly one typed image or text")
if self.image_base64 is not None:
_bounded_base64(self.image_base64, 8 * 1024 * 1024)
return self
class VisionEmbeddingInvokeRequest(ServingModel):
items: list[VisionEmbeddingItem] = Field(min_length=1, max_length=4)
@model_validator(mode="after")
def bounded_total(self) -> VisionEmbeddingInvokeRequest:
encoded = sum(len(item.image_base64 or "") for item in self.items)
if encoded > 11_184_812:
raise ValueError("combined image payload exceeds 8 MiB")
return self
class VisionEmbeddingInvokeResponse(ServingModel):
capability: Literal["vision.embedding@1"] = "vision.embedding@1"
dimension: int = Field(ge=32, le=8192)
normalized: Literal[True] = True
embedding_space_id: uuid.UUID
data: list[list[float]] = Field(min_length=1, max_length=4)
request_id: uuid.UUID
execution: GatewayExecution
class SpeechTranscriptionInvokeRequest(ServingModel):
audio_base64: str
media_type: Literal["audio/wav"] = "audio/wav"
language: Literal["nl", "en", "fr", "auto"] = "auto"
@field_validator("audio_base64")
@classmethod
def bounded_audio(cls, value: str) -> str:
return _bounded_base64(value, 16 * 1024 * 1024)
class TranscriptionSegment(ServingModel):
start_seconds: float = Field(ge=0)
end_seconds: float = Field(ge=0)
text: str
class SpeechTranscriptionInvokeResponse(ServingModel):
capability: Literal["speech.transcription@1"] = "speech.transcription@1"
text: str
language: str
duration_seconds: float = Field(gt=0, le=120)
segments: list[TranscriptionSegment] = Field(default_factory=list)
request_id: uuid.UUID
execution: GatewayExecution
class OpenAIEmbeddingRequest(ServingModel):
model: Literal["rag.embedding", "rag.embedding@1"]
input: str | list[str]
class OpenAIEmbeddingItem(ServingModel):
object: Literal["embedding"] = "embedding"
index: int
embedding: list[float]
class OpenAIUsage(ServingModel):
prompt_tokens: int
total_tokens: int
class OpenAIEmbeddingResponse(ServingModel):
object: Literal["list"] = "list"
data: list[OpenAIEmbeddingItem]
model: Literal["rag.embedding"] = "rag.embedding"
usage: OpenAIUsage
class SchedulerBudgetResponse(ServingModel):
compute_node_id: uuid.UUID
node_name: str
accelerator_id: uuid.UUID
accelerator_uuid: str
accelerator_name: str
total_vram_bytes: int
observed_used_vram_bytes: int
external_vram_bytes: int
resident_vram_bytes: int
leased_vram_bytes: int
safety_reserve_bytes: int
schedulable_free_vram_bytes: int
pressure: bool
pressure_state: Literal["NORMAL", "ELEVATED", "HIGH", "CRITICAL"] = "NORMAL"
attribution_confidence: Literal["KNOWN", "ESTIMATED", "UNKNOWN"] = "UNKNOWN"
invariant_delta_bytes: int = 0
policy_revision: str = "m10-v1"
observed_at: datetime | None
class PlacementPlanRequest(ServingModel):
priority: Literal["production", "interactive", "background", "lab"] = "interactive"
deadline_remaining_ms: float | None = Field(default=None, gt=0, le=900_000)
class PlacementEvictionResponse(ServingModel):
deployment_id: uuid.UUID
capability: str
expected_reclaimed_bytes: int
reason: str
class PlacementPlanResponse(ServingModel):
id: uuid.UUID | None = None
deployment_id: uuid.UUID
capability: str
node_id: uuid.UUID
accelerator_id: uuid.UUID
policy_revision: str
verdict: Literal[
"ADMIT",
"ADMIT_AFTER_EVICTION",
"QUEUE",
"REJECT_CAPACITY",
"REJECT_HEALTH",
"REJECT_POLICY",
]
reason_codes: list[str]
required_vram_bytes: int
headroom_before_bytes: int
headroom_after_bytes: int
expected_cold_load_ms: float
evictions: list[PlacementEvictionResponse] = Field(default_factory=list)
decision_fingerprint: str
dry_run: bool
created_at: datetime
class SchedulerPolicyResponse(ServingModel):
revision: str
active: bool
configuration: dict[str, Any]
created_at: datetime
class SchedulerMetricsResponse(ServingModel):
counters: dict[str, int]
gauges: dict[str, int | str]
queue_seconds: dict[str, float]
class SchedulerPolicyUpdate(ServingModel):
lab_paused: bool
class CoResidencyEvidenceResponse(ServingModel):
left_deployment_id: uuid.UUID
left_capability: str
right_deployment_id: uuid.UUID
right_capability: str
left_alone_bytes: int
right_alone_bytes: int
expected_combined_bytes: int
measured_combined_bytes: int | None
status: Literal["PROVEN_SAFE", "EXPECTED_SAFE", "NOT_SAFE", "UNKNOWN"]
evidence: dict[str, Any] = Field(default_factory=dict)
measured_at: datetime | None
class CoResidencyEvidenceCreate(ServingModel):
left_deployment_id: uuid.UUID
right_deployment_id: uuid.UUID
@model_validator(mode="after")
def distinct_deployments(self) -> CoResidencyEvidenceCreate:
if self.left_deployment_id == self.right_deployment_id:
raise ValueError("co-residency evidence requires two distinct deployments")
return self
class ResidencyPolicyUpdate(ServingModel):
residency_policy: Literal["always_warm", "keep_warm", "load_on_demand", "lab_only"]
keep_warm_seconds: int = Field(default=900, ge=5, le=86400)
class GatewayRequestResponse(ServingModel):
request_id: uuid.UUID
capability: str
client: str | None
status: str
cold: bool | None
queue_time_ms: float | None
load_time_ms: float | None
inference_time_ms: float | None
total_latency_ms: float | None
failure_code: str | None
latency_breakdown: dict[str, float] = Field(default_factory=dict)
created_at: datetime
class AgentServingJobLease(ServingModel):
job_id: uuid.UUID
lease_token: str = Field(min_length=32, max_length=512)
lease_expires_at: datetime
operation: ServingOperation
deployment_id: uuid.UUID
artifact_set_id: uuid.UUID
revision_sha: str = Field(pattern=r"^[a-f0-9]{40,64}$")
artifact_root: str
artifact_relative_path: str
expected_manifest: dict[str, Any]
runtime_profile: dict[str, Any]
runtime_environment: dict[str, Any]
capability: Literal[
"rag.embedding",
"rag.reranking",
"document.ocr",
"vision.embedding",
"speech.transcription",
] = "rag.embedding"
embedding_space_id: uuid.UUID | None = None
expected_dimension: int | None = Field(default=None, ge=32, le=8192)
normalize: Literal[True] | None = True
input: list[str] | None = None
input_type: Literal["raw", "query", "document"] = "raw"
rerank_query: str | None = None
rerank_documents: list[RerankDocument] | None = None
top_n: int | None = Field(default=None, ge=1, le=10)
modality_payload: dict[str, Any] | None = None
request_id: uuid.UUID | None = None
@model_validator(mode="after")
def validate_capability_payload(self) -> AgentServingJobLease:
if self.operation != "invoke":
return self
if self.capability == "rag.embedding":
if self.embedding_space_id is None or self.expected_dimension is None:
raise ValueError("embedding invoke requires embedding identity")
if self.input is None or self.rerank_query is not None:
raise ValueError("embedding invoke payload is invalid")
elif self.capability == "rag.reranking" and (
self.rerank_query is None
or self.rerank_documents is None
or self.top_n is None
or self.input is not None
):
raise ValueError("reranking invoke payload is invalid")
elif self.capability not in {"rag.embedding", "rag.reranking"} and (
self.modality_payload is None or self.input is not None or self.rerank_query is not None
):
raise ValueError("modality invoke requires a typed modality payload")
return self
class AgentServingJobComplete(ServingModel):
lease_token: str = Field(min_length=32, max_length=512)
worker_instance_id: str = Field(min_length=1, max_length=255)
state: str
result: dict[str, Any] = Field(default_factory=dict)
metrics: dict[str, Any] = Field(default_factory=dict)
health: dict[str, Any] = Field(default_factory=dict)
runtime_facts: dict[str, Any] = Field(default_factory=dict)
class AgentServingJobFailure(ServingModel):
lease_token: str = Field(min_length=32, max_length=512)
worker_instance_id: str = Field(min_length=1, max_length=255)
failure_code: str = Field(min_length=1, max_length=64)
failure_message: str = Field(min_length=1, max_length=2000)
state: str = "failed"
details: dict[str, Any] = Field(default_factory=dict)
class AgentResidentState(ServingModel):
deployment_id: uuid.UUID
artifact_set_id: uuid.UUID
runtime_profile_id: uuid.UUID
runtime_environment_fingerprint: str = Field(pattern=r"^[a-f0-9]{64}$")
revision_sha: str = Field(pattern=r"^[a-f0-9]{40,64}$")
state: Literal["loading", "warm", "busy", "draining", "unloading", "failed"]
load_count: int = Field(ge=1)
resident_vram_bytes: int = Field(ge=0)
health: dict[str, Any] = Field(default_factory=dict)
class AgentServingStateReport(ServingModel):
worker_instance_id: str = Field(min_length=1, max_length=255)
deployment_id: uuid.UUID | None
state: Literal["cold", "loading", "warm", "busy", "draining", "unloading", "failed"]
load_count: int = Field(ge=0)
health: dict[str, Any] = Field(default_factory=dict)
observed_at: datetime
generation: int = Field(default=1, ge=1)
residencies: list[AgentResidentState] = Field(default_factory=list, max_length=32)
class AgentServingStateAck(ServingModel):
accepted: bool = True
desired_operation: Literal["none", "unload"] = "none"
@@ -0,0 +1,3 @@
from .collectors import NvidiaNvmlCollector, SystemHostCollector, build_nvml_collector
__all__ = ["NvidiaNvmlCollector", "SystemHostCollector", "build_nvml_collector"]
@@ -0,0 +1,339 @@
from __future__ import annotations
import platform
import socket
import sys
import uuid
from collections.abc import Callable
from contextlib import suppress
from datetime import UTC, datetime
from pathlib import Path
from typing import Any, TypeVar
import psutil # type: ignore[import-untyped]
from modelforge_api import __version__
from modelforge_api.domain.enums import Availability
from modelforge_api.domain.hardware import (
AcceleratorInventory,
AcceleratorTelemetry,
HostInventory,
NvidiaCollection,
ObservedValue,
StorageObservation,
)
T = TypeVar("T")
class NodeIdentityProvider:
"""Stable node identity: explicit override, OS machine ID, then persisted UUID."""
def __init__(
self,
identity_file: Path,
explicit_identity: str | None = None,
force_persisted: bool = False,
) -> None:
self.identity_file = identity_file
self.explicit_identity = explicit_identity
self.force_persisted = force_persisted
def resolve(self) -> tuple[str, str]:
if self.explicit_identity:
return self.explicit_identity, "configured"
if not self.force_persisted:
system_id = self._system_machine_id()
if system_id:
return str(uuid.uuid5(uuid.NAMESPACE_OID, system_id)), "os_machine_id"
try:
if self.identity_file.exists():
return self.identity_file.read_text(encoding="utf-8").strip(), "persisted_uuid"
value = str(uuid.uuid4())
self.identity_file.parent.mkdir(parents=True, exist_ok=True)
self.identity_file.write_text(value, encoding="utf-8")
return value, "persisted_uuid"
except OSError as exc:
raise RuntimeError("unable to establish stable node identity") from exc
@staticmethod
def _system_machine_id() -> str | None:
# `sys.platform`, not `os.name`: a type checker narrows on the former and not on the latter,
# so under `os.name` this block was analysed on Linux too — where winreg has no attributes.
# The images run on Linux, so the platform the release actually ships on was the one the
# type check could not see. They mean the same thing at runtime.
if sys.platform == "win32":
try:
import winreg
with winreg.OpenKey(
winreg.HKEY_LOCAL_MACHINE,
r"SOFTWARE\Microsoft\Cryptography",
) as key:
return str(winreg.QueryValueEx(key, "MachineGuid")[0])
except (OSError, ImportError):
return None
for path in (Path("/etc/machine-id"), Path("/var/lib/dbus/machine-id")):
try:
value = path.read_text(encoding="utf-8").strip()
if value:
return value
except OSError:
continue
return None
class SystemHostCollector:
def __init__(
self,
identity_provider: NodeIdentityProvider,
storage_paths: dict[str, Path],
) -> None:
self.identity_provider = identity_provider
self.storage_paths = storage_paths
@staticmethod
def _known_or_unknown(value: T | None, reason: str) -> ObservedValue[T]:
return (
ObservedValue.known(value)
if value is not None
else ObservedValue.absent(Availability.UNKNOWN, reason)
)
def collect(self) -> HostInventory:
identity_key, identity_source = self.identity_provider.resolve()
memory = psutil.virtual_memory()
cpu_model = platform.processor().strip() or None
physical = psutil.cpu_count(logical=False)
logical = psutil.cpu_count(logical=True)
storage: list[StorageObservation] = []
for purpose, path in self.storage_paths.items():
try:
usage = psutil.disk_usage(str(path))
storage.append(
StorageObservation(
purpose=purpose,
path=str(path.resolve()),
total_bytes=ObservedValue.known(usage.total),
used_bytes=ObservedValue.known(usage.used),
free_bytes=ObservedValue.known(usage.free),
)
)
except OSError as exc:
reason = f"{type(exc).__name__}: path unavailable"
unavailable = ObservedValue[int].absent(Availability.UNAVAILABLE, reason)
storage.append(
StorageObservation(
purpose=purpose,
path=str(path),
total_bytes=unavailable,
used_bytes=unavailable,
free_bytes=unavailable,
)
)
release = platform.release()
environment = "wsl2" if "microsoft" in release.lower() else "native"
return HostInventory(
identity_key=identity_key,
identity_source=identity_source,
hostname=socket.gethostname(),
display_name=socket.gethostname(),
os_name=platform.system() or "unknown",
os_version=self._known_or_unknown(platform.version() or None, "OS version unavailable"),
architecture=platform.machine() or "unknown",
kernel_version=self._known_or_unknown(release or None, "kernel unavailable"),
cpu_model=self._known_or_unknown(cpu_model, "CPU model unavailable"),
logical_cpu_count=self._known_or_unknown(logical, "logical CPU count unavailable"),
physical_core_count=self._known_or_unknown(physical, "physical core count unavailable"),
total_ram_bytes=ObservedValue.known(memory.total),
available_ram_bytes=ObservedValue.known(memory.available),
agent_version=__version__,
storage=storage,
metadata={"environment": environment},
)
class NvidiaNvmlCollector:
def __init__(self, api: Any) -> None:
self.api = api
self.nvml_error = getattr(api, "NVMLError", Exception)
self.not_supported = getattr(api, "NVMLError_NotSupported", ())
def _optional(
self, call: Callable[[], T], transform: Callable[[T], Any] | None = None
) -> ObservedValue[Any]:
try:
value = call()
return ObservedValue.known(transform(value) if transform else value)
except self.not_supported:
return ObservedValue.absent(Availability.UNSUPPORTED, "NVML metric not supported")
except self.nvml_error as exc:
return ObservedValue.absent(Availability.TEMPORARILY_FAILED, type(exc).__name__)
@staticmethod
def _text(value: Any) -> str:
return value.decode("utf-8", errors="replace") if isinstance(value, bytes) else str(value)
@staticmethod
def _cuda_version(value: int) -> str:
return f"{value // 1000}.{(value % 1000) // 10}"
def _architecture(self, handle: Any) -> ObservedValue[str]:
def transform(value: Any) -> str:
for name in dir(self.api):
if name.startswith("NVML_DEVICE_ARCH_") and getattr(self.api, name) == value:
return name.removeprefix("NVML_DEVICE_ARCH_").lower()
return f"nvml_arch_{value}"
function = getattr(self.api, "nvmlDeviceGetArchitecture", None)
if function is None:
return ObservedValue.absent(Availability.UNSUPPORTED, "architecture API unavailable")
return self._optional(lambda: function(handle), transform)
def collect(self) -> NvidiaCollection:
initialized = False
try:
self.api.nvmlInit()
initialized = True
count = self.api.nvmlDeviceGetCount()
driver = self._optional(self.api.nvmlSystemGetDriverVersion, self._text)
cuda_function = getattr(self.api, "nvmlSystemGetCudaDriverVersion_v2", None) or getattr(
self.api, "nvmlSystemGetCudaDriverVersion", None
)
cuda_driver = (
self._optional(cuda_function, self._cuda_version)
if cuda_function
else ObservedValue.absent(Availability.UNSUPPORTED, "CUDA driver API unavailable")
)
inventory: list[AcceleratorInventory] = []
telemetry: list[AcceleratorTelemetry] = []
failures: list[str] = []
observed_at = datetime.now(UTC)
for index in range(count):
try:
handle = self.api.nvmlDeviceGetHandleByIndex(index)
device_uuid = self._text(self.api.nvmlDeviceGetUUID(handle))
name = self._text(self.api.nvmlDeviceGetName(handle))
memory = self.api.nvmlDeviceGetMemoryInfo(handle)
pci = self._optional(
lambda: self.api.nvmlDeviceGetPciInfo(handle),
lambda value: self._text(value.busId),
)
compute = self._optional(
lambda: self.api.nvmlDeviceGetCudaComputeCapability(handle)
)
major = (
ObservedValue.known(int(compute.value[0]))
if compute.availability is Availability.KNOWN and compute.value is not None
else ObservedValue.absent(compute.availability, compute.reason)
)
minor = (
ObservedValue.known(int(compute.value[1]))
if compute.availability is Availability.KNOWN and compute.value is not None
else ObservedValue.absent(compute.availability, compute.reason)
)
mig_function = getattr(self.api, "nvmlDeviceGetMigMode", None)
mig = (
self._optional(lambda: mig_function(handle), lambda value: bool(value[0]))
if mig_function
else ObservedValue.absent(Availability.UNSUPPORTED, "MIG API unavailable")
)
inventory.append(
AcceleratorInventory(
device_index=index,
device_uuid=device_uuid,
pci_bus_id=pci,
name=name,
architecture=self._architecture(handle),
compute_capability_major=major,
compute_capability_minor=minor,
total_vram_bytes=ObservedValue.known(int(memory.total)),
driver_version=driver,
cuda_driver_version=cuda_driver,
mig_mode_current=mig,
inventory_at=observed_at,
)
)
utilization = self._optional(
lambda: self.api.nvmlDeviceGetUtilizationRates(handle)
)
gpu_util = (
ObservedValue.known(int(utilization.value.gpu))
if utilization.availability is Availability.KNOWN
and utilization.value is not None
else ObservedValue.absent(utilization.availability, utilization.reason)
)
mem_util = (
ObservedValue.known(int(utilization.value.memory))
if utilization.availability is Availability.KNOWN
and utilization.value is not None
else ObservedValue.absent(utilization.availability, utilization.reason)
)
telemetry.append(
AcceleratorTelemetry(
device_uuid=device_uuid,
observed_at=observed_at,
used_vram_bytes=ObservedValue.known(int(memory.used)),
free_vram_bytes=ObservedValue.known(int(memory.free)),
gpu_utilization_percent=gpu_util,
memory_utilization_percent=mem_util,
temperature_c=self._optional(
lambda: self.api.nvmlDeviceGetTemperature(
handle, self.api.NVML_TEMPERATURE_GPU
),
int,
),
power_draw_w=self._optional(
lambda: self.api.nvmlDeviceGetPowerUsage(handle),
lambda value: round(value / 1000, 3),
),
power_limit_w=self._optional(
lambda: self.api.nvmlDeviceGetEnforcedPowerLimit(handle),
lambda value: round(value / 1000, 3),
),
graphics_clock_mhz=self._optional(
lambda: self.api.nvmlDeviceGetClockInfo(
handle, self.api.NVML_CLOCK_GRAPHICS
),
int,
),
memory_clock_mhz=self._optional(
lambda: self.api.nvmlDeviceGetClockInfo(
handle, self.api.NVML_CLOCK_MEM
),
int,
),
fan_speed_percent=self._optional(
lambda: self.api.nvmlDeviceGetFanSpeed(handle), int
),
performance_state=self._optional(
lambda: self.api.nvmlDeviceGetPerformanceState(handle),
lambda value: f"P{value}",
),
)
)
except self.nvml_error as exc:
failures.append(f"device {index}: {type(exc).__name__}")
availability = Availability.KNOWN if not failures else Availability.TEMPORARILY_FAILED
return NvidiaCollection(
availability=availability,
reason="; ".join(failures)
or ("no NVIDIA devices detected" if count == 0 else None),
inventory=inventory,
telemetry=telemetry,
observed_at=observed_at,
)
except self.nvml_error as exc:
return NvidiaCollection(
availability=Availability.UNAVAILABLE, reason=type(exc).__name__
)
finally:
if initialized:
with suppress(self.nvml_error):
self.api.nvmlShutdown()
def build_nvml_collector() -> NvidiaNvmlCollector:
import pynvml # type: ignore[import-untyped]
return NvidiaNvmlCollector(pynvml)
+660
View File
@@ -0,0 +1,660 @@
import asyncio
import math
import time
import uuid
from collections.abc import AsyncIterator, Awaitable, Callable, Iterator
from contextlib import asynccontextmanager, contextmanager, suppress
from typing import Any, cast
import structlog
from fastapi import FastAPI, HTTPException, Request, Response
from fastapi.concurrency import run_in_threadpool
from fastapi.exceptions import RequestValidationError
from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import JSONResponse
from sqlalchemy.exc import SQLAlchemyError
from sqlalchemy.orm import Session
from modelforge_api import __version__
from modelforge_api.api.authorization import (
AccessBoundary,
access_boundary_for_request,
authenticate_operator_token,
request_body_limit_bytes,
required_capability_for_request,
)
from modelforge_api.api.request_limits import RequestBodyLimitMiddleware
from modelforge_api.api.routes import (
acquisition,
agent,
catalog,
evaluation,
hardware,
health,
lifecycle,
migrations,
observability,
recovery,
registry,
runtime,
serving,
)
from modelforge_api.db import engine, get_session
from modelforge_api.domain.observability import metrics, route_class
from modelforge_api.domain.release import build_identity
from modelforge_api.services.evaluation import EvaluationError
from modelforge_api.services.hardware_polling import HardwarePollingService
from modelforge_api.services.lifecycle import LifecycleError, LifecycleService
from modelforge_api.services.manifest_registry import ManifestRegistry
from modelforge_api.services.migration_engine import (
MigrationEngineError,
MigrationEngineService,
)
from modelforge_api.services.node_agent import (
AgentProtocolError,
NodeAgentService,
NodeAuthenticationEvidence,
)
from modelforge_api.services.node_decommission import NodeDecommissionError
from modelforge_api.services.node_liveness import NodeLivenessPollingService
from modelforge_api.services.observability import ObservabilityError, ObservabilityService
from modelforge_api.services.observability_polling import ObservabilityPollingService
from modelforge_api.services.project_registry import sync_project_registry
from modelforge_api.services.recovery import RecoveryError, RecoveryService
from modelforge_api.services.registry import RegistryError, seed_candidate_registry
from modelforge_api.services.serving import (
CapabilityAuthenticationEvidence,
ServingError,
ServingService,
)
from modelforge_api.services.serving_reconciliation import ServingReconciliationService
from modelforge_api.services.startup_validation import enforce_startup
from modelforge_api.settings import Settings, get_settings
settings = get_settings()
structlog.configure(processors=[structlog.processors.JSONRenderer()])
logger = structlog.get_logger()
def interactive_api_enabled_for(environment: str) -> bool:
"""Keep schema explorers off deployed/test surfaces unless development is explicit."""
return environment == "development"
@asynccontextmanager
async def lifespan(_app: FastAPI) -> AsyncIterator[None]:
# Configuration and schema compatibility are checked before anything else runs. In production a
# problem here stops the process; elsewhere it is logged, because finding out at startup rather
# than at first use is the point either way.
identity = build_identity(
source_commit=settings.build_commit,
built_at=settings.build_timestamp,
image_digest=settings.build_image_digest,
)
logger.info("modelforge_starting", **identity.as_dict(), environment=settings.env)
validation = enforce_startup(settings, engine)
for problem in validation.problems:
logger.error(
"startup_configuration_problem",
code=str(problem.code),
setting=problem.setting,
detail=problem.message,
)
for problem in validation.warnings:
logger.warning(
"startup_configuration_warning",
code=str(problem.code),
setting=problem.setting,
detail=problem.message,
)
if settings.lifecycle_reconciliation_enabled:
with Session(engine) as session:
lifecycle_service = LifecycleService(session)
lifecycle_service.ensure_defaults()
reconciled = lifecycle_service.reconcile_incomplete()
if reconciled:
logger.warning("lifecycle_operations_reconciled", count=reconciled)
if settings.migration_reconciliation_enabled:
with Session(engine) as session:
migration_service = MigrationEngineService(session)
migration_service.ensure_defaults()
pending_migrations = migration_service.pending_reconciliation_count()
if pending_migrations:
# External state is deliberately never guessed during startup. An operator or
# adapter must report observed truth through the reconciliation endpoint.
logger.warning(
"migration_cutovers_require_reconciliation", count=pending_migrations
)
if settings.recovery_reconciliation_enabled:
with Session(engine) as session:
recovery_service = RecoveryService(session, settings, "control_plane", "startup")
recovery_service.ensure_defaults()
interrupted_backups = recovery_service.reconcile_interrupted_backups()
interrupted_restores = recovery_service.reconcile_interrupted_restores()
if interrupted_backups or interrupted_restores:
# An interrupted backup is never restore eligible and an interrupted restore is
# never silently resumed; both surface for explicit operator review.
logger.warning(
"recovery_operations_reconciled",
interrupted_backups=interrupted_backups,
interrupted_restores=interrupted_restores,
)
if settings.registry_seed_on_startup:
with Session(engine) as session:
manifests = ManifestRegistry(settings.config_root)
seed_candidate_registry(session, manifests)
sync_result = sync_project_registry(session, manifests)
if sync_result.unavailable_contracts:
logger.warning(
"project_bindings_waiting_for_contracts",
bindings=sync_result.unavailable_contracts,
)
poller = HardwarePollingService(settings)
hardware_task = (
asyncio.create_task(poller.run()) if settings.hardware_refresh_on_startup else None
)
liveness = NodeLivenessPollingService(settings)
liveness_task = (
asyncio.create_task(liveness.run()) if settings.node_liveness_monitor_enabled else None
)
serving_reconciliation = ServingReconciliationService(settings, engine)
serving_task = (
asyncio.create_task(serving_reconciliation.run())
if settings.serving_reconciliation_enabled
else None
)
observability_poller = ObservabilityPollingService(settings, engine)
if settings.observability_monitor_enabled:
with Session(engine) as session:
ObservabilityService(session, settings).ensure_defaults()
observability_task = (
asyncio.create_task(observability_poller.run())
if settings.observability_monitor_enabled
else None
)
yield
if observability_task:
observability_poller.stop()
await observability_task
if serving_task:
serving_reconciliation.stop()
await serving_task
if liveness_task:
liveness.stop()
await liveness_task
if hardware_task:
poller.stop()
await hardware_task
interactive_api_enabled = interactive_api_enabled_for(settings.env)
app = FastAPI(
title="ITWorx ModelForge API",
version=__version__,
description="Local AI ModelOps and GPU control-plane API",
lifespan=lifespan,
docs_url="/docs" if interactive_api_enabled else None,
redoc_url="/redoc" if interactive_api_enabled else None,
openapi_url="/openapi.json" if interactive_api_enabled else None,
)
app.include_router(health.router)
app.include_router(lifecycle.router)
app.include_router(migrations.router)
app.include_router(observability.router)
app.include_router(recovery.router)
app.include_router(agent.router)
app.include_router(hardware.router)
app.include_router(catalog.router)
app.include_router(registry.router)
app.include_router(acquisition.router)
app.include_router(runtime.router)
app.include_router(serving.router)
app.include_router(evaluation.router)
@app.exception_handler(LifecycleError)
async def lifecycle_error(request: Request, exc: LifecycleError) -> JSONResponse:
correlation_id = getattr(request.state, "correlation_id", "unknown")
return JSONResponse(
status_code=exc.status_code,
content={
"error": {
"code": exc.code,
"message": exc.message,
"correlation_id": correlation_id,
}
},
)
@app.exception_handler(MigrationEngineError)
async def migration_engine_error(request: Request, exc: MigrationEngineError) -> JSONResponse:
correlation_id = getattr(request.state, "correlation_id", "unknown")
return JSONResponse(
status_code=exc.status_code,
content={
"error": {
"code": exc.code,
"message": exc.message,
"correlation_id": correlation_id,
"details": {},
}
},
)
@app.exception_handler(RecoveryError)
async def recovery_error(request: Request, exc: RecoveryError) -> JSONResponse:
correlation_id = getattr(request.state, "correlation_id", "unknown")
return JSONResponse(
status_code=exc.status_code,
content={
"error": {
"code": exc.code,
"message": exc.message,
"correlation_id": correlation_id,
"details": exc.details,
}
},
)
@app.exception_handler(ObservabilityError)
async def observability_error(request: Request, exc: ObservabilityError) -> JSONResponse:
correlation_id = getattr(request.state, "correlation_id", "unknown")
return JSONResponse(
status_code=exc.status_code,
content={
"error": {
"code": exc.code,
"message": exc.message,
"correlation_id": correlation_id,
"details": {},
}
},
)
@contextmanager
def _authentication_session(application: FastAPI) -> Iterator[Session]:
"""Use a test/application session override when present, otherwise a short owned session."""
override = application.dependency_overrides.get(get_session)
if override is None:
with Session(engine) as session:
yield session
return
provided = override()
if isinstance(provided, Session):
yield provided
return
iterator = iter(cast(Any, provided))
session = next(iterator)
if not isinstance(session, Session):
raise TypeError("get_session override did not provide a SQLAlchemy Session")
try:
yield session
finally:
with suppress(StopIteration):
next(iterator)
def _authenticate_node_before_body(
application: FastAPI,
request_settings: Settings,
authorization: str | None,
) -> NodeAuthenticationEvidence:
service_override = application.dependency_overrides.get(agent.get_agent_service)
if service_override is not None:
service = service_override()
if not isinstance(service, NodeAgentService):
raise TypeError("node authentication service override has an invalid type")
_identity, evidence = service.authenticate_with_evidence(authorization)
service.session.commit()
return evidence
with _authentication_session(application) as session:
service = NodeAgentService(session, request_settings)
_identity, evidence = service.authenticate_with_evidence(authorization)
session.commit()
return evidence
def _authenticate_capability_before_body(
application: FastAPI,
request_settings: Settings,
authorization: str | None,
capability: str,
) -> CapabilityAuthenticationEvidence:
service_override = application.dependency_overrides.get(serving.get_serving_service)
if service_override is not None:
service = service_override()
if not isinstance(service, ServingService):
raise TypeError("capability authentication service override has an invalid type")
_client, evidence = service.authenticate_with_evidence(authorization, capability)
return evidence
with _authentication_session(application) as session:
service = ServingService(
session,
request_settings,
ManifestRegistry(request_settings.config_root),
None,
)
_client, evidence = service.authenticate_with_evidence(authorization, capability)
return evidence
def _authentication_error(
status_code: int,
code: str,
message: str,
correlation_id: str,
details: dict[str, Any] | None = None,
) -> JSONResponse:
return JSONResponse(
status_code=status_code,
content={
"error": {
"code": code,
"message": message,
"correlation_id": correlation_id,
"details": details or {},
}
},
)
@app.middleware("http")
async def correlation_middleware(
request: Request, call_next: Callable[[Request], Awaitable[Response]]
) -> Response:
started = time.perf_counter()
correlation_id = request.headers.get("x-correlation-id") or str(uuid.uuid4())
request.state.correlation_id = correlation_id
boundary = access_boundary_for_request(
request.method,
request.url.path,
)
settings_provider = cast(
Callable[[], Settings],
request.app.dependency_overrides.get(get_settings, get_settings),
)
request_settings = settings_provider()
request.state.request_body_limit_bytes = request_body_limit_bytes(
request_settings,
boundary,
)
response: Response | None = None
if boundary is AccessBoundary.CONTROL_PLANE:
try:
request.state.principal = authenticate_operator_token(
request_settings,
request.headers.get("x-modelforge-admin-token"),
)
except HTTPException as exc:
response = _authentication_error(
exc.status_code,
f"http_{exc.status_code}",
str(exc.detail),
correlation_id,
)
elif boundary is AccessBoundary.NODE:
try:
request.state.node_authentication = await run_in_threadpool(
_authenticate_node_before_body,
request.app,
request_settings,
request.headers.get("authorization"),
)
except AgentProtocolError as exc:
response = _authentication_error(
exc.status_code,
exc.code,
str(exc),
correlation_id,
)
except SQLAlchemyError:
response = _authentication_error(
503,
"machine_authentication_unavailable",
"node authentication backend is unavailable",
correlation_id,
)
elif boundary is AccessBoundary.CAPABILITY_CLIENT:
capability = required_capability_for_request(request.method, request.url.path)
if capability is None:
response = _authentication_error(
503,
"machine_authentication_unavailable",
"capability authentication policy is unavailable",
correlation_id,
)
else:
try:
request.state.capability_authentication = await run_in_threadpool(
_authenticate_capability_before_body,
request.app,
request_settings,
request.headers.get("authorization"),
capability,
)
except ServingError as exc:
response = _authentication_error(
exc.status_code,
exc.code,
str(exc),
correlation_id,
exc.details,
)
except SQLAlchemyError:
response = _authentication_error(
503,
"machine_authentication_unavailable",
"capability authentication backend is unavailable",
correlation_id,
)
if response is None:
response = await call_next(request)
duration_seconds = time.perf_counter() - started
response.headers["x-correlation-id"] = correlation_id
body_error_status_code = getattr(
request.state,
"request_body_error_status_code",
None,
)
effective_status_code = (
body_error_status_code
if isinstance(body_error_status_code, int)
else response.status_code
)
labels = {
"method": request.method,
"route_class": route_class(request.url.path),
}
metrics.increment(
"modelforge_api_requests_total",
{**labels, "status_class": f"{effective_status_code // 100}xx"},
)
metrics.observe("modelforge_api_request_duration_seconds", labels, duration_seconds)
logger.info(
"http_request",
correlation_id=correlation_id,
method=request.method,
path=request.url.path,
status_code=effective_status_code,
duration_ms=duration_seconds * 1000,
)
return response
# Starlette wraps user middleware in reverse registration order. Register CORS after the
# request limiter and correlation/authentication middleware so it remains outermost and decorates
# pre-body errors as well as responses produced by routing and exception handlers. The pure ASGI
# limiter stays outside BaseHTTPMiddleware so empty request frames cannot be normalized away before
# its progress counters observe them; authentication still runs before the first wrapped receive.
app.add_middleware(RequestBodyLimitMiddleware)
app.add_middleware(
CORSMiddleware,
allow_origins=settings.cors_origin_list,
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
MAX_ECHOED_INPUT_CHARACTERS = 200
def _renderable(value: object) -> object:
"""Render a rejected value so the error response can always be serialised.
A body may legally contain values JSON cannot round-trip — `NaN` and `Infinity` are accepted by
Python's parser but rejected by the serialiser — and echoing one raw made the error handler
itself fail, turning a 422 into a server error. Rejected input is also unbounded by definition,
so it is truncated rather than mirrored back in full.
"""
if isinstance(value, float) and not math.isfinite(value):
return repr(value)
if isinstance(value, str):
return value[:MAX_ECHOED_INPUT_CHARACTERS]
if isinstance(value, bytes):
return value[:MAX_ECHOED_INPUT_CHARACTERS].decode("utf-8", "replace")
if isinstance(value, list | tuple):
return [_renderable(item) for item in value[:20]]
if isinstance(value, dict):
return {str(key): _renderable(item) for key, item in list(value.items())[:20]}
if isinstance(value, bool | int | float | type(None)):
return value
return repr(value)[:MAX_ECHOED_INPUT_CHARACTERS]
@app.exception_handler(RequestValidationError)
async def validation_error(request: Request, exc: RequestValidationError) -> JSONResponse:
correlation_id = getattr(request.state, "correlation_id", "unknown")
return JSONResponse(
status_code=422,
content={
"error": {
"code": "request_validation_failed",
"message": "Request validation failed",
"correlation_id": correlation_id,
"details": {
"errors": [
{key: _renderable(value) for key, value in error.items() if key != "ctx"}
for error in exc.errors()
]
},
}
},
)
@app.exception_handler(HTTPException)
async def http_error(request: Request, exc: HTTPException) -> JSONResponse:
correlation_id = getattr(request.state, "correlation_id", "unknown")
return JSONResponse(
status_code=exc.status_code,
content={
"error": {
"code": f"http_{exc.status_code}",
"message": str(exc.detail),
"correlation_id": correlation_id,
"details": {},
}
},
)
@app.exception_handler(AgentProtocolError)
async def agent_protocol_error(request: Request, exc: AgentProtocolError) -> JSONResponse:
correlation_id = getattr(request.state, "correlation_id", "unknown")
return JSONResponse(
status_code=exc.status_code,
content={
"error": {
"code": exc.code,
"message": str(exc),
"correlation_id": correlation_id,
"details": {},
}
},
)
@app.exception_handler(NodeDecommissionError)
async def node_decommission_error(request: Request, exc: NodeDecommissionError) -> JSONResponse:
correlation_id = getattr(request.state, "correlation_id", "unknown")
return JSONResponse(
status_code=exc.status_code,
content={
"error": {
"code": exc.code,
"message": exc.message,
"correlation_id": correlation_id,
"details": exc.details,
}
},
)
@app.exception_handler(RegistryError)
async def registry_error(request: Request, exc: RegistryError) -> JSONResponse:
correlation_id = getattr(request.state, "correlation_id", "unknown")
return JSONResponse(
status_code=exc.status_code,
content={
"error": {
"code": exc.code,
"message": str(exc),
"correlation_id": correlation_id,
"details": exc.details,
}
},
)
@app.exception_handler(ServingError)
async def serving_error(request: Request, exc: ServingError) -> JSONResponse:
correlation_id = getattr(request.state, "correlation_id", "unknown")
return JSONResponse(
status_code=exc.status_code,
content={
"error": {
"code": exc.code,
"message": str(exc),
"correlation_id": correlation_id,
"details": exc.details,
}
},
)
@app.exception_handler(EvaluationError)
async def evaluation_error(request: Request, exc: EvaluationError) -> JSONResponse:
correlation_id = getattr(request.state, "correlation_id", "unknown")
return JSONResponse(
status_code=exc.status_code,
content={
"error": {
"code": exc.code,
"message": str(exc),
"correlation_id": correlation_id,
"details": exc.details,
}
},
)
@app.get("/")
def root() -> dict[str, str]:
return {
"name": "ITWorx ModelForge",
"version": __version__,
"docs": "/docs" if interactive_api_enabled else "disabled",
"api": "/api/v1",
}
@@ -0,0 +1,3 @@
from .models import Base
__all__ = ["Base"]
@@ -0,0 +1,123 @@
from __future__ import annotations
import uuid
from datetime import datetime
from typing import TypeVar
from sqlalchemy import or_, select
from sqlalchemy.orm import Session
from modelforge_api.persistence.models import (
ArtifactInspection,
ArtifactJob,
ArtifactSet,
DownloadPlan,
DownloadPlanFile,
UpstreamFile,
UpstreamSnapshot,
)
T = TypeVar("T")
class AcquisitionRepository:
def __init__(self, session: Session) -> None:
self.session = session
def add(self, entity: T) -> T:
self.session.add(entity)
self.session.flush()
return entity
def latest_snapshot(self, model_id: uuid.UUID) -> UpstreamSnapshot | None:
return self.session.scalar(
select(UpstreamSnapshot)
.where(UpstreamSnapshot.model_id == model_id)
.order_by(UpstreamSnapshot.observed_at.desc(), UpstreamSnapshot.id.desc())
.limit(1)
)
def snapshot(self, snapshot_id: uuid.UUID) -> UpstreamSnapshot | None:
return self.session.get(UpstreamSnapshot, snapshot_id)
def files(self, snapshot_id: uuid.UUID) -> list[UpstreamFile]:
return list(
self.session.scalars(
select(UpstreamFile)
.where(UpstreamFile.snapshot_id == snapshot_id)
.order_by(UpstreamFile.path)
)
)
def artifact_set(self, artifact_set_id: uuid.UUID) -> ArtifactSet | None:
return self.session.get(ArtifactSet, artifact_set_id)
def artifact_set_for_variant(self, revision_id: uuid.UUID, variant: str) -> ArtifactSet | None:
return self.session.scalar(
select(ArtifactSet).where(
ArtifactSet.revision_id == revision_id, ArtifactSet.variant_key == variant
)
)
def artifact_sets(self, revision_id: uuid.UUID) -> list[ArtifactSet]:
return list(
self.session.scalars(
select(ArtifactSet)
.where(ArtifactSet.revision_id == revision_id)
.order_by(ArtifactSet.variant_key)
)
)
def plan(self, plan_id: uuid.UUID) -> DownloadPlan | None:
return self.session.get(DownloadPlan, plan_id)
def plan_by_key(self, key: str) -> DownloadPlan | None:
return self.session.scalar(select(DownloadPlan).where(DownloadPlan.idempotency_key == key))
def plan_files(self, plan_id: uuid.UUID) -> list[DownloadPlanFile]:
return list(
self.session.scalars(
select(DownloadPlanFile)
.where(DownloadPlanFile.plan_id == plan_id)
.order_by(DownloadPlanFile.ordinal)
)
)
def job(self, job_id: uuid.UUID) -> ArtifactJob | None:
return self.session.get(ArtifactJob, job_id)
def job_for_plan(self, plan_id: uuid.UUID) -> ArtifactJob | None:
return self.session.scalar(select(ArtifactJob).where(ArtifactJob.plan_id == plan_id))
def jobs(self, limit: int = 100) -> list[ArtifactJob]:
return list(
self.session.scalars(
select(ArtifactJob).order_by(ArtifactJob.created_at.desc()).limit(limit)
)
)
def claimable_job(self, node_id: uuid.UUID, now: datetime) -> ArtifactJob | None:
return self.session.scalar(
select(ArtifactJob)
.where(
ArtifactJob.compute_node_id == node_id,
ArtifactJob.cancel_requested.is_(False),
or_(
ArtifactJob.status == "queued",
(ArtifactJob.status.in_(("claimed", "downloading", "verifying", "promoting")))
& (ArtifactJob.lease_expires_at < now),
),
)
.order_by(ArtifactJob.created_at)
.with_for_update(skip_locked=True)
.limit(1)
)
def inspections(self, job_id: uuid.UUID) -> list[ArtifactInspection]:
return list(
self.session.scalars(
select(ArtifactInspection)
.where(ArtifactInspection.job_id == job_id)
.order_by(ArtifactInspection.file_path, ArtifactInspection.inspection_type)
)
)
@@ -0,0 +1,37 @@
"""Immutable names for the PostgreSQL audit privilege boundary.
Migration 0024 owns the DDL. Runtime code keeps only the callable contract and catalog policy
names here; it never receives the owner/migration credential or an arbitrary privileged SQL path.
"""
from __future__ import annotations
AUDIT_OWNER_ROLE = "modelforge"
AUDIT_RUNTIME_ROLE = "modelforge_runtime"
AUDIT_FUNCTION_SCHEMA = "modelforge_audit"
AUDIT_APPEND_FUNCTION = "append_event_v2"
AUDIT_MUTATION_TRIGGER_FUNCTION = "enforce_owner_mutation"
AUDIT_EVENT_MUTATION_TRIGGER = "trg_modelforge_audit_events_owner"
AUDIT_EVENT_TRUNCATE_TRIGGER = "trg_modelforge_audit_events_truncate_owner"
AUDIT_HEAD_MUTATION_TRIGGER = "trg_modelforge_audit_head_owner"
AUDIT_HEAD_TRUNCATE_TRIGGER = "trg_modelforge_audit_head_truncate_owner"
AUDIT_FUNCTION_SEARCH_PATH = "pg_catalog"
# ``pg_proc.prosrc`` hashes for the exact migration-0024 function bodies. Production startup
# attests these as well as owner/grants/search_path: a same-signature replacement is not the
# canonical boundary. The static migration test derives both values from its immutable SQL so a
# body edit cannot silently drift this copy.
AUDIT_APPEND_BODY_SHA256 = "c9154911f1b1b70f77fe2642de156c98358cd0423f0c3e2a75bc4f1ee4409a86"
AUDIT_GUARD_BODY_SHA256 = "8af5fc31f3b2c729445baaae902eccd9cb3d70b257d0a19e6399f538ee43c6bf"
POSTGRES_APPEND_AUDIT_SQL = """
select event_id, sequence, event_hash, occurred_at
from modelforge_audit.append_event_v2(
cast(:event_id as uuid), cast(:occurred_at as timestamptz),
:correlation_id, :actor_type, :actor_id, :action, :resource_type,
:resource_id, :outcome, cast(:details_json as jsonb),
:expected_event_count, :expected_last_sequence, :expected_last_event_hash,
:expected_hash_format, :expected_v2_start_sequence,
:expected_legacy_prefix_count, :expected_legacy_prefix_seal
)
""".strip()
@@ -0,0 +1,126 @@
from __future__ import annotations
import uuid
from typing import TypeVar
from sqlalchemy import select
from sqlalchemy.orm import Session
from modelforge_api.persistence.models import (
Accelerator,
AcceleratorTelemetryLatest,
ComputeNode,
HardwareInventoryRun,
HostTelemetryLatest,
NodeCredential,
NodeEnrollment,
StorageVolumeState,
)
T = TypeVar("T")
class HardwareRepository:
def __init__(self, session: Session) -> None:
self.session = session
def node_by_key(self, key: str) -> ComputeNode | None:
return self.session.scalar(select(ComputeNode).where(ComputeNode.key == key))
def node_by_key_for_update(self, key: str) -> ComputeNode | None:
return self.session.scalar(
select(ComputeNode).where(ComputeNode.key == key).with_for_update()
)
def nodes(self) -> list[ComputeNode]:
return list(self.session.scalars(select(ComputeNode).order_by(ComputeNode.hostname)))
def node(self, node_id: uuid.UUID) -> ComputeNode | None:
return self.session.get(ComputeNode, node_id)
def node_for_update(self, node_id: uuid.UUID) -> ComputeNode | None:
return self.session.scalar(
select(ComputeNode).where(ComputeNode.id == node_id).with_for_update()
)
def accelerators(self, node_id: uuid.UUID | None = None) -> list[Accelerator]:
query = select(Accelerator).order_by(Accelerator.device_index)
if node_id is not None:
query = query.where(Accelerator.compute_node_id == node_id)
return list(self.session.scalars(query))
def accelerator(self, accelerator_id: uuid.UUID) -> Accelerator | None:
return self.session.get(Accelerator, accelerator_id)
def accelerator_by_uuid(self, node_id: uuid.UUID, device_uuid: str) -> Accelerator | None:
return self.session.scalar(
select(Accelerator).where(
Accelerator.compute_node_id == node_id, Accelerator.device_uuid == device_uuid
)
)
def host_telemetry(self, node_id: uuid.UUID) -> HostTelemetryLatest | None:
return self.session.scalar(
select(HostTelemetryLatest).where(HostTelemetryLatest.compute_node_id == node_id)
)
def accelerator_telemetry(self, accelerator_id: uuid.UUID) -> AcceleratorTelemetryLatest | None:
return self.session.scalar(
select(AcceleratorTelemetryLatest).where(
AcceleratorTelemetryLatest.accelerator_id == accelerator_id
)
)
def storage(self, node_id: uuid.UUID) -> list[StorageVolumeState]:
return list(
self.session.scalars(
select(StorageVolumeState)
.where(StorageVolumeState.compute_node_id == node_id)
.order_by(StorageVolumeState.purpose)
)
)
def storage_by_key(
self, node_id: uuid.UUID, purpose: str, path: str
) -> StorageVolumeState | None:
return self.session.scalar(
select(StorageVolumeState).where(
StorageVolumeState.compute_node_id == node_id,
StorageVolumeState.purpose == purpose,
StorageVolumeState.path == path,
)
)
def latest_run(self) -> HardwareInventoryRun | None:
return self.session.scalar(
select(HardwareInventoryRun).order_by(HardwareInventoryRun.started_at.desc()).limit(1)
)
def enrollment(self, enrollment_id: uuid.UUID) -> NodeEnrollment | None:
return self.session.get(NodeEnrollment, enrollment_id)
def enrollment_by_hash(self, token_hash: str) -> NodeEnrollment | None:
return self.session.scalar(
select(NodeEnrollment).where(NodeEnrollment.token_hash == token_hash)
)
def enrollments(self) -> list[NodeEnrollment]:
return list(
self.session.scalars(select(NodeEnrollment).order_by(NodeEnrollment.created_at.desc()))
)
def credential(self, credential_id: uuid.UUID) -> NodeCredential | None:
return self.session.get(NodeCredential, credential_id)
def active_credential_for_node(self, node_id: uuid.UUID) -> NodeCredential | None:
return self.session.scalar(
select(NodeCredential).where(
NodeCredential.compute_node_id == node_id,
NodeCredential.revoked_at.is_(None),
)
)
def add(self, entity: T) -> T:
self.session.add(entity)
self.session.flush()
return entity
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,288 @@
from __future__ import annotations
import uuid
from typing import Any, TypeVar
from sqlalchemy import func, select
from sqlalchemy.orm import Session, selectinload
from modelforge_api.persistence.models import (
ArtifactLocation,
DerivedArtifact,
DerivedArtifactSource,
Model,
ModelArtifact,
ModelRevision,
RuntimeProfile,
StorageRoot,
)
T = TypeVar("T")
class RegistryRepository:
"""Persistence-only operations for the M2 registry aggregate."""
def __init__(self, session: Session) -> None:
self.session = session
def page_models(
self,
*,
page: int,
page_size: int,
search: str | None = None,
lifecycle: str | None = None,
source_type: str | None = None,
) -> tuple[list[Model], int]:
query = select(Model)
count_query = select(func.count()).select_from(Model)
filters: list[Any] = []
if search:
pattern = f"%{search.lower()}%"
filters.append(
func.lower(Model.display_name).like(pattern)
| func.lower(Model.key).like(pattern)
| func.lower(Model.upstream_source).like(pattern)
)
if lifecycle:
filters.append(Model.lifecycle == lifecycle)
if source_type:
filters.append(Model.source_type == source_type)
if filters:
query = query.where(*filters)
count_query = count_query.where(*filters)
total = int(self.session.scalar(count_query) or 0)
items = list(
self.session.scalars(
query.options(selectinload(Model.revisions).selectinload(ModelRevision.artifacts))
.order_by(Model.display_name, Model.id)
.offset((page - 1) * page_size)
.limit(page_size)
)
)
return items, total
def model(self, model_id: uuid.UUID) -> Model | None:
return self.session.scalar(
select(Model)
.where(Model.id == model_id)
.options(selectinload(Model.revisions).selectinload(ModelRevision.artifacts))
)
def model_by_key(self, key: str) -> Model | None:
return self.session.scalar(select(Model).where(Model.key == key))
def model_by_source(self, source: str) -> Model | None:
return self.session.scalar(select(Model).where(Model.upstream_source == source))
def revision(self, revision_id: uuid.UUID) -> ModelRevision | None:
return self.session.get(ModelRevision, revision_id)
def revision_by_commit(self, model_id: uuid.UUID, commit: str) -> ModelRevision | None:
return self.session.scalar(
select(ModelRevision).where(
ModelRevision.model_id == model_id,
ModelRevision.resolved_commit_sha == commit,
)
)
def revisions(self, model_id: uuid.UUID) -> list[ModelRevision]:
return list(
self.session.scalars(
select(ModelRevision)
.where(ModelRevision.model_id == model_id)
.order_by(ModelRevision.discovered_at.desc())
)
)
def artifact(self, artifact_id: uuid.UUID) -> ModelArtifact | None:
return self.session.scalar(
select(ModelArtifact)
.where(ModelArtifact.id == artifact_id)
.options(selectinload(ModelArtifact.locations))
)
def artifact_by_digest(self, revision_id: uuid.UUID, digest: str) -> ModelArtifact | None:
return self.session.scalar(
select(ModelArtifact).where(
ModelArtifact.revision_id == revision_id, ModelArtifact.sha256 == digest
)
)
def artifacts(self, revision_id: uuid.UUID) -> list[ModelArtifact]:
return list(
self.session.scalars(
select(ModelArtifact)
.where(ModelArtifact.revision_id == revision_id)
.options(selectinload(ModelArtifact.locations))
.order_by(ModelArtifact.filename)
)
)
def derived(self, derived_id: uuid.UUID) -> DerivedArtifact | None:
return self.session.scalar(
select(DerivedArtifact)
.where(DerivedArtifact.id == derived_id)
.options(
selectinload(DerivedArtifact.source_artifacts),
selectinload(DerivedArtifact.locations),
)
)
def derived_for_revision(self, revision_id: uuid.UUID) -> list[DerivedArtifact]:
return list(
self.session.scalars(
select(DerivedArtifact)
.where(DerivedArtifact.revision_id == revision_id)
.options(
selectinload(DerivedArtifact.source_artifacts),
selectinload(DerivedArtifact.locations),
)
.order_by(DerivedArtifact.filename)
)
)
def derived_sources(self, derived_id: uuid.UUID) -> list[DerivedArtifactSource]:
return list(
self.session.scalars(
select(DerivedArtifactSource)
.where(DerivedArtifactSource.derived_artifact_id == derived_id)
.order_by(DerivedArtifactSource.ordinal)
)
)
def storage_root(self, root_id: uuid.UUID) -> StorageRoot | None:
return self.session.get(StorageRoot, root_id)
def storage_roots(self, node_id: uuid.UUID | None = None) -> list[StorageRoot]:
query = select(StorageRoot).order_by(StorageRoot.name)
if node_id:
query = query.where(StorageRoot.compute_node_id == node_id)
return list(self.session.scalars(query))
def location(self, location_id: uuid.UUID) -> ArtifactLocation | None:
return self.session.get(ArtifactLocation, location_id)
def add(self, entity: T) -> T:
self.session.add(entity)
self.session.flush()
return entity
def dependencies(self, resource_type: str, resource_id: uuid.UUID) -> list[dict[str, str]]:
dependencies: list[dict[str, str]] = []
if resource_type == "model":
ids = self.session.scalars(
select(ModelRevision.id).where(ModelRevision.model_id == resource_id)
)
dependencies.extend(
{
"resource_type": "model_revision",
"resource_id": str(item),
"relation": "revision",
}
for item in ids
)
elif resource_type == "model_revision":
ids = self.session.scalars(
select(ModelArtifact.id).where(ModelArtifact.revision_id == resource_id)
)
dependencies.extend(
{
"resource_type": "model_artifact",
"resource_id": str(item),
"relation": "artifact",
}
for item in ids
)
derived_ids = self.session.scalars(
select(DerivedArtifact.id).where(DerivedArtifact.revision_id == resource_id)
)
dependencies.extend(
{
"resource_type": "derived_artifact",
"resource_id": str(item),
"relation": "derived",
}
for item in derived_ids
)
elif resource_type == "model_artifact":
links = self.session.scalars(
select(DerivedArtifactSource.derived_artifact_id).where(
DerivedArtifactSource.source_artifact_id == resource_id
)
)
dependencies.extend(
{
"resource_type": "derived_artifact",
"resource_id": str(item),
"relation": "source",
}
for item in links
)
locations = self.session.scalars(
select(ArtifactLocation.id).where(ArtifactLocation.artifact_id == resource_id)
)
dependencies.extend(
{
"resource_type": "artifact_location",
"resource_id": str(item),
"relation": "location",
}
for item in locations
)
artifact = self.session.get(ModelArtifact, resource_id)
if artifact:
profiles = self.session.scalars(
select(RuntimeProfile.id).where(
RuntimeProfile.artifact_sha256 == artifact.sha256
)
)
dependencies.extend(
{
"resource_type": "runtime_profile",
"resource_id": str(item),
"relation": "digest",
}
for item in profiles
)
elif resource_type == "derived_artifact":
sources = self.session.scalars(
select(DerivedArtifactSource.source_artifact_id).where(
DerivedArtifactSource.derived_artifact_id == resource_id
)
)
dependencies.extend(
{
"resource_type": "model_artifact",
"resource_id": str(item),
"relation": "source_lineage",
}
for item in sources
)
locations = self.session.scalars(
select(ArtifactLocation.id).where(
ArtifactLocation.derived_artifact_id == resource_id
)
)
dependencies.extend(
{
"resource_type": "artifact_location",
"resource_id": str(item),
"relation": "location",
}
for item in locations
)
elif resource_type == "storage_root":
locations = self.session.scalars(
select(ArtifactLocation.id).where(ArtifactLocation.storage_root_id == resource_id)
)
dependencies.extend(
{
"resource_type": "artifact_location",
"resource_id": str(item),
"relation": "location",
}
for item in locations
)
return dependencies
@@ -0,0 +1,64 @@
from __future__ import annotations
from typing import Any
from sqlalchemy import select
from sqlalchemy.orm import Session
from modelforge_api.services.audit import AuditContext, AuditWriter
from .models import AuditEvent, Base, Model
class Repository[Entity: Base]:
def __init__(self, session: Session, entity_type: type[Entity]) -> None:
self.session = session
self.entity_type = entity_type
def get(self, entity_id: Any) -> Entity | None:
return self.session.get(self.entity_type, entity_id)
def list(self) -> list[Entity]:
return list(self.session.scalars(select(self.entity_type)))
def add(self, entity: Entity) -> Entity:
self.session.add(entity)
self.session.flush()
return entity
class ModelRepository(Repository[Model]):
def __init__(self, session: Session) -> None:
super().__init__(session, Model)
class AuditRepository:
def __init__(self, session: Session) -> None:
self.session = session
def append(
self,
*,
correlation_id: str,
actor_type: str,
actor_id: str,
action: str,
resource_type: str,
resource_id: str | None,
outcome: str,
details: dict[str, Any] | None = None,
) -> AuditEvent:
return AuditWriter(
self.session,
context=AuditContext(
correlation_id=correlation_id,
actor_type=actor_type,
actor_id=actor_id,
),
).write(
action=action,
resource_type=resource_type,
resource_id=resource_id,
outcome=outcome,
details=details or {},
)
@@ -0,0 +1,23 @@
"""Identity registry for Engines constructed by the application process.
Alembic creates its own Engine and is intentionally absent. This registry is only a SQLite/test
and accidental-misuse defense; PostgreSQL permissions and triggers are the production boundary.
"""
from __future__ import annotations
from weakref import WeakSet
from sqlalchemy.engine import Connection, Engine
_APPLICATION_ENGINES: WeakSet[Engine] = WeakSet()
def register_application_engine(engine: Engine) -> Engine:
_APPLICATION_ENGINES.add(engine)
return engine
def is_application_connection(connection: Connection) -> bool:
return connection.engine in _APPLICATION_ENGINES
@@ -0,0 +1,187 @@
from __future__ import annotations
import uuid
from datetime import datetime
from typing import TypeVar
from sqlalchemy import or_, select
from sqlalchemy.orm import Session
from modelforge_api.persistence.models import (
Accelerator,
ArtifactLocation,
ArtifactSet,
ArtifactSetMember,
ComputeNode,
DeploymentCandidate,
ExecutionApproval,
Model,
ModelArtifact,
ModelRevision,
RuntimeCompatibilityAssessment,
RuntimeEnvironment,
RuntimeProbe,
RuntimeProbeMetric,
RuntimeProfile,
StorageRoot,
)
T = TypeVar("T")
class RuntimeRepository:
def __init__(self, session: Session) -> None:
self.session = session
def add(self, entity: T) -> T:
self.session.add(entity)
self.session.flush()
return entity
def environment(self, environment_id: uuid.UUID) -> RuntimeEnvironment | None:
return self.session.get(RuntimeEnvironment, environment_id)
def environment_by_fingerprint(self, fingerprint: str) -> RuntimeEnvironment | None:
return self.session.scalar(
select(RuntimeEnvironment).where(RuntimeEnvironment.fingerprint == fingerprint)
)
def environments(self) -> list[RuntimeEnvironment]:
return list(
self.session.scalars(
select(RuntimeEnvironment).order_by(RuntimeEnvironment.created_at.desc())
)
)
def profile(self, profile_id: uuid.UUID) -> RuntimeProfile | None:
return self.session.get(RuntimeProfile, profile_id)
def profile_by_fingerprint(self, fingerprint: str) -> RuntimeProfile | None:
return self.session.scalar(
select(RuntimeProfile).where(RuntimeProfile.fingerprint == fingerprint)
)
def profiles(self) -> list[RuntimeProfile]:
return list(
self.session.scalars(select(RuntimeProfile).order_by(RuntimeProfile.created_at.desc()))
)
def artifact_set(self, artifact_set_id: uuid.UUID) -> ArtifactSet | None:
return self.session.get(ArtifactSet, artifact_set_id)
def revision(self, revision_id: uuid.UUID) -> ModelRevision | None:
return self.session.get(ModelRevision, revision_id)
def model(self, model_id: uuid.UUID) -> Model | None:
return self.session.get(Model, model_id)
def set_artifacts(
self, artifact_set_id: uuid.UUID
) -> list[tuple[ArtifactSetMember, ModelArtifact]]:
rows = self.session.execute(
select(ArtifactSetMember, ModelArtifact)
.join(ModelArtifact, ModelArtifact.id == ArtifactSetMember.artifact_id)
.where(ArtifactSetMember.artifact_set_id == artifact_set_id)
.order_by(ArtifactSetMember.ordinal)
)
return [(member, artifact) for member, artifact in rows]
def artifact_locations(self, artifact_ids: list[uuid.UUID]) -> list[ArtifactLocation]:
if not artifact_ids:
return []
return list(
self.session.scalars(
select(ArtifactLocation).where(ArtifactLocation.artifact_id.in_(artifact_ids))
)
)
def storage_root(self, root_id: uuid.UUID) -> StorageRoot | None:
return self.session.get(StorageRoot, root_id)
def node(self, node_id: uuid.UUID) -> ComputeNode | None:
return self.session.get(ComputeNode, node_id)
def accelerators(self, node_id: uuid.UUID) -> list[Accelerator]:
return list(
self.session.scalars(
select(Accelerator).where(
Accelerator.compute_node_id == node_id,
Accelerator.status == "active",
)
)
)
def assessment(self, assessment_id: uuid.UUID) -> RuntimeCompatibilityAssessment | None:
return self.session.get(RuntimeCompatibilityAssessment, assessment_id)
def assessments(self) -> list[RuntimeCompatibilityAssessment]:
return list(
self.session.scalars(
select(RuntimeCompatibilityAssessment).order_by(
RuntimeCompatibilityAssessment.created_at.desc()
)
)
)
def approval(self, approval_id: uuid.UUID) -> ExecutionApproval | None:
return self.session.get(ExecutionApproval, approval_id)
def approvals(self, artifact_set_id: uuid.UUID | None = None) -> list[ExecutionApproval]:
query = select(ExecutionApproval).order_by(ExecutionApproval.approved_at.desc())
if artifact_set_id:
query = query.where(ExecutionApproval.artifact_set_id == artifact_set_id)
return list(self.session.scalars(query))
def probe(self, probe_id: uuid.UUID) -> RuntimeProbe | None:
return self.session.get(RuntimeProbe, probe_id)
def probe_by_key(self, key: str) -> RuntimeProbe | None:
return self.session.scalar(select(RuntimeProbe).where(RuntimeProbe.idempotency_key == key))
def probes(self) -> list[RuntimeProbe]:
return list(
self.session.scalars(select(RuntimeProbe).order_by(RuntimeProbe.created_at.desc()))
)
def claimable_probe(self, node_id: uuid.UUID, now: datetime) -> RuntimeProbe | None:
return self.session.scalar(
select(RuntimeProbe)
.where(
RuntimeProbe.compute_node_id == node_id,
RuntimeProbe.cancel_requested.is_(False),
or_(
RuntimeProbe.status == "queued",
RuntimeProbe.status.in_(
("preparing", "loading", "healthchecking", "ready", "unloading")
)
& (RuntimeProbe.lease_expires_at < now),
),
)
.order_by(RuntimeProbe.created_at)
.with_for_update(skip_locked=True)
.limit(1)
)
def candidate_for_probe(self, probe_id: uuid.UUID) -> DeploymentCandidate | None:
return self.session.scalar(
select(DeploymentCandidate).where(DeploymentCandidate.runtime_probe_id == probe_id)
)
def candidate(self, candidate_id: uuid.UUID) -> DeploymentCandidate | None:
return self.session.get(DeploymentCandidate, candidate_id)
def candidates(self) -> list[DeploymentCandidate]:
return list(
self.session.scalars(
select(DeploymentCandidate).order_by(DeploymentCandidate.created_at.desc())
)
)
def metrics(self, probe_id: uuid.UUID) -> list[RuntimeProbeMetric]:
return list(
self.session.scalars(
select(RuntimeProbeMetric)
.where(RuntimeProbeMetric.runtime_probe_id == probe_id)
.order_by(RuntimeProbeMetric.observed_at)
)
)
@@ -0,0 +1,424 @@
from __future__ import annotations
import uuid
from datetime import datetime, timedelta
from typing import TypeVar
from sqlalchemy import case, delete, func, or_, select
from sqlalchemy.orm import Session
from modelforge_api.persistence.models import (
Accelerator,
AcceleratorTelemetryLatest,
ArtifactLocation,
ArtifactSet,
ArtifactSetMember,
Capability,
CapabilityContract,
CapabilityDeployment,
CapabilityExperimentRoute,
CapabilityResourceEnvelope,
ComputeNode,
CoResidencyEvidence,
DeploymentCandidate,
EmbeddingSpace,
ExecutionApproval,
GatewayRequest,
ModelArtifact,
ModelRevision,
PlacementPlanRecord,
ProductionExecutionApproval,
ResidencyAllocation,
RuntimeEnvironment,
RuntimeProbe,
RuntimeProfile,
SchedulerAcceleratorState,
SchedulerPolicyRevision,
ServiceClient,
ServiceCredential,
ServingGpuLease,
ServingJob,
StorageRoot,
)
T = TypeVar("T")
class ServingRepository:
def __init__(self, session: Session) -> None:
self.session = session
def add(self, entity: T) -> T:
self.session.add(entity)
self.session.flush()
return entity
def capability(self, key: str) -> Capability | None:
return self.session.scalar(select(Capability).where(Capability.key == key))
def contract(self, key: str, version: int) -> CapabilityContract | None:
return self.session.scalar(
select(CapabilityContract)
.join(Capability, Capability.id == CapabilityContract.capability_id)
.where(Capability.key == key, CapabilityContract.version == version)
)
def candidate(self, candidate_id: uuid.UUID) -> DeploymentCandidate | None:
return self.session.get(DeploymentCandidate, candidate_id)
def probe(self, probe_id: uuid.UUID) -> RuntimeProbe | None:
return self.session.get(RuntimeProbe, probe_id)
def profile(self, profile_id: uuid.UUID) -> RuntimeProfile | None:
return self.session.get(RuntimeProfile, profile_id)
def environment(self, environment_id: uuid.UUID) -> RuntimeEnvironment | None:
return self.session.get(RuntimeEnvironment, environment_id)
def artifact_set(self, artifact_set_id: uuid.UUID) -> ArtifactSet | None:
return self.session.get(ArtifactSet, artifact_set_id)
def revision(self, revision_id: uuid.UUID) -> ModelRevision | None:
return self.session.get(ModelRevision, revision_id)
def node(self, node_id: uuid.UUID) -> ComputeNode | None:
return self.session.get(ComputeNode, node_id)
def accelerator(self, accelerator_id: uuid.UUID) -> Accelerator | None:
return self.session.get(Accelerator, accelerator_id)
def node_accelerators(self, node_id: uuid.UUID) -> list[Accelerator]:
return list(
self.session.scalars(
select(Accelerator).where(
Accelerator.compute_node_id == node_id,
Accelerator.status == "active",
)
)
)
def lock_accelerator(self, accelerator_id: uuid.UUID) -> Accelerator | None:
return self.session.scalar(
select(Accelerator).where(Accelerator.id == accelerator_id).with_for_update()
)
def telemetry(self, accelerator_id: uuid.UUID) -> AcceleratorTelemetryLatest | None:
return self.session.scalar(
select(AcceleratorTelemetryLatest).where(
AcceleratorTelemetryLatest.accelerator_id == accelerator_id
)
)
def set_artifacts(self, artifact_set_id: uuid.UUID) -> list[ModelArtifact]:
return list(
self.session.scalars(
select(ModelArtifact)
.join(ArtifactSetMember, ArtifactSetMember.artifact_id == ModelArtifact.id)
.where(ArtifactSetMember.artifact_set_id == artifact_set_id)
.order_by(ArtifactSetMember.ordinal)
)
)
def artifact_locations(self, artifact_ids: list[uuid.UUID]) -> list[ArtifactLocation]:
return list(
self.session.scalars(
select(ArtifactLocation).where(ArtifactLocation.artifact_id.in_(artifact_ids))
)
)
def storage_root(self, root_id: uuid.UUID) -> StorageRoot | None:
return self.session.get(StorageRoot, root_id)
def approval_by_fingerprint(self, fingerprint: str) -> ProductionExecutionApproval | None:
return self.session.scalar(
select(ProductionExecutionApproval).where(
ProductionExecutionApproval.evidence_fingerprint == fingerprint
)
)
def approval(self, approval_id: uuid.UUID) -> ProductionExecutionApproval | None:
return self.session.get(ProductionExecutionApproval, approval_id)
def execution_approval(self, approval_id: uuid.UUID) -> ExecutionApproval | None:
return self.session.get(ExecutionApproval, approval_id)
def approvals(self) -> list[ProductionExecutionApproval]:
return list(
self.session.scalars(
select(ProductionExecutionApproval).order_by(
ProductionExecutionApproval.approved_at.desc()
)
)
)
def embedding_space_by_digest(self, digest: str) -> EmbeddingSpace | None:
return self.session.scalar(
select(EmbeddingSpace).where(EmbeddingSpace.identity_digest == digest)
)
def embedding_space(self, space_id: uuid.UUID | None) -> EmbeddingSpace | None:
if space_id is None:
return None
return self.session.get(EmbeddingSpace, space_id)
def deployment_by_fingerprint(self, fingerprint: str) -> CapabilityDeployment | None:
return self.session.scalar(
select(CapabilityDeployment).where(
CapabilityDeployment.config_fingerprint == fingerprint
)
)
def deployment(self, deployment_id: uuid.UUID) -> CapabilityDeployment | None:
return self.session.get(CapabilityDeployment, deployment_id)
def deployments(self) -> list[CapabilityDeployment]:
return list(
self.session.scalars(
select(CapabilityDeployment).order_by(CapabilityDeployment.created_at.desc())
)
)
def experiment_route(self, route_key: str) -> CapabilityExperimentRoute | None:
return self.session.scalar(
select(CapabilityExperimentRoute).where(
CapabilityExperimentRoute.route_key == route_key
)
)
def experiment_route_by_id(self, route_id: uuid.UUID) -> CapabilityExperimentRoute | None:
return self.session.get(CapabilityExperimentRoute, route_id)
def experiment_routes(self) -> list[CapabilityExperimentRoute]:
return list(
self.session.scalars(
select(CapabilityExperimentRoute).order_by(
CapabilityExperimentRoute.created_at.desc()
)
)
)
def stable_deployment(self, contract_id: uuid.UUID) -> CapabilityDeployment | None:
return self.session.scalar(
select(CapabilityDeployment).where(
CapabilityDeployment.capability_contract_id == contract_id,
CapabilityDeployment.channel == "stable",
CapabilityDeployment.status == "stable",
CapabilityDeployment.production.is_(True),
)
)
def envelope(self, deployment_id: uuid.UUID) -> CapabilityResourceEnvelope | None:
return self.session.scalar(
select(CapabilityResourceEnvelope).where(
CapabilityResourceEnvelope.capability_deployment_id == deployment_id
)
)
def residency(
self, deployment_id: uuid.UUID, *, lock: bool = False
) -> ResidencyAllocation | None:
query = select(ResidencyAllocation).where(
ResidencyAllocation.capability_deployment_id == deployment_id
)
if lock:
query = query.with_for_update()
return self.session.scalar(query)
def accelerator_residencies(self, accelerator_id: uuid.UUID) -> list[ResidencyAllocation]:
return list(
self.session.scalars(
select(ResidencyAllocation)
.join(
CapabilityDeployment,
CapabilityDeployment.id == ResidencyAllocation.capability_deployment_id,
)
.where(CapabilityDeployment.accelerator_id == accelerator_id)
.order_by(ResidencyAllocation.created_at)
)
)
def client_by_name(self, name: str) -> ServiceClient | None:
return self.session.scalar(select(ServiceClient).where(ServiceClient.name == name))
def client(self, client_id: uuid.UUID) -> ServiceClient | None:
return self.session.get(ServiceClient, client_id)
def clients(self) -> list[ServiceClient]:
return list(self.session.scalars(select(ServiceClient).order_by(ServiceClient.created_at)))
def active_credential(self, client_id: uuid.UUID) -> ServiceCredential | None:
return self.session.scalar(
select(ServiceCredential).where(
ServiceCredential.service_client_id == client_id,
ServiceCredential.revoked_at.is_(None),
)
)
def credential_by_hash(
self, secret_hash: str
) -> tuple[ServiceCredential, ServiceClient] | None:
row = self.session.execute(
select(ServiceCredential, ServiceClient)
.join(ServiceClient, ServiceClient.id == ServiceCredential.service_client_id)
.where(ServiceCredential.secret_hash == secret_hash)
).first()
return (row[0], row[1]) if row else None
def recent_request_count(self, client_id: uuid.UUID, since: datetime) -> int:
return int(
self.session.scalar(
select(func.count(GatewayRequest.id)).where(
GatewayRequest.service_client_id == client_id,
GatewayRequest.created_at >= since,
)
)
or 0
)
def active_client_requests(self, client_id: uuid.UUID) -> int:
return int(
self.session.scalar(
select(func.count(GatewayRequest.id)).where(
GatewayRequest.service_client_id == client_id,
GatewayRequest.status.in_(("queued", "running")),
)
)
or 0
)
def active_gateway_requests(self) -> int:
return int(
self.session.scalar(
select(func.count(GatewayRequest.id)).where(
GatewayRequest.status.in_(("queued", "running"))
)
)
or 0
)
def gateway_requests(self, limit: int = 100) -> list[GatewayRequest]:
return list(
self.session.scalars(
select(GatewayRequest).order_by(GatewayRequest.created_at.desc()).limit(limit)
)
)
def queued_job_count(self, deployment_id: uuid.UUID) -> int:
return int(
self.session.scalar(
select(func.count(ServingJob.id)).where(
ServingJob.capability_deployment_id == deployment_id,
ServingJob.operation == "invoke",
ServingJob.status.in_(("queued", "running")),
)
)
or 0
)
def job(self, job_id: uuid.UUID) -> ServingJob | None:
return self.session.get(ServingJob, job_id)
def job_by_key(self, key: str) -> ServingJob | None:
return self.session.scalar(select(ServingJob).where(ServingJob.idempotency_key == key))
def claimable_job(self, node_id: uuid.UUID, now: datetime) -> ServingJob | None:
aging_cutoff = now - timedelta(minutes=5)
priority_order = case(
(ServingJob.priority == "production", 0),
((ServingJob.priority == "background") & (ServingJob.created_at < aging_cutoff), 1),
(
(ServingJob.priority.in_(("lab", "benchmark")))
& (ServingJob.created_at < aging_cutoff),
2,
),
(ServingJob.priority == "interactive", 1),
(ServingJob.priority == "background", 2),
(ServingJob.priority.in_(("lab", "benchmark")), 3),
else_=4,
)
return self.session.scalar(
select(ServingJob)
.where(
ServingJob.compute_node_id == node_id,
or_(
ServingJob.status == "queued",
(ServingJob.status == "running") & (ServingJob.lease_expires_at < now),
),
)
.order_by(priority_order, ServingJob.created_at)
.with_for_update(skip_locked=True)
.limit(1)
)
def active_leases(self, accelerator_id: uuid.UUID, now: datetime) -> list[ServingGpuLease]:
return list(
self.session.scalars(
select(ServingGpuLease).where(
ServingGpuLease.accelerator_id == accelerator_id,
ServingGpuLease.state.in_(("pending", "granted", "active", "releasing")),
ServingGpuLease.expires_at > now,
)
)
)
def lease_for_request(self, request_id: uuid.UUID) -> ServingGpuLease | None:
return self.session.scalar(
select(ServingGpuLease).where(ServingGpuLease.request_id == request_id)
)
def expired_leases(self, now: datetime) -> list[ServingGpuLease]:
return list(
self.session.scalars(
select(ServingGpuLease).where(
ServingGpuLease.state.in_(("pending", "granted", "active", "releasing")),
ServingGpuLease.expires_at <= now,
)
)
)
def active_scheduler_policy(self) -> SchedulerPolicyRevision | None:
return self.session.scalar(
select(SchedulerPolicyRevision)
.where(SchedulerPolicyRevision.active.is_(True))
.order_by(SchedulerPolicyRevision.created_at.desc())
.limit(1)
)
def scheduler_accelerator_state(
self, accelerator_id: uuid.UUID
) -> SchedulerAcceleratorState | None:
return self.session.scalar(
select(SchedulerAcceleratorState).where(
SchedulerAcceleratorState.accelerator_id == accelerator_id
)
)
def placement_plans(self, limit: int = 100) -> list[PlacementPlanRecord]:
return list(
self.session.scalars(
select(PlacementPlanRecord)
.order_by(PlacementPlanRecord.created_at.desc())
.limit(limit)
)
)
def trim_placement_plans(self, keep: int) -> None:
retained = (
select(PlacementPlanRecord.id)
.order_by(PlacementPlanRecord.created_at.desc())
.limit(keep)
)
self.session.execute(
delete(PlacementPlanRecord).where(PlacementPlanRecord.id.not_in(retained))
)
def co_residency_evidence(
self, left_id: uuid.UUID, right_id: uuid.UUID
) -> CoResidencyEvidence | None:
low, high = sorted((left_id, right_id), key=str)
return self.session.scalar(
select(CoResidencyEvidence).where(
CoResidencyEvidence.left_deployment_id == low,
CoResidencyEvidence.right_deployment_id == high,
)
)
@@ -0,0 +1 @@
"""External provider boundaries."""
@@ -0,0 +1,238 @@
from __future__ import annotations
from collections.abc import Iterable
from dataclasses import dataclass, field
from datetime import datetime
from pathlib import PurePosixPath
from typing import Any, Protocol
import httpx
from huggingface_hub import HfApi
from huggingface_hub.errors import (
GatedRepoError,
HfHubHTTPError,
RepositoryNotFoundError,
RevisionNotFoundError,
)
class HuggingFaceProviderError(RuntimeError):
code = "huggingface_error"
access_state = "error"
class HuggingFaceNotFound(HuggingFaceProviderError):
code = "repository_not_found"
access_state = "not_found"
class HuggingFaceGated(HuggingFaceProviderError):
code = "repository_gated"
access_state = "gated"
class HuggingFaceRevisionNotFound(HuggingFaceProviderError):
code = "revision_not_found"
access_state = "revision_not_found"
class HuggingFaceUnavailable(HuggingFaceProviderError):
code = "provider_unavailable"
access_state = "unavailable"
@dataclass(frozen=True)
class ProviderFile:
path: str
size_bytes: int | None
blob_id: str | None
upstream_sha256: str | None
file_format: str
role: str
risk_flags: tuple[str, ...] = ()
metadata: dict[str, Any] = field(default_factory=dict)
@dataclass(frozen=True)
class ProviderSnapshot:
repository_id: str
requested_revision: str
resolved_commit_sha: str
access_state: str
metadata: dict[str, Any]
card_metadata: dict[str, Any]
security_metadata: dict[str, Any]
source_updated_at: datetime | None
files: tuple[ProviderFile, ...]
class HuggingFaceProvider(Protocol):
def search(
self, query: str, *, limit: int, sort: str, pipeline_tag: str | None
) -> list[dict[str, Any]]: ...
def snapshot(self, repository_id: str, revision: str) -> ProviderSnapshot: ...
def classify_file(path: str) -> tuple[str, str, tuple[str, ...]]:
suffix = PurePosixPath(path.lower()).suffix
name = PurePosixPath(path.lower()).name
risks: list[str] = []
if suffix == ".safetensors":
return "safetensors", "weights", ()
if suffix == ".gguf":
return "gguf", "weights", ()
if suffix in {".bin", ".pt", ".pth", ".ckpt", ".pkl", ".pickle"}:
risks.append("pickle_or_executable_serialization")
return suffix.lstrip("."), "weights", tuple(risks)
if suffix == ".py":
return "python", "repository_code", ("remote_code",)
if name.endswith(".safetensors.index.json"):
return "json", "weight_index", ()
if suffix in {".json", ".model", ".txt", ".tiktoken"}:
role = "configuration" if "config" in name else "tokenizer"
return suffix.lstrip("."), role, ()
if suffix in {".md", ".rst"}:
return suffix.lstrip("."), "documentation", ()
return suffix.lstrip(".") or "unknown", "other", ()
def _card_dict(value: Any) -> dict[str, Any]:
if value is None:
return {}
if hasattr(value, "to_dict"):
result = value.to_dict()
return dict(result) if isinstance(result, dict) else {}
return dict(value) if isinstance(value, dict) else {}
def _lfs_value(lfs: Any, name: str) -> Any:
if lfs is None:
return None
return lfs.get(name) if isinstance(lfs, dict) else getattr(lfs, name, None)
class OfficialHuggingFaceProvider:
"""Narrow adapter over the official huggingface_hub client; no HTML scraping."""
def __init__(self, *, token: str | None = None, timeout: float = 30) -> None:
self.api = HfApi(token=token)
self.token = token
self.timeout = timeout
@staticmethod
def _search_item(item: Any) -> dict[str, Any]:
return {
"repository_id": item.id,
"resolved_commit_sha": getattr(item, "sha", None),
"access_state": "gated"
if getattr(item, "gated", False)
else "private"
if getattr(item, "private", False)
else "public",
"pipeline_tag": getattr(item, "pipeline_tag", None),
"library_name": getattr(item, "library_name", None),
"tags": list(getattr(item, "tags", None) or []),
"downloads": getattr(item, "downloads", None),
"likes": getattr(item, "likes", None),
"last_modified": getattr(item, "last_modified", None),
}
def search(
self, query: str, *, limit: int, sort: str, pipeline_tag: str | None
) -> list[dict[str, Any]]:
try:
items: Iterable[Any] = self.api.list_models(
search=query,
pipeline_tag=pipeline_tag,
sort=sort,
limit=limit,
full=True,
cardData=True,
token=self.token,
)
return [self._search_item(item) for item in items]
except (HfHubHTTPError, httpx.TimeoutException) as exc:
raise HuggingFaceUnavailable("Hugging Face search is unavailable") from exc
def snapshot(self, repository_id: str, revision: str) -> ProviderSnapshot:
try:
info = self.api.model_info(
repository_id,
revision=revision,
timeout=self.timeout,
securityStatus=True,
files_metadata=True,
token=self.token,
)
except GatedRepoError as exc:
raise HuggingFaceGated(f"repository {repository_id} is gated") from exc
except RevisionNotFoundError as exc:
raise HuggingFaceRevisionNotFound(
f"revision {revision} was not found for {repository_id}"
) from exc
except RepositoryNotFoundError as exc:
raise HuggingFaceNotFound(
f"repository {repository_id} was not found or is private"
) from exc
except (HfHubHTTPError, httpx.TimeoutException) as exc:
raise HuggingFaceUnavailable(
f"Hugging Face metadata is unavailable for {repository_id}"
) from exc
files: list[ProviderFile] = []
for sibling in info.siblings or []:
file_format, role, risks = classify_file(sibling.rfilename)
lfs = getattr(sibling, "lfs", None)
files.append(
ProviderFile(
path=sibling.rfilename,
size_bytes=getattr(sibling, "size", None) or _lfs_value(lfs, "size"),
blob_id=getattr(sibling, "blob_id", None),
upstream_sha256=_lfs_value(lfs, "sha256"),
file_format=file_format,
role=role,
risk_flags=risks,
metadata={
"lfs_pointer_size": _lfs_value(lfs, "pointer_size"),
"source": "huggingface_hub.model_info(files_metadata=true)",
},
)
)
card = _card_dict(getattr(info, "card_data", None))
if not info.sha:
raise HuggingFaceUnavailable(
f"Hugging Face did not resolve an exact commit for {repository_id}"
)
access = (
"gated"
if getattr(info, "gated", False)
else "private"
if getattr(info, "private", False)
else "public"
)
return ProviderSnapshot(
repository_id=info.id,
requested_revision=revision,
resolved_commit_sha=info.sha,
access_state=access,
metadata={
"repository_id": info.id,
"author": getattr(info, "author", None),
"pipeline_tag": getattr(info, "pipeline_tag", None),
"library_name": getattr(info, "library_name", None),
"tags": list(getattr(info, "tags", None) or []),
"downloads": getattr(info, "downloads", None),
"likes": getattr(info, "likes", None),
"gated": getattr(info, "gated", False),
"private": getattr(info, "private", False),
"source": "huggingface_hub.model_info",
},
card_metadata=card,
security_metadata={
"upstream_scanner": getattr(info, "security_repo_status", None),
"evidence_only": True,
"local_static_inspection": "not_run",
},
source_updated_at=getattr(info, "last_modified", None),
files=tuple(sorted(files, key=lambda item: item.path)),
)
+1
View File
@@ -0,0 +1 @@
@@ -0,0 +1,948 @@
from __future__ import annotations
import hashlib
import json
import secrets
import uuid
from datetime import UTC, datetime, timedelta
from typing import Any
from sqlalchemy import select
from sqlalchemy.orm import Session
from modelforge_api.domain.acquisition import (
AgentArtifactJobFile,
AgentArtifactJobLease,
AgentJobComplete,
AgentJobControl,
AgentJobFailure,
AgentJobProgress,
ArtifactJobResponse,
ArtifactSetResponse,
DiscoveryCandidate,
DiscoverySearchRequest,
DownloadPlanCreate,
DownloadPlanFileResponse,
DownloadPlanResponse,
UpstreamFileResponse,
UpstreamSnapshotResponse,
)
from modelforge_api.domain.enums import ArtifactStatus, NodeLiveness
from modelforge_api.persistence.acquisition_repository import AcquisitionRepository
from modelforge_api.persistence.models import (
ArtifactInspection,
ArtifactJob,
ArtifactJobAttempt,
ArtifactLocation,
ArtifactSet,
ArtifactSetMember,
ComputeNode,
DownloadPlan,
DownloadPlanFile,
Model,
ModelArtifact,
ModelRevision,
StorageRoot,
UpstreamFile,
UpstreamSnapshot,
)
from modelforge_api.providers.huggingface import (
HuggingFaceProvider,
HuggingFaceProviderError,
ProviderFile,
)
from modelforge_api.services.audit import AuditWriter
from modelforge_api.services.registry import RegistryConflict, RegistryNotFound, RegistryService
from modelforge_api.settings import Settings
class AcquisitionError(RegistryConflict):
code = "acquisition_error"
class ProviderBoundaryError(AcquisitionError):
status_code = 502
def _utcnow() -> datetime:
return datetime.now(UTC)
def _aware(value: datetime) -> datetime:
return value if value.tzinfo else value.replace(tzinfo=UTC)
def _canonical_hash(payload: dict[str, Any]) -> str:
return hashlib.sha256(
json.dumps(payload, sort_keys=True, separators=(",", ":")).encode()
).hexdigest()
def _safe_key(repository_id: str) -> str:
return repository_id.lower().replace("/", "--").replace("_", "-")[:120]
def _select_artifact_files(files: list[ProviderFile]) -> tuple[str, str, list[ProviderFile]]:
safe_weights = [
item for item in files if item.role == "weights" and item.file_format == "safetensors"
]
gguf_weights = [item for item in files if item.role == "weights" and item.file_format == "gguf"]
other_weights = [
item
for item in files
if item.role == "weights" and item not in safe_weights and item not in gguf_weights
]
support = [
item
for item in files
if item.role in {"configuration", "tokenizer", "weight_index"}
and "remote_code" not in item.risk_flags
]
if safe_weights:
return (
"safetensors-default",
"Safetensors files were preferred; duplicate pickle/GGUF weight variants were excluded.",
sorted(safe_weights + support, key=lambda item: item.path),
)
if gguf_weights:
return (
"gguf-default",
"No safetensors were advertised; the GGUF variant was selected without pickle weights.",
sorted(gguf_weights + support, key=lambda item: item.path),
)
return (
"upstream-default",
"No safe tensor variant was advertised; upstream weights retain explicit risk flags.",
sorted(other_weights + support, key=lambda item: item.path),
)
class AcquisitionService:
def __init__(
self,
session: Session,
settings: Settings,
provider: HuggingFaceProvider,
*,
actor_type: str = "operator",
actor_id: str = "local-operator",
) -> None:
self.session = session
self.settings = settings
self.provider = provider
self.repo = AcquisitionRepository(session)
self.audit = AuditWriter(session, actor_type, actor_id)
def _commit(self) -> None:
self.session.commit()
def search(self, request: DiscoverySearchRequest) -> list[DiscoveryCandidate]:
try:
results = self.provider.search(
request.query,
limit=request.limit,
sort=request.sort,
pipeline_tag=request.pipeline_tag,
)
except HuggingFaceProviderError as exc:
raise ProviderBoundaryError(str(exc), {"provider_code": exc.code}) from exc
sources = {
item.upstream_source: item.id
for item in self.session.scalars(
select(Model).where(Model.source_type == "huggingface")
)
}
response: list[DiscoveryCandidate] = []
for item in results:
repository_id = str(item["repository_id"])
response.append(
DiscoveryCandidate(
**item,
matched_model_id=sources.get(repository_id),
upstream_facts={
"source": "huggingface_hub.list_models",
"pipeline_tag": item.get("pipeline_tag"),
"tags": item.get("tags", []),
},
local_interpretation={
"candidate_only": True,
"approval": "not_evaluated",
"hardware_compatibility": "not_measured",
},
)
)
self.audit.write(
"HF_DISCOVERY_SEARCHED",
"upstream_provider",
"huggingface",
{"query": request.query, "result_count": len(response)},
)
self._commit()
return response
def refresh_model(self, model_id: uuid.UUID, revision: str) -> UpstreamSnapshotResponse:
model = self.session.get(Model, model_id)
if not model:
raise RegistryNotFound("model not found")
if model.source_type != "huggingface":
raise AcquisitionError("only Hugging Face models can use this provider boundary")
try:
source = self.provider.snapshot(model.upstream_source, revision)
except HuggingFaceProviderError as exc:
self.audit.write(
"HF_METADATA_REFRESH_FAILED",
"model",
str(model.id),
{"provider_code": exc.code, "access_state": exc.access_state},
outcome="blocked",
)
self._commit()
raise ProviderBoundaryError(
str(exc), {"provider_code": exc.code, "access_state": exc.access_state}
) from exc
now = _utcnow()
snapshot = self.repo.add(
UpstreamSnapshot(
model_id=model.id,
provider="huggingface",
repository_id=source.repository_id,
requested_revision=revision,
resolved_commit_sha=source.resolved_commit_sha,
access_state=source.access_state,
metadata_snapshot=source.metadata,
card_metadata=source.card_metadata,
security_metadata=source.security_metadata,
source_updated_at=source.source_updated_at,
observed_at=now,
stale_after=now + timedelta(seconds=self.settings.hf_snapshot_ttl_seconds),
)
)
for item in source.files:
self.repo.add(
UpstreamFile(
snapshot_id=snapshot.id,
path=item.path,
size_bytes=item.size_bytes,
blob_id=item.blob_id,
upstream_sha256=item.upstream_sha256,
file_format=item.file_format,
role=item.role,
risk_flags=list(item.risk_flags),
metadata_snapshot=item.metadata,
)
)
existing_revision = self.session.scalar(
select(ModelRevision).where(
ModelRevision.model_id == model.id,
ModelRevision.resolved_commit_sha == source.resolved_commit_sha,
)
)
exact_revision = existing_revision or self.repo.add(
ModelRevision(
model_id=model.id,
upstream_revision=revision,
resolved_commit_sha=source.resolved_commit_sha,
metadata_snapshot={
"upstream_snapshot_id": str(snapshot.id),
"provider": "huggingface",
"observed_at": now.isoformat(),
},
discovered_at=now,
immutable_at=now,
)
)
variant, reason, selected = _select_artifact_files(list(source.files))
artifact_set = self.repo.artifact_set_for_variant(exact_revision.id, variant)
if artifact_set is None and selected:
missing_sizes = [item.path for item in selected if item.size_bytes is None]
artifact_set = self.repo.add(
ArtifactSet(
revision_id=exact_revision.id,
snapshot_id=snapshot.id,
variant_key=variant,
label=variant.replace("-", " ").title(),
selection_reason=reason,
selected_paths=[item.path for item in selected],
total_size_bytes=sum(item.size_bytes or 0 for item in selected),
file_count=len(selected),
availability="remote",
status="remote",
completeness="incomplete_metadata" if missing_sizes else "planned",
security_status="risk_detected"
if any(item.risk_flags for item in selected)
else "unverified",
license_status="captured_unreviewed"
if source.card_metadata.get("license")
else "unknown",
immutable_at=now,
)
)
model.upstream_metadata = source.metadata | {
"resolved_commit_sha": source.resolved_commit_sha,
"latest_snapshot_id": str(snapshot.id),
"metadata_observed_at": now.isoformat(),
}
model.license_metadata = {
"status": "captured_unreviewed" if source.card_metadata.get("license") else "unknown",
"declared": source.card_metadata.get("license"),
"source": "huggingface_model_card",
"approval": "not_evaluated",
}
model.interpretation_metadata = model.interpretation_metadata | {
"discovery_interpretation": {
"pipeline_tag": source.metadata.get("pipeline_tag"),
"safe_default_variant": variant if selected else None,
"hardware_compatibility": "not_measured",
"evidence_status": "upstream_metadata_only",
}
}
self.audit.write(
"HF_METADATA_REFRESHED",
"model",
str(model.id),
{
"snapshot_id": str(snapshot.id),
"resolved_commit_sha": source.resolved_commit_sha,
"file_count": len(source.files),
"artifact_set_id": str(artifact_set.id) if artifact_set else None,
},
)
self.audit.write(
"REVISION_RESOLVED",
"model_revision",
str(exact_revision.id),
{
"requested_revision": revision,
"resolved_commit_sha": source.resolved_commit_sha,
"new_revision": existing_revision is None,
},
)
self._commit()
return self.snapshot_response(snapshot.id)
def snapshot_response(self, snapshot_id: uuid.UUID) -> UpstreamSnapshotResponse:
snapshot = self.repo.snapshot(snapshot_id)
if not snapshot:
raise RegistryNotFound("upstream snapshot not found")
return UpstreamSnapshotResponse(
id=snapshot.id,
model_id=snapshot.model_id,
provider=snapshot.provider,
repository_id=snapshot.repository_id,
requested_revision=snapshot.requested_revision,
resolved_commit_sha=snapshot.resolved_commit_sha,
access_state=snapshot.access_state,
metadata_snapshot=snapshot.metadata_snapshot,
card_metadata=snapshot.card_metadata,
security_metadata=snapshot.security_metadata,
source_updated_at=snapshot.source_updated_at,
observed_at=snapshot.observed_at,
stale=_aware(snapshot.stale_after) <= _utcnow(),
stale_after=snapshot.stale_after,
files=[
UpstreamFileResponse.model_validate(item) for item in self.repo.files(snapshot.id)
],
)
def latest_snapshot(self, model_id: uuid.UUID) -> UpstreamSnapshotResponse:
if not self.session.get(Model, model_id):
raise RegistryNotFound("model not found")
snapshot = self.repo.latest_snapshot(model_id)
if not snapshot:
raise RegistryNotFound("model has no upstream metadata snapshot")
return self.snapshot_response(snapshot.id)
def artifact_sets(self, revision_id: uuid.UUID) -> list[ArtifactSetResponse]:
if not self.session.get(ModelRevision, revision_id):
raise RegistryNotFound("model revision not found")
return [
ArtifactSetResponse.model_validate(item)
for item in self.repo.artifact_sets(revision_id)
]
def create_plan(self, request: DownloadPlanCreate) -> DownloadPlanResponse:
artifact_set = self.repo.artifact_set(request.artifact_set_id)
root = self.session.get(StorageRoot, request.storage_root_id)
node = self.session.get(ComputeNode, request.compute_node_id)
if not artifact_set:
raise RegistryNotFound("artifact set not found")
if not root:
raise RegistryNotFound("storage root not found")
if not node:
raise RegistryNotFound("compute node not found")
if root.compute_node_id != node.id:
raise AcquisitionError("storage root belongs to a different compute node")
if not root.agent_path:
raise AcquisitionError("storage root has no node-agent path mapping")
if (
node.decommissioned_at is not None
or not node.enabled
or node.liveness_state != NodeLiveness.ONLINE
):
raise AcquisitionError("target compute node is not online and enabled")
if "artifact.acquire.v1" not in node.agent_capabilities:
raise AcquisitionError("target agent does not advertise artifact.acquire.v1")
snapshot = self.repo.snapshot(artifact_set.snapshot_id)
revision = self.session.get(ModelRevision, artifact_set.revision_id)
if not snapshot or not revision:
raise AcquisitionError("artifact set provenance is incomplete")
if snapshot.access_state != "public" and not self.settings.hf_token:
raise AcquisitionError(
f"repository access is {snapshot.access_state}; Hugging Face authentication is required"
)
source_files = {item.path: item for item in self.repo.files(snapshot.id)}
files = [source_files[path] for path in artifact_set.selected_paths if path in source_files]
if len(files) != artifact_set.file_count or any(item.size_bytes is None for item in files):
raise AcquisitionError("artifact set has incomplete file-size metadata")
decision = RegistryService.capacity_decision(root, artifact_set.total_size_bytes)
if not decision.allowed:
self.audit.write(
"DOWNLOAD_PLAN_BLOCKED",
"artifact_set",
str(artifact_set.id),
decision.model_dump(mode="json"),
outcome="blocked",
)
self._commit()
raise AcquisitionError(decision.reason, decision.model_dump(mode="json"))
now = _utcnow()
payload = {
"artifact_set_id": str(artifact_set.id),
"repository_id": snapshot.repository_id,
"resolved_commit_sha": revision.resolved_commit_sha,
"compute_node_id": str(node.id),
"storage_root_id": str(root.id),
"target_root": root.agent_path,
"files": [
{
"path": item.path,
"size_bytes": item.size_bytes,
"upstream_sha256": item.upstream_sha256,
"file_format": item.file_format,
"role": item.role,
"risk_flags": item.risk_flags,
}
for item in files
],
}
key = _canonical_hash(payload)
existing = self.repo.plan_by_key(key)
if existing:
return self.plan_response(existing.id)
preflight = {
"capacity": decision.model_dump(mode="json"),
"node_liveness": node.liveness_state,
"agent_capability": "artifact.acquire.v1",
"storage_agent_path": root.agent_path,
"exact_revision": True,
"trust_remote_code": False,
}
plan = self.repo.add(
DownloadPlan(
artifact_set_id=artifact_set.id,
compute_node_id=node.id,
storage_root_id=root.id,
repository_id=snapshot.repository_id,
resolved_commit_sha=revision.resolved_commit_sha,
total_size_bytes=artifact_set.total_size_bytes,
file_count=len(files),
status="planned",
idempotency_key=key,
preflight=preflight,
immutable_payload=payload,
planned_at=now,
expires_at=now + timedelta(seconds=request.expires_in_seconds),
immutable_at=now,
)
)
for ordinal, item in enumerate(files):
self.repo.add(
DownloadPlanFile(
plan_id=plan.id,
ordinal=ordinal,
path=item.path,
size_bytes=int(item.size_bytes or 0),
upstream_sha256=item.upstream_sha256,
file_format=item.file_format,
role=item.role,
risk_flags=item.risk_flags,
)
)
self.audit.write(
"DOWNLOAD_PLAN_CREATED",
"download_plan",
str(plan.id),
{"idempotency_key": key, "total_size_bytes": plan.total_size_bytes},
)
self._commit()
return self.plan_response(plan.id)
def approve_plan(self, plan_id: uuid.UUID) -> DownloadPlanResponse:
plan = self.repo.plan(plan_id)
if not plan:
raise RegistryNotFound("download plan not found")
if plan.status == "ready":
return self.plan_response(plan.id)
if plan.status != "planned":
raise AcquisitionError("only a planned download plan can be approved")
if _aware(plan.expires_at) <= _utcnow():
plan.status = "stale"
self._commit()
raise AcquisitionError("download plan is stale; create a new immutable plan")
artifact_set = self.repo.artifact_set(plan.artifact_set_id)
if not artifact_set:
raise AcquisitionError("download plan lost its artifact set")
if artifact_set.security_status == "risk_detected":
self.audit.write(
"DOWNLOAD_PLAN_APPROVAL_BLOCKED",
"download_plan",
str(plan.id),
{"reason": "artifact selection contains blocked security risks"},
outcome="blocked",
)
self._commit()
raise AcquisitionError("artifact selection contains blocked security risks")
plan.status = "ready"
artifact_set.status = "ready"
self.audit.write(
"DOWNLOAD_PLAN_APPROVED",
"download_plan",
str(plan.id),
{
"resolved_commit_sha": plan.resolved_commit_sha,
"file_count": plan.file_count,
"security_preflight": "passed",
},
)
self._commit()
return self.plan_response(plan.id)
def plan_response(self, plan_id: uuid.UUID) -> DownloadPlanResponse:
plan = self.repo.plan(plan_id)
if not plan:
raise RegistryNotFound("download plan not found")
return DownloadPlanResponse(
**{
name: getattr(plan, name)
for name in DownloadPlanResponse.model_fields
if name not in {"files", "stale"}
},
stale=_aware(plan.expires_at) <= _utcnow(),
files=[
DownloadPlanFileResponse.model_validate(item)
for item in self.repo.plan_files(plan.id)
],
)
def execute_plan(self, plan_id: uuid.UUID) -> ArtifactJobResponse:
plan = self.repo.plan(plan_id)
if not plan:
raise RegistryNotFound("download plan not found")
existing = self.repo.job_for_plan(plan.id)
if existing:
return ArtifactJobResponse.model_validate(existing)
if plan.status != "ready":
raise AcquisitionError("download plan requires explicit approval before execution")
if _aware(plan.expires_at) <= _utcnow():
plan.status = "stale"
self._commit()
raise AcquisitionError("download plan is stale; create a new immutable plan")
root = self.session.get(StorageRoot, plan.storage_root_id)
node = self.session.get(ComputeNode, plan.compute_node_id)
if not root or not node:
raise AcquisitionError("download target no longer exists")
decision = RegistryService.capacity_decision(root, plan.total_size_bytes)
if not decision.allowed:
self.audit.write(
"ARTIFACT_DOWNLOAD_BLOCKED",
"download_plan",
str(plan.id),
decision.model_dump(mode="json"),
outcome="blocked",
)
self._commit()
raise AcquisitionError(
"execution capacity preflight failed", decision.model_dump(mode="json")
)
if node.liveness_state != NodeLiveness.ONLINE or not node.enabled:
raise AcquisitionError("target compute node is not online and enabled")
job = self.repo.add(
ArtifactJob(
plan_id=plan.id,
compute_node_id=plan.compute_node_id,
storage_root_id=plan.storage_root_id,
status="queued",
idempotency_key=_canonical_hash({"job_for_plan": str(plan.id)}),
total_bytes=plan.total_size_bytes,
)
)
plan.status = "queued"
artifact_set = self.repo.artifact_set(plan.artifact_set_id)
if artifact_set:
artifact_set.status = "queued"
self.audit.write(
"ARTIFACT_DOWNLOAD_QUEUED",
"artifact_job",
str(job.id),
{"plan_id": str(plan.id), "compute_node_id": str(job.compute_node_id)},
)
self._commit()
return ArtifactJobResponse.model_validate(job)
def jobs(self) -> list[ArtifactJobResponse]:
return [ArtifactJobResponse.model_validate(item) for item in self.repo.jobs()]
def job(self, job_id: uuid.UUID) -> ArtifactJobResponse:
job = self.repo.job(job_id)
if not job:
raise RegistryNotFound("artifact job not found")
return ArtifactJobResponse.model_validate(job)
def cancel(self, job_id: uuid.UUID) -> ArtifactJobResponse:
job = self.repo.job(job_id)
if not job:
raise RegistryNotFound("artifact job not found")
if job.status in {"completed", "failed", "cancelled"}:
raise AcquisitionError("terminal artifact jobs cannot be cancelled")
job.cancel_requested = True
if job.status == "queued":
job.status = "cancelled"
job.completed_at = _utcnow()
self.audit.write(
"DOWNLOAD_CANCELLED" if job.status == "cancelled" else "ARTIFACT_JOB_CANCEL_REQUESTED",
"artifact_job",
str(job.id),
{},
)
self._commit()
return ArtifactJobResponse.model_validate(job)
def retry(self, job_id: uuid.UUID) -> ArtifactJobResponse:
job = self.repo.job(job_id)
if not job:
raise RegistryNotFound("artifact job not found")
if job.status != "failed":
raise AcquisitionError("only failed artifact jobs can be retried explicitly")
plan = self.repo.plan(job.plan_id)
node = self.session.get(ComputeNode, job.compute_node_id)
root = self.session.get(StorageRoot, job.storage_root_id)
if not plan or not node or not root:
raise AcquisitionError("artifact job target or immutable plan no longer exists")
if not node.enabled or node.liveness_state != NodeLiveness.ONLINE:
raise AcquisitionError("target compute node is not online and enabled")
decision = RegistryService.capacity_decision(root, plan.total_size_bytes)
if not decision.allowed:
raise AcquisitionError(
"retry capacity preflight failed", decision.model_dump(mode="json")
)
previous_error = {"code": job.error_code, "message": job.error_message}
job.status = "queued"
job.cancel_requested = False
job.progress_bytes = 0
job.current_file = None
job.error_code = None
job.error_message = None
job.completed_at = None
job.lease_token_hash = None
job.lease_expires_at = None
job.result = {
"operator_retry": True,
"previous_error": previous_error,
"resume_from_quarantine": bool(job.quarantine_relative_path),
}
plan.status = "queued"
artifact_set = self.repo.artifact_set(plan.artifact_set_id)
if artifact_set:
artifact_set.status = "queued"
self.audit.write(
"ARTIFACT_JOB_OPERATOR_RETRY",
"artifact_job",
str(job.id),
{
"previous_attempts": job.attempt_count,
"resume_from_quarantine": bool(job.quarantine_relative_path),
},
)
self._commit()
return ArtifactJobResponse.model_validate(job)
@staticmethod
def _lease_hash(token: str) -> str:
return hashlib.sha256(token.encode()).hexdigest()
def claim_next(self, node: ComputeNode) -> AgentArtifactJobLease | None:
now = _utcnow()
job = self.repo.claimable_job(node.id, now)
if not job:
return None
plan = self.repo.plan(job.plan_id)
root = self.session.get(StorageRoot, job.storage_root_id)
if not plan or not root or not root.agent_path:
raise AcquisitionError("claimable job has an invalid target")
token = f"mflease_{secrets.token_urlsafe(48)}"
job.lease_token_hash = self._lease_hash(token)
job.lease_expires_at = now + timedelta(seconds=120)
job.status = "claimed"
job.attempt_count += 1
job.started_at = job.started_at or now
self.repo.add(
ArtifactJobAttempt(job_id=job.id, attempt=job.attempt_count, status="claimed")
)
self.audit.write(
"ARTIFACT_DOWNLOAD_STARTED",
"artifact_job",
str(job.id),
{"attempt": job.attempt_count, "compute_node_id": str(node.id)},
)
self._commit()
return AgentArtifactJobLease(
job_id=job.id,
lease_token=token,
lease_expires_at=job.lease_expires_at,
repository_id=plan.repository_id,
resolved_commit_sha=plan.resolved_commit_sha,
storage_root_id=root.id,
target_root=root.agent_path,
total_size_bytes=plan.total_size_bytes,
reserve_bytes=root.reserve_bytes,
reserve_percent=root.reserve_percent,
files=[
AgentArtifactJobFile.model_validate(item) for item in self.repo.plan_files(plan.id)
],
)
def _leased_job(self, job_id: uuid.UUID, node: ComputeNode, token: str) -> ArtifactJob:
job = self.repo.job(job_id)
if not job:
raise RegistryNotFound("artifact job not found")
if job.compute_node_id != node.id:
raise AcquisitionError("artifact job belongs to a different node")
if not job.lease_token_hash or not secrets.compare_digest(
job.lease_token_hash, self._lease_hash(token)
):
raise AcquisitionError("invalid artifact-job lease")
if not job.lease_expires_at or _aware(job.lease_expires_at) <= _utcnow():
raise AcquisitionError("artifact-job lease expired")
return job
def progress(
self, job_id: uuid.UUID, node: ComputeNode, request: AgentJobProgress
) -> AgentJobControl:
job = self._leased_job(job_id, node, request.lease_token)
if request.progress_bytes < job.progress_bytes or request.progress_bytes > job.total_bytes:
raise AcquisitionError("artifact-job progress is out of bounds")
job.status = request.status
job.progress_bytes = request.progress_bytes
job.current_file = request.current_file
first_quarantine_observation = (
job.quarantine_relative_path is None and request.quarantine_relative_path is not None
)
job.quarantine_relative_path = request.quarantine_relative_path
job.lease_expires_at = _utcnow() + timedelta(seconds=120)
if first_quarantine_observation:
self.audit.write(
"ARTIFACT_QUARANTINED",
"artifact_job",
str(job.id),
{"quarantine_relative_path": request.quarantine_relative_path},
)
self._commit()
return AgentJobControl(
accepted=True,
cancel_requested=job.cancel_requested,
lease_expires_at=job.lease_expires_at,
)
def complete(
self, job_id: uuid.UUID, node: ComputeNode, request: AgentJobComplete
) -> ArtifactJobResponse:
job = self._leased_job(job_id, node, request.lease_token)
if job.cancel_requested:
raise AcquisitionError("artifact job was cancelled")
plan = self.repo.plan(job.plan_id)
if not plan:
raise AcquisitionError("artifact job lost its immutable plan")
expected = {item.path: item for item in self.repo.plan_files(plan.id)}
if set(expected) != {item.path for item in request.files}:
raise AcquisitionError("completed file inventory does not match immutable plan")
for item in request.files:
planned = expected[item.path]
if planned.size_bytes != item.size_bytes:
raise AcquisitionError(f"size mismatch for {item.path}")
if planned.upstream_sha256 and planned.upstream_sha256 != item.sha256:
raise AcquisitionError(f"upstream checksum mismatch for {item.path}")
for finding in item.inspections:
self.repo.add(
ArtifactInspection(
job_id=job.id,
file_path=item.path,
inspection_type=str(finding.get("type", "static"))[:64],
status=str(finding.get("status", "observed"))[:32],
severity=str(finding.get("severity", "info"))[:32],
evidence=dict(finding.get("evidence", {})),
)
)
if finding.get("severity") == "block":
raise AcquisitionError(f"blocking static inspection for {item.path}")
artifact_set = self.repo.artifact_set(plan.artifact_set_id)
if not artifact_set:
raise AcquisitionError("artifact set not found")
for ordinal, item in enumerate(request.files):
planned = expected[item.path]
artifact = self.session.scalar(
select(ModelArtifact).where(
ModelArtifact.revision_id == artifact_set.revision_id,
ModelArtifact.sha256 == item.sha256,
)
)
if artifact is None:
artifact = self.repo.add(
ModelArtifact(
revision_id=artifact_set.revision_id,
filename=item.path,
artifact_type=planned.role,
serialization_format=planned.file_format,
sha256=item.sha256,
size_bytes=item.size_bytes,
security_status="static_checks_passed_unapproved",
license_status=artifact_set.license_status,
status=ArtifactStatus.VERIFIED,
verification_details={
"job_id": str(job.id),
"integrity": "streaming_sha256",
"upstream_checksum": planned.upstream_sha256,
"supply_chain_approval": "not_evaluated_m4",
},
quarantined=False,
verified_at=_utcnow(),
immutable_at=_utcnow(),
)
)
location = self.session.scalar(
select(ArtifactLocation).where(
ArtifactLocation.storage_root_id == job.storage_root_id,
ArtifactLocation.relative_path == item.relative_path,
)
)
if location is None:
self.repo.add(
ArtifactLocation(
artifact_id=artifact.id,
storage_root_id=job.storage_root_id,
relative_path=item.relative_path,
status=ArtifactStatus.VERIFIED,
size_bytes=item.size_bytes,
observed_sha256=item.sha256,
last_checked_at=_utcnow(),
)
)
if self.session.get(ArtifactSetMember, (artifact_set.id, artifact.id)) is None:
self.repo.add(
ArtifactSetMember(
artifact_set_id=artifact_set.id,
artifact_id=artifact.id,
ordinal=ordinal,
required=True,
)
)
artifact_set.status = "verified"
artifact_set.availability = "local"
artifact_set.completeness = "complete"
artifact_set.security_status = "static_checks_passed_unapproved"
plan.status = "completed"
job.status = "completed"
job.progress_bytes = job.total_bytes
job.current_file = None
job.promoted_relative_path = request.promoted_relative_path
job.completed_at = _utcnow()
job.lease_token_hash = None
job.lease_expires_at = None
job.result = {
"files": len(request.files),
"capacity_observation": request.capacity_observation,
"integrity_verified": True,
"supply_chain_approved": False,
}
root = self.session.get(StorageRoot, job.storage_root_id)
capacity = request.capacity_observation
if root:
observed_capacity = capacity.get("capacity_bytes")
observed_free = capacity.get("free_bytes")
if isinstance(observed_capacity, int) and isinstance(observed_free, int):
root.capacity_bytes = observed_capacity
root.free_bytes = observed_free
root.capacity_observed_at = _utcnow()
root.status = RegistryService.capacity_decision(root, 0).status
root.validation_details = {
"source": "artifact_job_completion",
"job_id": str(job.id),
"agent_path": capacity.get("agent_path"),
}
attempt = self.session.scalar(
select(ArtifactJobAttempt)
.where(ArtifactJobAttempt.job_id == job.id)
.order_by(ArtifactJobAttempt.attempt.desc(), ArtifactJobAttempt.started_at.desc())
.limit(1)
)
if attempt:
attempt.status = "completed"
attempt.completed_at = _utcnow()
self.audit.write(
"ARTIFACT_SET_ACQUIRED",
"artifact_set",
str(artifact_set.id),
{"job_id": str(job.id), "file_count": len(request.files), "approved": False},
)
for action in ("ARTIFACT_DOWNLOADED", "ARTIFACT_VERIFIED", "ARTIFACT_PROMOTED"):
self.audit.write(
action,
"artifact_set",
str(artifact_set.id),
{"job_id": str(job.id), "file_count": len(request.files)},
)
self._commit()
return ArtifactJobResponse.model_validate(job)
def fail(
self, job_id: uuid.UUID, node: ComputeNode, request: AgentJobFailure
) -> ArtifactJobResponse:
job = self._leased_job(job_id, node, request.lease_token)
retry = request.retryable and job.attempt_count < 3 and not job.cancel_requested
job.status = "queued" if retry else "cancelled" if job.cancel_requested else "failed"
job.error_code = request.error_code
job.error_message = request.error_message
job.result = {"failure_details": request.details, "retry_scheduled": retry}
job.lease_token_hash = None
job.lease_expires_at = None
if not retry:
job.completed_at = _utcnow()
attempt = self.session.scalar(
select(ArtifactJobAttempt)
.where(ArtifactJobAttempt.job_id == job.id)
.order_by(ArtifactJobAttempt.attempt.desc(), ArtifactJobAttempt.started_at.desc())
.limit(1)
)
if attempt:
attempt.status = "retry_scheduled" if retry else job.status
attempt.completed_at = _utcnow()
plan = self.repo.plan(job.plan_id)
if plan:
plan.status = job.status
action = (
"DOWNLOAD_CANCELLED"
if job.status == "cancelled"
else "ARTIFACT_JOB_RETRY_SCHEDULED"
if retry
else "ARTIFACT_REJECTED"
if request.error_code in {"static_inspection_blocked", "verification_digest_mismatch"}
else "ARTIFACT_DOWNLOAD_FAILED"
)
self.audit.write(
action,
"artifact_job",
str(job.id),
{"error_code": request.error_code, "retryable": request.retryable},
outcome="blocked",
)
self._commit()
return ArtifactJobResponse.model_validate(job)
@@ -0,0 +1,558 @@
"""Canonical, append-only audit writes, checkpoints and verification.
The chain head is a single serialization point. PostgreSQL writers use a transaction-scoped
advisory lock; SQLite test databases use an engine-scoped process lock held until the owning
transaction ends. An event and the durable singleton checkpoint advance in the same transaction.
"""
from __future__ import annotations
import hashlib
import json
import threading
import uuid
from collections.abc import Iterable
from dataclasses import dataclass
from datetime import UTC, datetime
from typing import Any, Protocol
from weakref import WeakKeyDictionary
from sqlalchemy import event, select, text
from sqlalchemy.engine import Connection, Engine
from sqlalchemy.orm import Session
from modelforge_api.domain.audit import (
AUDIT_CHAIN_SINGLETON_ID,
AUDIT_CURRENT_HASH_FORMAT,
AUDIT_EMPTY_LEGACY_PREFIX_SEAL,
AUDIT_HASH_FORMAT_V1,
AUDIT_HASH_FORMAT_V2,
AUDIT_LEGACY_PREFIX_DOMAIN,
canonical_audit_payload_and_hash,
canonical_audit_payload_text_and_hash,
normalise_audit_event_id,
normalise_audit_timestamp,
)
from modelforge_api.persistence.models import (
AuditChainHead,
AuditEvent,
_append_canonical_audit_event,
)
AUDIT_CHAIN_POSTGRES_LOCK_KEY = int.from_bytes(b"MF_AUDIT", byteorder="big", signed=False)
_SQLITE_LOCK_INFO_KEY = "modelforge_audit_chain_lock"
_SQLITE_LOCKS: WeakKeyDictionary[Engine, threading.Lock] = WeakKeyDictionary()
_SQLITE_LOCKS_GUARD = threading.Lock()
class AuditChainIntegrityError(RuntimeError):
"""The writer cannot safely append to the authoritative audit chain."""
class AuditEventLike(Protocol):
id: Any
sequence: int
occurred_at: Any
correlation_id: str
actor_type: str
actor_id: str
action: str
resource_type: str
resource_id: str | None
outcome: str
details: dict[str, Any]
previous_event_hash: str | None
event_hash: str
hash_format: str
canonical_payload: str | None
@dataclass(frozen=True, slots=True)
class AuditContext:
"""Authenticated/request identity seam for audit-producing services.
Existing callers can keep passing their actor fields directly. The API authentication boundary
can instead construct this context with its request correlation id without changing chain code.
"""
actor_type: str
actor_id: str
correlation_id: str | None = None
@dataclass(frozen=True, slots=True)
class AuditChainCheckpoint:
"""Transport-neutral checkpoint used by local and restored-database verification."""
singleton_id: int
event_count: int
last_sequence: int
last_event_hash: str | None
hash_format: str
v2_start_sequence: int
legacy_prefix_count: int
legacy_prefix_seal: str
@dataclass(frozen=True, slots=True)
class AuditEventRecord:
"""Transport-neutral event used when recovery reads a database through psql."""
id: Any
sequence: int
occurred_at: Any
correlation_id: str
actor_type: str
actor_id: str
action: str
resource_type: str
resource_id: str | None
outcome: str
details: dict[str, Any]
previous_event_hash: str | None
event_hash: str
hash_format: str
canonical_payload: str | None = None
def _decode_canonical_audit_payload(value: str) -> dict[str, Any]:
def reject_duplicate_keys(pairs: list[tuple[str, Any]]) -> dict[str, Any]:
result: dict[str, Any] = {}
for key, item in pairs:
if key in result:
raise ValueError(f"duplicate canonical audit payload key {key!r}")
result[key] = item
return result
decoded = json.loads(value, object_pairs_hook=reject_duplicate_keys)
if not isinstance(decoded, dict):
raise ValueError("canonical audit payload is not an object")
return decoded
def _event_payload_violations(audit_event: AuditEventLike) -> list[str]:
"""Verify one event's semantic payload and exact stored hash bytes."""
identity = str(audit_event.id)
if audit_event.hash_format == AUDIT_HASH_FORMAT_V1:
violations: list[str] = []
if audit_event.canonical_payload is not None:
violations.append(f"legacy audit event {identity} unexpectedly stores a v2 payload")
try:
_, expected_hash = canonical_audit_payload_and_hash(
correlation_id=audit_event.correlation_id,
actor_type=audit_event.actor_type,
actor_id=audit_event.actor_id,
action=audit_event.action,
resource_type=audit_event.resource_type,
resource_id=audit_event.resource_id,
outcome=audit_event.outcome,
details=audit_event.details,
previous_event_hash=audit_event.previous_event_hash,
hash_format=audit_event.hash_format,
event_id=audit_event.id,
occurred_at=audit_event.occurred_at,
)
except (TypeError, ValueError):
violations.append(f"audit event {identity} has a non-canonical payload")
else:
if audit_event.event_hash != expected_hash:
violations.append(
f"audit event {identity} content hash does not match its payload"
)
return violations
if audit_event.hash_format != AUDIT_HASH_FORMAT_V2:
return [f"audit event {identity} has an unsupported hash format"]
canonical_payload = audit_event.canonical_payload
if not isinstance(canonical_payload, str):
return [f"audit event {identity} has no exact v2 canonical payload"]
violations = []
if hashlib.sha256(canonical_payload.encode("utf-8")).hexdigest() != audit_event.event_hash:
violations.append(f"audit event {identity} content hash does not match stored bytes")
try:
decoded_payload = _decode_canonical_audit_payload(canonical_payload)
_, expected_text, _expected_hash = canonical_audit_payload_text_and_hash(
correlation_id=audit_event.correlation_id,
actor_type=audit_event.actor_type,
actor_id=audit_event.actor_id,
action=audit_event.action,
resource_type=audit_event.resource_type,
resource_id=audit_event.resource_id,
outcome=audit_event.outcome,
details=audit_event.details,
previous_event_hash=audit_event.previous_event_hash,
hash_format=audit_event.hash_format,
event_id=audit_event.id,
occurred_at=audit_event.occurred_at,
)
expected_payload = _decode_canonical_audit_payload(expected_text)
except (json.JSONDecodeError, TypeError, ValueError):
violations.append(f"audit event {identity} has a non-canonical payload")
else:
canonical_decoded = json.dumps(
decoded_payload, sort_keys=True, separators=(",", ":"), ensure_ascii=False
)
canonical_expected = json.dumps(
expected_payload, sort_keys=True, separators=(",", ":"), ensure_ascii=False
)
if canonical_decoded != canonical_expected:
violations.append(
f"audit event {identity} content hash bytes do not describe its event columns"
)
return violations
def _legacy_prefix_entry(event_row: AuditEventLike) -> bytes:
payload = {
"sequence": int(event_row.sequence),
"id": normalise_audit_event_id(event_row.id),
"occurred_at": normalise_audit_timestamp(event_row.occurred_at),
"event_hash": str(event_row.event_hash),
}
return json.dumps(payload, sort_keys=True, separators=(",", ":")).encode("utf-8") + b"\n"
def legacy_audit_prefix_seal(events: Iterable[AuditEventLike]) -> str:
"""Seal immutable v1 identities/timestamps without rewriting approved legacy events."""
digest = hashlib.sha256()
digest.update(AUDIT_LEGACY_PREFIX_DOMAIN)
for audit_event in events:
digest.update(_legacy_prefix_entry(audit_event))
return digest.hexdigest()
EMPTY_LEGACY_PREFIX_SEAL = AUDIT_EMPTY_LEGACY_PREFIX_SEAL
def audit_chain_violations(
events: Iterable[AuditEventLike], checkpoint: AuditChainHead | AuditChainCheckpoint | None
) -> list[str]:
"""Verify sequence, links, versioned hashes, the prefix seal and durable checkpoint."""
violations: list[str] = []
if checkpoint is None:
violations.append("audit chain checkpoint is missing")
cutover = 1
prefix_count = 0
else:
cutover = int(checkpoint.v2_start_sequence)
prefix_count = int(checkpoint.legacy_prefix_count)
if checkpoint.singleton_id != AUDIT_CHAIN_SINGLETON_ID:
violations.append("audit chain checkpoint has an invalid singleton id")
if checkpoint.hash_format != AUDIT_CURRENT_HASH_FORMAT:
violations.append("audit chain checkpoint names an unsupported current hash format")
if cutover < 1 or prefix_count != cutover - 1:
violations.append("audit chain checkpoint has an invalid v2 cutover")
previous: AuditEventLike | None = None
seen_sequences: set[int] = set()
seen_event_ids: set[str] = set()
observed = 0
prefix_observed = 0
prefix_digest = hashlib.sha256()
prefix_digest.update(AUDIT_LEGACY_PREFIX_DOMAIN)
for expected_sequence, audit_event in enumerate(events, start=1):
observed += 1
identity = str(audit_event.id)
try:
normalised_identity = normalise_audit_event_id(audit_event.id)
except ValueError:
violations.append(f"audit event {identity} has a non-canonical UUID")
else:
if normalised_identity in seen_event_ids:
violations.append(f"audit event UUID {normalised_identity} is duplicated")
seen_event_ids.add(normalised_identity)
sequence = int(audit_event.sequence)
if sequence in seen_sequences:
violations.append(f"audit sequence {sequence} is duplicated at event {identity}")
seen_sequences.add(sequence)
if sequence != expected_sequence:
violations.append(
f"audit event {identity} has sequence {sequence}; expected {expected_sequence}"
)
expected_link = previous.event_hash if previous is not None else None
if audit_event.previous_event_hash != expected_link:
position = "first event" if previous is None else f"event after {previous.id}"
violations.append(
f"audit event {identity} has an invalid previous hash for the {position}"
)
expected_format = AUDIT_HASH_FORMAT_V1 if sequence < cutover else AUDIT_HASH_FORMAT_V2
if audit_event.hash_format != expected_format:
violations.append(
f"audit event {identity} uses {audit_event.hash_format!r}; "
f"expected {expected_format!r} at sequence {sequence}"
)
violations.extend(_event_payload_violations(audit_event))
if sequence <= prefix_count:
prefix_observed += 1
try:
prefix_digest.update(_legacy_prefix_entry(audit_event))
except (TypeError, ValueError):
violations.append(
f"legacy audit event {identity} has a non-canonical identity or timestamp"
)
previous = audit_event
if checkpoint is not None:
observed_last_sequence = int(previous.sequence) if previous is not None else 0
observed_last_hash = previous.event_hash if previous is not None else None
if int(checkpoint.event_count) != observed:
violations.append(
f"audit checkpoint records {checkpoint.event_count} events; observed {observed}"
)
if int(checkpoint.last_sequence) != observed_last_sequence:
violations.append(
"audit checkpoint last sequence does not match the retained event suffix"
)
if checkpoint.last_event_hash != observed_last_hash:
violations.append("audit checkpoint last hash does not match the retained event suffix")
if prefix_observed != prefix_count:
violations.append(
f"audit checkpoint seals {prefix_count} legacy events; observed {prefix_observed}"
)
if checkpoint.legacy_prefix_seal != prefix_digest.hexdigest():
violations.append("audit legacy-prefix seal does not match immutable legacy history")
return violations
def _audit_engine(session: Session) -> Engine:
bind = session.get_bind()
if isinstance(bind, Connection):
return bind.engine
return bind
def _acquire_audit_write_lock(session: Session) -> None:
"""Hold the chain-head lock until the current root transaction completes."""
engine = _audit_engine(session)
dialect = engine.dialect.name
if dialect == "postgresql":
session.execute(
text("SELECT pg_advisory_xact_lock(:lock_key)"),
{"lock_key": AUDIT_CHAIN_POSTGRES_LOCK_KEY},
)
return
if dialect != "sqlite":
raise RuntimeError(f"audit-chain writes do not support the {dialect!r} database dialect")
if _SQLITE_LOCK_INFO_KEY in session.info:
return
if not session.in_transaction():
session.begin()
with _SQLITE_LOCKS_GUARD:
lock = _SQLITE_LOCKS.setdefault(engine, threading.Lock())
lock.acquire()
session.info[_SQLITE_LOCK_INFO_KEY] = lock
@event.listens_for(Session, "after_transaction_end")
def _release_sqlite_audit_write_lock(session: Session, transaction: Any) -> None:
if transaction.parent is not None:
return
lock = session.info.pop(_SQLITE_LOCK_INFO_KEY, None)
if lock is not None:
lock.release()
def _checkpoint_from_mapping(row: Any) -> AuditChainCheckpoint:
return AuditChainCheckpoint(
singleton_id=int(row.singleton_id),
event_count=int(row.event_count),
last_sequence=int(row.last_sequence),
last_event_hash=row.last_event_hash,
hash_format=str(row.hash_format),
v2_start_sequence=int(row.v2_start_sequence),
legacy_prefix_count=int(row.legacy_prefix_count),
legacy_prefix_seal=str(row.legacy_prefix_seal),
)
def _load_checkpoint(session: Session) -> AuditChainCheckpoint:
row = session.execute(
select(
AuditChainHead.singleton_id,
AuditChainHead.event_count,
AuditChainHead.last_sequence,
AuditChainHead.last_event_hash,
AuditChainHead.hash_format,
AuditChainHead.v2_start_sequence,
AuditChainHead.legacy_prefix_count,
AuditChainHead.legacy_prefix_seal,
).where(AuditChainHead.singleton_id == AUDIT_CHAIN_SINGLETON_ID)
).one_or_none()
if row is not None:
return _checkpoint_from_mapping(row)
raise AuditChainIntegrityError(
"audit chain checkpoint is missing; only schema creation or the audited migration may seed it"
)
def _assert_checkpoint_is_appendable(
session: Session, checkpoint: AuditChainCheckpoint
) -> AuditEvent | None:
"""Validate the locked checkpoint and constant-size retained tail before append.
Migration and recovery seal/verify the complete immutable prefix. Runtime append therefore
proves the checkpoint shape, current tail hash/link and compare-and-set predecessor in O(1).
A privileged edit in older middle history is intentionally the responsibility of the explicit
strict invariant/recovery gates; ordinary SQLAlchemy audit DML is blocked separately.
"""
violations: list[str] = []
if checkpoint.singleton_id != AUDIT_CHAIN_SINGLETON_ID:
violations.append("checkpoint singleton id is invalid")
if checkpoint.hash_format != AUDIT_CURRENT_HASH_FORMAT:
violations.append("checkpoint hash format is unsupported")
if checkpoint.event_count < 0 or checkpoint.last_sequence < 0:
violations.append("checkpoint counts cannot be negative")
if checkpoint.event_count != checkpoint.last_sequence:
violations.append("checkpoint event count and last sequence disagree")
if checkpoint.v2_start_sequence < 1:
violations.append("checkpoint v2 cutover is invalid")
if checkpoint.legacy_prefix_count != checkpoint.v2_start_sequence - 1:
violations.append("checkpoint legacy-prefix count and v2 cutover disagree")
if checkpoint.legacy_prefix_count > checkpoint.event_count:
violations.append("checkpoint legacy prefix exceeds the retained event count")
if (
len(checkpoint.legacy_prefix_seal) != 64
or any(character not in "0123456789abcdef" for character in checkpoint.legacy_prefix_seal)
):
violations.append("checkpoint legacy-prefix seal is malformed")
if (
checkpoint.legacy_prefix_count == 0
and checkpoint.legacy_prefix_seal != EMPTY_LEGACY_PREFIX_SEAL
):
violations.append("empty legacy-prefix checkpoint has the wrong seal")
tail = list(
session.scalars(
select(AuditEvent)
.order_by(AuditEvent.sequence.desc(), AuditEvent.id.desc())
.limit(2)
.execution_options(populate_existing=True)
)
)
latest = tail[0] if tail else None
if checkpoint.event_count == 0:
if latest is not None:
violations.append("empty checkpoint has a retained audit tail")
if checkpoint.last_event_hash is not None:
violations.append("empty checkpoint carries a last-event hash")
else:
if latest is None:
violations.append("non-empty checkpoint has no retained audit tail")
elif (
latest.sequence != checkpoint.last_sequence
or latest.event_hash != checkpoint.last_event_hash
):
violations.append("checkpoint does not identify the current retained audit tail")
if checkpoint.last_event_hash is None:
violations.append("non-empty checkpoint has no last-event hash")
elif len(checkpoint.last_event_hash) != 64 or any(
character not in "0123456789abcdef"
for character in checkpoint.last_event_hash
):
violations.append("checkpoint last-event hash is malformed")
if latest is not None:
expected_format = (
AUDIT_HASH_FORMAT_V1
if latest.sequence < checkpoint.v2_start_sequence
else AUDIT_HASH_FORMAT_V2
)
if latest.hash_format != expected_format:
violations.append("retained audit tail uses the wrong hash format")
payload_violations = _event_payload_violations(latest)
violations.extend(
f"retained audit tail: {violation}" for violation in payload_violations
)
if latest.sequence == 1:
if latest.previous_event_hash is not None:
violations.append("first retained audit event has a previous hash")
if len(tail) != 1:
violations.append("checkpoint count one has more than one retained event")
elif latest.sequence > 1:
if len(tail) != 2:
violations.append("retained audit tail has no predecessor")
else:
predecessor = tail[1]
if predecessor.sequence != latest.sequence - 1:
violations.append("retained audit tail predecessor is not contiguous")
if latest.previous_event_hash != predecessor.event_hash:
violations.append("retained audit tail link does not match its predecessor")
if violations:
raise AuditChainIntegrityError(
"audit checkpoint/tail failed pre-append verification: "
+ "; ".join(violations[:5])
)
return latest
class AuditWriter:
def __init__(
self,
session: Session,
actor_type: str | None = None,
actor_id: str | None = None,
*,
context: AuditContext | None = None,
) -> None:
if context is not None:
if actor_type is not None or actor_id is not None:
raise ValueError("pass either an audit context or actor fields, not both")
resolved = context
else:
if actor_type is None or actor_id is None:
raise ValueError("audit actor type and id are required")
resolved = AuditContext(actor_type=actor_type, actor_id=actor_id)
self.session = session
self.context = resolved
def write(
self,
action: str,
resource_type: str,
resource_id: str | None,
details: dict[str, Any],
outcome: str = "success",
) -> AuditEvent:
_acquire_audit_write_lock(self.session)
try:
checkpoint = _load_checkpoint(self.session)
previous = _assert_checkpoint_is_appendable(self.session, checkpoint)
event_id = uuid.uuid4()
occurred_at = datetime.now(UTC)
return _append_canonical_audit_event(
self.session,
event_id=event_id,
occurred_at=occurred_at,
correlation_id=self.context.correlation_id or str(uuid.uuid4()),
actor_type=self.context.actor_type,
actor_id=self.context.actor_id,
action=action,
resource_type=resource_type,
resource_id=resource_id,
outcome=outcome,
details=details,
previous_event_hash=previous.event_hash if previous else None,
expected_event_count=checkpoint.event_count,
expected_last_sequence=checkpoint.last_sequence,
expected_last_event_hash=checkpoint.last_event_hash,
expected_hash_format=checkpoint.hash_format,
expected_v2_start_sequence=checkpoint.v2_start_sequence,
expected_legacy_prefix_count=checkpoint.legacy_prefix_count,
expected_legacy_prefix_seal=checkpoint.legacy_prefix_seal,
)
except Exception:
# A caller must never be able to catch an audit failure and commit an unaudited domain
# mutation or a detached event. Roll back the complete owning transaction fail-closed.
self.session.rollback()
raise
@@ -0,0 +1,267 @@
from __future__ import annotations
import hashlib
import json
from typing import Any, cast
from sqlalchemy import select
from sqlalchemy.orm import Session
from modelforge_api.domain.capability_evaluation import (
ALLOWED_METRICS,
CapabilityAdvisorResponse,
CapabilityCaseResult,
CapabilityEvaluationCase,
CapabilityEvaluationRunCreate,
CapabilityEvaluationRunResponse,
CapabilityEvaluationSuiteCreate,
CapabilityEvaluationSuiteResponse,
CapabilityRecommendationState,
EvaluationType,
MetricDefinition,
)
from modelforge_api.persistence.models import (
Capability,
CapabilityContract,
CapabilityDeployment,
CapabilityEvaluationRun,
CapabilityEvaluationSuite,
)
from modelforge_api.services.serving import ServingError
def _digest(value: Any) -> str:
raw = json.dumps(value, sort_keys=True, separators=(",", ":"), default=str)
return hashlib.sha256(raw.encode()).hexdigest()
class CapabilityEvaluationService:
def __init__(self, session: Session) -> None:
self.session = session
def _contract(self, key: str, version: int) -> CapabilityContract | None:
return self.session.scalar(
select(CapabilityContract)
.join(Capability, Capability.id == CapabilityContract.capability_id)
.where(Capability.key == key, CapabilityContract.version == version)
)
def create_suite(
self, request: CapabilityEvaluationSuiteCreate
) -> CapabilityEvaluationSuiteResponse:
contract = self._contract(request.capability, request.contract_version)
if not contract:
raise ServingError(404, "CAPABILITY_NOT_FOUND", "capability contract was not found")
contract_type = str(contract.contract.get("estate", {}).get("evaluation_type", ""))
if contract_type != request.evaluation_type:
raise ServingError(409, "EVALUATION_TYPE_MISMATCH", "suite type differs from capability contract")
definition = request.model_dump(mode="json")
digest = _digest(definition)
existing = self.session.scalar(
select(CapabilityEvaluationSuite).where(
CapabilityEvaluationSuite.definition_digest == digest
)
)
if existing:
return self.suite_response(existing)
suite = CapabilityEvaluationSuite(
capability_contract_id=contract.id,
key=request.key,
evaluation_type=request.evaluation_type,
revision=request.revision,
dataset_revision=request.dataset_revision,
definition_digest=digest,
metric_definitions={item.name: item.model_dump(mode="json") for item in request.metrics},
case_definitions=[item.model_dump(mode="json") for item in request.cases],
thresholds=request.thresholds,
)
self.session.add(suite)
self.session.commit()
self.session.refresh(suite)
return self.suite_response(suite)
def suite_response(self, suite: CapabilityEvaluationSuite) -> CapabilityEvaluationSuiteResponse:
contract = self.session.get(CapabilityContract, suite.capability_contract_id)
capability = self.session.get(Capability, contract.capability_id) if contract else None
if not contract or not capability:
raise ServingError(409, "EVALUATION_PROVENANCE_INCOMPLETE", "suite contract is missing")
return CapabilityEvaluationSuiteResponse(
id=suite.id,
capability=capability.key,
contract_version=contract.version,
key=suite.key,
evaluation_type=cast(EvaluationType, suite.evaluation_type),
revision=suite.revision,
dataset_revision=suite.dataset_revision,
definition_digest=suite.definition_digest,
metrics=[
MetricDefinition.model_validate(item)
for item in suite.metric_definitions.values()
],
cases=[
CapabilityEvaluationCase.model_validate(item)
for item in suite.case_definitions
],
thresholds={key: float(value) for key, value in suite.thresholds.items()},
created_at=suite.created_at,
)
def suites(self) -> list[CapabilityEvaluationSuiteResponse]:
rows = self.session.scalars(
select(CapabilityEvaluationSuite).order_by(CapabilityEvaluationSuite.created_at)
).all()
return [self.suite_response(item) for item in rows]
def create_run(self, request: CapabilityEvaluationRunCreate) -> CapabilityEvaluationRunResponse:
suite = self.session.get(CapabilityEvaluationSuite, request.suite_id)
deployment = self.session.get(CapabilityDeployment, request.capability_deployment_id)
if not suite or not deployment:
raise ServingError(404, "EVALUATION_TARGET_NOT_FOUND", "suite or deployment was not found")
if suite.capability_contract_id != deployment.capability_contract_id:
raise ServingError(409, "EVALUATION_TARGET_MISMATCH", "suite and deployment capabilities differ")
declared = set(suite.metric_definitions)
if set(request.metrics) != declared or declared - ALLOWED_METRICS[suite.evaluation_type]:
raise ServingError(422, "INVALID_METRICS", "aggregate metrics do not match the suite")
expected_cases = {str(item["key"]) for item in suite.case_definitions}
actual_cases = {item.case_key for item in request.cases}
if actual_cases != expected_cases:
raise ServingError(422, "INVALID_CASE_SET", "run must contain every immutable suite case exactly once")
evidence_payload = request.model_dump(mode="json")
digest = _digest(evidence_payload)
existing = self.session.scalar(
select(CapabilityEvaluationRun).where(CapabilityEvaluationRun.evidence_digest == digest)
)
if existing:
return self.run_response(existing)
run = CapabilityEvaluationRun(
suite_id=suite.id,
capability_deployment_id=deployment.id,
status=request.status,
metric_values=request.metrics,
case_results=[item.model_dump(mode="json") for item in request.cases],
resource_metrics=request.resource_metrics,
environment_fingerprint=request.environment_fingerprint,
evidence_digest=digest,
evidence={**request.evidence, "payload_persisted": False, "content_logged": False},
started_at=request.started_at,
completed_at=request.completed_at,
)
self.session.add(run)
self.session.commit()
self.session.refresh(run)
return self.run_response(run)
def run_response(self, run: CapabilityEvaluationRun) -> CapabilityEvaluationRunResponse:
suite = self.session.get(CapabilityEvaluationSuite, run.suite_id)
if not suite:
raise ServingError(409, "EVALUATION_PROVENANCE_INCOMPLETE", "run suite is missing")
return CapabilityEvaluationRunResponse(
id=run.id,
suite_id=run.suite_id,
capability_deployment_id=run.capability_deployment_id,
evaluation_type=cast(EvaluationType, suite.evaluation_type),
status=run.status,
metrics=run.metric_values,
cases=[CapabilityCaseResult.model_validate(item) for item in run.case_results],
resource_metrics=run.resource_metrics,
environment_fingerprint=run.environment_fingerprint,
evidence_digest=run.evidence_digest,
evidence=run.evidence,
started_at=run.started_at,
completed_at=run.completed_at,
created_at=run.created_at,
)
def runs(self) -> list[CapabilityEvaluationRunResponse]:
rows = self.session.scalars(
select(CapabilityEvaluationRun).order_by(CapabilityEvaluationRun.created_at)
).all()
return [self.run_response(item) for item in rows]
def advisor(self) -> list[CapabilityAdvisorResponse]:
contracts = self.session.execute(
select(CapabilityContract, Capability)
.join(Capability, Capability.id == CapabilityContract.capability_id)
.order_by(Capability.key, CapabilityContract.version)
).all()
result: list[CapabilityAdvisorResponse] = []
for contract, capability in contracts:
deployment = self.session.scalar(
select(CapabilityDeployment)
.where(
CapabilityDeployment.capability_contract_id == contract.id,
CapabilityDeployment.status.in_(["candidate", "approved", "active", "stable"]),
)
.order_by(CapabilityDeployment.production.desc(), CapabilityDeployment.created_at.desc())
)
if not deployment:
result.append(CapabilityAdvisorResponse(
capability=capability.key,
contract_version=contract.version,
state="REQUIRES_MORE_EVIDENCE",
reasons=["no active capability deployment has proven this contract"],
))
continue
run = self.session.scalar(
select(CapabilityEvaluationRun)
.join(CapabilityEvaluationSuite, CapabilityEvaluationSuite.id == CapabilityEvaluationRun.suite_id)
.where(
CapabilityEvaluationRun.capability_deployment_id == deployment.id,
CapabilityEvaluationSuite.capability_contract_id == contract.id,
)
.order_by(CapabilityEvaluationRun.completed_at.desc())
)
if not run:
if deployment.production:
result.append(CapabilityAdvisorResponse(
capability=capability.key,
contract_version=contract.version,
state="KEEP_CURRENT",
deployment_id=deployment.id,
reasons=["current production deployment remains active; no comparable new typed run supersedes it"],
))
continue
result.append(CapabilityAdvisorResponse(
capability=capability.key,
contract_version=contract.version,
state="LAB_READY",
deployment_id=deployment.id,
reasons=["runtime deployment is available; modality evaluation is still required"],
))
continue
suite = self.session.get(CapabilityEvaluationSuite, run.suite_id)
if suite is None:
raise ServingError(
409, "EVALUATION_PROVENANCE_INCOMPLETE", "run suite is missing"
)
failures = [item["case_key"] for item in run.case_results if item["status"] != "passed"]
threshold_failures: list[str] = []
for name, threshold in suite.thresholds.items():
definition = suite.metric_definitions[name]
value = float(run.metric_values[name])
direction = definition["direction"]
if (direction == "higher_is_better" and value < float(threshold)) or (
direction == "lower_is_better" and value > float(threshold)
):
threshold_failures.append(name)
if run.status != "completed" or failures or threshold_failures:
reasons = []
if run.status != "completed":
reasons.append("latest evaluation run did not complete")
if failures:
reasons.append("case failures: " + ",".join(failures))
if threshold_failures:
reasons.append("threshold failures: " + ",".join(threshold_failures))
state: CapabilityRecommendationState = "BLOCKED"
else:
reasons = ["completed typed evaluation satisfies every declared threshold"]
state = "PROMOTION_ELIGIBLE"
result.append(CapabilityAdvisorResponse(
capability=capability.key,
contract_version=contract.version,
state=state,
deployment_id=deployment.id,
evaluation_run_id=run.id,
reasons=reasons,
))
return result
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,395 @@
from __future__ import annotations
import hashlib
import json
from dataclasses import asdict, dataclass, field
from datetime import UTC, datetime, timedelta
from enum import StrEnum
from typing import Any
class AttributionConfidence(StrEnum):
KNOWN = "KNOWN"
ESTIMATED = "ESTIMATED"
UNKNOWN = "UNKNOWN"
class PressureState(StrEnum):
NORMAL = "NORMAL"
ELEVATED = "ELEVATED"
HIGH = "HIGH"
CRITICAL = "CRITICAL"
class PlacementVerdict(StrEnum):
ADMIT = "ADMIT"
ADMIT_AFTER_EVICTION = "ADMIT_AFTER_EVICTION"
QUEUE = "QUEUE"
REJECT_CAPACITY = "REJECT_CAPACITY"
REJECT_HEALTH = "REJECT_HEALTH"
REJECT_POLICY = "REJECT_POLICY"
class ResidencyPolicy(StrEnum):
ALWAYS_WARM = "always_warm"
KEEP_WARM = "keep_warm"
LOAD_ON_DEMAND = "load_on_demand"
LAB_ONLY = "lab_only"
PRIORITY_RANK = {"production": 0, "interactive": 1, "background": 2, "lab": 3, "benchmark": 3}
@dataclass(frozen=True)
class SchedulerPolicy:
revision: str = "m10-v1"
reserve_minimum_bytes: int = 1_073_741_824
reserve_percentage: float = 0.05
runtime_margin_bytes: int = 268_435_456
deployment_margin_minimum_bytes: int = 134_217_728
deployment_margin_percentage: float = 0.10
request_execution_floor_bytes: int = 67_108_864
elevated_headroom_percentage: float = 0.20
high_headroom_percentage: float = 0.10
critical_headroom_percentage: float = 0.03
recovery_extra_percentage: float = 0.03
pressure_stable_seconds: int = 30
eviction_cooldown_seconds: int = 60
global_queue_limit: int = 128
placement_history_limit: int = 500
lab_paused: bool = False
@dataclass(frozen=True)
class LeaseAllocation:
reserved_bytes: int
materialized_bytes: int = 0
@property
def future_bytes(self) -> int:
return max(0, self.reserved_bytes - self.materialized_bytes)
@dataclass(frozen=True)
class AccountingSnapshot:
total_bytes: int
observed_used_bytes: int | None
resident_bytes: int
future_lease_bytes: int
reserve_bytes: int
external_bytes: int
schedulable_bytes: int
attribution: AttributionConfidence
invariant_delta_bytes: int
pressure: PressureState
@dataclass(frozen=True)
class ResidentCandidate:
deployment_id: str
capability: str
resident_bytes: int
active_requests: int
priority: str
policy: str
idle_since: datetime | None
cold_load_ms: float
last_used_at: datetime | None
pinned: bool = False
@dataclass(frozen=True)
class PlacementInput:
deployment_id: str
capability: str
node_id: str
accelerator_id: str
priority: str
required_bytes: int
cold_load_ms: float
is_resident: bool
envelope_stale: bool
runtime_healthy: bool
node_eligible: bool
deadline_remaining_ms: float | None = None
@dataclass(frozen=True)
class PlannedEviction:
deployment_id: str
capability: str
expected_reclaimed_bytes: int
reason: str
@dataclass(frozen=True)
class PlacementDecision:
verdict: PlacementVerdict
reason_codes: tuple[str, ...]
required_bytes: int
headroom_before_bytes: int
headroom_after_bytes: int
evictions: tuple[PlannedEviction, ...] = field(default_factory=tuple)
fingerprint: str = ""
def evidence(self) -> dict[str, Any]:
result = asdict(self)
result["verdict"] = self.verdict.value
result["evictions"] = [asdict(item) for item in self.evictions]
return result
def dynamic_reserve(total_bytes: int, policy: SchedulerPolicy) -> int:
return max(
policy.reserve_minimum_bytes,
int(total_bytes * policy.reserve_percentage),
policy.runtime_margin_bytes,
)
def required_envelope(peak_bytes: int, policy: SchedulerPolicy) -> int:
margin = max(
policy.deployment_margin_minimum_bytes,
int(peak_bytes * policy.deployment_margin_percentage),
)
return max(0, peak_bytes) + margin
def pressure_for(
headroom: int, total: int, previous: PressureState = PressureState.NORMAL
) -> PressureState:
ratio = headroom / total if total > 0 else 0.0
thresholds = {
PressureState.CRITICAL: 0.03,
PressureState.HIGH: 0.10,
PressureState.ELEVATED: 0.20,
}
if ratio <= thresholds[PressureState.CRITICAL]:
return PressureState.CRITICAL
if ratio <= thresholds[PressureState.HIGH]:
return PressureState.HIGH
if ratio <= thresholds[PressureState.ELEVATED]:
return PressureState.ELEVATED
# Recovery has a 3%-of-total gap. Persistence of the candidate state is
# handled by the accelerator pressure record in the service.
if previous == PressureState.CRITICAL and ratio <= 0.06:
return previous
if previous == PressureState.HIGH and ratio <= 0.13:
return previous
if previous == PressureState.ELEVATED and ratio <= 0.23:
return previous
return PressureState.NORMAL
def calculate_accounting(
*,
total_bytes: int,
observed_used_bytes: int | None,
resident_bytes: int,
leases: list[LeaseAllocation],
policy: SchedulerPolicy,
telemetry_fresh: bool = True,
previous_pressure: PressureState = PressureState.NORMAL,
) -> AccountingSnapshot:
total = max(0, total_bytes)
resident = max(0, resident_bytes)
reserve = dynamic_reserve(total, policy)
future = sum(item.future_bytes for item in leases)
if observed_used_bytes is None or not telemetry_fresh:
return AccountingSnapshot(
total,
observed_used_bytes,
resident,
future,
reserve,
max(0, total - resident),
0,
AttributionConfidence.UNKNOWN,
0,
PressureState.CRITICAL,
)
observed = min(total, max(0, observed_used_bytes))
external = max(0, observed - resident)
attributed = external + resident
delta = observed - attributed
tolerance = max(67_108_864, int(total * 0.01))
confidence = (
AttributionConfidence.KNOWN if abs(delta) <= tolerance else AttributionConfidence.ESTIMATED
)
# NVML used already contains resident allocations. Only the unmaterialized
# portion of leases is additionally reserved, preventing double counting.
schedulable = max(0, total - max(observed, resident) - future - reserve)
pressure = pressure_for(schedulable, total, previous_pressure)
return AccountingSnapshot(
total,
observed,
resident,
future,
reserve,
external,
schedulable,
confidence,
delta,
pressure,
)
def _eviction_key(candidate: ResidentCandidate) -> tuple[int, int, float, float]:
policy_penalty = {
ResidencyPolicy.LAB_ONLY.value: 0,
ResidencyPolicy.LOAD_ON_DEMAND.value: 1,
ResidencyPolicy.KEEP_WARM.value: 2,
ResidencyPolicy.ALWAYS_WARM.value: 10,
}.get(candidate.policy, 5)
last_use = candidate.last_used_at or candidate.idle_since or datetime.min.replace(tzinfo=UTC)
# Safety/priority precede reload cost; among equivalent candidates evict
# the cheaper and least-recently-used residency first.
return (
policy_penalty,
-PRIORITY_RANK.get(candidate.priority, 4),
candidate.cold_load_ms,
last_use.timestamp(),
)
def plan_placement(
request: PlacementInput,
snapshot: AccountingSnapshot,
residents: list[ResidentCandidate],
policy: SchedulerPolicy,
*,
global_queue_depth: int = 0,
) -> PlacementDecision:
reasons: list[str] = []
verdict: PlacementVerdict
evictions: list[PlannedEviction] = []
needed = max(policy.request_execution_floor_bytes, request.required_bytes)
if not request.node_eligible or not request.runtime_healthy:
verdict = PlacementVerdict.REJECT_HEALTH
reasons.append("NODE_UNAVAILABLE" if not request.node_eligible else "RUNTIME_UNHEALTHY")
elif request.envelope_stale or snapshot.attribution is AttributionConfidence.UNKNOWN:
verdict = PlacementVerdict.REJECT_HEALTH
reasons.append("SCHEDULER_STATE_STALE")
elif policy.lab_paused and PRIORITY_RANK.get(request.priority, 4) >= PRIORITY_RANK["lab"]:
verdict = PlacementVerdict.REJECT_POLICY
reasons.append("POLICY_BLOCKED")
elif (
request.deadline_remaining_ms is not None
and not request.is_resident
and request.cold_load_ms > request.deadline_remaining_ms
):
verdict = PlacementVerdict.REJECT_POLICY
reasons.append("DEADLINE_CANNOT_BE_MET")
elif snapshot.pressure is PressureState.CRITICAL:
verdict = PlacementVerdict.REJECT_CAPACITY
reasons.append("EXTERNAL_GPU_PRESSURE")
elif (
snapshot.pressure in {PressureState.HIGH, PressureState.ELEVATED}
and PRIORITY_RANK.get(request.priority, 4) >= PRIORITY_RANK["background"]
and not request.is_resident
):
verdict = (
PlacementVerdict.QUEUE
if global_queue_depth < policy.global_queue_limit
else PlacementVerdict.REJECT_CAPACITY
)
reasons.append(
"EXTERNAL_GPU_PRESSURE" if verdict is PlacementVerdict.QUEUE else "QUEUE_FULL"
)
elif needed <= snapshot.schedulable_bytes:
verdict = PlacementVerdict.ADMIT
reasons.append(
"RESIDENT_WARM_HIT" if request.is_resident else "MEASURED_CAPACITY_AVAILABLE"
)
else:
reclaim = 0
for candidate in sorted(residents, key=_eviction_key):
if (
candidate.deployment_id == request.deployment_id
or candidate.active_requests > 0
or candidate.pinned
or candidate.policy == ResidencyPolicy.ALWAYS_WARM.value
or PRIORITY_RANK.get(candidate.priority, 4) < PRIORITY_RANK.get(request.priority, 4)
):
continue
reason = (
"IDLE_LAB_EVICTED_FOR_PRODUCTION"
if PRIORITY_RANK.get(request.priority, 4) <= PRIORITY_RANK["interactive"]
and PRIORITY_RANK.get(candidate.priority, 4) >= PRIORITY_RANK["lab"]
else "CAPACITY_REBALANCE"
)
evictions.append(
PlannedEviction(
candidate.deployment_id, candidate.capability, candidate.resident_bytes, reason
)
)
reclaim += candidate.resident_bytes
if needed <= snapshot.schedulable_bytes + reclaim:
break
if needed <= snapshot.schedulable_bytes + reclaim:
verdict = PlacementVerdict.ADMIT_AFTER_EVICTION
reasons.append("MANAGED_IDLE_EVICTION_REQUIRED")
elif any(item.active_requests > 0 for item in residents):
verdict = PlacementVerdict.QUEUE
reasons.append("RESIDENCY_CONFLICT")
evictions = []
else:
verdict = PlacementVerdict.REJECT_CAPACITY
reasons.append("INSUFFICIENT_SCHEDULABLE_VRAM")
evictions = []
after = max(
0,
snapshot.schedulable_bytes
+ sum(item.expected_reclaimed_bytes for item in evictions)
- (
needed
if verdict in {PlacementVerdict.ADMIT, PlacementVerdict.ADMIT_AFTER_EVICTION}
else 0
),
)
evidence = {
"policy_revision": policy.revision,
"request": asdict(request),
"snapshot": asdict(snapshot),
"verdict": verdict.value,
"reasons": reasons,
"evictions": [asdict(item) for item in evictions],
}
fingerprint = hashlib.sha256(
json.dumps(evidence, sort_keys=True, default=str, separators=(",", ":")).encode()
).hexdigest()
return PlacementDecision(
verdict,
tuple(reasons),
needed,
snapshot.schedulable_bytes,
after,
tuple(evictions),
fingerprint,
)
@dataclass
class PressureHysteresis:
state: PressureState = PressureState.NORMAL
candidate: PressureState | None = None
candidate_since: datetime | None = None
changed_at: datetime = field(default_factory=lambda: datetime.now(UTC))
def observe(
self, candidate: PressureState, now: datetime, stable_seconds: int = 30
) -> PressureState:
if candidate == self.state:
self.candidate = None
self.candidate_since = None
return self.state
if self.candidate != candidate:
self.candidate = candidate
self.candidate_since = now
return self.state
if self.candidate_since and now - self.candidate_since >= timedelta(seconds=stable_seconds):
self.state = candidate
self.changed_at = now
self.candidate = None
self.candidate_since = None
return self.state
@@ -0,0 +1,27 @@
from pathlib import Path
from sqlalchemy.orm import Session
from modelforge_api.hardware.collectors import (
NodeIdentityProvider,
SystemHostCollector,
build_nvml_collector,
)
from modelforge_api.services.hardware_inventory import HardwareInventoryService
from modelforge_api.settings import Settings
def build_hardware_service(session: Session, settings: Settings) -> HardwareInventoryService:
host = SystemHostCollector(
NodeIdentityProvider(
settings.node_identity_file,
settings.node_identity,
force_persisted=settings.node_identity_mode == "persisted",
),
{
"model_cache": Path(settings.hf_home),
"artifacts": Path(settings.artifact_root),
"quarantine": Path(settings.quarantine_root),
},
)
return HardwareInventoryService(session, host, build_nvml_collector())
@@ -0,0 +1,557 @@
from __future__ import annotations
import threading
from datetime import UTC, datetime
from typing import Any, TypeVar
from sqlalchemy.orm import Session
from modelforge_api.domain.agent_protocol import AGENT_PROTOCOL_VERSION
from modelforge_api.domain.enums import (
AgentHealth,
Availability,
HardwareStatus,
InventoryRunStatus,
NodeLiveness,
ObservationSource,
)
from modelforge_api.domain.hardware import (
AcceleratorCollector,
AcceleratorState,
AcceleratorTelemetry,
HardwareOverview,
HardwareSnapshot,
HardwareState,
HostCollector,
NodeState,
ObservedValue,
StorageState,
)
from modelforge_api.persistence.hardware_repository import HardwareRepository
from modelforge_api.persistence.models import (
Accelerator,
AcceleratorTelemetryLatest,
ComputeNode,
HardwareInventoryRun,
HostTelemetryLatest,
StorageVolumeState,
)
from modelforge_api.services.audit import AuditWriter
class HardwareRefreshBusy(RuntimeError):
pass
T = TypeVar("T")
def _aware(value: datetime) -> datetime:
return value if value.tzinfo else value.replace(tzinfo=UTC)
class HardwareInventoryService:
_refresh_lock = threading.Lock()
def __init__(
self,
session: Session,
host_collector: HostCollector,
accelerator_collector: AcceleratorCollector,
source: ObservationSource = ObservationSource.LOCAL_CONTROL_PLANE,
received_at: datetime | None = None,
observation_sequence: int | None = None,
) -> None:
self.session = session
self.repository = HardwareRepository(session)
self.host_collector = host_collector
self.accelerator_collector = accelerator_collector
self.source = source
self.received_at = received_at
self.observation_sequence = observation_sequence
def _audit(
self,
action: str,
resource_type: str,
resource_id: str | None,
details: dict[str, Any],
) -> None:
AuditWriter(self.session, "system", "hardware-inventory").write(
action, resource_type, resource_id, details
)
@staticmethod
def _value(observation: ObservedValue[T]) -> T | None:
return observation.value if observation.availability is Availability.KNOWN else None
def refresh(self) -> HardwareState:
if not self._refresh_lock.acquire(blocking=False):
raise HardwareRefreshBusy("hardware refresh already in progress")
started = datetime.now(UTC)
received_at = self.received_at or datetime.now(UTC)
run = HardwareInventoryRun(
status=InventoryRunStatus.RUNNING,
source=self.source,
summary={},
started_at=started,
observation_sequence=self.observation_sequence,
received_at=received_at,
)
self.repository.add(run)
try:
host = self.host_collector.collect()
nvidia = self.accelerator_collector.collect()
snapshot = HardwareSnapshot(host=host, nvidia=nvidia)
node = self.repository.node_by_key(host.identity_key)
created = node is None
if node is None:
node = ComputeNode(key=host.identity_key, hostname=host.hostname)
self.repository.add(node)
elif node.decommissioned_at is not None:
raise RuntimeError(
"decommissioned compute-node identity requires explicit recovery enrollment"
)
previous_fingerprint = node.hardware_fingerprint
node.hostname = host.hostname
if created or not node.display_name:
node.display_name = host.display_name
node.identity_source = host.identity_source
node.os_name = host.os_name
node.os_version = self._value(host.os_version)
node.architecture = host.architecture
node.kernel_version = self._value(host.kernel_version)
node.cpu_model = self._value(host.cpu_model)
node.logical_cpu_count = self._value(host.logical_cpu_count)
node.physical_core_count = self._value(host.physical_core_count)
node.total_ram_bytes = self._value(host.total_ram_bytes)
node.agent_version = host.agent_version
node.status = HardwareStatus.ACTIVE
node.status_reason = None
node.hardware_fingerprint = snapshot.fingerprint
node.inventory = host.model_dump(
mode="json", exclude={"storage", "available_ram_bytes"}
)
node.last_seen_at = host.inventory_at
node.inventory_at = host.inventory_at
node.observation_source = self.source
node.last_inventory_observed_at = host.inventory_at
node.last_inventory_received_at = received_at
node.inventory_sequence = self.observation_sequence or node.inventory_sequence
if self.source is ObservationSource.LOCAL_CONTROL_PLANE:
node.liveness_state = NodeLiveness.ONLINE
node.last_heartbeat_at = received_at
self.session.flush()
if created:
self._audit(
"NODE_DISCOVERED", "compute_node", str(node.id), {"hostname": node.hostname}
)
elif previous_fingerprint and previous_fingerprint != snapshot.fingerprint:
self._audit(
"NODE_INVENTORY_CHANGED"
if self.source is ObservationSource.REMOTE_AGENT
else "HARDWARE_CHANGED",
"compute_node",
str(node.id),
{
"previous_fingerprint": previous_fingerprint,
"fingerprint": snapshot.fingerprint,
},
)
host_latest = self.repository.host_telemetry(node.id)
if host_latest is None:
host_latest = HostTelemetryLatest(
compute_node_id=node.id, observed_at=host.inventory_at
)
self.repository.add(host_latest)
host_latest.available_ram_bytes = self._value(host.available_ram_bytes)
host_latest.availability = {
"available_ram_bytes": host.available_ram_bytes.model_dump(mode="json")
}
host_latest.observed_at = host.inventory_at
host_latest.received_at = received_at
for storage in host.storage:
state = self.repository.storage_by_key(node.id, storage.purpose, storage.path)
if state is None:
state = StorageVolumeState(
compute_node_id=node.id,
purpose=storage.purpose,
path=storage.path,
observed_at=host.inventory_at,
)
self.repository.add(state)
state.total_bytes = self._value(storage.total_bytes)
state.used_bytes = self._value(storage.used_bytes)
state.free_bytes = self._value(storage.free_bytes)
state.availability = {
key: getattr(storage, key).model_dump(mode="json")
for key in ("total_bytes", "used_bytes", "free_bytes")
}
state.observed_at = host.inventory_at
state.received_at = received_at
seen: set[str] = set()
telemetry_by_uuid = {item.device_uuid: item for item in nvidia.telemetry}
for observed in nvidia.inventory:
seen.add(observed.device_uuid)
accelerator = self.repository.accelerator_by_uuid(node.id, observed.device_uuid)
new_accelerator = accelerator is None
if accelerator is None:
accelerator = Accelerator(
compute_node_id=node.id,
device_index=observed.device_index,
device_uuid=observed.device_uuid,
name=observed.name,
)
self.repository.add(accelerator)
accelerator.device_index = observed.device_index
accelerator.pci_bus_id = self._value(observed.pci_bus_id)
accelerator.name = observed.name
accelerator.vendor = observed.vendor
accelerator.architecture = self._value(observed.architecture)
accelerator.compute_capability_major = self._value(
observed.compute_capability_major
)
accelerator.compute_capability_minor = self._value(
observed.compute_capability_minor
)
accelerator.total_vram_bytes = self._value(observed.total_vram_bytes)
accelerator.memory_total_mb = (
round(accelerator.total_vram_bytes / 1024 / 1024)
if accelerator.total_vram_bytes is not None
else None
)
accelerator.driver_version = self._value(observed.driver_version)
accelerator.cuda_version = self._value(observed.cuda_driver_version)
accelerator.mig_mode_current = self._value(observed.mig_mode_current)
accelerator.status = HardwareStatus.ACTIVE
accelerator.status_reason = None
accelerator.inventory_source = observed.source
accelerator.last_seen_at = observed.inventory_at
accelerator.inventory_at = observed.inventory_at
accelerator.capabilities = observed.model_dump(mode="json")
self.session.flush()
if new_accelerator:
self._audit(
"ACCELERATOR_DISCOVERED",
"accelerator",
str(accelerator.id),
{"device_uuid": accelerator.device_uuid, "name": accelerator.name},
)
telemetry = telemetry_by_uuid.get(observed.device_uuid)
if telemetry:
latest = self.repository.accelerator_telemetry(accelerator.id)
if latest is None:
latest = AcceleratorTelemetryLatest(
accelerator_id=accelerator.id, observed_at=telemetry.observed_at
)
self.repository.add(latest)
for field in (
"used_vram_bytes",
"free_vram_bytes",
"gpu_utilization_percent",
"memory_utilization_percent",
"temperature_c",
"power_draw_w",
"power_limit_w",
"graphics_clock_mhz",
"memory_clock_mhz",
"fan_speed_percent",
"performance_state",
):
setattr(latest, field, self._value(getattr(telemetry, field)))
latest.availability = {
field: getattr(telemetry, field).model_dump(mode="json")
for field in (
"used_vram_bytes",
"free_vram_bytes",
"gpu_utilization_percent",
"memory_utilization_percent",
"temperature_c",
"power_draw_w",
"power_limit_w",
"graphics_clock_mhz",
"memory_clock_mhz",
"fan_speed_percent",
"performance_state",
)
}
latest.observed_at = telemetry.observed_at
latest.received_at = received_at
if nvidia.availability is Availability.KNOWN:
for accelerator in self.repository.accelerators(node.id):
if (
accelerator.device_uuid not in seen
and accelerator.status != HardwareStatus.MISSING
):
accelerator.status = HardwareStatus.MISSING
accelerator.status_reason = (
"not observed in latest successful NVML enumeration"
)
self._audit(
"ACCELERATOR_MISSING",
"accelerator",
str(accelerator.id),
{"device_uuid": accelerator.device_uuid},
)
run.compute_node_id = node.id
run.status = (
InventoryRunStatus.SUCCEEDED
if nvidia.availability is Availability.KNOWN
else InventoryRunStatus.DEGRADED
)
run.fingerprint = snapshot.fingerprint
run.summary = {
"accelerator_count": len(nvidia.inventory),
"nvidia_availability": nvidia.availability,
"nvidia_reason": nvidia.reason,
}
run.completed_at = datetime.now(UTC)
run.observed_at = host.inventory_at
self.session.commit()
return self.state()
except Exception as exc:
self.session.rollback()
failed = HardwareInventoryRun(
status=InventoryRunStatus.FAILED,
source="local_collectors",
summary={},
error=f"{type(exc).__name__}: {exc}",
started_at=started,
completed_at=datetime.now(UTC),
observation_sequence=self.observation_sequence,
received_at=received_at,
)
self.session.add(failed)
self.session.flush()
self._audit(
"INVENTORY_FAILED",
"hardware_inventory_run",
str(failed.id),
{"error": failed.error},
)
self.session.commit()
raise
finally:
self._refresh_lock.release()
@staticmethod
def _observed(
payload: dict[str, Any],
key: str,
value: Any = None,
default: Availability = Availability.UNKNOWN,
) -> ObservedValue[Any]:
data = payload.get(key)
if isinstance(data, dict) and "availability" in data:
return ObservedValue.model_validate(data)
return ObservedValue.known(value) if value is not None else ObservedValue.absent(default)
def _accelerator_state(self, accelerator: Accelerator) -> AcceleratorState:
facts = accelerator.capabilities or {}
latest = self.repository.accelerator_telemetry(accelerator.id)
telemetry = None
if latest:
telemetry = AcceleratorTelemetry(
device_uuid=accelerator.device_uuid,
observed_at=latest.observed_at,
**{
field: self._observed(latest.availability, field, getattr(latest, field))
for field in (
"used_vram_bytes",
"free_vram_bytes",
"gpu_utilization_percent",
"memory_utilization_percent",
"temperature_c",
"power_draw_w",
"power_limit_w",
"graphics_clock_mhz",
"memory_clock_mhz",
"fan_speed_percent",
"performance_state",
)
},
)
return AcceleratorState(
id=str(accelerator.id),
node_id=str(accelerator.compute_node_id),
status=HardwareStatus(accelerator.status),
status_reason=accelerator.status_reason,
device_index=accelerator.device_index,
device_uuid=accelerator.device_uuid,
pci_bus_id=self._observed(facts, "pci_bus_id", accelerator.pci_bus_id),
name=accelerator.name,
vendor=accelerator.vendor,
architecture=self._observed(facts, "architecture", accelerator.architecture),
compute_capability_major=self._observed(
facts, "compute_capability_major", accelerator.compute_capability_major
),
compute_capability_minor=self._observed(
facts, "compute_capability_minor", accelerator.compute_capability_minor
),
total_vram_bytes=self._observed(
facts, "total_vram_bytes", accelerator.total_vram_bytes
),
driver_version=self._observed(facts, "driver_version", accelerator.driver_version),
cuda_driver_version=self._observed(
facts, "cuda_driver_version", accelerator.cuda_version
),
mig_mode_current=self._observed(
facts, "mig_mode_current", accelerator.mig_mode_current
),
first_seen_at=accelerator.first_seen_at,
last_seen_at=accelerator.last_seen_at,
inventory_at=accelerator.inventory_at,
telemetry=telemetry,
)
def state(self) -> HardwareState:
nodes: list[NodeState] = []
for node in self.repository.nodes():
active_credential = self.repository.active_credential_for_node(node.id)
facts = node.inventory or {}
host_latest = self.repository.host_telemetry(node.id)
available = self._observed(
host_latest.availability if host_latest else {},
"available_ram_bytes",
host_latest.available_ram_bytes if host_latest else None,
)
storage = [
StorageState(
id=str(item.id),
purpose=item.purpose,
path=item.path,
total_bytes=self._observed(item.availability, "total_bytes", item.total_bytes),
used_bytes=self._observed(item.availability, "used_bytes", item.used_bytes),
free_bytes=self._observed(item.availability, "free_bytes", item.free_bytes),
observed_at=item.observed_at,
)
for item in self.repository.storage(node.id)
]
accelerators = [
self._accelerator_state(item) for item in self.repository.accelerators(node.id)
]
nodes.append(
NodeState(
id=str(node.id),
identity_key=node.key,
identity_source=node.identity_source,
hostname=node.hostname,
display_name=node.display_name or node.hostname,
status=HardwareStatus(node.status),
status_reason=node.status_reason,
os_name=node.os_name,
os_version=self._observed(facts, "os_version", node.os_version),
architecture=node.architecture,
kernel_version=self._observed(facts, "kernel_version", node.kernel_version),
cpu_model=self._observed(facts, "cpu_model", node.cpu_model),
logical_cpu_count=self._observed(
facts, "logical_cpu_count", node.logical_cpu_count
),
physical_core_count=self._observed(
facts, "physical_core_count", node.physical_core_count
),
total_ram_bytes=self._observed(facts, "total_ram_bytes", node.total_ram_bytes),
available_ram_bytes=available,
agent_version=node.agent_version,
first_seen_at=node.first_seen_at,
last_seen_at=node.last_seen_at,
inventory_at=node.inventory_at,
hardware_fingerprint=node.hardware_fingerprint,
enabled=node.enabled,
liveness=NodeLiveness(node.liveness_state),
agent_health=(
AgentHealth.REVOKED
if node.observation_source == ObservationSource.REMOTE_AGENT
and active_credential is None
else (
AgentHealth.HEALTHY
if node.agent_protocol_version == AGENT_PROTOCOL_VERSION
and node.enabled
else (
AgentHealth.INCOMPATIBLE
if node.agent_protocol_version is not None
and node.agent_protocol_version != AGENT_PROTOCOL_VERSION
else AgentHealth.UNKNOWN
)
)
),
observation_source=ObservationSource(node.observation_source),
protocol_version=node.agent_protocol_version,
supported_capabilities=node.agent_capabilities or [],
agent_started_at=node.agent_started_at,
last_heartbeat_at=node.last_heartbeat_at,
last_inventory_received_at=node.last_inventory_received_at,
last_telemetry_received_at=node.last_telemetry_received_at,
inventory_age_seconds=(
max(
0,
int(
(
datetime.now(UTC) - _aware(node.last_inventory_received_at)
).total_seconds()
),
)
if node.last_inventory_received_at
else None
),
telemetry_age_seconds=(
max(
0,
int(
(
datetime.now(UTC) - _aware(node.last_telemetry_received_at)
).total_seconds()
),
)
if node.last_telemetry_received_at
else None
),
last_connection_error=node.last_connection_error,
role=node.role,
labels=node.labels or {},
production_eligible=node.production_eligible,
lab_eligible=node.lab_eligible,
benchmark_eligible=node.benchmark_eligible,
generation=node.generation,
decommissioned_at=node.decommissioned_at,
decommission_reason=node.decommission_reason,
decommissioned_by=node.decommissioned_by,
environment=(facts.get("metadata") or {}).get("environment"),
storage=storage,
accelerators=accelerators,
)
)
latest = self.repository.latest_run()
accelerators = [item for node in nodes for item in node.accelerators]
inventory_state = HardwareStatus.PENDING
if latest is not None:
inventory_state = {
InventoryRunStatus.RUNNING: HardwareStatus.PENDING,
InventoryRunStatus.SUCCEEDED: HardwareStatus.ACTIVE,
InventoryRunStatus.DEGRADED: HardwareStatus.DEGRADED,
InventoryRunStatus.FAILED: HardwareStatus.UNAVAILABLE,
}[InventoryRunStatus(latest.status)]
status = HardwareStatus.PENDING if not nodes else HardwareStatus.ACTIVE
reason = (
latest.error
if latest and latest.status == InventoryRunStatus.FAILED
else (latest.summary.get("nvidia_reason") if latest else "inventory has not run")
)
return HardwareState(
overview=HardwareOverview(
status=status,
inventory_state=inventory_state,
node_count=len(nodes),
accelerator_count=len(
[item for item in accelerators if item.status is HardwareStatus.ACTIVE]
),
last_inventory_at=latest.completed_at if latest else None,
reason=reason,
),
nodes=nodes,
)
@@ -0,0 +1,38 @@
from __future__ import annotations
import asyncio
import logging
from sqlalchemy.orm import Session
from modelforge_api.db import engine
from modelforge_api.services.hardware_factory import build_hardware_service
from modelforge_api.settings import Settings
logger = logging.getLogger(__name__)
class HardwarePollingService:
def __init__(self, settings: Settings) -> None:
self.settings = settings
self._stop = asyncio.Event()
def _refresh_once(self) -> None:
with Session(engine) as session:
build_hardware_service(session, self.settings).refresh()
async def run(self) -> None:
while not self._stop.is_set():
try:
await asyncio.to_thread(self._refresh_once)
except Exception:
logger.exception("hardware inventory poll failed")
try:
await asyncio.wait_for(
self._stop.wait(), self.settings.hardware_poll_interval_seconds
)
except TimeoutError:
continue
def stop(self) -> None:
self._stop.set()
@@ -0,0 +1,810 @@
"""M16 executable platform invariants.
An invariant is a property that must hold no matter what fault was injected. Writing them down in
prose is not enough for a release gate: each one here is a query the platform can run against its
own authoritative state, before a chaos scenario, after it, and after recovery.
These checks are read-only. They never mutate state, never call an external system, and never
depend on the observability database being healthy, so they stay usable exactly when monitoring is
the thing that failed.
"""
from __future__ import annotations
from datetime import UTC, datetime
from enum import StrEnum
from typing import Any
from pydantic import BaseModel, ConfigDict, Field
from sqlalchemy import func, select
from sqlalchemy.orm import Session
from modelforge_api.persistence.models import (
Accelerator,
AcceleratorTelemetryLatest,
ArtifactJob,
ArtifactLocation,
AuditChainHead,
AuditEvent,
BackupSet,
CapabilityContract,
CapabilityDeployment,
ComputeNode,
Deployment,
EmbeddingSpace,
GatewayRequest,
GpuLease,
HostTelemetryLatest,
LifecycleOperation,
MigrationCutoverOperation,
ModelArtifact,
NodeCredential,
ResidencyAllocation,
RestoreOperation,
RestorePlan,
RuntimeProbe,
SchedulerAcceleratorState,
ServiceClient,
ServiceCredential,
ServingGpuLease,
ServingJob,
StorageRoot,
StorageVolumeState,
)
from modelforge_api.services.audit import audit_chain_violations
# Lease and residency states that claim a live GPU reservation right now.
ACTIVE_LEASE_STATES = ("pending", "granted", "active", "held")
ACTIVE_RESIDENCY_STATES = ("loading", "resident", "unloading")
TERMINAL_LIFECYCLE_STAGES = ("COMMITTED", "ROLLED_BACK", "FAILED")
TERMINAL_CUTOVER_STAGES = ("COMMITTED", "ROLLED_BACK", "FAILED")
class InvariantSeverity(StrEnum):
CRITICAL = "CRITICAL"
HIGH = "HIGH"
class InvariantStatus(StrEnum):
HOLDS = "HOLDS"
VIOLATED = "VIOLATED"
NOT_APPLICABLE = "NOT_APPLICABLE"
class InvariantResult(BaseModel):
model_config = ConfigDict(extra="forbid")
key: str
name: str
severity: InvariantSeverity
status: InvariantStatus
observed: int
detail: str
violations: list[str] = Field(default_factory=list)
class InvariantReport(BaseModel):
model_config = ConfigDict(extra="forbid")
observed_at: datetime
checked: int
holding: int
violated: int
results: list[InvariantResult]
@property
def ok(self) -> bool:
return self.violated == 0
def _result(
key: str,
name: str,
severity: InvariantSeverity,
violations: list[str],
holds_detail: str,
violated_detail: str,
) -> InvariantResult:
if violations:
return InvariantResult(
key=key,
name=name,
severity=severity,
status=InvariantStatus.VIOLATED,
observed=len(violations),
detail=violated_detail,
violations=violations[:25],
)
return InvariantResult(
key=key,
name=name,
severity=severity,
status=InvariantStatus.HOLDS,
observed=0,
detail=holds_detail,
)
def _no_duplicate_production_stable(session: Session) -> InvariantResult:
rows = session.execute(
select(CapabilityDeployment.capability_contract_id, func.count())
.where(
CapabilityDeployment.production.is_(True),
CapabilityDeployment.status == "stable",
)
.group_by(CapabilityDeployment.capability_contract_id)
.having(func.count() > 1)
).all()
return _result(
"single_production_stable",
"At most one stable production deployment per capability contract",
InvariantSeverity.CRITICAL,
[
f"contract {contract} has {count} stable production deployments"
for contract, count in rows
],
"every capability contract has at most one stable production deployment",
"a capability contract has more than one stable production deployment",
)
def _no_duplicate_node_identity(session: Session) -> InvariantResult:
rows = session.execute(
select(ComputeNode.key, func.count())
.where(ComputeNode.enabled.is_(True))
.group_by(ComputeNode.key)
.having(func.count() > 1)
).all()
hardware = session.execute(
select(ComputeNode.hardware_fingerprint, func.count())
.where(
ComputeNode.enabled.is_(True),
ComputeNode.hardware_fingerprint.is_not(None),
)
.group_by(ComputeNode.hardware_fingerprint)
.having(func.count() > 1)
).all()
violations = [f"identity key {key} is claimed by {count} enabled nodes" for key, count in rows]
violations += [
f"hardware fingerprint {fingerprint} is claimed by {count} enabled nodes"
for fingerprint, count in hardware
]
return _result(
"single_node_identity",
"No two enabled nodes claim the same identity or hardware",
InvariantSeverity.CRITICAL,
violations,
"every enabled node holds a distinct identity and hardware fingerprint",
"more than one enabled node claims the same hardware",
)
def _single_active_node_credential(session: Session) -> InvariantResult:
rows = session.execute(
select(NodeCredential.compute_node_id, func.count())
.where(NodeCredential.revoked_at.is_(None))
.group_by(NodeCredential.compute_node_id)
.having(func.count() > 1)
).all()
return _result(
"single_active_node_credential",
"A node has at most one unrevoked credential",
InvariantSeverity.CRITICAL,
[f"node {node} has {count} active credentials" for node, count in rows],
"no node holds more than one active credential",
"a node holds more than one active credential",
)
def _no_stale_gpu_lease(session: Session, now: datetime | None = None) -> InvariantResult:
moment = now or datetime.now(UTC)
rows = session.scalars(
select(ServingGpuLease).where(
ServingGpuLease.state.in_(ACTIVE_LEASE_STATES),
ServingGpuLease.released_at.is_(None),
ServingGpuLease.expires_at.is_not(None),
)
)
violations = []
for lease in rows:
expires = lease.expires_at
if expires is None:
continue
aware = expires if expires.tzinfo else expires.replace(tzinfo=UTC)
if aware < moment:
violations.append(
f"lease {lease.id} is {lease.state} but expired at {aware.isoformat()}"
)
return _result(
"no_stale_gpu_lease",
"No expired GPU lease is still presented as active",
InvariantSeverity.CRITICAL,
violations,
"every active GPU lease is within its expiry",
"an expired GPU lease is still held as current truth",
)
def _no_mixed_embedding_space(session: Session) -> InvariantResult:
rows = session.execute(
select(
EmbeddingSpace.capability_contract_id,
func.count(func.distinct(EmbeddingSpace.identity_digest)),
).group_by(EmbeddingSpace.capability_contract_id)
).all()
deployments = session.execute(
select(
CapabilityDeployment.capability_contract_id,
func.count(func.distinct(CapabilityDeployment.embedding_space_id)),
)
.where(
CapabilityDeployment.production.is_(True),
CapabilityDeployment.status == "stable",
CapabilityDeployment.embedding_space_id.is_not(None),
)
.group_by(CapabilityDeployment.capability_contract_id)
.having(func.count(func.distinct(CapabilityDeployment.embedding_space_id)) > 1)
).all()
violations = [
f"contract {contract} serves {count} embedding spaces from stable production"
for contract, count in deployments
]
detail = (
f"{len(rows)} contracts carry embedding-space identities; stable production serves one each"
)
return _result(
"no_mixed_embedding_space",
"Stable production never serves two embedding spaces for one contract",
InvariantSeverity.CRITICAL,
violations,
detail,
"stable production serves more than one embedding space for a capability contract",
)
def _no_lifecycle_commit_without_evidence(session: Session) -> InvariantResult:
rows = session.scalars(
select(LifecycleOperation).where(LifecycleOperation.stage == "COMMITTED")
)
violations = []
for operation in rows:
plan = operation.promotion_plan_id
if plan is None:
violations.append(f"operation {operation.id} committed without a promotion plan")
continue
if not operation.approver or not operation.executor:
violations.append(
f"operation {operation.id} committed without recorded approver and executor"
)
return _result(
"lifecycle_commit_has_evidence",
"No lifecycle operation commits without its plan, approver and executor",
InvariantSeverity.CRITICAL,
violations,
"every committed lifecycle operation carries its approval evidence",
"a lifecycle operation committed without approval evidence",
)
def _no_cutover_commit_without_validation(session: Session) -> InvariantResult:
rows = session.scalars(
select(MigrationCutoverOperation).where(MigrationCutoverOperation.stage == "COMMITTED")
)
violations = [
f"cutover {item.id} committed without an external state fingerprint"
for item in rows
if not item.external_state_fingerprint
]
return _result(
"cutover_commit_has_validation",
"No migration cutover commits without an external-state fingerprint",
InvariantSeverity.CRITICAL,
violations,
"every committed cutover records the external state it observed",
"a cutover committed without observing external truth",
)
def _no_restore_from_unverified_backup(session: Session) -> InvariantResult:
rows = session.execute(
select(RestoreOperation.id, BackupSet.backup_id, BackupSet.state)
.join(BackupSet, BackupSet.id == RestoreOperation.backup_set_id)
.where(RestoreOperation.state != "PLANNED")
).all()
violations = [
f"restore {operation} ran against backup {backup} in state {state}"
for operation, backup, state in rows
if state not in ("VERIFIED", "EXPIRED")
]
plans = session.execute(
select(RestorePlan.id, BackupSet.backup_id, BackupSet.state).join(
BackupSet, BackupSet.id == RestorePlan.backup_set_id
)
).all()
violations += [
f"restore plan {plan} targets backup {backup} that was never verified"
for plan, backup, state in plans
if state in ("PLANNED", "CREATING", "CREATED", "FAILED")
]
return _result(
"restore_requires_verified_backup",
"No restore is planned or executed from an unverified backup",
InvariantSeverity.CRITICAL,
violations,
"every restore plan and operation references a verified backup",
"a restore referenced a backup that was never verified",
)
def _revoked_credentials_stay_revoked(session: Session) -> InvariantResult:
violations: list[str] = []
for label, revoked_rows in (
(
"node",
session.scalars(
select(NodeCredential).where(NodeCredential.revoked_at.is_not(None))
).all(),
),
(
"service",
session.scalars(
select(ServiceCredential).where(ServiceCredential.revoked_at.is_not(None))
).all(),
),
):
for credential in revoked_rows:
used = credential.last_used_at
revoked = credential.revoked_at
if used is None or revoked is None:
continue
used_aware = used if used.tzinfo else used.replace(tzinfo=UTC)
revoked_aware = revoked if revoked.tzinfo else revoked.replace(tzinfo=UTC)
if used_aware > revoked_aware:
violations.append(
f"{label} credential {credential.id} was used after it was revoked"
)
return _result(
"revoked_credentials_stay_revoked",
"No revoked credential is ever accepted again",
InvariantSeverity.CRITICAL,
violations,
"no revoked node or service credential records a later successful use",
"a revoked credential was accepted after revocation",
)
def _capability_clients_are_not_operators(session: Session) -> InvariantResult:
"""A gateway client may only name capabilities; operator and node scopes are a different plane."""
violations: list[str] = []
for client in session.scalars(select(ServiceClient)):
allowed = client.allowed_capabilities
capabilities = allowed if isinstance(allowed, list) else []
forbidden = [
capability
for capability in capabilities
if isinstance(capability, str)
and (
capability.startswith(("admin", "operator", "node."))
or capability in {"recovery", "lifecycle", "migration"}
)
]
if forbidden:
violations.append(
f"service client {client.id} claims non-capability scopes {forbidden}"
)
return _result(
"capability_clients_are_not_operators",
"No capability client holds operator or node scopes",
InvariantSeverity.CRITICAL,
violations,
"every service client is scoped to capabilities only",
"a capability client carries operator or node scopes",
)
def _no_unsafe_artifact_promoted(session: Session) -> InvariantResult:
rows = session.execute(
select(ModelArtifact.id, ModelArtifact.filename, ModelArtifact.security_status)
.join(ArtifactLocation, ArtifactLocation.artifact_id == ModelArtifact.id)
.where(
ArtifactLocation.status == "verified",
ModelArtifact.security_status.in_(("blocked", "quarantined", "unverified")),
)
.distinct()
).all()
return _result(
"no_unsafe_artifact_promoted",
"No artifact is promoted to a verified location while its security status is unsafe",
InvariantSeverity.CRITICAL,
[
f"artifact {artifact} ({filename}) is verified on disk but {status}"
for artifact, filename, status in rows
],
"every verified artifact location holds an artifact that passed its security checks",
"an unsafe artifact occupies a verified location",
)
def _no_orphan_serving_work(session: Session, now: datetime | None = None) -> InvariantResult:
moment = now or datetime.now(UTC)
jobs = session.scalars(
select(ServingJob).where(ServingJob.status.in_(("queued", "leased", "running")))
)
violations = []
for job in jobs:
expires = job.lease_expires_at
if expires is None:
continue
aware = expires if expires.tzinfo else expires.replace(tzinfo=UTC)
if aware < moment:
violations.append(
f"serving job {job.id} is {job.status} with a lease expired at {aware.isoformat()}"
)
residency = session.scalars(
select(ResidencyAllocation).where(ResidencyAllocation.state.in_(ACTIVE_RESIDENCY_STATES))
)
for allocation in residency:
node = session.get(ComputeNode, allocation.compute_node_id)
if node is not None and not node.enabled:
violations.append(
f"residency {allocation.id} is {allocation.state} on disabled node {node.id}"
)
return _result(
"no_orphan_serving_work",
"No queued or leased serving work survives its lease, and no residency sits on a disabled node",
InvariantSeverity.CRITICAL,
violations,
"no orphaned serving job or residency allocation exists",
"orphaned serving work survived recovery",
)
def _no_hidden_auto_promotion(session: Session) -> InvariantResult:
rows = session.scalars(
select(CapabilityDeployment).where(
CapabilityDeployment.production.is_(True),
CapabilityDeployment.status.in_(("stable", "draining")),
)
)
violations = [
f"production deployment {item.id} has no recorded production approval"
for item in rows
if item.production_approval_id is None
]
return _result(
"no_hidden_auto_promotion",
"Every production deployment carries an explicit production approval",
InvariantSeverity.CRITICAL,
violations,
"every production deployment references its production approval",
"a production deployment exists without an approval",
)
def _audit_chain_is_intact(session: Session) -> InvariantResult:
events = list(
session.scalars(select(AuditEvent).order_by(AuditEvent.sequence, AuditEvent.id))
)
checkpoint = session.get(AuditChainHead, 1)
violations = audit_chain_violations(events, checkpoint)
return _result(
"audit_chain_intact",
"The audit trail has a strict sequence and canonical content-hash chain",
InvariantSeverity.CRITICAL,
violations,
f"{len(events)} audit events form one contiguous, content-verified chain",
"the audit trail contains a gap, fork, invalid link or content-hash mismatch",
)
def _decommissioned_nodes_are_terminal(session: Session) -> InvariantResult:
violations: list[str] = []
nodes = list(
session.scalars(select(ComputeNode).where(ComputeNode.decommissioned_at.is_not(None)))
)
for node in nodes:
if (
node.enabled
or node.status != "decommissioned"
or node.liveness_state != "decommissioned"
):
violations.append(
f"node {node.id} is decommissioned but enabled={node.enabled}, "
f"status={node.status} and liveness={node.liveness_state}"
)
if node.production_eligible or node.lab_eligible or node.benchmark_eligible:
violations.append(f"node {node.id} is decommissioned but retains scheduler eligibility")
if node.inventory or node.agent_capabilities:
violations.append(f"node {node.id} is decommissioned but retains current inventory")
active_credentials = (
session.scalar(
select(func.count())
.select_from(NodeCredential)
.where(
NodeCredential.compute_node_id == node.id,
NodeCredential.revoked_at.is_(None),
)
)
or 0
)
if active_credentials:
violations.append(f"node {node.id} retains {active_credentials} active credentials")
for model, status_column, terminal in (
(ArtifactJob, ArtifactJob.status, ("completed", "failed", "cancelled")),
(RuntimeProbe, RuntimeProbe.status, ("completed", "failed", "cancelled")),
(ServingJob, ServingJob.status, ("completed", "failed", "cancelled")),
):
active = (
session.scalar(
select(func.count())
.select_from(model)
.where(
model.compute_node_id == node.id,
status_column.not_in(terminal),
)
)
or 0
)
if active:
violations.append(f"node {node.id} retains {active} active {model.__tablename__}")
residency = (
session.scalar(
select(func.count())
.select_from(ResidencyAllocation)
.where(ResidencyAllocation.compute_node_id == node.id)
)
or 0
)
if residency:
violations.append(f"node {node.id} retains {residency} active residencies")
accelerator_ids = list(
session.scalars(select(Accelerator.id).where(Accelerator.compute_node_id == node.id))
)
if accelerator_ids:
active_serving_leases = (
session.scalar(
select(func.count())
.select_from(ServingGpuLease)
.where(
ServingGpuLease.accelerator_id.in_(accelerator_ids),
ServingGpuLease.state.not_in(
("released", "expired", "failed", "cancelled")
),
)
)
or 0
)
active_legacy_leases = (
session.scalar(
select(func.count())
.select_from(GpuLease)
.where(
GpuLease.accelerator_id.in_(accelerator_ids),
GpuLease.state.not_in(("released", "expired", "failed", "cancelled")),
)
)
or 0
)
current_accelerator_state = sum(
int(
session.scalar(
select(func.count()).select_from(model).where(column.in_(accelerator_ids))
)
or 0
)
for model, column in (
(AcceleratorTelemetryLatest, AcceleratorTelemetryLatest.accelerator_id),
(SchedulerAcceleratorState, SchedulerAcceleratorState.accelerator_id),
)
)
if active_serving_leases + active_legacy_leases:
violations.append(
f"node {node.id} retains {active_serving_leases + active_legacy_leases} "
"active GPU leases"
)
if current_accelerator_state:
violations.append(
f"node {node.id} retains {current_accelerator_state} current accelerator rows"
)
active_accelerators = (
session.scalar(
select(func.count())
.select_from(Accelerator)
.where(
Accelerator.id.in_(accelerator_ids),
Accelerator.status != "decommissioned",
)
)
or 0
)
if active_accelerators:
violations.append(
f"node {node.id} retains {active_accelerators} non-decommissioned accelerators"
)
active_deployments = sum(
int(
session.scalar(
select(func.count())
.select_from(model)
.where(
model.compute_node_id == node.id,
status.not_in(("retired", "deprecated", "failed", "archived")),
)
)
or 0
)
for model, status in (
(CapabilityDeployment, CapabilityDeployment.status),
(Deployment, Deployment.status),
)
)
active_gateway_requests = (
session.scalar(
select(func.count())
.select_from(GatewayRequest)
.where(
GatewayRequest.compute_node_id == node.id,
GatewayRequest.status.in_(("queued", "running")),
)
)
or 0
)
if active_deployments or active_gateway_requests:
violations.append(
f"node {node.id} retains {active_deployments} active deployments and "
f"{active_gateway_requests} gateway requests"
)
unsafe_roots = (
session.scalar(
select(func.count())
.select_from(StorageRoot)
.where(
StorageRoot.compute_node_id == node.id,
(StorageRoot.writable.is_(True) | (StorageRoot.status != "unavailable")),
)
)
or 0
)
if unsafe_roots:
violations.append(f"node {node.id} retains {unsafe_roots} writable storage roots")
current_node_state = sum(
int(
session.scalar(select(func.count()).select_from(model).where(column == node.id))
or 0
)
for model, column in (
(HostTelemetryLatest, HostTelemetryLatest.compute_node_id),
(StorageVolumeState, StorageVolumeState.compute_node_id),
)
)
if current_node_state:
violations.append(f"node {node.id} retains {current_node_state} current telemetry rows")
return _result(
"decommissioned_nodes_are_terminal",
"Decommissioned nodes cannot authenticate, schedule, or retain current runtime truth",
InvariantSeverity.CRITICAL,
violations,
"every decommissioned node is a terminal tombstone with no active work or current state",
"a decommissioned node retained mutable current state",
)
def _contracts_have_one_stable_identity(session: Session) -> InvariantResult:
rows = session.execute(
select(
CapabilityContract.id,
func.count(func.distinct(CapabilityDeployment.artifact_set_id)),
)
.join(
CapabilityDeployment,
CapabilityDeployment.capability_contract_id == CapabilityContract.id,
)
.where(
CapabilityDeployment.production.is_(True),
CapabilityDeployment.status == "stable",
)
.group_by(CapabilityContract.id)
.having(func.count(func.distinct(CapabilityDeployment.artifact_set_id)) > 1)
).all()
return _result(
"stable_identity_is_singular",
"Stable production serves exactly one artifact set per contract",
InvariantSeverity.CRITICAL,
[
f"contract {contract} serves {count} artifact sets from stable production"
for contract, count in rows
],
"every contract serves a single stable production artifact set",
"a contract serves more than one stable production artifact set",
)
CHECKS = (
_no_duplicate_production_stable,
_contracts_have_one_stable_identity,
_no_duplicate_node_identity,
_single_active_node_credential,
_no_stale_gpu_lease,
_no_mixed_embedding_space,
_no_lifecycle_commit_without_evidence,
_no_cutover_commit_without_validation,
_no_restore_from_unverified_backup,
_revoked_credentials_stay_revoked,
_capability_clients_are_not_operators,
_no_unsafe_artifact_promoted,
_no_orphan_serving_work,
_no_hidden_auto_promotion,
_audit_chain_is_intact,
_decommissioned_nodes_are_terminal,
)
def check_invariants(session: Session, now: datetime | None = None) -> InvariantReport:
"""Run every platform invariant against authoritative state and report each outcome."""
moment = now or datetime.now(UTC)
results: list[InvariantResult] = []
for check in CHECKS:
try:
results.append(check(session, moment)) # type: ignore[call-arg]
except TypeError:
results.append(check(session))
violated = sum(item.status is InvariantStatus.VIOLATED for item in results)
return InvariantReport(
observed_at=moment,
checked=len(results),
holding=sum(item.status is InvariantStatus.HOLDS for item in results),
violated=violated,
results=results,
)
def invariant_keys() -> tuple[str, ...]:
"""Stable ordering used by the chaos harness and the release gate."""
return tuple(
result.key
for result in (
InvariantResult(
key=key,
name=key,
severity=InvariantSeverity.CRITICAL,
status=InvariantStatus.HOLDS,
observed=0,
detail="",
)
for key in (
"single_production_stable",
"stable_identity_is_singular",
"single_node_identity",
"single_active_node_credential",
"no_stale_gpu_lease",
"no_mixed_embedding_space",
"lifecycle_commit_has_evidence",
"cutover_commit_has_validation",
"restore_requires_verified_backup",
"revoked_credentials_stay_revoked",
"capability_clients_are_not_operators",
"no_unsafe_artifact_promoted",
"no_orphan_serving_work",
"no_hidden_auto_promotion",
"audit_chain_intact",
"decommissioned_nodes_are_terminal",
)
)
)
def summarise(report: InvariantReport) -> dict[str, Any]:
return {
"observed_at": report.observed_at.isoformat(),
"checked": report.checked,
"holding": report.holding,
"violated": report.violated,
"violations": [
{"key": item.key, "detail": item.detail, "examples": item.violations}
for item in report.results
if item.status is InvariantStatus.VIOLATED
],
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,122 @@
from __future__ import annotations
from collections.abc import Callable
from functools import lru_cache
from pathlib import Path
from typing import Any, cast
import yaml # type: ignore[import-untyped]
from modelforge_api.domain.contracts import (
BenchmarkSuiteManifest,
CandidateRegistryManifest,
CapabilityContractManifest,
PolicyDefaults,
ProjectManifest,
)
from modelforge_api.settings import get_settings
PROJECT_ROOT = Path(__file__).resolve().parents[4]
class ManifestRegistry:
"""Validated, read-only M0 registry backed by version-controlled manifests.
Parsed manifests are memoised for the lifetime of the instance. They are read-only
configuration mounted into the container and cannot change without restarting the process, so
re-reading them per call bought nothing and cost a great deal: one readiness probe used to
perform 77 YAML reads across 14 files — one file ten times — and take 528 ms. That was the whole
of the 638 ms p50 M16 measured on /api/v1/health/ready under load, which is a real operational
risk for an orchestrator with a tight probe timeout.
The cache is per-instance rather than global so a test pointing at a temporary directory still
gets what is on disk when it builds its own registry.
"""
def __init__(self, config_root: Path | None = None) -> None:
self.config_root = config_root or get_settings().config_root
self._cache: dict[str, Any] = {}
def _memoised(self, key: str, produce: Callable[[], Any]) -> Any:
if key not in self._cache:
self._cache[key] = produce()
return self._cache[key]
@staticmethod
def _read_yaml(path: Path) -> dict[str, Any]:
with path.open("r", encoding="utf-8") as handle:
payload = yaml.safe_load(handle)
if not isinstance(payload, dict):
raise TypeError(f"manifest must be an object: {path}")
return payload
def capabilities(self) -> list[CapabilityContractManifest]:
return cast(
"list[CapabilityContractManifest]", self._memoised("capabilities", self._capabilities)
)
def _capabilities(self) -> list[CapabilityContractManifest]:
manifests = [
CapabilityContractManifest.model_validate(self._read_yaml(path))
for path in sorted((self.config_root / "capabilities").glob("*.yaml"))
]
identities = [(item.capability, item.version) for item in manifests]
if len(identities) != len(set(identities)):
raise ValueError("duplicate capability contract identity")
return manifests
def projects(self) -> list[ProjectManifest]:
return cast("list[ProjectManifest]", self._memoised("projects", self._projects))
def _projects(self) -> list[ProjectManifest]:
manifests = [
ProjectManifest.model_validate(self._read_yaml(path))
for path in sorted((self.config_root / "projects").glob("*.yaml"))
]
capabilities = self.capabilities()
by_identity = {(item.capability, item.version): item for item in capabilities}
capability_versions = set(by_identity)
for project in manifests:
for key, binding in project.bindings.items():
if (key, binding.contract_version) not in capability_versions:
raise ValueError(
f"{project.project.id} binds missing capability contract {key}@{binding.contract_version}"
)
contract = by_identity[key, binding.contract_version]
if (
contract.upgrade_class.value == "requires_reindex"
and binding.migration_support != "reindex"
):
raise ValueError(
f"{project.project.id}/{key} must declare reindex migration support"
)
return manifests
def candidates(self) -> CandidateRegistryManifest:
return cast("CandidateRegistryManifest", self._memoised("candidates", self._candidates))
def _candidates(self) -> CandidateRegistryManifest:
path = self.config_root / "models" / "initial-candidates.yaml"
return CandidateRegistryManifest.model_validate(self._read_yaml(path))
def benchmarks(self) -> list[BenchmarkSuiteManifest]:
return cast("list[BenchmarkSuiteManifest]", self._memoised("benchmarks", self._benchmarks))
def _benchmarks(self) -> list[BenchmarkSuiteManifest]:
return [
BenchmarkSuiteManifest.model_validate(self._read_yaml(path))
for path in sorted((self.config_root / "benchmarks").glob("*.yaml"))
]
def policies(self) -> PolicyDefaults:
return cast("PolicyDefaults", self._memoised("policies", self._policies))
def _policies(self) -> PolicyDefaults:
return PolicyDefaults.model_validate(
self._read_yaml(self.config_root / "policies" / "defaults.yaml")
)
@lru_cache
def get_manifest_registry() -> ManifestRegistry:
return ManifestRegistry()

Some files were not shown because too many files have changed in this diff Show More