feat(simulation): P4 pkt. 3+4 — demo-transkriptet pinnet, frø-setningen avledet [skip-docs]

Kriterium 6 er selv-identitet: to kjøringer av en regredert demo er like enige som
to av en riktig. Fasiten forlater derfor prosessen. stdout pinnes ORDRETT (og er
dermed demoens abortsti); stderr normaliseres på nøyaktig to MÅLTE miljø-spann —
site-packages-prefikset og temp-katalogen — med po-sim- holdt synlig, fordi det er
en egenskap ved programmet og ikke ved miljøet. Pinnet stderr = fire linjer.
Kontrollen som forbyr at masken vokser er load-bearing: en droppende normaliserer
med fasiten regenerert under seg holder BEGGE likhets-testene grønne.

Pkt. 4: planens forhåndsskrevne frø-setning sa «én av de TO tidligere dommene».
Målt mot levert VEGLYS-bundle henter Kjøring B TRE — én fulgte med kunnskapsbasen,
to er demoens egne, én per tidsskala. Splitten avledes derfor fra kjøringen; en
håndskrevet «én av tre» ville vært den andre kopien som drifter.

Fem mutasjoner alle røde + grønn kontroll (hele suiten hver gang): ett byte i en
stdout-linje · detach dempingen · over-normaliser stderr · literal splitt · detach
frø-setningens print. Byte- og detach-mutasjonene ble fanget av KUN golden-testen;
den literale splitten av KUN skille-testen.

793 -> 801 passed / 4 skipped.
This commit is contained in:
Kjell Tore Guttormsen 2026-08-09 22:12:25 +02:00
commit 50232fb88d
7 changed files with 422 additions and 2 deletions

View file

@ -345,6 +345,31 @@ Python ≥3.10. MAF (`agent-framework-core` 1.9.0). Pakkehåndtering: `uv`. To b
(`tests/test_demo_stderr_quiet_loadbearing.py`), fem mutasjoner røde + grønn kontroll: detach
`main()`-kallet (subprosess-testen er ENESTE som fanger det — de tre filter-testene installerer
filteret selv) · la filteret droppe alt · installer ved import · pluss de to entry-point-mutasjonene.
- **Demo-transkriptet er sjekket inn som fasit, og masken er SPANN-avgrenset (P4 pkt. 3):**
kriterium 6 er selv-identitet — to kjøringer av en REGREDERT demo er like enige som to kjøringer av
en riktig, så fasiten må forlate prosessen. `tests/golden/demo-transcript.stdout` er stdout ORDRETT
(målt byte-identisk over kjøringer OG i fersk klon), og er derfor også demoens abortsti: feiler
live-kjøringen, ER fila transkriptet. `…​.stderr` er normalisert på nøyaktig to MÅLTE miljø-spann —
`site-packages`-prefikset og temp-katalogen bak `(arbeidskopi: …)`, der `po-sim-`-prefikset holdes
SYNLIG fordi det er en egenskap ved programmet (`mkdtemp(prefix=…)`), ikke ved miljøet; pinnet
stderr er fire linjer. **Masken må ikke kunne vokse:** P4 pkt. 2 betalte for at en NY advarsel
fortsatt når stderr, og en normalisering som maskerte hele linjer ville opphevet det i ett trekk —
derfor er `test_normalisation_does_not_mask_a_new_warning` kontrollen som forbyr det (MÅLT: en
droppende normaliserer med fasiten regenerert under seg holder BEGGE likhets-testene grønne og
felles kun av kontrollen). `PYTHONIOENCODING` pinnes, ellers måler sammenligningen operatørens
locale i stedet for programmet. **Regenerering er en beslutning, aldri rydding** — fasiten kan ikke
bevise sin egen kjøring, den pinner outputen P3-kriteriene ble målt mot.
- **Frø-setningen AVLEDES fra kjøringen (P4 pkt. 4):** demoen sier høyt hvor Kjøring B's tidligere
dommer kommer fra, og splitten (`_verdict_origin_line`) regnes ut — linja rett over printer allerede
antallet, så en håndskrevet «én av tre» ville vært den andre kopien som drifter (samme regel som
pkt. 0-baselinen), og ville blitt sagt uendret etter at en framtidig bundle shipper en ANDRE frøsatt
dom. Klassifisereren er de to markørene demoen alt sporer; den hviler på at den frøsatte dommen
bærer INGEN av dem, som MÅLES på levert bundle. **Planens forhåndsskrevne ordlyd var FEIL mot
levert innhold** («én av de TO») — målt henter Kjøring B TRE: én fulgte med kunnskapsbasen, to er
demoens egne (én per tidsskala). Load-bearing MÅLT
(`tests/test_p4_honesty_sentences_loadbearing.py` + golden-transkriptet), fem mutasjoner alle røde
+ grønn kontroll: ett byte i en stdout-linje · detach dempingen · over-normaliser stderr · literal
splitt (fanget av INGENTING i 800 tester bortsett fra skille-testen) · detach frø-setningens print.
- **Delt ekspert-persona som Agent Skill (§8, framework-nøytral):** ekspert-reviewer-personaen bor i
`shared/skills/expert-reviewer/` (`SKILL.md` + `references/example-verdict.json`) og er den ENE
delte artefakten begge stacker instansierer reviewer-en fra. `shared/` forblir REN DATA — MAF-siden

