15. Distribuce je jeden model a každý povrch, který nástroj dodává, je jeho projekcí
Kontext
Majordomus neměl instalaci. Rychlý start byl git clone, server MCP potřeboval toolchain Rustu, aby se při prvním použití sám sestavil, a nic v repozitáři nevědělo, pro které platformy lze program vůbec sestavit. Kdo chtěl nástroj zkusit, musel se napřed stát přispěvatelem.
Zřejmá oprava — napsat install.sh, přidat workflow releasu — reprodukuje v části repozitáře, kterou nikdo nečte, přesně to selhání, které tento projekt existuje, aby zachytil. Podporovaná platforma by musela být pravdivá na pěti místech: matice buildu v souboru workflow, příkaz case v instalátoru, tabulka v dokumentaci, blok na webu a cokoli, co nástroj říká sám o sobě. Pět kopií jednoho faktu, žádná nekontrolovaná proti ostatním, všechny upravované člověkem, který spěchá, protože připravuje release. Selhání není hypotetické: prvním příznakem je release, který staví pět artefaktů, zatímco instalátor nabízí šest, a ten, kdo to zjistí, je ten, kdo nástroj nemůže nainstalovat.
Druhý problém je to, za co si curl … | sh zaslouží kritiku. Skript se nečte, archiv se neověřuje, selhání v půli zanechá rozbitou binárku tam, kde byla funkční, a nepodporovaný stroj dostane matoucí chybu místo odpovědi. Nic z toho není vlastní vzoru; vše je to, co se stane, když je instalátor napsán jako pohodlí, ne jako program s kontraktem.
Rozhodnutí
Distribuce má jeden kanonický strojově čitelný model, share/distribution.yaml, a každý povrch, který nástroj dodává, z něj vychází nebo je s ním mechanicky kontrolován.
Model deklaruje binárku, repozitář, ze kterého se publikují releasy, kanonickou URL instalátoru a výchozí hodnoty, funkci pojmenování archivu a každý cíl, o kterém má projekt názor — podporovaný, experimentální, nebo nedostupný se zaznamenaným důvodem. Cíl, který nedeklaruje, neexistuje, pro nic.
Z něj se generuje: matice sestavení release, kterou workflow čte, tabulka platforem uvnitř publikovaného instalátoru, tabulka platforem v instalační příručce, datová sada, kterou web vykresluje, a veřejná metadata každého zaznamenaného release. Názvy artefaktů pocházejí z jedné funkce, ke které se každý volající dostane přes majordomus distribution artifact; žádný skript, workflow ani dokument žádný neskládá. majordomus generate --check odmítne strom, ve kterém jakákoli projekce zaostala.
Model se dodává v share/ místo pod .ai/ z téhož důvodu jako share/commands.yaml: je to výrok o tom, jak se nástroj chová, nikoli kontext pro workera, a cestuje s nainstalovanou kopií, takže nainstalovaný spustitelný soubor odpovídá na stejné otázky jako checkout.
Release se zaznamenává, ne popisuje. .ai/repo/releases/<tag>.yaml zapisuje pipeline z artefaktů, které byly skutečně nahrány, s každým digestem a velikostí přečtenými ze souboru; veřejná metadata se generují z těchto záznamů a stabilní ukazatel se při každém generování odvozuje jako nejvyšší verze mezi stabilními, nestažené z oběhu. Release, kterému chybí podporovaný cíl, nelze zaznamenat, a proto jej nelze publikovat.
Instalátor je program s kontraktem: jen HTTPS, každá hodnota přečtená z metadat se porovná s tvarem, který musí mít, digest a velikost artefaktu se ověří před jakýmkoli rozbalením, archiv se odmítne, pokud nese absolutní cestu, traversal, odkaz nebo položku mimo svůj adresář, rozbalený spustitelný soubor se jednou spustí, aby se prokázalo, že hlásí vyřešenou verzi, a launcher se nahradí přejmenováním, takže jakékoli dřívější selhání nechá předchozí instalaci přesně tak, jak byla. Žádné sudo, žádný toolchain, žádný záložní build ze zdrojů.
Zamítnuté alternativy
Ručně psaná matice workflow a ručně psaný instalátor. Rychlejší na napsání, a je to uspořádání, jehož drift byl tento repozitář postaven odhalovat. Zamítnuto podle vlastní doktríny.
Generování celého instalátoru z Rustu. Šestisetřádkový shellový program vložený do řetězcového literálu nelze zkontrolovat nástrojem shellcheck, nelze jej číst v diffu a nelze jej spustit z checkoutu. Dělení je takové: chování v ručně psané šabloně, fakta ve vygenerované oblasti uvnitř ní — což pak vidí shellcheck a behaviorální případy, protože běží proti vygenerovanému výstupu, nikoli proti šabloně.
Dodávat jen Rust spustitelný soubor. Příkaz, který člověk spouští, je bash: majordomus init je lib/init.sh a spustitelný soubor výslovně neimplementuje životní cyklus úlohy. Archiv jen se spustitelným souborem by nainstaloval něco, co neumí to, co úvodní stránka říká lidem udělat dál. Archiv nese obojí, což také znamená, že nainstalovaná kopie servíruje MCP bez toolchainu Rustu — požadavek na toolchain byl druhým důvodem, proč adopce uvázla.
Nechat instalátor scrapovat API GitHubu nebo stránku releases. Instalátor by závisel na povrchu, který tento projekt neřídí a nemůže proti němu testovat. Čte místo toho dva JSON dokumenty, které tento repozitář generuje.
Odložit Windows vynecháním z modelu. Mlčení vypadá jako přehlédnutí. Je deklarováno unavailable se svým důvodem, instalátor jej odmítne jménem a dokumentace říká proč — což je rozdíl mezi rozhodnutím a mezerou.
Důsledky
Přidání platformy je jedna úprava modelu a just derive. Instalátor ji nabídne, release ji sestaví na runneru, který model jmenoval, příručka a web ji uvádějí a invariant úplnosti nyní vyžaduje artefakt pro ni v každém budoucím release. Nic jiného se neupravuje a test/cases/84_distribution_model.sh to dokazuje přidáním cíle do kopie modelu a požadavkem, aby se objevil ve všech třech projekcích.
Dvě verze se musí udržovat v souladu kontrolou, ne odvozením: verze crate a verze bin/majordomus. Žádná nemůže číst druhou za běhu — nainstalovaný strom nemá Cargo.toml a crate se sestavuje dřív, než shellový nástroj existuje — takže scripts/release-version --check je to, co z nich dělá jeden fakt, a běží dřív, než release cokoli sestaví.
Změněno ADR 0085 (navrženo), 2026-09-24. Už ne dvě verze udržované v souladu: jedna autorská, jedna promítnutá. Manifest crate je jediné místo, kde se verze zapisuje;
share/version.txtje jeho vygenerovaná projekce, dodávaná v každém archivu vedle nástroje, který ji čte při startu.generate --checkodmítne zastaralou projekci ascripts/release-version --checkstále běží před tím, než release cokoli sestaví — nyní dokazuje, že nástroj vypisuje autoritu.
Nainstalované uspořádání je verzovaný strom plus dva generované launchery místo jediného souboru v ~/.local/bin. To dělá výměnu atomickou a rollback triviální a stojí to jeden proces navíc při každém spuštění a o něco delší odpověď na otázku „co odinstalace odstraní“.
Publikace je dvoukroková: pipeline commitne záznam a regenerovaná metadata na výchozí větev a existující workflow Pages je nasadí. Smoke test proto čeká, až web jmenuje nový tag, než z něj instaluje. Jedna cesta publikace, trocha latence a žádná druhá pipeline nasazení.
Podepisování provenience není hotovo. Kontrolní součty váží artefakt k jeho metadatům, ale nedokazují, kdo metadata napsal; to je uvedeno v docs/DISTRIBUTION.md jako odložené zlepšení, ne naznačeno mlčením.