fix(maf): en vakt som gikk inert i STILLHET, funnet ved aa loefte pinnen (F15, ORDRE 20260829T155150Z)

MAF core 1.9.0 -> 1.16.0, orchestrations 1.0.1 -> 1.1.1. De to kan ikke loeftes
hver for seg: orchestrations 1.1.1 krever selv core>=1.15.0.

Iron Law: vakt-testen kjoert ROED mot 1.9.0 (2 failed) FOER pinnen ble roert.
Gulvet bor i EN konstant og pyproject-asserten deriverer sin streng fra den.

NEVNER: 16 private/ugaranterte former, derivert fra repoets EGNE siteringer,
alle 16 sjekket mot begge versjoner, 2 endret seg. Kjent-positiv: MiddlewareFailure
flippet NO -> YES. KP-kandidaten _compaction.py ble FORKASTET (teller 0 i begge,
diskriminerer ingenting).

DEN FARLIGE ENDRINGEN er den ordren navnga - formen som fortsatt importerer, men
har flyttet semantikk i stillhet. En park skriver naa TO checkpoints og bare EN
baerer plan-review-typen, saa en feildeklarert _ALLOWED_CHECKPOINT_TYPES toemmer
ikke lenger listingen: den taper nOEyaktig den checkpointen som betyr noe,
get_latest returnerer den ANDRE, og _parks `latest is None`-vakt passerte mens
kjOEringen svarte rc=0 og skrev et spOErsmaal som aldri kan baere svaret. Vakten
sjekker naa EGENSKAPEN den alltid mente (request_id in pending_request_info_events
- et DEKLARERT felt) i stedet for symptomet som pleide aa innebaere den, og fjerner
dermed en privat avhengighet i stedet for aa legge til en.

ExperimentalWarning-paret P4 pkt. 2 betalte for aa BEHOLDE er borte fordi MAF
sluttet aa sende det: _feature_stage.py emitterer ved FOERSTE BRUK, ikke ved import.
Goldenens stderr regenerert som BESLUTNING (fire -> to linjer); site-packages-
maskeringen BEHOLDT (spannet er ubebodd, ikke pensjonert).

Load-bearing MAALT mot HELE suiten, gronn kontroll 1089/5, stdout BYTE-UENDRET
(ea8c534773acdbe41ae68f2c55724d69aaf8be4f): M1 revert av vakten -> 1 rod.
EN mutasjon ble IKKE rod og staar som aerlighets-grense, ikke som gate: spikens
checkpoint_ids[-1] er rekkefolge-avhengig (Path.glob), altsaa flaky.

Rapport: docs/2026-09-02-f15-maf-pinnen.md

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Kjell Tore Guttormsen 2026-09-02 19:35:49 +02:00
commit ef2f1cbe61
15 changed files with 366 additions and 54 deletions

View file

@ -1534,18 +1534,27 @@ async def _park(
``get_latest`` rather than the last entry of a listing: it picks by timestamp
(``_checkpoint.py:424``), while the listing's order is whatever ``Path.glob`` returned.
An EMPTY listing is refused (``CheckpointUnreadable``) rather than parked around. That is the
one place this layer is louder than the framework: a blocked deserialisation is swallowed into
a warning upstream, so the alternative to raising here is a question file whose answer can
never be applied an exploration that fails as an absence, days later, to somebody who has
already written their answer.
The checkpoint named here must be the one that carries THIS question, and that is asserted
against the declared ``pending_request_info_events`` field rather than inferred from the
listing being non-empty. **The weaker form went inert under core 1.16.0 (F15, measured):** a
park now writes TWO checkpoints, and only one of them carries a plan-review type. A
mis-declared ``_ALLOWED_CHECKPOINT_TYPES`` therefore no longer empties the listing it drops
exactly the checkpoint that matters, ``get_latest`` returns the OTHER one, and the old
``latest is None`` guard passed while the question file named a checkpoint that can never bear
the answer. That is the silent dead question this layer exists to refuse, so the guard now
checks the property it always meant rather than the symptom that used to imply it.
Refusing is where this layer is louder than the framework: a blocked deserialisation is
swallowed into a warning upstream, so the alternative to raising here is a question whose
answer can never be applied an exploration that fails as an absence, days later, to somebody
who has already written their answer.
"""
latest = await checkpoint_storage(checkpoint_dir).get_latest(workflow_name=workflow.name)
if latest is None:
if latest is None or str(request.request_id) not in latest.pending_request_info_events:
raise CheckpointUnreadable(
f"the exploration reached a plan review but no checkpoint could be read back from "
f"{checkpoint_dir!r}: without one the review can never be resumed, so it is refused "
f"here rather than written as a question nobody can answer"
f"the exploration reached a plan review but no checkpoint carrying it could be read "
f"back from {checkpoint_dir!r}: without one the review can never be resumed, so it is "
f"refused here rather than written as a question nobody can answer"
)
review = request.data
return ParkedExploration(

View file

@ -218,7 +218,7 @@ def tracing_notice(setup: TracingSetup) -> str | None:
``None`` when tracing is off omission, never an empty row (``mandate.announce``'s rule, the
one ``cost_baseline_notice`` and ``skipped_links_notice`` follow). Here it is load-bearing past
style: the pinned demo stderr is four lines, and a "tracing: off" row would have made it five.
style: the pinned demo stderr is two lines, and a "tracing: off" row would have made it three.
The OTLP form names the VARIABLE beside the value, because the variable is what the operator
edits and prints one row per declared endpoint rather than only the first, so a run exporting