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:
parent
2d1264088e
commit
bb4807aff7
4 changed files with 56 additions and 9 deletions
10
CLAUDE.md
10
CLAUDE.md
|
|
@ -434,11 +434,17 @@ Python ≥3.10. MAF (`agent-framework-core` 1.9.0). Pakkehåndtering: `uv`. To b
|
||||||
gates på **fire** ting — de to Foundry-variablene, `PORTFOLIO_MODEL_MAP`, og et TREDJE, distinkt
|
gates på **fire** ting — de to Foundry-variablene, `PORTFOLIO_MODEL_MAP`, og et TREDJE, distinkt
|
||||||
`PORTFOLIO_LIVE_FULL_RUN` lest på **truthiness** (4b-invarianten). **Den tredje variabelen er
|
`PORTFOLIO_LIVE_FULL_RUN` lest på **truthiness** (4b-invarianten). **Den tredje variabelen er
|
||||||
load-bearing, ikke pynt:** `test_foundry_profile_live.py` (klient-probe) og `test_portfolio_live.py`
|
load-bearing, ikke pynt:** `test_foundry_profile_live.py` (klient-probe) og `test_portfolio_live.py`
|
||||||
(fan-out) gater på nøyaktig SAMME to variabler, så å gjenbruke det paret ville betydd at en
|
(fan-out) gatet på nøyaktig SAMME to variabler, så å gjenbruke det paret ville betydd at en
|
||||||
operatør som eksporterer dem for den BILLIGE ett-ords-proben også fyrer den dyre fullkjøringen —
|
operatør som eksporterer dem for den BILLIGE ett-ords-proben også fyrer den dyre fullkjøringen —
|
||||||
altså at måleprotokollens stige («bevis så mye som mulig før det dyre trinnet, så en feil er
|
altså at måleprotokollens stige («bevis så mye som mulig før det dyre trinnet, så en feil er
|
||||||
attribuerbar») kollapser til ett trinn. **MÅLT:** med begge Foundry-variablene satt SKIPPET den
|
attribuerbar») kollapser til ett trinn. **MÅLT:** med begge Foundry-variablene satt SKIPPET den
|
||||||
dyre, og den billige var grønn. `PORTFOLIO_MODEL_MAP` er med av en annen grunn — attribusjon: uten
|
dyre, og den billige var grønn. **Regelen gjelder HVER betalt arm, ellers er den ingen regel:**
|
||||||
|
`test_portfolio_live.py` passerer ingen `client_factory` og er derfor selv en betalt kjøring — den
|
||||||
|
fyrte på to-variabel-paret fra et bart `uv run pytest`, og ble gatet på den TREDJE variabelen i
|
||||||
|
samme slengen. Å la den stå ville gjort denne raden halvt usann den dagen den ble skrevet; en
|
||||||
|
invariant som beskriver én av to armer er en påstand flaten gjør om seg selv uten dekning, som er
|
||||||
|
nøyaktig Fase 3-klassen. Den billige klient-proben beholder to-variabel-gaten med vilje — den ER
|
||||||
|
det billige trinnet. `PORTFOLIO_MODEL_MAP` er med av en annen grunn — attribusjon: uten
|
||||||
den feiler kjøringen av en KONFIGURASJONS-årsak som ser ut som en modell-feil.
|
den feiler kjøringen av en KONFIGURASJONS-årsak som ser ut som en modell-feil.
|
||||||
**Asserten bor i ÉN kopi** (`conftest.assert_full_run_contract`, kø-(p)) og er smal med vilje:
|
**Asserten bor i ÉN kopi** (`conftest.assert_full_run_contract`, kø-(p)) og er smal med vilje:
|
||||||
fraværet av `{run_id}-parse-failures.json` (økt 35-invarianten «filens tilstedeværelse er
|
fraværet av `{run_id}-parse-failures.json` (økt 35-invarianten «filens tilstedeværelse er
|
||||||
|
|
|
||||||
|
|
@ -151,6 +151,35 @@ uten den feiler kjøringen av en *konfigurasjons*-årsak som ser ut som en model
|
||||||
|
|
||||||
## 6. Den bundne fullkjøringen — målt 14.08 kl. 19:54
|
## 6. Den bundne fullkjøringen — målt 14.08 kl. 19:54
|
||||||
|
|
||||||
|
### Oppsettet, reproduserbart
|
||||||
|
|
||||||
|
Model-mappet ligger **out-of-tree** med vilje: `data/model_map.json` i treet bærer
|
||||||
|
`REPLACE-WITH-FOUNDRY-DEPLOYMENT`-plassholdere, og tenant-spesifikke deployment-navn skal aldri
|
||||||
|
committes (B12). `PORTFOLIO_MODEL_MAP` peker på en fil operatøren eier:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"local": {"default": "qwen3:4b", "proposer": "qwen3:4b", "checker": "qwen3:4b"},
|
||||||
|
"azure": {
|
||||||
|
"default": "gpt-4-1-mini",
|
||||||
|
"proposer": "gpt-4-1-mini",
|
||||||
|
"checker": "gpt-4-1-mini"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
```bash
|
||||||
|
export PORTFOLIO_FOUNDRY_PROJECT_ENDPOINT="https://po-foundry-ktg.services.ai.azure.com/api/projects/po-project"
|
||||||
|
export PORTFOLIO_FOUNDRY_DEPLOYMENT="gpt-4-1-mini"
|
||||||
|
export PORTFOLIO_MODEL_MAP="/absolutt/sti/til/model_map.live.json"
|
||||||
|
export PORTFOLIO_LIVE_FULL_RUN=1 # BETALER — utelat den for alt annet enn trinn 4
|
||||||
|
```
|
||||||
|
|
||||||
|
Merk at `PORTFOLIO_LIVE_FULL_RUN` bevisst settes SIST og alene for det dyre trinnet: uten den er
|
||||||
|
trinn 2 og 3 gratis-nok til å kjøres fritt, og det er hele stigens poeng.
|
||||||
|
|
||||||
|
### Stigen
|
||||||
|
|
||||||
Stigen på nytt, samme disiplin som §1. Alt under er kjørt.
|
Stigen på nytt, samme disiplin som §1. Alt under er kjørt.
|
||||||
|
|
||||||
| Trinn | Kommando | Utfall |
|
| Trinn | Kommando | Utfall |
|
||||||
|
|
|
||||||
|
|
@ -10,11 +10,13 @@ from __future__ import annotations
|
||||||
|
|
||||||
import json
|
import json
|
||||||
from collections.abc import Callable, Sequence
|
from collections.abc import Callable, Sequence
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
from agent_framework import BaseChatClient
|
from agent_framework import BaseChatClient
|
||||||
|
|
||||||
from portfolio_optimiser.reference_domain import load_reference_projects
|
from portfolio_optimiser.reference_domain import load_reference_projects
|
||||||
|
from portfolio_optimiser.run import RunResult
|
||||||
from portfolio_optimiser.simulation import ScriptedChatClient
|
from portfolio_optimiser.simulation import ScriptedChatClient
|
||||||
from portfolio_optimiser.verdicts import VerdictStore, seed_store
|
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
|
#: 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
|
#: ``validated`` would be asserting that the model reasons well, which no schema can promise and
|
||||||
#: which one paid run could not establish anyway.
|
#: 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.
|
"""Assert the Fase 1b full-run contract on a completed ``run_project`` result.
|
||||||
|
|
||||||
Two things, and deliberately nothing else:
|
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
|
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.
|
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"
|
artefact = Path(outbox_dir) / f"{run_id}-parse-failures.json"
|
||||||
if artefact.exists():
|
if artefact.exists():
|
||||||
# Quote the evidence in the failure message: the whole point of økt 35 was that the operator
|
# Quote the evidence in the failure message: the whole point of økt 35 was that the operator
|
||||||
|
|
|
||||||
|
|
@ -2,8 +2,16 @@
|
||||||
|
|
||||||
NOT default CI: skips cleanly without a configured Foundry endpoint + deployment. When set,
|
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
|
``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
|
project, hard token-capped per D6). SC9 is unchanged — this arm stays skipped offline.
|
||||||
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 os
|
||||||
|
|
@ -14,12 +22,16 @@ from portfolio_optimiser.run import PortfolioResult, run_portfolio
|
||||||
|
|
||||||
_ENDPOINT = os.environ.get("PORTFOLIO_FOUNDRY_PROJECT_ENDPOINT")
|
_ENDPOINT = os.environ.get("PORTFOLIO_FOUNDRY_PROJECT_ENDPOINT")
|
||||||
_DEPLOYMENT = os.environ.get("PORTFOLIO_FOUNDRY_DEPLOYMENT")
|
_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(
|
@pytest.mark.skipif(
|
||||||
_NO_FOUNDRY,
|
_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:
|
async def test_portfolio_live_azure_fanout() -> None:
|
||||||
# No client_factory -> the real AZURE backend is used; hard-capped per D6.
|
# No client_factory -> the real AZURE backend is used; hard-capped per D6.
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue