35. Server checkoutu je jedním ze serverů repozitáře a jeho stav se obsluhuje jako každý jiný fakt
Kontext
ADR 0003 rozhodlo o jednom sdíleném serveru na repozitář: první majordomus mcp naváže loopbackové HTTP vedle své relace stdio, každý další se připojí a místem setkání je jeden lease soubor v lokální polovině checkoutu. Volba, kterou popisuje, obstála — lease každého tvaru se převezme, klient není nikdy odmítnut, vyměněný spustitelný soubor o lease přijde — a byla napsána dřív, než ADR 0021 dalo tomuto repozitáři propojené worktree jako běžný způsob práce na větvi.
Audit vstupu do repozitáře (docs/ENTRY_AUDIT.md, změřeno 2026-09-10) zjistil, o čem se obě rozhodnutí nikdy nesladila. Lease žije v kořeni checkoutu, kořen je nejbližší .ai/manifest.yaml, každý worktree jeden má a identita, kterou sonda porovnává, je digest tohoto kořene. „Jeden server na repozitář“ tedy byl jeden server na checkout: propojený worktree zvolil vlastní server na vlastním portu s vlastní nástěnkou peerů a žádné čtení žádného lease neříkalo, že dva takové servery patří k sobě. Relace v jednom worktree neviděla peery jiného, sekce peerů v context četla nástěnku primárního checkoutu z worktree hádáním cesty k primárnímu a scénář worktree z mandátu — stejný repozitář, odlišné worktree, peeři, kteří rozliší rozsahy — nebyl selhávající; byl nedosažitelný.
Tentýž audit našel lease čtený ručně na čtyřech místech: volba, read-only serving, snímek prostředí a dva shellové čtenáři (jq v lib/context.sh, sed v .just/serve.just), bez typu mezi nimi. To, co lease zaznamenává — pid, adresu, čas startu, executable — bylo na disku a na žádném povrchu; routa health počítala peery a o procesu neříkala nic; připravenost měla tři nesouvisející definice (existuje výstup adresáře; registr je neprázdný; TCP spojení uspělo); a žádná se neptala na jedinou otázku, kterou už třída zastaralého serveru stála den: je proces za touto adresou kód, který je na disku?
Rozhodnutí
Server obsluhuje checkout a každý checkout git repozitáře patří jednomu repozitáři. Volba se nemění: jedna lease na checkout, v lokální polovině checkoutu, jedno atomické vytvoření, každý zastaralý tvar převzat. Mění se to, že servery jednoho repozitáře lze nyní rozlišit od serverů dvou a vypsat z kteréhokoli z nich:
repository::git_identityse ptá gitu, kterému repozitáři checkout patří — adresář git, který sdílejí všechny pracovní stromy — a vytvoří z něj digest. Digest je jedna hodnota pro každý checkout repozitáře a nikdy cesta. Trasa indexu (/) ji vrací vedle vlastníhorepository_idcheckoutu, slinked_worktree, aby čtenář kteréhokoli serveru věděl, jakého repozitáře je jedním z nich a zda je primární. Kde se gitu nelze zeptat, pole chybí; repozitář vrstvy nemusí být pod správou verzí a toto rozhodnutí u takového nic nemění.- Checkouty repozitáře se čtou z vlastního registru gitu (
git worktree list, čtečka, kterou ADR 0021 už udržuje), nikdy ze souboru tohoto nástroje. Žádný registr serverů neexistuje: server se najde tam, kde je jeho checkout, přes lease, který tento checkout už má.
Lease je jeden typ, čtený jednou. lease::LeaseDocument je to, co server zapisuje a co každý čtenář parsuje; lease::LeaseFile::read je to jediné čtení a nikdy nezklame: chybějící, prázdný, poškozený a dokument jsou jeho čtyři odpovědi a co s každou udělat, je rozhodnutí čtenáře. Volba, serving, objevování služeb prostředí a stav serveru čtou všechny přes něj. Klíč, který čtenář nezná, se ignoruje, takže lease zapsaný novějším spustitelným souborem se stále čte; klíč, který starší lease nemá, dostane výchozí hodnotu. Dokument získává version, verzi spustitelného souboru, vedle identity spustitelného souboru, kterou už nesl; proces, který lease publikuje, si ponechává dokument, který zapsal (lease::held), aby mohl říct, co zapsal, aniž by četl zpět vlastní soubor.
Stav serveru je schopnost. server.status — GET /api/v1/server, nástroj majordomus_server, zdroj majordomus://server — odpovídá, kde server tohoto checkoutu stojí, změřeno proti tomu, co by obsluhoval tento spustitelný soubor: absent (žádný lease), starting (lease stále platí), ready (odpovídá pro tento checkout, ze souboru na disku, v této verzi), outdated (odpovídá, ale z jiné verze nebo ze souboru, který byl od startu nahrazen) nebo stale (neodpovídá, nebo to není lease), s důvodem; lease, který tento proces drží, když je serverem; a každý checkout repozitáře, primární první, každý s leasem, stavem a počtem peerů, které jeho server drží. Rozhodnutí je čistá funkce souboru leasu, jeho stáří, jedné sondy a verze tohoto spustitelného souboru, testovaná větev po větvi; schopnost čte soubory a servery při každém volání a nic necachuje, protože leasy zapisují jiné procesy.
Jak široká je odpověď, rozhoduje volající. Výpis každého checkoutu stojí čtení lease a sondu na každý a stroj se stovkou registrovaných worktrees platí obojí stokrát — což by volající, který chce vědět jen o checkoutu, ve kterém je, platit neměl. Vstup nese jedno pole, checkouts: repository, každý checkout, který git registruje, a this, pouze tento. this je levné z konstrukce, ne z filtrování — git se nikdy nedotazuje na registr work trees, takže se nevyčísluje žádný jiný checkout, nečte se žádný jiný lease a nesonduje žádný jiný server — a nespouští ani žádný vlastní proces: zda je checkout primární, plyne ze stat, který git identita už udělala, a jeho větev se čte z jeho vlastního HEAD. Výchozí je repository, protože to je odpověď, kterou schopnost dávala před existencí pole, a je to kompatibilitní povrch; čtenáři, kteří vždy chtěli jen tento checkout, si o něj říkají jménem.
Vstup konverguje a server, který nevlastní žádný klient, má omezenou životnost. serve ensure přečte lease a otestuje server, který jmenuje, jako to dělá volba, a když žádný neodpovídá, spustí jeden jako samostatný proces — serve --fallback --idle, jeho log vedle lease — a pak čeká, až je připraven; serve stop ukončí ten, který lease jmenuje, pokud odpovídá za tento checkout; serve status je projekce server.status v příkazové řádce. Startovní událost poskytovatele volá ensure (session.ensure_server_on_start, ve výchozím stavu zapnuto) a briefing uvádí, kde server stojí, takže agent má server před prvním připojením; launcher MCP vždy konvergoval stejně přes volbu; shell vstupující do repozitáře je informován, ale není obsloužen, protože vstupní hook nesmí nic spouštět (project.envrc-is-an-adapter) a shell není klient. Server spuštěný ensure skončí, když po --idle sekund nebyl připojen žádný peer, což udržuje větu ADR 0003 — žádný proces bez klienta — pravdivou v čase, nikoli v každém okamžiku.
Oznámení přežije server, kterému bylo učiněno. Most uchovává argumenty posledního přijatého oznámení svého klienta a říká je znovu po opětovném připojení nebo je po převzetí přenese na vlastní nástěnku; jediné místo, které vidí oznámení i opětovné připojení, je to, které je opakuje, a žádný worker nemusí nic pamatovat.
Volba držitele dodrží své sliby i při pomalém startu. Držitel udržuje svůj lease čerstvý, dokud se vrstva načítá, takže studený start pomalejší než bind grace se nikdy nepovažuje za opuštěný; převzetí odstraní jen soubor, který posoudilo; držitel, jehož lease byl při bindování převzat, odmítne publikovat a degraduje; server, jehož lease je převzat později, ho přestane nárokovat a skončí se svými peery; a vlastní čtečka serveru zapomene relace, které přestaly pingovat, na každé cestě.
Zamítnuté alternativy
- Přesun lease do primárního checkoutu, jeden server na git repozitář. Odpovídá doslovně slovům mandátu a obsluhuje špatný strom: server obsluhuje vrstvu checkoutu, ve kterém byl spuštěn, a relace na feature branchi připojená k serveru primárního by četla pravidla, rozhodnutí a plán trunku. Jeden proces indexující každý worktree by byl poctivou podobou tohoto rozhodnutí; je to větší změna než tento slice a toto rozhodnutí ji nevylučuje — server obsluhující několik checkoutů by zde byl stále uveden, jednou za checkout.
- Registr serverů pod lokální polovinou primárního checkoutu. Soubor, do kterého každý server zapisuje a ze kterého se sám odstraňuje, je druhý lease se stejnými způsoby selhání a novým vlastním zastaralým stavem. Git už checkouty registruje; lease, který každý má, stačí.
- Porovnávání verzí ve volbě. Lease jmenující server jiné verze se přebírá na
fix/stale-runtime-is-loud, což je vlastní větev tohoto rozhodnutí; stav hlásí nesoulad jakooutdateda chování volby je třeba měnit v této větvi. - Server spuštěný při
cd. Žádá o to mandát, který to podnítil; vstupní hook nesmí nic spouštět, ADR 0003 odmítá proces bez klienta a shell není klient. Vstup agentem konverguje; vstup shellem hlásí. - Démon se supervizorem. Zamítnuto ADR 0003 a zbytečné:
ensureje idempotentní a bezpečné vůči souběhu díky volbě a server, který končí při nečinnosti, nepotřebuje, aby ho někdo hlídal. - Zabíjení zastaralého serveru z
ensure. Server, který odpovídá, je něčí; stav ho jmenujeoutdateds nápravou,stopho ukončí, když je požádán jménem, a nahrazený spustitelný soubor ztratí svůj lease volbou jako dřív.
Důsledky
server.status, pole git_repository_id a linked_worktree trasy index, LeaseDocument a jeho pole version se stávají kompatibilními plochami. Každý worktree nese svůj vlastní server jako dříve; nové je, že každý z nich umí pojmenovat ostatní.
context je první čtenář, který chce úzkou otázku: ptá se na checkout, v němž běží, a na nic jiného, a platit za každý checkout repozitáře bylo to, co učinilo dotaz na server dražším než nedotazovat se.
Sekce peerů v context a recepty .just/serve.just stále čtou lease samy; jsou to čtenáři, které dodatek ADR 0003 ponechal v shellu, a přesunou se na odpověď spustitelného souboru s příštím slicem (docs/ENTRY_AUDIT.md, fáze 09). Tento slice přistál téhož dne; viz dodatek níže.
Scénář worktree z mandátu je nyní dosažitelný: dva worktree jednoho repozitáře hlásí stejné git.id, odlišná checkout_id a servery a počty peerů toho druhého. Peer stále nenese na nástěnce vlastní worktree; to je věc dalšího rozhodnutí, nad žurnálem nástěnky, který přináší ADR 0034.
tests/server_status.rs drží případ dvou worktree a případ zastaralého leasu; tests/shared_units.rs a tests/mcp_shared.rs drží volbu jako dřív, přes totéž typované čtení.
Dodatek, 2026-09-18: dva shellové čtecí skripty se přesunuly a kontrola drží jejich počet na jedné
Odstavec výše odložil poslední dva shellové čtenáře lease na fázi 09. Přesunuli se v den, kdy byl tento záznam napsán, v 534e8fb7bc — feat(mcp): the shared server's lease has one reader, and a gate that keeps it — takže odložení nebylo nikdy dosaženo a tento záznam od té doby popisuje strom, který už neexistuje.
Co ten řez udělal a co jej drží dnes:
lib/context.shse ptáserve status --repo <root> --format jsona čtelease.urlodpovědi; receptopenv.just/serve.justse ptá téže schopnosti na adresu serveru tohoto checkoutu. Ani jeden soubor neotevírá;scripts/cockpit-probe, třetí čtenář, který odstavec nejmenoval, se přesunul s nimi.- Pravidlem je
project.the-lease-is-read-once, kontrolouscripts/ci/lease-reader-checka dluh, který ratchet drží, je.ai/repo/lease-reader-baseline.txt— prázdný, takže počet je jeden čtenář, ne jeden čtenář plus zaznamenané výjimky. test/cases/110_lease_reader.shspouští kontrolu nad fixtures, vloží čtenáře do každého jazyka, v němž ho lze napsat, a vyžaduje, aby ho odmítnutí pojmenovalo.
Změřeno 2026-09-18 na master 751eb3a581: kontrola hlásí 0 known reader(s) kromě apps/majordomus-cli/src/lease.rs a končí kódem 0; zasazení ručního parsování jq do lib/context.sh způsobí, že končí kódem 10, jmenuje soubor, shodná jména a opravu; odstranění sondy obnoví kód 0. Nezávisle na kontrole jsou jedinými sledovanými soubory, které jmenují název souboru leasu mimo čtečku, testy a dokumentaci, .envrc, který žádá direnv, aby ho sledoval, a nikdy ho neotevře, a sama kontrola.
Na rozhodnutí se nic nemění. Tento dodatek existuje proto, aby záznam přestal odporovat stromu, o kterém rozhodl.