"""Fase 3 — GATED live Azure portfolio run (SC7 / SC9). NOT default CI: skips cleanly without a configured Foundry endpoint + deployment. When set, ``run_portfolio`` fans out over the reference portfolio on the REAL ``azure`` profile (one project, hard token-capped per D6). SC9 is unchanged — this arm stays skipped offline. **``PORTFOLIO_LIVE_FULL_RUN`` added 14.08 (økt 40), and the reason is this file, not the new one.** This test passes NO ``client_factory``, so it drives the real Azure backend: it is a PAID run, and until now it fired on the same two variables as ``test_foundry_profile_live.py``'s one-word probe. That is exactly the collapse the new full-run gate was designed to prevent — an operator exporting the pair to run the CHEAP rung would have paid for this fan-out too, from a bare ``uv run pytest``. The rule the sibling file states must hold for EVERY paid arm, or it is not a rule; leaving this one on the two-var pair would have made the CLAUDE.md invariant half-true the day it was written. Truthiness, not presence (Fase 4b): an exported-but-empty value must not arm a paid run. """ import os import pytest from portfolio_optimiser.run import PortfolioResult, run_portfolio _ENDPOINT = os.environ.get("PORTFOLIO_FOUNDRY_PROJECT_ENDPOINT") _DEPLOYMENT = os.environ.get("PORTFOLIO_FOUNDRY_DEPLOYMENT") _OPTED_IN = bool(os.environ.get("PORTFOLIO_LIVE_FULL_RUN")) _NO_FOUNDRY = not (_ENDPOINT and _DEPLOYMENT and _OPTED_IN) @pytest.mark.skipif( _NO_FOUNDRY, reason=( "paid fan-out not armed (set PORTFOLIO_LIVE_FULL_RUN=1 alongside " "PORTFOLIO_FOUNDRY_PROJECT_ENDPOINT + PORTFOLIO_FOUNDRY_DEPLOYMENT)" ), ) async def test_portfolio_live_azure_fanout() -> None: # No client_factory -> the real AZURE backend is used; hard-capped per D6. result = await run_portfolio(["FV42-GSV-E1"], "azure", max_tokens=2000) assert isinstance(result, PortfolioResult) assert len(result.runs) == 1