fix(1b): regelen gjelder HVER betalt arm - fan-out-testen var fortsatt ugatet [skip-docs]

Forrige commit skrev en CLAUDE.md-invariant om at en betalt test far sin egen
opt-in, men gatet bare den NYE testen. test_portfolio_live.py passerer ingen
client_factory og er derfor selv en betalt kjoring - den fyrte pa
to-variabel-paret fra et bart `uv run pytest`. Invarianten var altsa halvt
usann den dagen den ble skrevet, som er nyaktig Fase 3-klassen (en pastand
flaten gjor om seg selv uten dekning).

MALT etter fiksen, med begge Foundry-vars satt og UTEN PORTFOLIO_LIVE_FULL_RUN:
  test_foundry_profile_live   PASSED   (det billige trinnet, med vilje pa to vars)
  test_portfolio_live         SKIPPED  (var betalt og ugatet for dette)
  test_full_run_live          SKIPPED

Ovrige oppfolgingspunkter fra review:
- assert_full_run_contract far ekte type hints (repoets "type hints overalt";
  mypy src ser ikke tests/, sa den ville rotnet ubemerket).
- Maleprotokollen SS6 far oppsettet STATE peker pa - model-map-formen og de fire
  export-linjene. Pekeren lovet noe seksjonen ikke hadde.

869 passed / 5 skipped, uendret. ruff+format+mypy rene.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GF7va4cpRiuf79kTzAi3vW
This commit is contained in:
Kjell Tore Guttormsen 2026-08-14 20:12:47 +02:00
commit bb4807aff7
4 changed files with 56 additions and 9 deletions

View file

@ -10,11 +10,13 @@ from __future__ import annotations
import json
from collections.abc import Callable, Sequence
from pathlib import Path
import pytest
from agent_framework import BaseChatClient
from portfolio_optimiser.reference_domain import load_reference_projects
from portfolio_optimiser.run import RunResult
from portfolio_optimiser.simulation import ScriptedChatClient
from portfolio_optimiser.verdicts import VerdictStore, seed_store
@ -214,7 +216,7 @@ def docs_dir(tmp_path) -> str:
#: and ``rejected`` satisfy the contract — a P90 rejection is a run that CONCLUDED, and demanding
#: ``validated`` would be asserting that the model reasons well, which no schema can promise and
#: which one paid run could not establish anyway.
def assert_full_run_contract(result, outbox_dir, run_id: str) -> None:
def assert_full_run_contract(result: RunResult, outbox_dir: str | Path, run_id: str) -> None:
"""Assert the Fase 1b full-run contract on a completed ``run_project`` result.
Two things, and deliberately nothing else:
@ -227,8 +229,6 @@ def assert_full_run_contract(result, outbox_dir, run_id: str) -> None:
Deliberately NOT asserted: ``checker_verdict``, token counts, ``validated_count``, or the
content of the proposal. Those are model-judgement claims, and one run cannot carry them.
"""
from pathlib import Path
artefact = Path(outbox_dir) / f"{run_id}-parse-failures.json"
if artefact.exists():
# Quote the evidence in the failure message: the whole point of økt 35 was that the operator

View file

@ -2,8 +2,16 @@
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). Mirrors ``test_foundry_profile_live.py:14-24``. SC9 is
unchanged this arm stays skipped offline.
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
@ -14,12 +22,16 @@ from portfolio_optimiser.run import PortfolioResult, run_portfolio
_ENDPOINT = os.environ.get("PORTFOLIO_FOUNDRY_PROJECT_ENDPOINT")
_DEPLOYMENT = os.environ.get("PORTFOLIO_FOUNDRY_DEPLOYMENT")
_NO_FOUNDRY = not (_ENDPOINT and _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="Foundry not configured (set PORTFOLIO_FOUNDRY_PROJECT_ENDPOINT + PORTFOLIO_FOUNDRY_DEPLOYMENT)",
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.