portfolio-optimiser/tests/test_public_surface_claims_loadbearing.py
Kjell Tore Guttormsen 28a420ab97 feat(3): flaten sier sant om seg selv, og to gater måler at den fortsetter å gjøre det
Fase 3 (AAA+ på publisert flate). Tre av planens premisser falt på måling og er
rettet FØR handling, ikke etterpå:

* GOVERNANCE-raden hadde feil tiltak. Planen sa «skriv den»; org-ops D11 sier én
  kanonisk fil som hvert repo LENKER, og filen er nå publisert (målt: HTTP 200 på
  open/repo-standard). Å skrive vår egen ville gjort oss til kopi nr. 12 av en
  fil D11-bølgen holder på å rydde vekk. README lenker den, i samme form som
  repo-mailbox bruker, og bus-faktor 1 står uttalt i den kanoniske teksten.
* Release-objektet for v1.0.0 FINNES allerede på open/ (id 155, CHANGELOG-kropp,
  siden rendrer) — det som mangler er vedlegg, ikke objektet.
* WARN RELEASE-STALE fyrer ikke, og kan ikke: regelen sammenligner utgivelse mot
  tagg og er strukturelt blind for repo med null utgivelser (org-ops hovedbok
  #18). Gaten var OK/20 sjekker FØR arbeidet startet, så den kan ikke tjene som
  verifikasjon for denne fasen. Bevisene er Forgejo-APIet, filinnholdet og
  ren-klon-kjøringen.

A5-defekten rettet: env.template:21 sa at credential resolves via
DefaultAzureCredential. Den har aldri gjort det — backends.py:149 konstruerer
ManagedIdentityCredential eller AzureCliCredential, og Learns MAF-veiledning
navngir den spesifikke credentialen NETTOPP for å unngå probing. En operatør som
kopierte templaten ble fortalt at feil identitet ville bli brukt.

To load-bearing gater (Iron Law: begge røde før fiksen, 2 failed / 7 passed):

1. env.template navngir de credentials backends.py faktisk konstruerer, og ingen
   linje utgir DefaultAzureCredential for å være mekanismen. LINJEFORANKRET, ikke
   delstreng: backends.py NAVNGIR klassen fire ganger i kommentarene som
   begrunner hvorfor den ikke brukes, så en fil-bred substring-gate ville vært
   rød på nøyaktig den prosaen den beskytter (repoets 08-09-klasse, fjerde gang).
2. README-ens wheel-filnavn bærer versjonen bygget stempler på fila. Uten den
   ville en versjonsbump stille etterlatt en publisert install-kommando som peker
   på en fil som ikke finnes.

Hver positiv assert er paret med en KONTROLL på at det søkes etter noe som
finnes — en ekstraktor som stille finner null lager en gate som bare kan bli
grønn.

MUTASJONER MÅLT MOT HELE SUITEN, begge røde på riktig test og på INGEN annen:
gjeninnfør den usanne credential-påstanden (2 røde, 844 grønne) · la
wheel-filnavnet drifte til 1.0.0 (1 rød, 845 grønne). Restaurert fra scratchpad
+ shasum -c mellom hver. Bumpen selv var den andre mutasjonen: pyproject 1.0.0 →
1.1.0 gjorde README-gaten rød alene, før README ble rettet.

SECURITY.md: varslingsfrist (minst én minor-release og aldri under 30 dager
mellom kunngjøring og fjerning, med sikkerhetskritisk fjerning som uttalt
unntak). Støttetabellen er bevisst VERSJONSFRI — et release-nummer skrevet der
ville drevet ved neste tagg, altså samme defektklasse som gate 2 fanger.

CLAUDE.md beholdt på flaten med en engelsk innramming øverst (operatørvalg): den
sier hva fila er for en fremmed. Innholdet er repoets sterkeste bevis på at hver
beslutning er målt; å fjerne det ville fjernet bevis, ikke friksjon.

Versjon 1.1.0 — synket i pyproject, __init__, test_smoke og README-kommandoen.
1.0.0-treet kan ikke produsere en kjørbar wheel (force-include kom etter taggen,
målt: git show v1.0.0:pyproject.toml har den ikke), så en wheel hengt på den
utgivelsen ville vært nøyaktig den usanne påstanden denne fasen finnes for å
fjerne. Operatøren valgte bumpen framfor et vedlegg som ikke virker.

846 passed / 4 skipped (fra 837). ruff + format + mypy rene.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011ckyg3Pc6k7FRuR6fDGQLJ
2026-08-14 06:57:25 +02:00

139 lines
6.9 KiB
Python

"""Load-bearing gates on two claims the PUBLISHED surface makes about itself.
AAA+ criterion A5 is that no claim on the public surface is untrue. Two of this repo's claims are
made in prose that no test could see, and both 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.
Both gates read the source artefacts as RAW TEXT, because that is the only thing that can see prose.
Both 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 4e
``azure.yaml`` gate is line-anchored too).
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"
# 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")
# `[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."
)
@pytest.mark.parametrize("artefact", [_ENV_TEMPLATE, _README, _PYPROJECT, _BACKENDS])
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"