"""Opt-in V54 deterministic model-offline degradation acceptance.""" import os import pytest from sqlalchemy import create_engine, text from app.core.common.identifiers import new_governance_uid from app.core.mcp.identity import AgentIdentity from app.core.mcp.persistence import PostgresAuditSink from app.core.orchestration.agent.planner import SchedulingPlanner pytestmark = pytest.mark.integration DATABASE_URL = "postgresql://dataops:dataops-test-password@127.0.0.1:15432/dataops" class OfflineModel: provider = "deepseek" model_name = "deepseek-chat" def generate(self, **_kwargs): raise TimeoutError("the model provider is unavailable") class ContextTools: def call_tool(self, name, _arguments): return { "get_sla_constraints": {"deadline": "08:00"}, "list_datasource_capabilities": { "items": [], "count": 0, "truncated": False, }, "get_execution_history": { "items": [{"status": "success"}], "count": 1, "truncated": False, }, }[name] class ForbiddenSchedulingTools: def call_tool(self, name, _arguments): raise AssertionError(f"offline model must not call scheduling tool {name}") class ForbiddenVerifier: def verify(self, *_args, **_kwargs): raise AssertionError("offline model must not start Canary verification") def test_model_offline_keeps_fixed_schedule_and_persists_trace(): if os.environ.get("RUN_V54_AGENT_L3") != "1": pytest.skip("set RUN_V54_AGENT_L3=1 for the V54 Agent acceptance") engine = create_engine(DATABASE_URL, pool_pre_ping=True) correlation_id = new_governance_uid() dataflow_uid = new_governance_uid() source_uid = new_governance_uid() identity = AgentIdentity( subject="v54-model-offline", roles=frozenset({"scheduler"}), business_domains=frozenset({"sales"}), environments=frozenset({"test"}), correlation_id=correlation_id, ) planner = SchedulingPlanner( model=OfflineModel(), context_tools=ContextTools(), scheduling_tools=ForbiddenSchedulingTools(), canary_verifier=ForbiddenVerifier(), audit=PostgresAuditSink(engine), model_timeout_seconds=1, ) try: result = planner.run( identity, { "business_goal": "Keep the published schedule stable", "dataflow_uid": dataflow_uid, "business_domain": "sales", "environment": "test", "available_window": { "start": "2026-07-20T06:00:00+08:00", "end": "2026-07-20T08:00:00+08:00", }, "authorized_resources": [source_uid], "canary_inputs": {}, }, ) assert result == { "status": "degraded", "mode": "fixed_schedule", "reason_code": "model_unavailable", "action": "continue_published_schedule", "formal_engine_changed": False, "correlation_id": correlation_id, } with engine.connect() as connection: row = ( connection.execute( text( "SELECT actor_subject, model_provider, model_name, " "prompt_version, schema_version, decision, decision_detail " "FROM public.workflow_plan_audits " "WHERE correlation_id = CAST(:id AS uuid) " "AND action = 'ai_planning_closed_loop'" ), {"id": correlation_id}, ) .mappings() .one() ) assert row["actor_subject"] == "v54-model-offline" assert row["model_provider"] == "deepseek" assert row["schema_version"] == "v54" assert row["decision"] == "failed" assert row["decision_detail"]["reason_code"] == "model_unavailable" assert "provider is unavailable" not in str(row["decision_detail"]) finally: with engine.begin() as connection: connection.execute( text( "DELETE FROM public.workflow_plan_audits " "WHERE correlation_id = CAST(:id AS uuid)" ), {"id": correlation_id}, ) engine.dispose()