Jak tento repozitář rozhoduje, kde fakt žije, co smí být zapsáno dvakrát a co se musí odvodit. Doprovodný soubor je HARDCODING_LEDGER.yaml, který zaznamenává každé místo, kde jsou níže uvedená pravidla právě porušena, s příkazem k reprodukci pro každé.
Tvrzení zde popisující chování, které ještě není implementováno, jsou formulována jako cíle.
Pravidlo
Jeden fakt má jednoho kanonického vlastníka. Každé další místo, kde se fakt objevuje, je z vlastníka odvozeno, nebo je kontrolou, že se oba shodují.
Selhání, kterému se tím předchází, není neuklizenost. Je to selhání, které nástroj existuje chytat: deklarované pravidlo, které nic nevynucuje, zdokumentovaný příkaz, který už neexistuje, počet, který byl kdysi pravdivý. Každá taková vada začíná jedním faktem zapsaným na dvou místech, z nichž se jedno změní.
Co se počítá jako napevno zapsané
Ne každá konstanta je vada a pročištění, které s nimi zachází stejně, vytváří nečitelnou nepřímost a skutečný drift přitom míjí.
Kontrakt zůstává doslovný. Jsou to rozhodnutí, ne zjistitelná fakta, a jejich sepsání je to, čím se stávají kontraktem:
| Druh | Příklad zde |
|---|---|
| Konstanty protokolu | exit kódy v lib/common.sh |
| Verze schémat | version: 1 v každém kanonickém souboru |
| Standardní názvy souborů | .ai/repo/policy.yaml, CLAUDE.md |
| Výchozí hodnoty bootstrapu | název výchozího profilu |
| Rozhodnutí o podpoře | operační systémy v matici CI |
| Samotné schéma | share/allow/*.txt, které definují povolené klíče |
| Redakční úsudek | sloupce publika a účelu v docs/README.md |
Zrcadlo je vada. Seznam je zrcadlem, když jeho obsah už zná něco jiného:
| Zrcadlo | Kde je pravda |
|---|---|
| obsah souborového systému | adresář |
| Příkazy CLI | dispečerská tabulka |
| tests | test/cases/ a metadata v každém případu |
| routes | generované kolekce obsahu |
| doktríny | soubory pravidel pod .ai/repo/rules/ |
| providers | deklarované projekce |
| tvrzení | docs/CLAIMS.yaml |
| počty čehokoli z výše uvedeného | to, co se počítá |
Rozlišující otázka není „mohlo by to být data?“, ale „ví už to něco jiného a budou se ty dvě rozcházet?“ Kurátorský seznam, jehož úplnost se vynucuje proti adresáři, který popisuje, není zrcadlo — docs/README.md je vzor: řádky jsou redakční a scripts/generate-site-data odmítne build, když se soubor v docs/ neobjeví v žádném řádku.
Kanonické vlastníky
| Entita | Kanonický vlastník | Zjišťování | Odvozené povrchy |
|---|---|---|---|
| Politika | .ai/repo/policy.yaml | schéma v share/allow/policy.txt | projekce providerů |
| Profil | .ai/repo/profiles/*.yaml | glob adresáře | kontext, požadavky na dokončení, web |
| Doktrína | .ai/repo/rules/ a vendorované balíčky | průchod registrem | check, finish, doctor, watch, web |
| Validátor | mj_validate_* v lib/ | sken zdrojů, srovnaný s registrem | rozdělování doktrín |
| Tvrzení | docs/CLAIMS.yaml | průchod registrem | stránky záruk, docs/SITE_CLAIMS.md |
| Odpovědnost | docs/RESPONSIBILITIES.yaml | průchod registrem | web, registr doktríny |
| Dokument | docs/*.md + tabulky indexu | glob adresáře, index vynucen | trasy webu |
| Příkaz (nástroj shellu) | share/commands.yaml | ověřeno proti dispatch tabulce | dokumenty, stránky, ukázky, pokrytí, graf příkazů |
| Příkaz (spustitelný soubor) | deklarace clap, apps/majordomus-cli/src/cli.rs | průchod stromem | --help, docs/generated/cli.*, /docs/cli/**, graf příkazů |
| Pracovní postup | justfile a .just/*.just | just --dump --dump-format json | snapshot prostředí, banner, graf příkazů |
| Sémantika příkazů (efekt, interaktivita, požadavky, zdroje hodnot, aliasy kompatibility) | command_graph/semantics.rs, vedle deklarace | úplnost vynucená proti stromu clap v obou směrech | graf a každá jeho projekce |
| Kde je příkaz vystaven | command_graph/policy.rs | odvozeno z efektu a interaktivity | most workflow, doplňování, MCP, HTTP, Cockpit, reference |
| Typ události | share/events.yaml | průchod registrem | validace knihy, history, docs/SCHEMAS.md |
| Projekce | .ai/repo/policy.yaml projections[] | policy walk | update, doctor, watch |
| Poskytovatel kontextu | cíl: jedna tabulka v lib/context.sh | průchod registrem | sestavení, zahazování podle rozpočtu, JSON |
| Generovaný artefakt | generate::Target a Document, které každý cíl sestavuje | plán generování | docs/generated/**, share/allow/*.txt, bootstrapy poskytovatelů, site/data/registry/registry.json — a docs/generated/artifacts.{json,yaml,md}, generovaný index všech |
Řádky označené target jsou entity, které dnes nemají vlastníka. Každá je řádkem v knize s důkazem, proč ho potřebuje.
Odvozování, ne duplikace
Odvozený povrch se regeneruje od svého vlastníka a nikdy není autoritou. Tři zde generované povrchy jsou instrukční soubory poskytovatelů (majordomus update), data a obsah webu (scripts/generate-site-data) a docs/SITE_CLAIMS.md. Každý je otiskován nebo porovnáván s čerstvým generováním, takže ruční úprava je hlášené selhání, ne tichá divergence.
Dvě vlastnosti, které musí mít každý generátor:
- Transakční. Vykreslit do dočasného umístění, zvalidovat celý výsledek, pak nahradit. Generátor, který zkrátí svůj výstup a pak objeví chybu, zničil poslední dobrý stav.
majordomus updateto dělá; generátor webu vykresluje do dočasného adresáře a porovnává. - Úplná čerstvost. Kontrola, že generovaný artefakt je aktuální, musí iterovat to, co generátor vyprodukoval, ne seznam, který někdo napsal vedle. Zjištění
generated-json-inventoryv knize je tato vlastnost, která chybí.
Události
Trvalá změna stavu je událost. Kniha je jen k připisování a už je kanonickým záznamem; cíl je nad ní registrovaný slovník, ne druhý log.
share/events.yaml deklaruje každý název, který kniha přijímá, příkaz, který ho zapisuje, a klíče obsahu, které ten název vyžaduje. mj_ledger_append odmítne neregistrovaný název a takový, kterému chybí povinné pole; history --event odmítne filtrovat podle názvu, který není deklarován, místo aby odpověděl prázdným výsledkem, který by dal deklarovaný, ale chybějící název; history --validate hlásí uložený řádek, jehož název nezná žádný čtenář. Předtím překlep v názvu vytvořil trvalý záznam, který každý čtenář potichu ignoroval, a docs/SCHEMAS.md dokumentoval událost bootstrap, kterou nikdy nic nezapsalo.
Práci utvářela dvě omezení:
- Stávající názvy se ponechávají. Přejmenování rozbije každou existující knihu, včetně té vlastní tohoto repozitáře, a čtenář, který tiše vynechá vše před přejmenováním, je horší než nekonzistentní název. Slovník je registrován tak, jak byl napsán.
- Pořadí knihy je nosné.
mj_record_rankpoužívá pozici v knize k rozhodování shod rozlišení mezi záznamy vytvořenými ve stejné vteřině. Nic nesmí řádky přeuspořádat, přepsat ani rotovat, aniž by s tím počítalo.
Vykreslení je jediné místo, kde smí větev na událost stále existovat, protože to, jak se událost čte člověku, je prezentace, ne slovník. Existuje právě jedna taková větev, v lib/history.sh, a sesouhlasuje se s registrem místo odstranění: událost bez vykreslení i vykreslení události, která není deklarována, obojí selhává.
Jediná chybějící událost, kterou stále stojí za to přidat, je odmítnuté finish: přijaté dokončení se zaznamená a odmítnutí nezanechá stopu, takže úloha odmítnutá čtyřikrát vypadá přesně jako ta, která prošla napoprvé. test/cases/32_refusal_lifecycle.sh provede čtyři odmítnutí a nese tvrzení, které bude platit, až událost existuje.
Demonstrace jsou projekce, ne popisy
Stránka příkazu nese interaktivní demonstraci. Riziko v tom je zřejmé: stránka, která popisuje chování, se stává druhou implementací, a ta druhá není nikdy ta, která se dodává.
Takže ukázka se nepíše. test/fixtures/commands/<command>.json drží sadu scénářů — situaci, spuštěný příkaz, očekávaný návratový kód, řádky, které výstup musí obsahovat, a vysvětlení. test/cases/34_command_fixtures.sh provede každý z nich proti skutečné binárce v čerstvém repozitáři a generátor webu vloží tentýž soubor do stránky. To, co stránka ukazuje, je to, co test tvrdil.
Dva důsledky stojí za zmínku:
- Ve fixture žije žádný shell. Příprava je skutečný skript pod
test/fixtures/commands/setup/, složený načítáním (sourcing), který stránka zobrazuje doslova a případ jej načítá. Nic nespouští řetězec, který přišel z dat, což zachovává pravidlo protievala zároveň činí demonstraci poctivou. - Generátor odmítá, nedegraduje. Veřejný příkaz bez fixture, bez id demonstrace, bez behaviorálního případu nebo bez negativního případu zastaví sestavení. Neexistuje stav „testy brzy“ pro příkaz, o kterém projekt tvrdí, že ho podporuje.
Každé odmítnutí se vyvolá dřív, než se zapíše bajt výstupu. exit uvnitř skupiny { ... } | jq . > file zabije jen skupinu, zatímco přesměrování už cíl zkrátilo — odmítnutí zapsané tam proto hlásí špatný exit kód a nechá po sobě zničený soubor. Validace běží první; vysílající pipeline nemůže selhat na politice.
Stejné fixtures obsluhují druhý povrch. Stránka odpovědnosti ukazuje příkaz, který na ni působí, jak odmítá a přijímá, vykreslený z fixture toho příkazu — takže ukázka na /supervises/finish/ a ta na /commands/finish/ jsou tytéž scénáře, provedené týmž případem, a nemohou se rozcházet.
Uspořádání se vyplácí. Psaní fixtures našlo vadu, kterou nenašlo nic jiného: rev-parse HEAD v repozitáři bez commitů vypíše na stdout doslovný řetězec HEAD a potom selže, takže se k němu připojila záloha || printf 'NONE' a vzniklo pole identity s vloženým novým řádkem — trvale poškozený řádek v ledgeru jen pro připojování, pro každého, kdo spustil init a update před svým prvním commitem.
Kontrola odkazů musí jít oběma směry
Pravidlo, které prochází ze stránky k věcem, na které odkazuje, najde jen odkazy, které jsou špatné. Nenajde odkazy, které chybí, protože chybějící odkaz je z této strany z principu neviditelný — na stránce není co kontrolovat.
Vada, která to učinila konkrétním: šest tvrzení v docs/CLAIMS.yaml deklarovalo responsibility: plan, stránka /supervises/plan/ neodkazovala na žádné z nich a každá kontrola se dívala ven ze stránky. Procházka ze strany tvrzení — každé tvrzení, které jmenuje odpovědnost, se musí objevit na stránce této odpovědnosti — to najde okamžitě.
Relace se tedy kontroluje v obou směrech, nebo se nekontroluje vůbec:
- každé id, které stránka jmenuje, musí existovat, a
- každý záznam, který stránku jmenuje, musí z ní být odkazován.
Druhá je ta, kterou nikdo nepíše, a je to ta, která vadu zachytí.
Dnes existují dvě taková pravidla, why a supervises, každé zná jedno spojení. To je snesitelné. Třetí je bod, v němž by se měl sám vztah stát daty — dvě kolekce, pole držící id a tvar trasy — a jedno pravidlo by je mělo procházet. Napsat třetí ručně psanou verzi by byla stejná chyba jako mapa, z níž vznikla vada této sekce.
Mutace musí dokázat, že mutovala
Test, který něco rozbije a pak hledá selhání, je jen tak dobrý jako to rozbití. sed mlčí, když jeho vzor nic nenajde: skončí s 0, aniž by cokoli změnil, a případ pak tvrdí, že no-op nevyvolal selhání, což platí triviálně. Případ zůstane zelený a nic nedokazuje, a to od okamžiku, kdy kód, který mutoval, změnil tvar.
Každá sonda proto nejdřív ověří, že se uplatnila, a teprve pak, co způsobila:
sed 's/<pattern>/<replacement>/' "$SRC" > "$PROBE"
grep -q '<the replacement>' "$PROBE" || { echo " the probe did not take"; exit 1; }
Dvě zpřesnění, obě odnaučená v tomto repozitáři tvrdě:
- Absence potřebuje obrácené tvrzení.
sed '/x/d'nezanechá žádnou náhradu k nalezení, takže strážce ověřuje, že věc zmizela, ne že je tu něco nového. - Shodujte podle tvaru, ne podle obsahu. Sonda, která jmenuje dnešní seznam příkazů, přestane mutovat v den, kdy někdo příkaz přidá. To je táž vada jako validátor, který mlčky přestane běžet, a
35_future_commandji měl do hodiny po napsání — chytil ji jeho vlastní hlídač.
Mutace, která tiše přestane mutovat, a pravidlo, které se tiše přestane vynucovat, jsou táž chyba, a tento repozitář existuje právě kvůli té druhé.
Sebeřízení
Majordomus dohlíží na tento repozitář týmiž registry, které dodává. Neexistuje žádný inventář specifický pro sebe sama ani kódová cesta jen pro testy: doktríny, které tu běží, jsou pravidla pod .ai/repo/rules/ a zapojení, které doctor sjednocuje, je zapojení deklarované v .ai/repo/policy.yaml.
Vlastnost, kterou to má vytvářet, vyjádřená jako cíl: přidání příkazu, doktríny, providera, typu události, projekce nebo tvrzení vyžaduje definovat entitu, implementovat chování a přidat testy — a každý další povrch následuje, aniž by si správce musel pamatovat sekundární krok registrace. Kde to zatím neplatí, ledger to říká.
Jak přidat po jednom od každého
Záměrně krátké. Pokud seznam níže roste, architektura regresovala.
- Doktrína — napište pravidlo pod
.ai/repo/rules/s jehox-majordomus.validatora napište funkcimj_validate_<validator>, kterou jmenuje.doctorobě sesouhlasí a selže, pokud kterákoli chybí. - Tvrzení — přidejte položku do
docs/CLAIMS.yamla napištedocs/claims/<id>.md. Generátor webu odmítne sestavovat bez detailní stránky a zaručené tvrzení bez cesty k testu je chyba. - Dokument — napište
docs/<NAME>.mda přidejte jeho řádek do jedné ze dvou tabulek vdocs/README.md. Trasa, stránka a položka indexu následují. - Profil — přidejte
.ai/repo/profiles/<name>.yaml. Objevuje se podle glob vzoru a validuje hodoctor. - Příkaz — deklarujte jej tam, kde program, který jej implementuje, deklaruje své příkazy: větev clap a její příklad pro executable, položka v
share/commands.yamla větev dispatche pro shell nástroj, recept pro workflow. Anotujte jej vcommand_graph/semantics.rsjen tehdy, pokud není jen pro čtení a neinteraktivní. Následují most workflow, shell doplňování obou povrchů, strojové povrchy, které politika připouští, Cockpit a reference.docs/COMMANDS.mdje celé.
Přidání typu události, poskytovatele nebo projekce zatím není takto krátké. To jsou zbývající řádky priority 1 a 2 ledgeru. Přidání příkazu takto krátké bylo, když graf příkazů nahradil seznamy pro každý povrch: viz docs/COMMANDS.md a pravidlo project.commands-are-projections.