Zdůvodnění
2026-09-11 byl na origin/master v faba8fdef spuštěn sdílený server, zeptal se na head repozitáře, dostal commit a zeptal se znovu:
HEAD before 2e70f780ba5cddd9f42b4d139456645357d72fac
the API said 2e70f780ba5cddd9f42b4d139456645357d72fac
HEAD after 2c6f59557d650903ee582bdb80dd0b164ed4028c
the API said 2e70f780ba5cddd9f42b4d139456645357d72fac
Stejnou zastaralou odpověď vracelo MCP i Cockpit, pravidlo commitnuté za běhu serveru chybělo v objects a pracovní strom hlásil clean, i když byl špinavý. Nikde nic neříkalo, že obraz je starý.
To je nejdražší tvar, jaký špatná odpověď může mít. Relace připojená k tomuto serveru celý den čte repozitář, který už neexistuje, rozhoduje podle něj a nikdo jí to neřekne — odpovědi jsou dobře utvářené, sebejisté a vypadají aktuálně a jedinou nápravou je restart, o kterém nikdo neví, že jej má provést. Server existuje proto, aby několik pracovníků sdílelo jedno čtení repozitáře; sdílené čtení o hodiny zastaralé je horší než žádné, protože každý pracovník by jinak přečetl repozitář sám.
Požadované chování
crate::live::Live drží repozitář, který čte dlouho žijící proces. Každá cesta, která se chystá použít kontext, jej odtud bere, a vzetí porovná crate::live::Stamp — velikost a čas změny řídicích souborů gitu, asi osm volání stat — s tím, při němž byla sestavena aktuální generace. Když se liší, požadavek, který si toho všiml, přebuduje vrstvu; druhý požadavek přicházející uprostřed přebudování už posun viděl, takže počká na toto přebudování a je zodpovězen z generace, kterou vytvoří, ne z té, o níž se ví, že je zastaralá.
Vše, co je projekcí indexu, se memoizuje na generaci, ne na proces: dokument OpenAPI, výpisy nástrojů a zdrojů MCP, rozlišené webové plochy. Context::continuing nese přes přestavbu to, co není projekcí ničeho — nástěnku peerů, běžící provádění, executor schopností — protože reload není restart a peer nesmí ztratit své místo kvůli tomu, že někdo commitnul.
Toto zpřesňuje project.rust-hot-path a nezmírňuje to. Čítače toho pravidla se týkají práce, kterou požadavek opakuje nad repozitářem, jenž se nezměnil, a nehýbou se ani při libovolném počtu takových požadavků. Toto pravidlo přidává, že práce se odvede jednou, když se repozitář změnil, požadavkem, který si toho všiml, a ne nikdy.
Jednorázový příkaz, měření výkonu a test berou Live::pinned a nic neplatí: obraz nemůže zastarat uvnitř procesu, který žije 40 ms, a nutit je dokazovat ho při každém volání by byla regrese koupená za nic.
Chování při selhání
Vrstva, která se po přesunu nenačte, není pro klienta chyba: dál se servíruje poslední generace, která se načetla, a důvod se zaloguje. Selhané načtení nespotřebuje změnu: generace si ponechá razítko a počet zápisů, při nichž byla sestavena, a selhání se zaznamená vedle ní, takže se stejný stav čte znovu, jakmile uplyne RETRY_AFTER — nikdy ne při každém požadavku, nikdy se na to nerezignuje — a další přesun či zápis se čte hned. Požadavek, který čekal na přestavbu, jež pak selhala, se odbaví stejně, z generace, která se načetla: neopakuje načtení, u něhož čekal na selhání, takže jedno selhání stojí jedno načtení, kolik požadavků se za ním ve frontě nahromadilo. Repozitář, který není pracovním stromem gitu, nemá přesuny ke sledování a je připnut, což se v logu řekne jednou.
Ověření
test/cases/150_server_sees_the_current_tree.sh: server se spustí, zaznamená se jeho head, vznikne commit a tentýž proces — ne restartovaný — se znovu zeptáme přes HTTP API a přes relaci MCP otevřenou před commitem. Objekt commitnutý v tom commitu musí být v indexu a peer musí být stále na nástěnce.
Vlastní testy crate::live drží kontrolu na ceně, která ji umožňuje: razítko se pořídí hluboko pod milisekundu, protože běží před každým požadavkem, na který server odpovídá.