5. Jeden plán projekce nad kanonickými vstupy, pojmenovaní vlastníci každé pravdy a web jako pohled na registr
Rozšiřuje ADR 2 a ADR 4.
Kontext
ADR 2 a ADR 4 učinily z každého rozhraní Rust spustitelného souboru projekci jednoho registru schopností a sladily vygenerované dokumenty pod docs/generated/ s generate --check. Dvě projekce, se kterými se každý pracovník setká dřív než s čímkoli jiným, zůstávaly mimo tento pipeline. Bootstrapy providerů, AGENTS.md, CLAUDE.md, GEMINI.md, vykresloval jen update shellového nástroje a posuzovalo je jen porovnání razítek shellového doctor; CI je s politikou neporovnávalo, takže pravidlo ručně napsané do AGENTS.md by prošlo merge. Web neměl na registr žádný pohled a stránka, která by jej ukazovala, by musela ručně udržovat druhý seznam operací a druhů. Operátor chtěl jednu zkompilovanou řídicí rovinu: kanonické vstupy, deterministický registr a každý soubor providera, dokument a stránku jako jednosměrnou projekci z něj, přičemž ruční úprava jakékoli projekce shodí sestavení místo pouhého varování.
Rozhodnutí
- Vlastníci jsou pojmenováni a existují dvě kanonické domény, ne jeden adresář. Distribuce nástroje vlastní gramatiku:
share/kinds.yamlříká, co je druh a jak se čte,share/schemas/*.schema.jsončemu musí vyhovovat jeho metadata,share/providers/*.tmpljak se čte bootstrap poskytovatele. Repozitář vlastní věty:.ai/repo/**drží jeho pravidla, politiku, profily, prompty, ADR, tvrzení a znalosti a může přidávat vlastní druhy, schémata a šablony, aniž by předefinoval ty distribuční..ai/local/**je stav checkoutu, nikdy zdroj. V dozorovaném repozitáři žádný adresář.majordomus/není: bootstrapy už říkají, že pokud existuje, je instalací nástroje a ne kontextem, a invarianta, že tentýž commit a tatáž verze Majordoma dávají tentýž registr, řadí gramatiku k verzi, ne ke commitu. Repozitář nástroj připíná; gramatiku nevendoruje. - Politika a šablony jsou kanonické vstupy projekcí poskytovatelů.
apps/majordomus-cli/src/policy.rsčte.ai/repo/policy.yamla profily stejným čtenářem podmnožiny, jaký používá shellový nástroj;providers.rsvykresluje každý cílprojections[]ze své šablony, cíl v režimu souboru opatří razítkem (hash politiky, hash obsahu) a cíl v režimu regionu vsadí mezi jeho značky, přičemž hostitelský dokument nechá být. Bajty jsou bajty shellového nástroje, včetně razítka, takže se oba shodují v obou směrech (test/cases/93_rust_provider_projections.sh). Deklarace, kterou nelze vytvořit, se odmítne s důvodem a nic se nezapíše: cíl mimo repozitář, poskytovatel bez šablony, token, který politika neumí vyplnit, cílalways_loadedpřes rozpočet. - Jeden plán sestavuje každý artefakt.
generate::plan(app, targets)je jedna funkce zamajordomus generateagenerate --check: dokument OpenAPI, reference a stránky modulů, manifest registru a matice měření výkonu z registru; allow-listy ze schémat; bootstrapy poskytovatelů z politiky a šablon; dataset registru webu z registru a indexu. Nic jiného artefakty nesestavuje a--checkporovná celý plán bajt po bajtu a skončí s 10 s uvedením každého zastaralého souboru. CI ho spouští ve workflow validate i ve workflow Pages, takže ani merge, ani nasazení nepokračuje ze zastaralé projekce. - Web vykresluje registr; nepopisuje ho.
site/data/registry/ registry.json(majordomus-site-registry/v1) nese otisk a počty registru, každou vestavěnou schopnost s jejími vystaveními, každý modul, otisk indexu a každý objekt vrstvy bez jeho obsahu, každý druh s jeho schématem a deklarované projekce poskytovatelů./registry/je rozloží. Žádná časová razítka, žádné absolutní cesty, žádný stav gitu, takže tentýž strom dá stejné bajty. Adresář patří jen spustitelnému souboru v Rustu; shell generátor webu vlastnísite/data/generated/celé a soubor, který nezapsal, hlásí jako sirotka, takže žádný adresář nemá dva zapisovatele. - Generování teče jedním směrem. Nic nečte bootstrap, generovaný dokument, datovou sadu webu ani výpis MCP zpět do
.ai/. Projekce se znovu generuje nebo odmítne, nikdy nesluče proti proudu. - Kanonická změna dosáhne přesně té projekce, které se týká. Pravidlo přidané do vrstvy pohne otiskem indexu, otiskem registru a datasetem webu a bootstrapy nechá být; změna politiky pohne bootstrapy a dokument OpenAPI nechá být.
tests/projections.rssleduje obě mutace přesplan, vidí, jakcheckpokaždé jmenuje ten jeden zastaralý artefakt, regeneruje a vidí, že druhý plán vytvoří stejné bajty.
Matice vlastnictví
| pravda | kanonický vlastník | projekce (nikdy se neupravují) |
|---|---|---|
| co druh je, jak se čte, jeho schéma | share/kinds.yaml, share/schemas/ (přírůstky repozitáře pod .ai/repo/knowledge/) | share/allow/*.txt, index, tabulka druhů na webu |
| pravidla repozitáře, politika, profily, prompty, ADR, tvrzení, znalosti | .ai/repo/** | každý zdroj MCP, majordomus context, bootstrapy, web |
| jak se čte bootstrap poskytovatele | share/providers/*.tmpl (přepsání repozitářem pod .ai/repo/providers/) s .ai/repo/policy.yaml | AGENTS.md, CLAUDE.md, GEMINI.md, ... |
| každá spustitelná operace, její schémata, vystavení, politika cache a měření výkonu | deklarace capability! a module! pod apps/majordomus-cli/src/capability/builtin/ (ADR 4) | Nástroje MCP, trasy HTTP, OpenAPI, Swagger UI, příkazy capabilities, docs/generated/*, cíle měření výkonu, tabulka schopností webu |
| identita registru pro tento strom | otisk, který registr vypočítá | klíč cache executoru, site/data/registry/registry.json |
| tvrzení, plán, plán rozvoje, dokumenty zobrazené na webu | docs/CLAIMS.yaml, docs/claims/*.md, docs/*.md, záznamy plánu | site/data/generated/*.json až scripts/generate-site-data |
Shimy, s kritérii jejich odstranění
majordomus update(shell) zůstává interaktivním zapisovatelem s--dry-run,--diffa--forcea shellovédoctorstále porovnává cíl s jeho razítkem. Oba vykreslují stejné bajty jakogenerate providers; případ 93 selže ve chvíli, kdy se rozejdou. Odchází, až příkazy životního cyklu shellového nástroje přejdou do spustitelného souboru adoctorbude volatgenerate --checkmísto vlastního porovnávání razítek.scripts/generate-site-data(shell) stále odvozuje data webu o tvrzeních, plánu, plánu rozvoje a dokumentech ze souborů, které je vlastní. Odchází sekci po sekci, jak se index učí tyto druhy; dataset registru je první sekce, která shell verzi nikdy neměla.- Blokové tokeny (
{{PROFILE_TABLE}},{{FINISH_CONTRACT}},{{REQUIRED_SECTIONS}}) stále vykresluje shellupdatea spustitelný soubor je odmítá jménem. Žádná dodávaná šablona je nepoužívá; ze shell nástroje se odstraní, až zmizí jehoupdate.
Zamítnuté alternativy
- Adresář
.majordomus/v každém dohlíženém repozitáři nesoucí gramatiku. Padesát repozitářů by neslo padesát kopií čtyřiceti schémat, které by se rozcházely, a druhy distribuce by už nebyly druhy distribuce. Manifest už existuje jako.ai/manifest.yaml, v YAML; změna jeho formátu kvůli diagramu je zbytečný chaos. - Dataset webu uvnitř
site/data/generated/. Shellový generátor celý adresář nahrazuje a hlásí osiřelé soubory; dva zapisovatelé v jednom adresáři jsou drift, který toto rozhodnutí odstraňuje. - Číst
docs/generated/registry.jsonz webu místo druhého datasetu. Zola čte data jen z adresáře webu a ten manifest záměrně nenese otisk ani deklarativní objekt, protože se musí měnit, když se změní kód, a ne když se přidá dokument; web potřebuje obojí. Souhrny builtin se objevují v obou souborech, odvozené týmž kódem z téhož registru: dvě projekce, jeden zdroj. - Vyplnění nenastaveného tokenu prázdným řetězcem, jak to dělá shellový nástroj. Bootstrap, který čte „the checkpoint interval it sets is ``“, je vada politiky; spustitelný soubor ji odmítne se jménem tokenu.
- Odmítnutí přepsat ručně upravený bootstrap, jako to dělá
updatebez--force.generateje lék kontroly a přepíše projekci; úprava je vidět v diffu a v odmítnutí--check, kde má být posuzována.
Důsledky
Úprava pravidla, tvrzení nebo ADR nyní mění dataset registru webu a změna se musí znovu vygenerovat a commitnout s ní, přesně jak už docs/generated/ vyžadovalo od změny schopnosti; just generate dělá obojí a CI jmenuje zastaralý soubor. Bootstrap se nikdy neupravuje: upravuje se politika nebo šablona a spouští se majordomus generate providers. Web má jednu stránku, která se nemůže rozejít se spustitelným souborem.
Dodatek, 2026-09-05: fáze, hranice a web jako plný pohled
Výše uvedené rozhodnutí dalo webu jednu stránku registru. Dokončení vyvolalo dvě otázky, které toto ADR nechalo otevřené, a odpovědi jsou zapsány zde, nikoli v novém ADR, protože jde o totéž rozhodnutí dotažené do konce, ne o další.
Kde se oba generátory potkávají. Generátor webu (scripts/generate-site-data) nečte Rust a spustitelný soubor nezná Zolu. Hranicí jsou data, která spustitelný soubor už zapisuje: docs/generated/registry.json, projekce závislá jen na kódu, dává generátoru webu id, moduly a zdrojové soubory, ze kterých dělá trasy a odkazy (site/data/generated/executable.json, zástupné soubory obsahu pod site/content/registry/); site/data/registry/registry.json, dataset zavedený tímto ADR, nyní nese vše, co stránka o spustitelném souboru říká — každý deskriptor celý, moduly, příkazovou řádku tak, jak ji deklaruje clap, nástroje a zdroje MCP, trasy HTTP, cíle měření, pokrytí, politiku a přijaté baseline — a šablony to čtou a nic neopakují. Projekce použitá jako rozhraní mezi fázemi je stále projekce: generate --check odmítne manifest, který už neříká to, co deklarace, a manifest je mezi vstupy webu, takže hash webu se hýbe s kódem.
Pořadí a proč jsou tři fáze. Dataset vypisuje každý objekt vrstvy a dokumenty, které odvozuje generátor webu (docs/SITE_CLAIMS.md, docs/PLAN_STATUS.md), jsou objekty vrstvy. Proto scripts/derive spouští majordomus generate (projekce jen z kódu), pak generátor webu a pak znovu majordomus generate pro projekce, které čtou index. Žádná fáze nečte svůj vlastní výstup, výstupy fáze C čte jen build webu a druhý běh na čistém stromu nic nezmění. scripts/derive-check je jediná kontrola jen pro čtení nad oběma generátory; volají ji CI a workflow Pages.
Co z toho vlastní web. Trasy, pravidlo pro slug (. jako -, sdílené s kotvami reference API), seskupení tvrzení podle cesty jejich implementace a narativní stránky (README crate, docs/MCP.md), které promítá. Nic jiného: id schopnosti napsané do šablony, generátoru nebo nav.toml je odmítnuto kontrolou scripts/site-check.
Commit opustil odvozená data. source.json nesl commit pro patičku, což učinilo generátor webu neidempotentním na čistém stromu a vyňalo jeden soubor z pravidla, jemuž podléhá každý jiný odvozený soubor. Commit je fakt buildu: scripts/site-build zapisuje build.json, obsluhováno na /build.json, a nic commitnutého nejmenuje commit, do něhož přistává.