Jak se push do master stává veřejným, kolik smí stát, jak se to měří a co brání tomu, aby příští změna rychlost nenápadně vzala zpět.
GITHUB_PAGES_ARCHITECTURE.md říká, čím web je — kanonické zdroje, generátory, kontroly. Tento dokument je o cestě: latenci, rozpočtech a důkazech za oběma. CI.md je validační pipeline vedle něj. PAGES_STATUS.md nese aktuální čísla, generovaná; nic zde není naměřená hodnota, záměrně.
Cíl
Číslo, na kterém záleží, není, jak dlouho workflow běží. Je to
T_public = when the public site serves this commit − when the push was accepted
Cíl je T_public < 60 s, kdykoli limitujícím faktorem není vlastní plánování GitHubu, a rozlišení v té větě je nosné. Dvě poloviny:
controlled checkout, tool setup, build, check, the push to gh-pages
external the Actions queue, runner allocation, GitHub's own "pages build and deployment"
Jen to první je tohoto repozitáře k utracení. Obojí se měří; jen první je rozpočtováno a jen první může shodit běh. Nasazení, jehož řízená cesta trvala 30 s a jehož runner čekal 4 minuty ve frontě, zde není regrese, a souhrn jobu to říká těmito slovy, místo aby hlásil jedno číslo, které skrývá, která polovina se pohnula.
Tvar
flowchart LR push["push to master<br>(on paths that can<br>change the site)"] push --> validate["validate.yml<br>decides whether a<br>change may merge"] validate --> plan["plan"] plan --> structure["structure"] plan --> suite["suite"] plan --> rust["rust"] plan --> coverage["coverage"] plan --> bench["bench"] plan --> site["site<br>(build, check,<br>browser probe)"] plan --> macos["macos"] structure & suite & rust & coverage & bench & site & macos --> ci["ci<br>the status a branch<br>rule requires"] push --> pages["pages.yml<br>decides what the<br>public site shows"] pages --> rustdocjob["rustdoc job<br>the crate's reference"] rustdocjob -->|"needs, the tree"| job["deploy job"] job --> checkout["checkout"] --> setup["setup"] --> build["build"] --> check["check"] --> ghpages["push gh-pages"] --> measure["measure publication"]
Obojí běží vedle sebe na stejném commitu. Publikace bývala úlohou uvnitř validate.yml, která potřebovala plan, structure, suite, rust a site — sadu, která rozhoduje, zda se změna smí sloučit, z nichž většina nemůže změnit publikovaný bajt. Web tak šel na veřejnost desítky minut po pushi, který ho změnil.
Na cestě publikace je vše, co může učinit publikované bajty špatnými:
- commitnutá odvozená data jsou pro tento strom aktuální,
- reference rustdoc crate se vyrábí z tohoto commitu — je součástí publikovaných bajtů a nikdy se necommituje (
RUSTDOC.md), - web se staví z obou, s referencí složenou v
/rustdoc, - projde každá statická kontrola výstupu (
scripts/site-check: metadata a landmarky na každé trase, žádný nevykreslený oddělovač šablony, každý interní odkaz se vyřeší, žádný vzdálený asset, žádný přihlašovací údaj, každá dlaždice stránka, grafy propojené, poskytovaná provenience je tohoto commitu, každý složený povrch přítomen a sestaven z tohoto commitu).
Co ne, a proč je to bezpečné: behaviorální sada, kontroly cratu jiné než krok dokumentace, který vyrábí referenci, pokrytí, sada pro macOS, kontrola měření výkonu a sonda prohlížeče. Každá z nich hlídá repozitář, ne bajty, každá stále běží na témže commitu v validate.yml a každá stále brání mergi přes status ci. Přesunout kontrolu mimo publikační cestu je rozumné jen tehdy, dokud je stále někde; test/cases/97_pages_fast_path.sh selže, pokud se některá z nich objeví v pages.yml nebo zmizí z validate.yml.
Sonda v prohlížeči je zajímavý případ. Dokáže najít skutečnou vadu v publikovaných bajtech — route, která přetéká při 320 px — a stojí minuty. Zůstává v validate.yml, kde brání mergi, protože route, která přetéká, je vada k opravě, a ne deployment k zablokování: držet kvůli ní každou publikaci by stálo každý commit plnou sondu, aby se ochránilo před vzácným, který regreduje, a commit, který regreduje, se nepublikuje o nic rychleji tím, že se čekalo.
Co to zpomalovalo a tvar každé opravy
Každá níže uvedená hodnota je změřená a žádná není napsána tady. Aktuální hodnoty jsou ve sledované základní linii stroje, který je měřil, .ai/repo/benchmarks/pages/, zapsané pomocí scripts/pages benchmark --write-baseline; srovnání před a po každé změně je v commitu, který ji provedl, kam je ukládá project.performance-evidence. Kam se v rámci jedné generace ztrácí čas, ukazuje MJ_TIMING=1 scripts/generate-site-data, seřazené podle fází.
Generátor běžel třikrát pro jeden commit
site-build volal generate-site-data; první asserce site-check byla generate-site-data --check; a úloha site spustila generate-site-data --check před oběma. Tři úplné generace stejných dat pro jeden commit, každá z nich dominovaná majordomus usecase run, který spouští všech 31 scénářů případů užití, každý v jednorázovém repozitáři — testovací sada uvnitř generátoru dat, na kritické cestě nasazení.
Řešením není zrychlit generování; je to přestat klást stejnou otázku třikrát. site-build --no-data vykresluje commitnutá data; site-check --no-sync přeskakuje kontrolu synchronizace; job site si ponechává přesně jedno generování, průchod --check, což je jediný ze tří, který něco skutečně dokazuje (stagne čerstvé generování z kanonických zdrojů a porovná ho s tím, co je commitnuto).
Cesta publikace potřebuje stejnou odpověď a nemůže si dovolit ani jednu generaci, proto klade levnější otázku se stejným významem:
scripts/generate-site-data --fingerprint
Toto je vlastní source_hash generátoru — hash jeho vlastního kanonického vstupního seznamu o 467 položkách, v řádkovém formátu, z něhož se source.json vždy počítal — vyhodnocený v jednom hashovacím procesu místo 467, což je rozdíl mezi pětinou sekundy a lepší částí minuty. mj_inputs_hash je nyní jeho jedinou implementací, takže --fingerprint a source.json se nemohou rozcházet. scripts/pages current obě porovná: shoda znamená, že commitnutá projekce je pro tento strom aktuální a její vykreslení je vykreslením kanonických zdrojů.
Strom, jehož commitnutá odvozená data jsou zastaralá, se odmítne, nepřegeneruje se. Odvozená data jsou projekce; projekce, která nesouhlasí se svými zdroji, je vada k opravě u zdroje a publikační cesta, která by ji potichu zakryla, by publikovala něco, co žádná kontrola neviděla.
Odvozený obsah se commituje ze stejného důvodu
site/data/generated je jen polovina toho, co generátor vytváří. Druhá polovina je odvozený obsah — site/content/{docs,guarantees,commands,doctrines,plan,use-cases, applications,skills,registry,profiles,supervises,why,features}/ a projekce site/content-src/*.md — a Zola vykresluje ten, ne JSON. Proto je také commitnut. Čerstvý klon nemá jiný způsob, jak jej získat: site-build --no-data generátor nespouští, což je celý důvod, proč se build vejde do rozpočtu, a generování obsahu navíc by stálo asi dvojnásobek dvaceti sekund, které build smí trvat. Ponechaný ignorovaný by rychlá cesta nedokázala sestavit klon vůbec; první odkaz do sekce, která neexistuje, shodí celé vykreslení.
To, co drží commitnutou projekci poctivou, není její ignorování, ale táž kontrola, pod kterou žijí data. scripts/generate-site-data --check porovná každou odvozenou stránku s novou stagingovou generací a jmenuje každou osiřelou, takže upravená nebo zastaralá stránka se odmítne přesně jako zastaralá data, a scripts/site-check ověřuje, že sekce jsou commitnuté a neignorované, nikoli naopak. Nic pod site/content/ není na nikom, aby to upravoval ručně: soubor, ze kterého se promítá, je originál, a --check to řekne ve chvíli, kdy se projekce pohne.
site-check spustilo sedm tisíc procesů
Pět smyček přes 416 sestavených stránek, z nichž každá spouští grep, sed a awk na stránku: asi osmnáct procesů na trasu. scripts/lib/site-pages.awk nese všechna tato tvrzení v jednom procesu. Je to přepis, ne redesign — výstup je na tomto webu bajt po bajtu stejný a patnáct záměrně zmutovaných stránek (jedna na tvrzení plus kontrola) dává stejná zjištění ze staré implementace i z nové.
Výsledek
scripts/pages benchmark -n 5 # the distribution, as JSON
scripts/pages benchmark -n 5 --write-baseline # and into this platform's baseline
cat .ai/repo/benchmarks/pages/baseline.*.json
O řád mimo řízenou cestu build-a-kontrola a téměř všechno, co zbývá, je site-check. To je další věc, na kterou má smysl zaútočit, a je pojmenována pod Zbývající úzká místa, místo aby zůstala implicitní.
Model
Vše, co smí cesta stát, a vše, na co reaguje, je deklarováno jednou v .ai/repo/ci/pages.yaml. Ani workflow, ani skript nenese vlastní rozpočet, seznam cest ani název kontroly.
scripts/pages budget # the controlled budgets
scripts/pages budget --json # the same, for a reader that is not a person
scripts/pages paths # what a push must touch for the site to be able to changeZneplatnění spouštěčem
Cesty spouštěčů jsou odvozeny, nikdy psány dvakrát. Model jmenuje kontrolu, která buildí web (site-build); třídy gates.yaml, které tuto kontrolu jmenují, jsou třídy, jejichž cesty mohou web změnit; jejich pathspecy plus vlastní extra modelu jsou blokem paths: workflow.
Třída, která eskaluje celý plán (gates: full), záměrně není jednou z nich. Ta eskalace říká, kolik validace si změna pipeline zaslouží; neříká nic o tom, z čeho se odvozují publikované bajty. Změna scripts/ci-plan nebo justfile způsobí běh každé kontroly a nemůže pohnout jediným pixelem, takže nespouští žádné nasazení. Nulová práce je rychlejší než optimalizovaná zbytečná práce.
test/cases/97_pages_fast_path.sh porovná blok workflow s scripts/pages paths a selže při jakémkoli rozdílu, takže se nemohou rozejít. Když změníte třídu v gates.yaml, blok přegenerujte:
scripts/pages paths | sed 's/^/ - /' # paste into the paths: block of pages.ymlRozpočty
Rozpočty jsou jen řízená latence, v sekundách, za tepla, na linuxovém runneru hostovaném GitHubem. Stanovují se z měření — řádků časování, které zapisuje každé nasazení — a od změny jednoho se očekává, že odkáže na běh, který ji odůvodnil (project.performance-evidence). Studený běh platí za cache, které nemůže obnovit, a má vlastní, volnější mez. Cíl od začátku do konce se hlásí u každého nasazení a nevynucuje, protože jeho větší polovina patří GitHubu.
Dvě strukturní pravidla, která první sada rozpočtů porušila a která teď tvrdí test/cases/97_pages_fast_path.sh:
- Každý řádek, který běh měří, má rozpočet. Soubor časování dostane šest řádků —
checkout-setupz workflow,fingerprintabuildzscripts/pages build,checkalocal-checkzscripts/pages check,publishz workflow. Původní model jmenovalcheckoutasetupzvlášť a nepojmenoval nic profingerprintanilocal-check, takže tři ze šesti řádků každého souhrnu úlohy tiskly prázdnou buňku rozpočtu a přitom se sčítaly do celku. controlledlze dosáhnout sečtením jeho vlastních řádků. Bylo to 75, zatímco fáze dávaly dohromady 105, takže souhrn byl přísnější než části, z nichž se skládá: běh mohl být v rozpočtu každé jednotlivé fáze a přesto ho překročit, aniž by bylo na co ukázat.
A budget is a warning and a step bound is a kill, and the two are tied. A step of pages.yml
named after a warm row (build, check, publish), or a job named after one (rustdoc),
carries a timeout-minutes of at least budget.bound.percent of that row's budget, and
scripts/ci/step-bound-check refuses a smaller one. scripts/pages report names a row NEAR
when it spent more than budget.bound.near_percent of its step's bound, as a warning on the
run, before the run that crosses it. Until 2026-10-20 nothing tied them: the check step kept
the 3 minutes set while the site was English only and took 167-171 s against them; the Czech
copy doubled its pages, run 39392245576 was killed at 192 s, publish was skipped and the site
stayed old.
So a change that multiplies the pages — a language copy, a new family of generated pages, a
newly composed surface — re-derives the warm rows from a measured run of that change, in the
same pull request, and raises the step bounds with them. The rows are read per step from
gh api repos/korczis/prismatic-majordomus/actions/runs/<id>/jobs, as below; a row nobody
re-derived reads the old site, and its bound kills the new one.
The re-derivation of 2026-10-20
The Czech copy (ADR 0218) doubled what every phase reads. Six runs of pages.yml that passed
check with it, 39438897759 to 39485870985 (2026-10-20T14:24Z to 19:43Z); four of them
published, so publish and controlled are read from four.
| řádek | min | medián | p90 | max | rozpočet |
|---|---|---|---|---|---|
checkout-setup | 18 | 21 | 22 | 25 | 32 |
build | 200 | 270 | 294 | 314 | 400 |
check | 296 | 504 | 509 | 517 | 660 |
publish | 32 | 54 | 54 | 54 | 70 |
rustdoc | 172 | 179 | 181 | 183 | 230 |
| řízeno | 723 | 1011 | 1011 | 1057 | 1398 |
fingerprint and local-check are not shown apart from the steps that run them and keep 3 s
each. The rustdoc row is now measured (the job, checkout to upload, plus the download of its
artifact) and replaces the provisional 90. The cold bound keeps its allowances over the warm
total: 1398 + 105 + 150 = 1653. With budget.bound.percent at 150 the bounds follow: build
11 minutes, check 18, publish 3, the rustdoc job 20.
Not all of the growth is the corpus. Twice the pages explains twice the check, and it took
three times; it does not explain eighteen times the build (15 s on the English site, run
39228746039). The budgets describe what the path costs now, so that a breach means a change;
why the build costs this is the first entry under "Remaining bottlenecks" below.
Znovuodvození z 2026-09-12
The set before the one above, over the English site. The budgets written when this path was built were never changed after it, and by 2026-09-12
they were failing every deployment. They came from
.ai/repo/benchmarks/pages/baseline.macos-arm64.json — a local macOS arm64 laptop, five
samples, check median 9799 ms — over a site of 417 routes. The site check walks is now
1021 routes, 2.45x that baseline, and that directory's own README says a baseline of one
machine says nothing about another.
Náhrada je změřena na stroji, který posuzuje: pětadvacet běhů pages.yml, které dosáhly kroku publikace, 2026-09-10T22:56Z až 2026-09-12T06:38Z, čteno z gh api repos/korczis/prismatic-majordomus/actions/runs/<id>/jobs.
| řádek | min | medián | p90 | max | rozpočet |
|---|---|---|---|---|---|
checkout-setup | 7 | 10 | 12 | 15 | 18 |
fingerprint | 0 | 0 | 0 | 1 | 3 |
build | 4 | 4 | 5 | 6 | 8 |
check | 30 | 38 | 46 | 47 | 60 |
local-check | 0 | 0 | 0 | 1 | 3 |
publish | 13 | 20 | 24 | 24 | 28 |
rustdoc | — | — | — | — | 90 |
| řízeno | 60 | 75 | 84 | 86 | 210 |
Řádek rustdoc je úloha, která vyrábí referenci crate (ADR 86), od checkoutu po upload, a stažení jejího artefaktu v úloze nasazení. V okně výše není její žádný běh, takže její rozpočet je prozatímní: scripts/rust-check --doc naměřil 12, 6 a 6 s za tepla a 34 s za studena na Apple M5 Pro dne 2026-09-24 a 90 dovoluje tomuto kroku asi trojnásobek na runneru se čtyřmi vCPU plus asi 60 s na toolchain, cache, checkout a přenos artefaktu. .ai/repo/ci/pages.yaml to říká vedle čísla a časové řádky prvních dvaceti běhů je nahradí. Řízený rozpočet je stále součtem jeho řádků: 120 před referencí, 210 s ní.
Nic se nezhoršilo. Za týchž běhů se checkout-setup držel na 7–15 s a build na 4–6 s, takže se obě domény cache níže obnovují; a check stálo 41 ms na trasu při 896 trasách dne 2026-09-10 (37 s) proti 45 ms na trasu při 1022 trasách dne 2026-09-12 (46 s). Jeho růst je korpus, který prochází, ne vada v tom, jak ho prochází. Zastaralý byl rozpočet.
Studená mez se posunula s ním. V tom okně se neobjevil žádný studený běh, takže si ponechává příspěvek, který vyjadřoval starý pár — 180 proti teplým 75, tedy 105 s pro necachovaný npm ci a stažení Zola — nad novým teplým rozpočtem: 120 + 105 = 225. Job rustdoc přidává vlastní studenou cenu, stejně provizorní jako jeho teplý řádek — metadata každé závislosti se staví před dokumentováním crate, asi o 100 s víc na runneru a 150 se stažením toolchainu — takže studená mez je nyní 210 + 105 + 150 = 465.
Cache
Každá doména se zneplatňuje jen svými vlastními vstupy, takže změna dokumentace nevyřadí toolchain a změna lockfile nevyřadí web.
| cache | klíč | co zásah ušetří |
|---|---|---|
node_modules | runner, hlavní verze Node, package-lock.json | celé npm ci, nejen jeho stahování |
| vlastní cache npm | actions/setup-node | stahování, když první selže |
| Zola | taiki-e/install-action | stažení releasu |
Záměrně neexistuje cache generovaných dat webu ani vykresleného výstupu. Commitnutá projekce tu roli už hraje, je sledovaným souborem, ne cache, a je prokázána jako aktuální za 169 ms — cache před ní by byla druhým zdrojem pravdy koupeným za nic (project.cache-is-invisible, project.derived-once).
Chcete-li cache záměrně zneplatnit, změňte její klíčový vstup: zvyšte hlavní verzi Node nebo lockfile, nebo verzi Zola v .github/actions/setup-site/action.yml. Smazání každé cache musí vytvořit stejné publikované bajty; to není ambice, ale aserce — test/cases/97_pages_fast_path.sh porovnává build, který přeskočil generování, s tím, který ne, bajt po bajtu, a test/cases/51_derived_artifacts_committed.sh přegeneruje každý odvozený artefakt z jeho kanonických zdrojů a rozdíl odmítne.
Jak se měří publikace
Úspěch workflow není publikace. Každý build zapisuje site/static/build.json, servírované na /build.json, s názvem commitu, z něhož byl sestaven — fakt buildu, nikdy commitnutý soubor. Po pushi do gh-pages úloha dotazuje tuto URL s vyřazenou cache, dokud neuvede tento commit:
scripts/pages verify --commit "$(git rev-parse HEAD)"
To čekání je vlastní „pages build and deployment“ GitHubu, které větev poskytuje. Hlásí se jako externí latence; nic neshodí, protože pomalé poskytování GitHubu není selhání tohoto repozitáře.
Co web servíruje, jakmile servíruje tento commit, je jiná otázka, a ta se vynucuje. /build.json pojmenovává jen commit, ze kterého byl web sestaven, takže nasazení, které ztratilo odkaz na crate nebo neslo odkaz ze staršího buildu, by výše uvedené čekání prošlo. scripts/pages verify-rustdoc --commit "$(git rev-parse HEAD)" čte samotný veřejný odkaz — složený povrch zaznamenaný v /build.json a jeho vlastní /rustdoc/surface.json, úvodní stránku, index knihovny s jejími styly a skripty, stránku modulu, vyhledávací index a stránku konstanty COMMIT v crate — a nasazení selže, pokud kterákoli z nich chybí nebo pojmenovává jiný commit (RUSTDOC.md).
Souhrn úlohy každého nasazení proto nese fáze proti jejich rozpočtům, řízený součet proti jeho rozpočtu, frontu a sestavení Pages vedle nich a jeden řádek verdiktu:
Pages SLO: PASS — controlled path <n> s / <budget> s, push to public <n> s / <target> s
a když je řízená cesta v rámci svého rozpočtu a celek ne, shrnutí to řekne přesně těmito slovy, a nehlásí selhání: zbytek je fronta Actions a vlastní build Pages u GitHubu, který tento repozitář neutrácí, a to je vnější limit, a ne zde způsobená regrese.
Co způsobuje červený běh
Běh deploye je červený tehdy a jen tehdy, když selhalo nasazení. Do 2026-09-12 to neplatilo. scripts/pages report končí s 10 při překročení rozpočtu a workflow jej spouštěl jako běžný krok po pushi do gh-pages — takže se měření již hotové práce stalo verdiktem o této práci.
Mezi 2026-09-11T22:36Z a 2026-09-12T06:38Z hlásilo jedenáct běhů tohoto workflow z onoho kroku failure (90a875cf2, 8ce9d3157, e98bfd44a, 48a546ebb, 0136bd9eb, b144b2110, bbd5a9791, 94a018ded, 497d56d40, 180687c8f, 4a4ad6aac). Každý z nich úspěšně provedl push gh-pages a nejnovější byl ověřen od začátku do konce: živý web servíroval 4a4ad6aac. Ve stejném okně skutečně selhal při publikaci právě jeden běh — d5e27a500, jehož krok build selhal, krok publish byl přeskočen, a jeho krok s reportem proto prošel, protože nebylo téměř co sčítat.
Takže běh, který nepublikoval, byl ten, kde byla kontrola rozpočtu zelená, a nesl stejný závěr failure jako jedenáct těch, které publikovaly. Na úrovni, kterou kdokoli čte první — fajfka u commitu, gh run list, notifikace — byly oba totožné; rozeznala je jen anotace uvnitř běhu. A Markdown zprávy jde do $GITHUB_STEP_SUMMARY, a ne do logu, takže otevření logu ukázalo Process completed with exit code 10 bez čísla nad ním.
flowchart TD
P{"did the push to gh-pages happen?"}
P -- no --> R["run is RED<br/>::error Not published<br/>pages-live refuses on the next validation"]
P -- yes --> G{"did GitHub's own build of gh-pages error?"}
G -- yes --> R2["run is RED<br/>::error GitHub's Pages build errored"]
G -- "no, or not yet known" --> N["::notice Published<br/>run is GREEN"]
N --> B{"controlled path within budget?"}
B -- yes --> OK["Pages SLO: PASS in the job summary"]
B -- no --> W["run stays GREEN<br/>::warning naming the total and the phase that spent it<br/>OVER BUDGET in the summary, pages-slo in the log"]
Rozpočet se nikde jinde nezmírňuje. scripts/pages report stále končí exit kódem 10 při překročení, pro člověka i pro každou kontrolu, která ho volá; --advisory předává přesně jeden volající — krok po publikaci, který měří nasazení, jež už proběhlo — a test/cases/97_pages_fast_path.sh ověřuje, že to zůstane přesně jeden. Překročení stále při každém běhu vytvoří:
- anotace
::warningna běhu, nesoucí součet, rozpočet a fázi nad ním; - úplná tabulka fází a tučný verdikt OVER BUDGET v souhrnu jobu;
- řádek
pages-slo verdict=… controlled=… budget=… over=…v logu, odkud se četlo jedenáct červených běhů výše a kde se dříve nevypisovalo vůbec nic; verdict,controlled,budgetaoverjako výstupy kroku pro čtenáře, který není člověk.
A row near its step bound is reported the same way and never changes the verdict: a
::warning titled "Pages step near its bound", a NEAR line in the job summary, and near= in
the pages-slo line and the step's outputs.
Je to totéž rozdělení, jaké už dělá sonda publikace výše, a ze stejného důvodu: kontrola nasazení, které už proběhlo, je kontrola, která se obchází. To, co odmítne publikaci, která se nestala, je kontrola pages-live v .ai/repo/ci/gates.yaml při další validaci masteru.
Lokálně
Stejné příkazy, jaké spouští CI. Žádná sémantika buildu jen pro GitHub neexistuje.
scripts/pages current # is the committed derived data current for this tree?
scripts/rust-check --doc # the crate's reference, which the build composes at /rustdoc
scripts/pages build # render it
scripts/pages check # every static check over the output
scripts/pages benchmark -n 5 # the controlled path, as JSON
scripts/site-deploy # the full gate and the push, the way a person deploys
scripts/pages build odmítne strom, jehož odvozená data jsou zastaralá, a řekne vám oba hashe.
scripts/pages current také zaznamenává svůj verdikt spolu se stromem, který zkontroloval, do .ai/local/state/generation/check.json. Hook pre-commit ji spouští, takže po commitu je tento strom stromem HEAD a kontrola verification.docs preflightu čte záznam (docs/PREFLIGHT.md). Neprovedená (unstaged) úprava se zaznamená bez stromu, protože zkontrolované soubory nejsou stromem, který jmenuje index. Náprava je vždy stejná: scripts/generate-site-data (nebo just derive) a commitnout výsledek.
Nasazení ručně je stále nejrychlejší cesta
Web se servíruje z větve gh-pages a kdo tuto větev pushne, nasazuje (docs/claims/site-deploy-one-path.md). Člověk u terminálu tedy nemá před sebou žádnou frontu Actions:
scripts/site-deploy # gate, build, check, push
Proto se publikace nepřesunula na nativní tok artefaktu Pages (actions/configure-pages → upload-pages-artifact → deploy-pages), který by ušetřil sekundy, které GitHub tráví sestavením větve: nasměrování GitHub Pages na „GitHub Actions“ jako zdroj by učinilo běh workflow jediným způsobem publikace a postavilo by frontu Actions před jedinou cestu, která dnes žádnou nemá. Zdroj z větve stojí vlastní „pages build and deployment“ GitHubu a zachovává ruční nasazení. Co to stojí, je měřitelné, ne zapamatovatelné:
gh run list --workflow "pages-build-deployment" --limit 20 \
--json createdAt,updatedAt --jq '.[] | (.updatedAt | fromdate) - (.createdAt | fromdate)'
Pokud fronta přestane být dominantním členem a to se stane omezující podmínkou, vyplatí se kompromis znovu zvážit — s měřením, ne s preferencí.
Zbývající úzká místa podle očekávaného přínosu
buildandcheckwith the Czech copy: 270 and 504 s, median, of a 1011 s controlled path (2026-10-20).buildwas 15 s over the English site, so doubling the pages made it about eighteen times slower, which the corpus does not explain and a profile ofscripts/site-buildshould.checkwas 167-171 s and is now three times that. Before the Czech copy its remaining cost was the per-record loops that shell out tojqonce per claim, command, capability, use case and plan record; the same treatment that took the per-page loops from 25 s to under a second applies: onejqprogram per family instead of one process per record. Until those land, the pages the site adds are what movescontrolled, and the budget above is what will say so.- Fronta Actions. V tomto repozitáři, kde najednou pushuje několik worktrees, runy čekaly na runner minuty (
gh run list --json createdAt,startedAt). Nic z těchto souborů to nezkrátí. Eskalací, pokud je řízená cesta pohodlně v rozpočtu a frontu tvoří to, co držíT_publicpřes minutu, je zahřátý self-hosted runner označený jen pro tuto úlohu — vyhodnocený na základě měření, s uvedenými vlastními náklady na bezpečnost, izolaci a údržbu, a ne dříve. - Vlastní build Pages na GitHubu pro
gh-pages. Odstranitelné jen vzdáním se ruční deploy cesty, jak výše. npm cina studené cache. Omezeno cachenode_modules; studená cesta je to, co pokrývá studený rozpočet.
Změna čehokoli z toho
- Nový kanonický vstup generátoru webu patří do seznamu
INPUTSvscripts/generate-site-dataa nikam jinam; otisk, kontrola čerstvosti a spouštěč z toho vyplývají. - Nová cesta, která může změnit web, patří do třídy
gates.yaml, která jmenuje kontrolusite-build; pak znovu vygenerujte blokpaths:workflow, jak je uvedeno výše. - A new budget, or a changed one, goes into
.ai/repo/ci/pages.yamland cites the runs that justified it, in the file, with their numbers — the block there is the worked example. A budget derived on a laptop does not bound a runner, and one derived over a site of half the size does not bound this one.controlledmust stay at least the sum of its phases, and every row the run measures must have a key. A raised row raises the bound of the step that runs it in the same commit, to at leastbudget.bound.percentof the row, orscripts/ci/step-bound-checkrefuses it. - Nový časový řádek — volání
rownebotimedvscripts/pages, neboprintf 'pages\t…'v workflow — potřebuje v témže commitu klíč rozpočtu stejného názvu, jinak se sečte do celku a ukáže prázdnou buňku rozpočtu.test/cases/97_pages_fast_path.shto zamítne. - Nic změřeného po pushi do
gh-pagesse nesmí stát verdiktem běhu. Běh je červený pro selhané nasazení a pro nic jiného; nález latence je::warning, souhrn a strojově čitelný řádek. Veřejná reference na/rustdoc/, která chybí, je rozbitá nebo je sestavena z jiného commitu, je selhané nasazení, ne měření, a proto jescripts/pages verify-rustdoctvrdý krok (ADR 86), zatímcoscripts/pages verify, jehož čekání je čekáním GitHubu, zůstává reportem. - Nová kontrola nad publikovanými bajty patří do
scripts/site-check, který spouští cesta publikace. Nová kontrola nad repozitářem patří do modelu kontrol, který ji nespouští.