13. Každá webová plocha se zjišťuje od svého producenta, řeší se jednou a promítá
Kontext
Tento repozitář vystavuje přes HTTP každý týden víc a ví o tom na víc místech každý týden. Dnes jsou čtyři povrchy servírovány jedním procesem a jmenovány jednotlivě v jeho routeru — JSON index na /, dokument OpenAPI, Swagger UI na /docs, MCP na /mcp a nyní Cockpit na /cockpit, odbavovaný Cockpit::owns před větvemi match. Každý prefix je konstanta přesně na jednom místě, což je správně; ručně psané je odbavování a pátý povrch znamená pátou větev. Mezitím je publikovaný web úplně jiný svět: .ai/repo/ci/pages.yaml jmenuje jeden výstupní adresář, scripts/site-deploy ví, jak ho pushnout, .github/workflows/pages.yml nese cesty, které ho spouštějí, a nic v žádném ze světů nedokáže jmenovat povrchy toho druhého.
Přicházejí další dva — zpráva o testech a zpráva o měření výkonu — a ty rozhodují o tvaru. Přidány stejně jako poslední čtyři by každý potřeboval trasu v routeru, výstupní cestu ve skriptu, třídu cesty v modelu kontroly, krok kopírování v publikaci, řádek v tabulce dokumentace a zmínku v deployment workflow. Šest registrací pro jeden adresář HTML je definicí děravé abstrakce a šestý povrch by stál totéž znovu.
Odpovědí je vzor, který repozitář už uplatňuje na schopnosti, druhy, dovednosti, případy užití a rozhodnutí: jedna kanonická deklarace, každý jiný povrch její projekcí (ADR 0002, ADR 0004, ADR 0005). Webové povrchy jsou poslední částí tohoto repozitáře, která takto nefunguje.
Rozhodnutí
Webový povrch je vyhodnocený objekt s identitou, druhem, cestou připojení, producentem a původem každé hodnoty. Tři druhy pokrývají to, co tento repozitář má a očekává: StaticDirectory (generovaný strom připojený pod prefixem), NativeRoute (cesta, na kterou odpovídá sám proces) a Redirect. Nic se nejmenuje TestsSurface ani CockpitRoute: ty se liší daty, ne chováním.
Objevování stojí na konvenci, ze tří zdrojů odvozování, a žádný z nich není seznam, který by udržoval člověk:
- registr schopností pro vše, na co program odpovídá sám — trasy jsou už deklarovány jednou v
capability!a promítnuty do CLI, MCP, OpenAPI a reference, takže topologie čte tytéž deklarace; - generovaný webový kořen,
target/web/<id>/, pro statické zprávy: výrobce zapisuje svůj vlastní adresář a vedle artefaktusurface.json, který uvádí záměr, jejž nelze odvodit průchodem souborového systému — mount, který chce, zda má index, co ho sestavilo; - konfigurace webu pro aplikaci Zola, která vlastní
/a staví se do vlastního výstupu, ne do výstupu někoho jiného.
Explicitní deklarace existuje jen pro záměr, který nelze bezpečně odvodit, sídlí u producenta, který záměr má, a zapisuje ji ten producent, ne že by se kopírovala do centrálního manifestu. Rozřešený manifest se generuje pro diagnostiku a předání; je jednorázový a nic jej nečte jako pravdu, kterou nelze přepočítat.
Jedno řešení krmí každého konzumenta: směrovací tabulku serveru, statickou kompozici, kterou publikace nahrává, validátor, který běží v CI, příkazy web list a web explain a generovanou topologii dokumentace. Vlastnictví tras se validuje, ne ponechává pořadí vkládání: povrch vlastní svůj mount a vše pod ním, dva povrchy si nesmějí nárokovat jednu cestu a vnořený nárok je chyba, pokud ho vnější povrch nedeklaruje.
Zamítnuté alternativy
Centrální manifest vypisující každý povrch. Je to druhý registr, který repozitář už odmítá pro pravidla, druhy a schopnosti: správný v den, kdy je napsán, a špatný při prvním přejmenování, které nikdo nepropagoval. Deklarace u producenta drží fakt tam, kde se změna děje.
Držet oba světy odděleně — dynamické routy v routeru, statickou publikaci ve skriptech. Je to současný stav a je důvodem, proč statický report nelze obsluhovat lokálně a dynamická routa se nemůže objevit v publikované dokumentaci. Jeden model, který používají oba, stojí méně než dva, které je třeba udržovat v souladu z paměti.
Odvozovat vše, bez jakékoli deklarace. Cesta připojení je záměr: nic v adresáři s HTML neříká, zda patří na /tests nebo /reports/tests, a konvence, která ji odvodí z názvu adresáře, potichu přejmenuje povrch, když někdo adresář přejmenuje. Jedno deklarované pole, které napíše producent, je levnější než takové překvapení.
Samostatný proces brány před stávajícím serverem. Další proces k dohledu, další port ke koordinaci a další místo, kde může být trasa špatně. Program už odpovídá na HTTP a už drží registr; plochy, na které neodpovídá sám, jsou adresáře, které může připojit.
Důsledky
Router přestává jmenovat povrchy: ptá se vyřešené topologie a nový povrch se servíruje, protože byl objeven, ne proto, že byla přidána větev. To je změna kódu, který práce na Cockpitu aktivně píše, takže se dělá v jednom dohodnutém okně, ne postupně pod rukama jeho autora.
Producent statického reportu má nyní jedinou povinnost — zapsat svůj výstup do vlastního adresáře pod generovaným webovým kořenem s surface.json — a získá servírování, validaci, kompozici, publikaci i výpis, aniž by se čehokoli z toho dotkl. Povinnost je dost malá na to, aby ji šlo vyjádřit pravidlem, a dost mechanická na to, aby šla zkontrolovat.
target/web/ se stává vygenerovaným stavem se stejným postavením jako každý jiný odvozený strom: je reprodukovatelný, nikdy se nečte jako zdroj a zastaralý artefakt je validační zjištění, ne tiše publikovaná stránka.
Co nástroj nemůže rozhodnout, zůstává tam, kam patří: zda má povrch vůbec existovat a jaké má být jeho připojení, jsou rozhodnutí, která činí člověk a zaznamenává producent. Validátor kontroluje, že rozhodnutí je koherentní — ne že bylo moudré.