"""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.+)$", 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[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[^"]+)"', 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"