docs(state): record the kø-z MCP-timeout invariant in CLAUDE.md

Documents the anyio.fail_after-vs-asyncio.wait_for finding and the
cancelled_caught ownership gate, alongside the existing kø-x task-group
invariant it extends.
This commit is contained in:
Kjell Tore Guttormsen 2026-08-03 21:26:04 +02:00
commit 1b7fafe222

View file

@ -67,6 +67,30 @@ Python ≥3.10. MAF (`agent-framework-core` 1.9.0). Pakkehåndtering: `uv`. To b
argument og returnerer et error-result. De to er separate sømmer med vilje. Load-bearing MÅLT
(`tests/test_ingest_golden_mcp.py`), fem mutasjoner alle røde: detach unwrappingen · detach
`initialize()` · gjør feilkoden generisk · detach `isError`-grenen · endre ett byte av bodyen.
- **MCP-timeouten må komponeres MED anyios eget cancel scope, ikke `asyncio.wait_for` utenfra
(kø-(z), 2026-08-03):** STATE-premisset ("en utypet `TimeoutError` re-raises urørt") var FEIL —
målt mot en EKTE hengende server ga `asyncio.wait_for(run(), timeout=...)` aldri en
`TimeoutError` i det hele tatt; den kansellerer `run()` UTENFRA strukturen anyio selv eier
(`stdio_client`/`ClientSession`), og de to kansellerings-mekanismene komponerer ikke — målt
utfall var en `anyio.BrokenResourceError` inni en `BaseExceptionGroup` (en bakgrunns-reader-task
mistet skrive-enden midt i nedrigging). Fiksen er `anyio.fail_after(timeout_seconds)` NESTET
INNI begge task-gruppene, der anyio rigger ned sin egen struktur rent og raiser en ren
`TimeoutError`. **Innsnevringen dekker IKKE bare typen:** builtin `TimeoutError` er også
`socket.timeout` (≥3.10) og `asyncio.TimeoutError` (≥3.11), så et ubetinget `except TimeoutError`
ville mislabelt en HVILKEN SOM HELST `TimeoutError` som `mcp_timeout` — reviewet FØR commit
(advisor) fant nøyaktig dette. Retteslen er `anyio.CancelScope.cancelled_caught`: kun scopet som
faktisk traff SIN EGEN deadline tjener `mcp_timeout`-koden, ellers re-raises urørt (speiler
`_unwrap_ingest_error`s eierskaps-regel). **Ingen levende utløser finnes i dag** for en
"fremmed" `TimeoutError` på denne stien — MÅLT: MCPs eget per-request read-timeout
(`ClientSession.send_request`) konverterer sin `anyio.fail_after` til `McpError` FØR den når oss,
og en tool som raiser `TimeoutError` server-side blir et ordinært `isError`-resultat (samme
som enhver annen tool-exception) — begge verifisert empirisk, ikke antatt. Diskriminatoren er
likevel pinned med en syntetisk test (raiser fra `StdioServerParameters`-konstruksjon, inni
fail_after-scopet men FØR noen task group), fordi defektklassen ellers ikke har en nåbar sti å
bevise den mot. Load-bearing MÅLT (`tests/test_ingest_golden_mcp.py`) mot HELE 625-suiten, fire
mutasjoner alle røde: detach hele oversettelsen · revert til `asyncio.wait_for` · relabel koden ·
detach `cancelled_caught`-gaten (behold kun scope-presence). `anyio` promotert fra transitiv
(via `mcp`) til deklarert direkte dep (`pyproject.toml`) — modulen importerer den nå direkte.
- **Stoppkriterier + budsjett-tak påkrevd ved oppstart** (fail-fast, aldri ubegrenset loop).
- **Group Chat maker-checker** som debatt-default (IKKE Magentic, som er eksperimentell).
- **To falsifiserere, samme kandidat (Steg 3/4, målbilde §2/§6):** den deterministiske validatoren