View file

@ -257,10 +257,11 @@ et planlagt utfall, ikke en krise — generalprøve nr. 0 mandag har allerede ve
IKKE være literal — de to gjenstående `ExperimentalWarning`-linjene bærer en absolutt sti inn i
`.venv/…/site-packages`, som er ulik i fersk klon og arbeidskopi (målt). Normaliser på BEGGE:
site-packages-stien OG `po-sim-`-suffikset. Pinnet stderr er da fire linjer, ikke to.
3. **Golden-transkript:** sjekk inn demo-outputen som fasit-fil og diff mot den — selvidentitet
3. **Golden-transkript ✔ (2026-08-09)** — se UTFØRT-blokka under punkt 4.
*(Planteksten:)* sjekk inn demo-outputen som fasit-fil og diff mot den — selvidentitet
(kriterium 6) fanger ikke-determinisme, men ikke regresjon mellom onsdag og torsdag.
**Transkriptet er også demoens abortsti** (gjort eksplisitt i P4.5).
4. **To ferdigskrevne setninger** inn i ærlighets-teksten (mønsteret fra innholdsgate-planen §5).
4. **To ferdigskrevne setninger ✔ (2026-08-09)** inn i ærlighets-teksten (mønsteret fra innholdsgate-planen §5).
NO-GO-varianten er OMSKREVET etter I1: «kostnads-forankringen er aktiv også i reserve-eksemplet,
men kostnadstallene der er syntetiske — avledet av manuset, ikke levert av et fagmiljø»
(fallback hvis pkt. 0 faller: den gamle setningen «kostnads-forankringen er ikke aktiv i
@ -269,6 +270,42 @@ et planlagt utfall, ikke en krise — generalprøve nr. 0 mandag har allerede ve
5. **`[project.scripts]` (flyttet HIT fra S1.c, I3) ✔ (2026-08-09, `ab7f45a`)** — se UTFØRT-blokka
rett under.
**UTFØRT — punkt 3 og 4 (2026-08-09). 793 → 801 passed / 4 skipped.** Punktene ble gjort i ÉN økt
og i denne rekkefølgen fordi pkt. 4 endrer stdout: en fasit pinnet før den ville vært foreldet i
samme økt. Testene ble skrevet FØR begge (målt rød: pkt. 3 på manglende fasit-fil, pkt. 4 på
manglende `_verdict_origin_line`), og fasiten ble generert til slutt — ETTER at P3-kriteriene var
re-verifisert mot den nye outputen (åtte steg-linjer; `REJECTED` på P90-stagen og `VALIDATED 445500`
for samme kandidat; begge markører `False` i Kjøring A og `True` i B; kostbaselinen erklært).
*Punkt 3:* `tests/golden/demo-transcript.stdout` er ORDRETT (ingen normalisering, ingen toleranse) —
det gjør fila brukbar som abortsti, siden den ER det operatøren ville sett. `…​.stderr` normaliserer
nøyaktig de to spannene som ble målt miljø-avhengige: `site-packages`-prefikset og temp-katalogen bak
`(arbeidskopi: …)`. `po-sim-`-prefikset holdes SYNLIG — det tilhører programmet, ikke miljøet — mens
`TMPDIR`-rota og suffikset maskeres. Pinnet stderr = fire linjer, som pkt. 2 forutsa. Bredden på
maskeringen er selv under test: `test_normalisation_does_not_mask_a_new_warning` mater en syntetisk
EKSTRA linje gjennom samme normaliserer og krever at den slutter å matche.
*Punkt 4 — den forhåndsskrevne frø-setningen var FEIL, og målingen fanget det.* Planen sa «én av de
to tidligere dommene i Kjøring B fulgte med eksempelet». Målt mot levert VEGLYS-bundle henter
Kjøring B **tre**: den frøsatte (`verdict-veglys-fro.md`), Steg-8-promoteringen og Steg-7-innboksen —
altså **én fulgte med, to er demoens egne, én per tidsskala**. Setningen ville vært en falsk påstand
sagt på scenen om et tall som står printet linja over. Splitten er derfor **avledet**
(`_verdict_origin_line`), ikke skrevet ned: en håndskrevet «én av tre» er den andre kopien som
drifter (samme regel som punkt 0), og ville blitt sagt uendret etter at en framtidig bundle shipper
en andre frøsatt dom. GO-varianten av provenans-setningen sto allerede live som `_VEGLYS_PROVENANCE`
siden P3; den var IKKE positivt asserted noe sted (anker-testen utelukker bare reservens setning), så
den er nå pinnet mot fasiten.
**Load-bearing MÅLT mot HELE suiten (~123 s hver), fem mutasjoner alle røde + grønn kontroll:**
ett byte i en stdout-linje (`FORSTÅ``FORSTA`) · detach rund-taks-dempingen i `main()` ·
over-normaliser stderr (drop advarsels-linjene) **med fasiten regenerert under den** — begge
likhets-testene forble GRØNNE, kun kontrollen felte den, som er hele grunnen til at kontrollen finnes ·
literal splitt i stedet for avledet · detach frø-setningens print. **To målinger er verdt å merke:**
byte-mutasjonen og detach-mutasjonen ble fanget av **kun** golden-testen — 800 andre tester merket
ingenting, som er nøyaktig gapet kriterium 6 ikke dekker; og den literale splitten ble fanget av
**kun** skille-testen (golden-testen forble grønn, siden literalen printer identisk tekst for den
leverte bundelen).
**UTFØRT — punkt 5, 2 og 1 (2026-08-09, `ab7f45a`). 775 → 785 passed / 4 skipped.**
*Punkt 5:* to konsoll-kommandoer — `portfolio-optimiser` (`run:main`) og `portfolio-optimiser-demo`

View file

@ -704,6 +704,28 @@ def _decision_line(result: RunResult) -> str:
return f"FORKASTET — {o.reason}"
def _verdict_origin_line(verdicts: Sequence[Verdict], *, marker: str, inbox_marker: str) -> str:
"""P4 pkt. 4, the seed sentence: of Run B's previous verdicts, how many came WITH the knowledge
base and how many the demo learned in this session.
Said out loud on stage, so it has to be true of the content actually being run and the split
is therefore DERIVED, never written down. The line above it already prints the retrieved count;
a hand-written "one of three" would be a second copy of that number, and would go on being said
unchanged after a bundle shipped a second seeded verdict.
The classifier is the two markers the demo has traced all along: a retrieved verdict carrying one
of them is one this session produced (the Step-8 promotion, the Step-7 inbox note). That rests on
the seeded verdict carrying NEITHER, which is measured on the delivered bundle in
``tests/test_p4_honesty_sentences_loadbearing.py`` rather than assumed here.
"""
learned = sum(1 for v in verdicts if marker in v.rationale or inbox_marker in v.rationale)
seeded = len(verdicts) - learned
return (
f" av disse fulgte {seeded} av {len(verdicts)} med kunnskapsbasen; "
f"de øvrige {learned} er dem demoen lærte i denne økten"
)
def _run_trace_lines(result: RunResult, *, marker: str, marker_in_prompt: bool) -> list[str]:
"""Steps 1-7 of one run, one labelled line per step (``method-spec`` §3), for the walkthrough.
@ -842,6 +864,11 @@ def main(argv: list[str] | None = None) -> int: # pragma: no cover - console tr
print("\nKJØRING B (re-seedet kunnskapsbase + innboksen lest)")
print(" samme åtte steg kjøres igjen; her vises kun det som ENDRET seg:")
print(f" tidligere dommer hentet for kandidaten: {len(result.run_b.retrieved)}")
print(
_verdict_origin_line(
result.run_b.retrieved, marker=result.marker, inbox_marker=result.inbox_marker
)
)
print(
f" markør '{result.marker}' (Steg 8, wiki) i hypotese-prompten: "
f"{result.marker_in_run_b_prompt} (forventet True)"

View file

@ -0,0 +1,4 @@
<SITE-PACKAGES>/agent_framework/_skills.py:121: ExperimentalWarning: [SKILLS] SkillResource is experimental and may change or be removed in future versions without notice.
<SITE-PACKAGES>/agent_framework/_harness/_memory.py:651: ExperimentalWarning: [HARNESS] MemoryStore is experimental and may change or be removed in future versions without notice.
(arbeidskopi: <TMPDIR>/po-sim-<SUFFIKS>)

View file

@ -0,0 +1,61 @@
==============================================================================
OFFLINE SIMULERING — skriptede agent-svar, INGEN ekte modell.
Beviser dataflyten, den deterministiske ryggraden og at læringssløyfa lukkes.
Beviser IKKE at en levende modell ville produsert dette — forslag og dom er skriptet.
==============================================================================
KUNNSKAPSBASE: veglys-fv-soer — kostbaseline erklært (ENERGI-VEGLYS-EL 4386150 x 1)
validatorens stage 0 avstemmer forslagets kostlinjer mot disse, FØR løseren
tallene er levert i kunnskapsbasen — utledet av fagkilder (Håndbok V124, NMFV), ikke av demo-manuset
KJØRING A (VEGLYS-FV-SOER — fersk kunnskapsbase, ingen tidligere dommer)
Steg 1 — FORSTÅ KONTEKSTEN (navigert kunnskapsbase + tidligere dommer)
navigerte konseptfiler (5): kilder-veglys-realisering.md, tiltak-adaptiv-styring.md, tiltak-led-utskifting.md, veglys-fv-soer.md, metode-ipmvp-a.md
tidligere dommer hentet for kandidaten: 0
markør 'realiseringsgrad=0.79' i hypotese-prompten: False
Steg 2 — HYPOTESE (kandidat med parametere)
tiltak: LED-utskifting av 2 500 eldre HPS-armaturer (114 W -> 70 W)
kostlinjer: ENERGI-VEGLYS-EL 4386150 x 1
påstått besparelse: 2100000 NOK
Steg 3 — DEBATT (maker-checker, Group Chat)
proposer (konvergert): {"measure":"LED-utskifting av 2 500 eldre HPS-armaturer (114 W -> 70 W)","affected_items":[…
checker (gate på resonnementet): VERDICT=APPROVE
Steg 4 — VALIDER / FALSIFISER (deterministisk, blokkerende)
hypotese #1: REJECTED (claimed saving 2100000 exceeds P90 feasible 1769915)
Steg 5 — FORBEDRE, INFORMERT OG BUNDET
#1: grunnen fra 2100000 NOK-hypotesen mates tilbake i neste forsøk (bundet av max_attempts)
etter forbedring: VALIDATED (påstått 445500 <= P90 1769915 NOK; tiltak: LED-utskifting av 2 500 eldre HPS-armaturer (114 W -> 70 W))
Steg 6 — FORKAST ELLER FORESLÅ (typet utfall forlater kjøringen)
FORESLÅTT — LED-utskifting av 2 500 eldre HPS-armaturer (114 W -> 70 W): 445500 NOK (validator=validated, checker=approve)
Steg 7 — SVAR PÅ TILBAKEMELDING (ekspert-persona, kort løkke i kjøringen)
dom: approved
begrunnelse: Godkjent med realiseringskorreksjon. Den modellerte besparelsen er teknisk korrekt fra parameterne og validatoren bekrefter at den er innenfor feasibelt omraade. Men i drift realiseres erfaringsvis ~79% av en timeplan-stipulert LED-besparelse i kontorbygg (realiseringsgrad=0.79) pga. overestimerte …
(forventet: markøren er FRAVÆRENDE her — dommen finnes ikke i wikien ennå)
MELLOM KJØRINGENE — to uavhengige tilbakemeldings-veier tas i bruk
Steg 7 (lang løkke) — EN EKSPERT LEGGER EN DOM I INNBOKSEN, ETTER KJØRINGEN
fil: verdict-innboks/STEG7-EKSPERT-DRIFTSNOTAT.json
bærer: realiseringsgrad=0.66 (ettersendt driftsmåling — ny kunnskap)
rollene byttes aldri: systemet LESER denne mappa, eksperten SKRIVER den
neste kjøring merger fila inn i minnet FØR hypotesen formes — dager kan gå
Steg 8 — PROMOTER GODKJENT KUNNSKAP (gatet wiki-promotering)
skrev: promoted-verdict-b26f6501ccf74eef.md (lenket i index.md, nøytral etikett)
bærer: realiseringsgrad=0.79 (personaens dom fra kjøring A)
gaten er fail-closed: kun en godkjent dom promoteres — rå agent-output aldri
KJØRING B (re-seedet kunnskapsbase + innboksen lest)
samme åtte steg kjøres igjen; her vises kun det som ENDRET seg:
tidligere dommer hentet for kandidaten: 3
av disse fulgte 1 av 3 med kunnskapsbasen; de øvrige 2 er dem demoen lærte i denne økten
markør 'realiseringsgrad=0.79' (Steg 8, wiki) i hypotese-prompten: True (forventet True)
markør 'realiseringsgrad=0.66' (Steg 7, innboks) i hypotese-prompten: True (forventet True)
utfall: FORESLÅTT — LED-utskifting av 2 500 eldre HPS-armaturer (114 W -> 70 W): 445500 NOK (validator=validated, checker=approve)
------------------------------------------------------------------------------
LÆRINGSSLØYFA ER LUKKET, PÅ BEGGE TIDSSKALAER: kunnskapen eksperten godkjente i
kjøring A nådde kjøring B's hypotese via den fil-baserte wikien (promoter ->
re-seed -> fold), OG driftsnotatet som ble lagt i innboksen etterpå nådde den via
fil-innboksen (skriv fil -> merge -> fold). Ingen av dem gikk gjennom minnet.
------------------------------------------------------------------------------

View file

@ -0,0 +1,142 @@
"""P4 pkt. 3 — the demo transcript is pinned as a checked-in fasit, and the pin is also the
demo's abort path.
**What this adds over criterion 6.** Criterion 6 is self-identity: run the demo twice, diff the two
outputs. It catches non-determinism, and nothing else two runs of a *regressed* demo agree with
each other just as happily as two runs of a correct one. Between the Wednesday freeze and the
Thursday stage there is no second run to compare against, so the fasit has to leave the process:
the transcript is checked in, and the demo is diffed against the file.
**Why stdout is literal and stderr is not.** stdout was measured byte-identical across runs AND
across a fresh clone (P4 pkt. 1), so it is pinned verbatim the file under ``golden/`` is exactly
what the operator sees, which is what makes it usable as the abort path: if the live run fails on
stage, that file IS the transcript. stderr carries two spans that are environment state rather than
program output, and both were measured to differ between a fresh clone and the working copy:
1. the absolute ``.../site-packages/`` prefix in the two ``ExperimentalWarning`` lines, and
2. the temp directory behind ``(arbeidskopi: ...)`` a fresh ``mkdtemp`` suffix every run, under a
``TMPDIR`` that is per-user.
Both are masked; everything else is compared byte for byte. Pinned stderr is four lines: the two
warnings, a blank line, and the ``arbeidskopi:`` line.
**The narrowness is the point, and it is under test.** P4 pkt. 2 damped the round-cap notices with a
filter keyed on the MESSAGE precisely so that a NEW warning from a MAF bump, or a subtree pull
would still reach stderr and trip this pin. A normalisation that masked whole lines would undo that
in one step and leave a pin that can no longer fail for the reason it exists. So the masking is
measured, not asserted: ``test_normalisation_does_not_mask_a_new_warning`` feeds a stderr with one
extra line through the same normaliser and requires it to STOP matching the fasit.
**Honesty about what a golden proves.** The fasit was generated from a run, so it cannot by itself
prove that run was right it pins the output the P3 criteria were measured against (eight step
lines; hypothesis #1 REJECTED on the P90 stage and the corrected one VALIDATED for the same
candidate; both learning paths reaching Run B). Regenerating it is therefore a decision, never
housekeeping: whoever regenerates re-takes those measurements.
"""
from __future__ import annotations
import os
import re
import subprocess
import sys
from pathlib import Path
import pytest
_GOLDEN_DIR = Path(__file__).resolve().parent / "golden"
_STDOUT_FASIT = _GOLDEN_DIR / "demo-transcript.stdout"
_STDERR_FASIT = _GOLDEN_DIR / "demo-transcript.stderr"
# Span 1: the interpreter's site-packages prefix, up to and including the separator. Anchored on
# the literal ``/site-packages/`` rather than on a home directory, so a checkout anywhere (or a
# different Python minor version) normalises to the same text.
_SITE_PACKAGES = re.compile(r"\S*/site-packages/")
# Span 2: the throwaway working copy. The ``po-sim-`` prefix is kept VISIBLE — it is a property of
# the program (``tempfile.mkdtemp(prefix="po-sim-")``), not of the environment — while the TMPDIR
# root and the random suffix, which are both, are masked.
_WORKING_COPY = re.compile(r"\(arbeidskopi: \S*/po-sim-\w+\)")
def normalise_stderr(text: str) -> str:
"""Mask the two measured environment-dependent spans, and nothing else."""
text = _SITE_PACKAGES.sub("<SITE-PACKAGES>/", text)
return _WORKING_COPY.sub("(arbeidskopi: <TMPDIR>/po-sim-<SUFFIKS>)", text)
@pytest.fixture(scope="module")
def demo() -> subprocess.CompletedProcess[str]:
"""Run the real demo once for this module.
Runs the ``-m`` form rather than the console script: it needs no assumption about PATH, and the
two forms were measured to write identical stdout when the entry point was added (P4 pkt. 5).
``PYTHONIOENCODING`` is pinned because the fasit is a file of UTF-8 bytes without it the
comparison would measure the operator's locale instead of the program.
"""
proc = subprocess.run(
[sys.executable, "-m", "portfolio_optimiser.simulation"],
capture_output=True,
text=True,
encoding="utf-8",
env={**os.environ, "PYTHONIOENCODING": "utf-8"},
check=False,
)
assert proc.returncode == 0, proc.stderr
return proc
def test_demo_stdout_matches_the_checked_in_transcript(
demo: subprocess.CompletedProcess[str],
) -> None:
"""T-P4.3a: stdout is byte-identical to the fasit — no normalisation, no tolerance.
RED on any change to a step line, a number, a label or the ordering. That is the whole point:
on demo day the operator has already read this file, and anything the program says that the
file does not is a surprise on stage.
"""
assert _STDOUT_FASIT.exists(), (
f"the pinned transcript is missing: {_STDOUT_FASIT}. It is generated by re-taking the P3 "
"measurements, not by copying whatever the demo currently prints."
)
assert demo.stdout == _STDOUT_FASIT.read_text(encoding="utf-8")
def test_demo_stderr_matches_the_normalised_transcript(
demo: subprocess.CompletedProcess[str],
) -> None:
"""T-P4.3b: stderr, with the two environment spans masked, is identical to the fasit.
RED when a new warning appears (a MAF bump, a subtree pull) and RED when the round-cap damping
is detached the two lines it drops would land right back in this comparison.
"""
assert _STDERR_FASIT.exists(), f"the pinned stderr is missing: {_STDERR_FASIT}"
assert normalise_stderr(demo.stderr) == _STDERR_FASIT.read_text(encoding="utf-8")
def test_normalisation_is_not_a_no_op(demo: subprocess.CompletedProcess[str]) -> None:
"""T-P4.3c (RED-proof for the test above): the raw stderr really does differ from the fasit.
Without this, T-P4.3b would be indistinguishable from a run where the environment spans happened
to be stable and the normaliser did nothing at all and a masking that never fires cannot be
said to be narrow.
"""
fasit = _STDERR_FASIT.read_text(encoding="utf-8")
assert demo.stderr != fasit
assert normalise_stderr(demo.stderr) == fasit
def test_normalisation_does_not_mask_a_new_warning(
demo: subprocess.CompletedProcess[str],
) -> None:
"""T-P4.3d (control): the masking is span-scoped, so an EXTRA stderr line still trips the pin.
This is the property P4 pkt. 2 paid for by keying the damping on the message rather than on the
logger. Measured here on a synthetic stderr rather than by provoking a real warning, because the
thing under test is the normaliser's reach — the real-warning path is what T-P4.3b covers.
"""
intruder = (
"/somewhere/lib/python3.12/site-packages/agent_framework/_new.py:1: DeprecationWarning: "
"something changed under us.\n"
)
polluted = intruder + demo.stderr
assert normalise_stderr(polluted) != _STDERR_FASIT.read_text(encoding="utf-8")

View file

@ -0,0 +1,124 @@
"""P4 pkt. 4 — the two honesty sentences the demo says out loud, and why one of them is DERIVED.
The plan pre-wrote two sentences for the stage. The first is the provenance claim about the cost
numbers, already live as ``_VEGLYS_PROVENANCE`` since the GO (P3): the baseline the validator's
stage 0 reconciles against was written by a domain team, not by the demo script.
The second is the seed sentence, about where Run B's "previous verdicts" come from. **Its pre-written
wording is wrong against the delivered content and was corrected against measurement.** The plan said
"one of the TWO previous verdicts in Run B came with the example — the other is the one the demo
learned"; measured against the delivered VEGLYS bundle, Run B retrieves THREE: one seeded
(``verdict-veglys-fro.md``), and TWO the demo produced itself one per time scale (the Step-8
promotion and the Step-7 inbox note). A sentence that says "two" would be a false claim made on
stage about a number printed one line above it.
**Why the split is computed and not written down.** The line right above already prints the retrieved
count. A hand-written "one of three" would be a second copy of that number in a different place
the exact drift the anchored-baseline discipline (P4 pkt. 0) refuses and it would go on being said
after a future bundle ships a second seeded verdict, silently. So the split is derived from the run,
and the classifier is the two markers the demo has been tracing all along: a retrieved verdict that
carries one of them is one the demo learned in this session; anything else came with the knowledge
base.
That classifier rests on a property of the delivered content the seeded verdict must carry NEITHER
marker so that property is measured here too, and not assumed.
"""
from __future__ import annotations
from pathlib import Path
from portfolio_optimiser.persona import load_persona_example
from portfolio_optimiser.simulation import (
_INBOX_MARKER,
_VEGLYS_PROVENANCE,
_delivered_bundle_dir,
_verdict_origin_line,
)
from portfolio_optimiser.verdicts import ProposalFeatures, Verdict, seed_store_from_bundle
# The Step-8 marker is the shared expert-reviewer skill's own, read at call time — the simulation
# takes it from the artifact rather than from a module constant, and so does this test.
_MARKER = load_persona_example().marker
def _verdict(vid: str, rationale: str) -> Verdict:
return Verdict(
id=vid,
proposal_features=ProposalFeatures(
affected_codes=frozenset({"ENERGI-VEGLYS-EL"}),
measure_type="LED-utskifting",
claimed_saving_nok=445500.0,
description="LED-utskifting",
),
decision="approved",
rationale=rationale,
)
def test_the_origin_line_reports_the_delivered_bundles_actual_split() -> None:
"""T-P4.4a: three retrieved, two carrying markers -> one came with the knowledge base.
This is the shape the demo prints today. RED if the classification is inverted or dropped.
"""
retrieved = [
_verdict("seeded", "frøsatt dom uten markør"),
_verdict("promoted", f"persona-dom ({_MARKER})"),
_verdict("inbox", f"driftsnotat ({_INBOX_MARKER})"),
]
line = _verdict_origin_line(retrieved, marker=_MARKER, inbox_marker=_INBOX_MARKER)
assert "1" in line and "2" in line
assert "kunnskapsbasen" in line
def test_the_split_is_computed_and_not_a_written_down_constant() -> None:
"""T-P4.4b (the distinguishing test): a bundle with a SECOND seeded verdict must move the
number.
An implementation that writes "1 of 3" into the string passes T-P4.4a and fails here which is
the whole reason this test exists. A test that only ever sees the delivered bundle's own split
cannot tell the two implementations apart, and would prove nothing.
"""
retrieved = [
_verdict("seeded-a", "frøsatt dom uten markør"),
_verdict("seeded-b", "en annen frøsatt dom uten markør"),
_verdict("promoted", f"persona-dom ({_MARKER})"),
_verdict("inbox", f"driftsnotat ({_INBOX_MARKER})"),
]
line = _verdict_origin_line(retrieved, marker=_MARKER, inbox_marker=_INBOX_MARKER)
assert "2 av 4" in line, line
def test_both_sentences_are_in_the_pinned_transcript() -> None:
"""T-P4.4d: the two sentences are on the screen the operator has already read.
Checked against the checked-in fasit rather than by starting a fourth demo subprocess the
fasit is byte-compared to a live run in ``test_golden_transcript_loadbearing``, so a sentence
that is in the file but no longer printed fails there, and a sentence dropped from BOTH fails
here. The provenance half needs this: the anchored-reserve test only rules the reserve's
sentence OUT, so emptying ``_VEGLYS_PROVENANCE`` would leave it green.
"""
fasit = (Path(__file__).resolve().parent / "golden" / "demo-transcript.stdout").read_text(
encoding="utf-8"
)
assert _VEGLYS_PROVENANCE in fasit
assert "med kunnskapsbasen; de øvrige" in fasit
def test_the_delivered_seed_verdict_carries_neither_marker() -> None:
"""T-P4.4c (RED-proof for the classifier): the property the split rests on, measured on the
delivered bundle rather than assumed.
If the seeded verdict's rationale ever contained one of the two markers, it would be counted as
something the demo learned, and the sentence on stage would overstate the loop by one. It would
also mean the marker was present in the knowledge base from the start which the Step-7 and
Step-8 load-bearing tests already forbid for their own reasons, but this line depends on it too.
"""
store = seed_store_from_bundle(str(_delivered_bundle_dir()))
seeded = [v for v in store.verdicts]
assert seeded, (
"the delivered bundle ships no seeded verdict — the classifier has nothing to see"
)
for verdict in seeded:
assert _MARKER not in verdict.rationale
assert _INBOX_MARKER not in verdict.rationale