"no live-model run yet" var falskt siden 14.08 (docs/2026-08-14-fase1b-forste-levende-kjoring.md): en live koersel mot gpt-4.1-mini konkluderte "rejected" — korrekt utfall, men modellen fant opp en kostkode. "sibling built in parallel" stemte ikke: po-claude er PARKERT (STATE.md). Begge punkt verifisert mot primaerkilde foer retting (Verifiseringsloven ansikt 3). Statusavsnittet er ogsaa gjort mer noekternt: ingen validerte forslag mot levende modell enna, ingen ekte ekspertdommer i treet (kun AI-forfattede froe, merket som saadan). Utvidet test_public_surface_claims_loadbearing.py med to nye rode->groenne gater (Iron Law): begge paastandene kan ikke gjeninnfores usett. Fant og fikset en linjebrudd-felle i egen test underveis (blockquote-wrap gjorde "no live-model run yet" usynlig for et raatt substring-soek). Ingen kodeendring i src/. Ingen push til open (Azure-gatet, egen ordre). 1025 passed / 5 skipped. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RF8QXXBWJ917Uj9zMf18nz
202 lines
10 KiB
Python
202 lines
10 KiB
Python
"""Load-bearing gates on claims the PUBLISHED surface makes about itself.
|
|
|
|
AAA+ criterion A5 is that no claim on the public surface is untrue. Four of this repo's claims are
|
|
made in prose that no test could see, and all four drift silently:
|
|
|
|
1. ``env.template`` tells the reader which credential the AZURE profile resolves. It said
|
|
``DefaultAzureCredential`` while :mod:`portfolio_optimiser.backends` has never constructed one —
|
|
Fase 4b picks ``ManagedIdentityCredential`` or ``AzureCliCredential`` by environment, and Learn's
|
|
MAF guidance names the specific credential *over* ``DefaultAzureCredential`` deliberately.
|
|
2. ``README.md`` publishes a wheel-install command that spells the wheel's FILENAME, and a wheel
|
|
filename carries the version. A version bump moves the file the build produces without touching
|
|
the README, leaving a stranger with an install command for a file that does not exist.
|
|
3. ``README.md``'s status paragraph said "no live-model run yet". Measured false as of the F1
|
|
review (2026-08-25): a live run against ``gpt-4.1-mini`` completed 2026-08-14 (see
|
|
``docs/2026-08-14-fase1b-forste-levende-kjoring.md``) and ended in a correct ``rejected``
|
|
outcome.
|
|
4. ``README.md``'s status paragraph said the Claude Agents SDK sibling is "built in parallel".
|
|
Measured false the same day: ``STATE.md`` records it as parked.
|
|
|
|
Most gates read the source artefacts as RAW TEXT, because that is the only thing that can see
|
|
prose. Gates 1 and 2 are LINE-ANCHORED rather than substring-matched: ``backends.py`` NAMES
|
|
``DefaultAzureCredential`` four times in the comments that explain why it is not used, so a
|
|
whole-file substring check would be red on exactly the prose it protects (this repo's 08-09 defect
|
|
class, and the reason the handover package's python-only gate matches archive member NAMES rather
|
|
than document prose). Gates 3 and 4 are literal regression guards on the specific wording measured
|
|
false — ``STATE.md`` is local-only (gitignored, not shipped to a fresh clone) and cannot serve as a
|
|
portable ground-truth source for gate 4, so that gate cannot verify the CURRENT sibling status,
|
|
only refuse the specific false claim already made once.
|
|
|
|
Each positive assertion is paired with a CONTROL that the thing being searched for is actually
|
|
present. An extractor that silently finds nothing makes a gate that can only ever be green, which
|
|
proves nothing.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import re
|
|
from pathlib import Path
|
|
|
|
import pytest
|
|
|
|
_REPO_ROOT = Path(__file__).resolve().parents[1]
|
|
_ENV_TEMPLATE = _REPO_ROOT / "env.template"
|
|
_README = _REPO_ROOT / "README.md"
|
|
_PYPROJECT = _REPO_ROOT / "pyproject.toml"
|
|
_BACKENDS = _REPO_ROOT / "src" / "portfolio_optimiser" / "backends.py"
|
|
_LIVE_RUN_EVIDENCE = _REPO_ROOT / "docs" / "2026-08-14-fase1b-forste-levende-kjoring.md"
|
|
|
|
# The credential is chosen on ONE assignment statement. Reading the credential names off that line —
|
|
# rather than off the whole module — is what keeps the explanatory comments out of the measurement.
|
|
_CREDENTIAL_ASSIGNMENT = re.compile(r"^\s*credential\s*=\s*(?P<expr>.+)$", re.MULTILINE)
|
|
_CREDENTIAL_CALL = re.compile(r"(\w*Credential)\s*\(")
|
|
|
|
# A wheel filename spells the distribution, the version and the tags. The version is the drifting part.
|
|
_WHEEL_FILENAME = re.compile(r"portfolio_optimiser-(?P<version>[0-9][^-\s]*)-py3-none-any\.whl")
|
|
|
|
# The completed-live-run record on the evidence doc, tolerant of the exact column width used there.
|
|
_LIVE_RUN_OUTCOME = re.compile(r"outcome_type:\s+rejected")
|
|
|
|
# `[project]`'s own version line: the value hatchling stamps into the wheel filename.
|
|
_PROJECT_VERSION = re.compile(r'^version\s*=\s*"(?P<version>[^"]+)"', re.MULTILINE)
|
|
|
|
|
|
def _constructed_credentials() -> set[str]:
|
|
"""The credential classes ``backends.py`` actually constructs, read off the assignment line."""
|
|
match = _CREDENTIAL_ASSIGNMENT.search(_BACKENDS.read_text(encoding="utf-8"))
|
|
if match is None:
|
|
return set()
|
|
return set(_CREDENTIAL_CALL.findall(match.group("expr")))
|
|
|
|
|
|
def _built_version() -> str:
|
|
match = _PROJECT_VERSION.search(_PYPROJECT.read_text(encoding="utf-8"))
|
|
assert match is not None, "pyproject.toml has no [project] version line"
|
|
return match.group("version")
|
|
|
|
|
|
# --------------------------------------------------------------------------------------------
|
|
# Control: the extractors find something. Without these, every gate below could pass vacuously.
|
|
# --------------------------------------------------------------------------------------------
|
|
|
|
|
|
def test_credential_extractor_finds_the_assignment() -> None:
|
|
"""CONTROL. If the assignment is reshaped, the gates below must fail loudly, not silently pass."""
|
|
constructed = _constructed_credentials()
|
|
assert len(constructed) == 2, (
|
|
"expected backends.py to construct exactly two credentials on one assignment; "
|
|
f"the extractor found {sorted(constructed)}"
|
|
)
|
|
assert all(name.endswith("Credential") for name in constructed)
|
|
|
|
|
|
def test_readme_publishes_a_wheel_install_command() -> None:
|
|
"""CONTROL. The version gate below is only meaningful while the README names a wheel file."""
|
|
found = _WHEEL_FILENAME.findall(_README.read_text(encoding="utf-8"))
|
|
assert found, (
|
|
"README.md no longer spells a wheel filename — the version gate has nothing to guard"
|
|
)
|
|
|
|
|
|
# --------------------------------------------------------------------------------------------
|
|
# A5 — env.template describes the credential the code actually uses
|
|
# --------------------------------------------------------------------------------------------
|
|
|
|
|
|
def test_env_template_names_the_credentials_backends_constructs() -> None:
|
|
"""Every credential the AZURE path can construct is named in the template the operator copies."""
|
|
template = _ENV_TEMPLATE.read_text(encoding="utf-8")
|
|
missing = sorted(name for name in _constructed_credentials() if name not in template)
|
|
assert not missing, (
|
|
f"env.template does not name {missing}, which backends.py constructs. An operator "
|
|
"reading the template cannot tell which identity the AZURE profile will use."
|
|
)
|
|
|
|
|
|
def test_env_template_does_not_claim_default_azure_credential() -> None:
|
|
"""No line may present ``DefaultAzureCredential`` as the resolution mechanism.
|
|
|
|
Line-anchored on purpose. ``backends.py`` names the class in prose to explain why it is NOT
|
|
used, and that explanation is legitimate; what is not legitimate is the template telling an
|
|
operator that resolution goes through a credential the code never constructs.
|
|
"""
|
|
offending = [
|
|
f"env.template:{number}: {line.strip()}"
|
|
for number, line in enumerate(_ENV_TEMPLATE.read_text(encoding="utf-8").splitlines(), 1)
|
|
if "DefaultAzureCredential" in line
|
|
]
|
|
assert not offending, (
|
|
"env.template claims a credential backends.py never constructs:\n" + "\n".join(offending)
|
|
)
|
|
|
|
|
|
# --------------------------------------------------------------------------------------------
|
|
# A5 — the published install command names the file the build actually produces
|
|
# --------------------------------------------------------------------------------------------
|
|
|
|
|
|
def test_readme_wheel_command_cites_the_built_version() -> None:
|
|
"""The wheel filename in the README must carry the version hatchling will stamp on it."""
|
|
built = _built_version()
|
|
cited = sorted(set(_WHEEL_FILENAME.findall(_README.read_text(encoding="utf-8"))))
|
|
drifted = [version for version in cited if version != built]
|
|
assert not drifted, (
|
|
f"README.md tells a reader to install portfolio_optimiser-{drifted[0]}-py3-none-any.whl, "
|
|
f"but the build produces version {built}. The published install command names a file that "
|
|
"does not exist."
|
|
)
|
|
|
|
|
|
# --------------------------------------------------------------------------------------------
|
|
# F1 (2026-08-25) — the README status paragraph does not repeat two claims measured false
|
|
# --------------------------------------------------------------------------------------------
|
|
|
|
|
|
def test_live_run_evidence_records_a_completed_run() -> None:
|
|
"""CONTROL. The negative assertion below is only meaningful while this run is on record."""
|
|
text = _LIVE_RUN_EVIDENCE.read_text(encoding="utf-8")
|
|
assert _LIVE_RUN_OUTCOME.search(text), (
|
|
f"{_LIVE_RUN_EVIDENCE} no longer records a completed live run with outcome_type: "
|
|
"rejected — the claim the status paragraph must not contradict has nothing to guard it"
|
|
)
|
|
|
|
|
|
def _readme_prose() -> str:
|
|
"""``README.md``'s status paragraph is a markdown blockquote wrapped across several lines,
|
|
each continued with ``"> "``. Rendered markdown collapses those soft line breaks into one
|
|
paragraph, so a raw substring search must normalise the same way first — otherwise a claim
|
|
that happens to wrap across a line boundary is invisible to the gate (measured: "no
|
|
live-model run yet" wraps as "no live-model\\n> run yet" in the raw file).
|
|
"""
|
|
text = _README.read_text(encoding="utf-8")
|
|
return re.sub(r"\n>\s*", " ", text)
|
|
|
|
|
|
def test_readme_status_does_not_claim_no_live_run() -> None:
|
|
"""A live run against a real endpoint completed 2026-08-14 (see ``_LIVE_RUN_EVIDENCE``); the
|
|
status paragraph may not say otherwise, even though no run has yet produced a *validated*
|
|
proposal against a live model.
|
|
"""
|
|
assert "no live-model run yet" not in _readme_prose(), (
|
|
"README.md claims no live-model run has happened, but one completed on 2026-08-14 "
|
|
f"(see {_LIVE_RUN_EVIDENCE.name})"
|
|
)
|
|
|
|
|
|
def test_readme_status_does_not_claim_sibling_built_in_parallel() -> None:
|
|
"""The Claude Agents SDK sibling is parked (``STATE.md``: 'po-claude PARKERT'); the status
|
|
paragraph may not claim active parallel development. ``STATE.md`` is local-only and is not
|
|
read here — this is a literal regression guard on the specific claim measured false on
|
|
2026-08-25, not a check against the sibling's current status.
|
|
"""
|
|
assert "built in parallel" not in _readme_prose(), (
|
|
"README.md claims the Claude Agents SDK sibling is built in parallel, but it is parked"
|
|
)
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
"artefact", [_ENV_TEMPLATE, _README, _PYPROJECT, _BACKENDS, _LIVE_RUN_EVIDENCE]
|
|
)
|
|
def test_guarded_artefacts_exist(artefact: Path) -> None:
|
|
"""CONTROL. A missing artefact must fail here rather than turn a gate into a no-op."""
|
|
assert artefact.is_file(), f"{artefact} is missing; the gates above would read nothing"
|