Co se změnilo ve verzi 2 a proč
Verze 1 říkala doslova, že vstup přes shell hlásí a neslouží, a kontrola odmítla podpříkaz serve nebo mcp kdekoli na vstupní cestě. ADR 0043 tuto klauzuli nahrazuje. Úvaha, kterou nahrazuje, směšovala dvě věci:
- ADR 0003 odmítá proces bez klienta a shell není klient. Námitka ADR 0003 se týká serveru, který přežije důvod své existence, a byla už zodpovězena v čase, ne v každém okamžiku: server, který
ensurespustí, dostaneDEFAULT_IDLE_SECONDSa skončí, když se po tu dobu nepřipojí žádný peer. Shell, který vstoupí do repozitáře, přečte jeho prostředí a odejde, je klient stejně jako agent, jehož relaci poskytovatel otevřel a zavřel. Obojí kryje tento nečinný život. - Soubor
.envrcnesmí růst o logiku. Pravda a beze změny:project.envrc-is-an-adapter@2si ponechává každé slovo svého odmítnutí a zužuje soubor na jedno volání. Nic z toho se nemění, když jedno volání, které činí, náhodou spouští proces, protože rozhodování — zda, na jakém portu, s jakou dobou nečinnosti, kdy mlčet, kdy odmítnout, protože spustitelný soubor je zastaralý — probíhá uvnitř spustitelného souboru.
Co stála uzavřená polovina, bylo celé zadání. Člověk, který vstoupil do tohoto repozitáře v terminálu, dostal banner, PATH a most workflow a žádný runtime: žádný Cockpit, žádné API, žádný Swagger a žádnou nástěnku peerů — tedy žádný způsob, jak by ostatní workeři na témže stroji zjistili, že nejsou sami, což je selhání, kterému project.work-is-claimed-before-it-is-built předchází a za které tento repozitář zaplatil víc než jednou. Nápravou byl příkaz, který si je třeba pamatovat, což je přesně to, proti čemu je návrh.
Verze 2 tedy zachovává každé odmítnutí, které udělala verze 1 ohledně buildování, čekání, selhávání a druhých názorů, posouvá klauzuli „neobsluhuje“ na „neobsluhuje sám sebe“ a přidává, co nové oprávnění potřebuje, aby zůstalo poctivé: jednu implementaci pro obě dveře, jeden přepínač, odmítnutí ensure ze zastaralého spustitelného souboru a deklarovaný časový rozpočet.
Zdůvodnění
docs/ENTRY_AUDIT.md, měřeno 2026-09-10, položilo jedinou otázku — když do tohoto repozitáře vstoupí člověk nebo agent, co se sblíží samo — a zjistilo, že odpověď zní „spouštěč MCP a nic víc“. Shell vstupující do repozitáře se o serveru nedozvěděl nic; relace, jejíž handshake MCP selhal, také nic; poskytovatel bez adaptéru životního cyklu ho nikdy neměl. ADR 0035 a řez, který jej implementoval, způsobily, že vstup konverguje: událost start poskytovatele volá serve ensure, briefing pojmenuje, v jakém stavu server je, existují serve status/ensure/stop a volba přežije start pomalejší než jeho bind grace.
Nic z toho nebylo pravidlo a každý kousek je od zrušení vzdálen jediný řádek, který udělá někdo rozumný. Přepínač lze přidat do politiky a nikdy ho nenaučit schéma, kde je neznámý klíč za běhu odmítnut a celá politika přestane jít zpracovat. Shim lze odpojit od poskytovatele, čehož si nevšimne nic než seznam vynucování v politice. Do .envrc může někdo, komu přijde bootstrapový příkaz příliš nepřímý, přidat serve ensure — a vrátit tak rozhodnutí do shellového souboru, který nic netestuje, proti němuž bylo napsáno project.envrc-is-an-adapter a který slovník pravidla nikdy nepokryl: zakazuje git, grep, curl a cargo a o vlastním serve nástroje neříká nic. Do kterékoli vstupní cesty lze přidat build, když někoho unaví vidět „the executable is not built“, čímž se cd promění v dvouminutovou kompilaci a událost startu v server sestavený ze stromu, který worker ještě nečetl. Druhý názor na to, v jakém stavu server je, lze vytvořit jedním curl vedle toho, který dostala událost startu, a v prvním okamžiku, kdy se obě zjištění ptají v různou dobu, si budou odporovat.
Tento repozitář má pro tvar takového selhání jméno: hlášení, které nikdo nemusí vyřídit, je hlášení, které nikdo nevyřizuje. docs/ENTRY.md je próza; test/cases/108 prokazuje chování jednou; ani jedno neodmítne změnu, která jej tiše odstraní.
Požadované chování
Vstup agentem konverguje. Startovní událost poskytovatele otevře epizodu a zajistí, že sdílený server repozitáře obsluhuje tento checkout, přes vlastní serve ensure spustitelného souboru — nikdy tím, že by sama spustila proces, a nikdy tím, že by se podruhé ptala na to, co už rozhoduje ensure. Shim, který událost nese, je deklarován v seznamu enforcement politiky, zapojen přes provider-hook:<provider>:session, takže majordomus doctor sladí zapojení, místo aby si čtenář musel vzpomenout to zkontrolovat. Polovina je přepínač, session.ensure_server_on_start, a klíč politiky je deklarován na čtyřech místech, nebo na žádném: v politice tohoto repozitáře, v kostře, z níž se nový repozitář zapisuje, v seznamu povolených a ve schématu.
Vstup přes shell také konverguje a sám nic nespouští. Soubor, který shell vyhodnotí při vstupu, provede jediné volání — bootstrapový příkaz spustitelného souboru, majordomus env enter — a toto volání zajistí runtime stejným způsobem jako událost startu. Samotný soubor a adaptér, který volá, nesmějí spouštět serve ani mcp nástroje, vyvolat builder ani pustit proces na pozadí: ten soubor běží při každém cd, na každém stroji, v libovolném shellu, který někdo zrovna používá, a rozhodnutí tam učiněné je rozhodnutí, které nic netestuje. project.envrc-is-an-adapter@2 říká, co smí vstupní bod číst a že provede jedno volání; toto říká, co to volání musí a nesmí dělat.
Obojí dveře spouštějí jednu implementaci. Událost startu poskytovatele a bootstrap příkaz konvergují přes stejnou funkci, commands::serve::converge, kterou vykresluje i serve ensure. Tři povrchy, jedno rozhodnutí — tvrzení, které project.interfaces-are-projections činí o každém jiném faktu v tomto repozitáři — protože dvě implementace otázky „už tento checkout obsluhuje server“ jsou dvě odpovědi čekající na rozpor, a to v jediném okamžiku, kdy rozpor znamená dva autoritativní servery.
Jeden přepínač, obě dveře. session.ensure_server_on_start je odpověď tohoto repozitáře na otázku „zvedne vstup sem runtime“ a řídí shell stejně jako událost startu. Druhý klíč pro cestu shellu se odmítá: repozitář, který odpovídá na jednu otázku na dvou místech, je repozitář, kde se obě odpovědi rozejdou. MAJORDOMUS_RUNTIME=off je totéž odmítnutí pro shell jedné osoby místo repozitáře a znamená je jen přesná hodnota off — překlep v něčím profilu nesmí tiše zabránit repozitáři ve startu.
Vstup nikdy nečeká na připravenost. Studený vstup přečte lease, zjistí, že nic neodpovídá, spustí server jako samostatný proces a vrátí se. Server se připraví vedle shellu, ne před ním, a watch_file vstupního souboru nad lease o chvíli později přivede publikovanou adresu do prostředí. --wait bootstrap příkazu má výchozí hodnotu nula a vstupní soubor ji nepředává. Vše, na co volání čeká, je omezeno (project.every-wait-is-bounded).
Vstup serveru nedědí nic od volajícího. Standardní vstup, výstup a chyba jsou přesměrovány; každý další deskriptor, který volající měl otevřený, se zavře mezi fork a exec. Nejde o úklid: direnv dává souboru, který vyhodnocuje, vlastní deskriptor a nevrátí se, dokud se ta roura nezavře, a server, který ji drží, drží shell po celý svůj nečinný život — 481 sekund, změřeno, 2026-09-11.
Vstup nikdy neselže. Každý výsledek za běhu je nejvýše jeden řádek na standardním chybovém výstupu a exit 0. Nenulový exit způsobí, že direnv ohlásí selhání celého prostředí, a člověku zanechá rozbitý shell v repozitáři, který je v pořádku.
Vstup má deklarovaný rozpočet. benchmark.budget.enter_ms a benchmark.budget.enter_cold_ms jsou klíče politiky jako každé jiné — deklarované v politice, skeletonu, allow listu a schématu — a případ měří obě. Vstup do adresáře člověk dělá desítkykrát denně a nikdy se nerozhodl na něj čekat a rozpočet, který žije jen v něčí paměti o tom, jak rychlé to dřív bylo, rozpočtem není.
Nic při vstupu program nesestavuje a nic se nezajišťuje ze zastaralého. Obě vstupní cesty najdou program, aniž by jej sestavovaly. Chybějící program nebo starší než jeho zdroje je pojmenován na standardním chybovém výstupu s receptem, který jej sestaví, a vstup stále končí nulou — a z něj se nezajistí žádný runtime. Server spuštěný ze zastaralého kódu odpovídá stromem, který už neexistuje, Cockpitu, API i každému dalšímu pracovníkovi, který je k němu připojen, a nikdo z nich nevidí, že proces je starší než checkout, který údajně obsluhuje. Obě cesty rozhodují o zastaralosti toutéž mj_rust_stale v lib/rust_bin.sh, takže checkout, kde se neshodují, nemůže existovat.
To, co se pracovníkovi řekne při vstupu, má jednoho autora. Briefing je předán tam, kde server stojí, jako argument událostí, která jej zajistila, a netvoří žádný vlastní názor — ani otevřením leasu, ani dotazem na stav serveru. Dvě čtení jednoho stavu jsou dvě odpovědi čekající na rozpor, a je to totéž tvrzení, které project.interfaces-are-projections uvádí o každém jiném povrchu. Že lease samotný má jednoho čtenáře, určuje project.the-lease-is-read-once pro každý soubor v repozitáři; toto pravidlo na něm závisí, místo aby jej opakovalo, a přidává jen polovinu, kterou to pravidlo nevidí, protože dotaz serveru přes HTTP žádný soubor neotevírá.
Server, který nikdo nevlastní, má omezený život. Server, který ensure spustí, dostane idle život, deklarovaný jednou jako konstanta větší než nula, a skončí, když po tak dlouhou dobu nebyl připojen žádný peer. Tak zůstává platné pravidlo ADR 0003 — žádný proces bez klienta — v čase, a ne jen v každém okamžiku.
Chování při selhání
scripts/ci/entry-converges, registrovaná jako kontrola entry-converges v .ai/repo/ci/gates.yaml a vybíraná každou třídou cest, která může pohnout kteroukoli stranou (shellový nástroj a vstupní bod, vrstva, distribuce, crate), hlásí jeden řádek na kontrolu a při jakémkoli nálezu skončí s exit 10. Mechanicky rozhoduje:
- že
.envrcabin/majordomus-envnespouštějí žádný builder, žádnou úlohu na pozadí ani podpříkazservečimcpnástroje, určeno podle tvaru, ne podle seznamu dnešních řádků; - že
.envrcvolá nástroj právě jednou, že tímto voláním je bootstrap příkaz a že nepředává žádné--wait, takže tento záznam se nemůže stát čekáním na připravenost; - že vstupní cesta shellu čte
session.ensure_server_on_starta respektujeMAJORDOMUS_RUNTIMEa že adaptér vypne runtime, když je executable zastaralý, místo aby z něj zajišťoval; - že bootstrap příkaz konverguje přes stejnou funkci jako
serve ensure, a ne přes druhou implementaci; - že
session.ensure_server_on_start,benchmark.budget.enter_msabenchmark.budget.enter_cold_msjsou každý deklarovány v politice, kostře, seznamu povolených a schématu; - že start shim odesílá
capture session --event start, že seznam vynucování v politice deklaruje záznamprovider-hook:<provider>:session, aby jejdoctorsesouhlasil, a že cesta startu spouštíserve ensure, čte přepínač, konzultuje kontrolu zastarání a nespouští žádný builder; - že skladatel briefingu se nezeptá serveru na jeho vlastní stav (polovina s lease patří
scripts/ci/lease-reader-checkpro celý repozitář a nečte se zde dvakrát); - že klidová doba života je jedna konstanta větší než nula a že ji spuštěný server dostane;
- že spuštěný server zavře zděděné deskriptory, aby volající čekající na vlastní rouru nebyl držen po dobu tohoto nečinného života.
Žádný validátor tohoto pravidla nežije v lib/. Který soubor shell vyhodnotí při vstupu, který poskytovatel spouští událost startu a kde tento repozitář deklaruje svou politiku, kostru a schéma, jsou fakta o tomto repozitáři, a validátor v lib/ by dodaný spustitelný soubor učil rozvržení jednoho repozitáře; .ai/repo/rules/project/README.md je místo, kde je tento rozdíl zapsán.
Ověření
test/cases/118_entry_converges_by_rule.sh prokazuje kontrolu mutací, proti fixture stromu, ne proti tomuto checkoutu: serve ensure přidaný do vstupního bodu, druhé volání nástroje v něm, --wait přidaný do bootstrapového volání, builder přidaný do kterékoli vstupní cesty, server na pozadí, přepínač nebo rozpočet vypuštěný ze schématu, shim, který přestane dispatchovat událost start, startovací cesta, která přestane zajišťovat nebo začne buildovat, vstupní cesta shellu, která přestane číst přepínač, adaptér, který zajišťuje ze zastaralého spustitelného souboru, briefing, který se ptá serveru na jeho vlastní stav, nulová doba nečinnosti a spawn, který zahodí --idle, se každý zasadí a každý se odmítne s pojmenovanou příčinou; stejná nezmutovaná fixture je přijata. Případ také ověřuje, že kontrola je deklarována v .ai/repo/ci/gates.yaml a vybírána třídami, které ji mohou pohnout, protože kontrola, kterou žádný plán nevybírá, je próza s návratovým kódem.
O čem kontrola záměrně nerozhoduje a rozhoduje recenzent:
- Tento vstup skutečně konverguje. Žádný skript nemůže vstoupit do repozitáře jako agent nebo jako shell. Dveře agenta jsou
test/cases/108_entry_converges_on_a_server.sh, které řídí shim providera s payloadem, jaký by provider poslal, v repozitáři, který zapsalinit: první start zajistí připravený server a pojmenuje ho v briefingu, druhý najde týž a nespustí nic,serve statusse shodne,serve stopho ukončí, vypnutý přepínač to řekne a nespustí nic, a chybějící spustitelný soubor je pojmenován, zatímco událost stále končí nulou. Dveře shellu jsoutest/cases/190_entry_is_automatic.sh: studený vstup vyzvedne runtime a vrátí se, aniž by na něj čekal, teplý vstup nespustí nic, nic nepřepíše a zůstane uvnitřbenchmark.budget.enter_ms, opakované vstupy na zdravém runtime nezmění žádný soubor, přepínač aMAJORDOMUS_RUNTIME=offho každý zastaví a zastaralý spustitelný soubor je pojmenován, ne z něj servírováno. Že obojí dveře konvergují na jeden server při souběhu, dokazujetest/cases/191_entry_races.sh, který spouští skutečné souběžné procesy: deset současných vstupů, lease s mrtvým pidem, obsazený port, server, který spadne při startu, a dva worktree najednou. Kontrola spouští případ 108, pokud nedostane--no-suite. - Že briefing říká něco užitečného. Kontrola vidí, že skladatel netvoří žádný druhý názor; nevidí, zda stojí za to přečíst řádek, který dostal.
- Že zde byla vůbec deklarována nová vstupní cesta. Poskytovatel, jehož adaptér životního cyklu přistane bez záznamu vynucování, nebo vstupní bod, který toto pravidlo nejmenuje, je nález recenzenta: kontrola čte cesty, které zná, a nemůže si všimnout cesty, o které jí nikdo neřekl. Související invariant — že poskytovatel je data a ne kód — je
project.providers-are-data. - Že lease má jednoho čtenáře. To rozhodují
project.the-lease-is-read-onceascripts/ci/lease-reader-checka dokazuje totest/cases/110_lease_reader.sh; tři shellové parsery, které ADR 0035 zaznamenal jako dluh fáze 09, tam byly splaceny. Toto pravidlo na tom závisí a nic z toho znovu nekontroluje. - Vlastní sliby volby při pomalém startu, zastaralém lease nebo bouři. To jsou
project.shared-server-resilience,apps/majordomus-cli/tests/mcp_shared.rs,tests/serve_lifecycle.rsatest/cases/90_mcp_shared_server.sh; toto pravidlo se týká toho, že vstup k této mašinérii dosáhne, ne mašinérie samotné.
docs/ENTRY.md je stránka provozovatele pro vše z toho a ADR 0035 je rozhodnutí.