Přeskočit na obsah

Tato stránka byla automaticky přeložena z angličtiny, jediného jazyka, ve kterém se tento web píše. Číst originál.

Release

release: changelog složený ze záznamů o vydání vrstvy, rozhodnutí datovaných v okně každého release a konvenčních commitů v jeho rozsahu; verze psaná na jednom místě, její projekce pro shellový nástroj a jediný příkaz, který ji zapisuje; zvýšení, které commity implikují, a důkaz pro něj; a každý povrch odvozený z obojího

Vykresleno z docs/RELEASE.md — stejný Markdown, který ukazuje GitHub.

Co tento projekt dodal, co by dodal příště a changelog, který to říká, jsou jedna typovaná hodnota, release::Changelog, složená jedním modulem pod apps/majordomus-cli/src/release/. Každý povrch, který cokoli z toho uvádí, vykresluje tuto hodnotu: majordomus release v příkazové řádce, HTTP routa /api/v1/changelog, MCP nástroj majordomus_changelog a zdroj majordomus://changelog a generovaný dokument pod docs/generated/. Nic z toho se nepíše. V tomto repozitáři není CHANGELOG.md a nemá být. Chování tak, jak je implementováno a testováno; kde se tento dokument a spustitelný soubor rozcházejí, je chybný dokument a mění se ve stejném commitu.

Příkazy, jejich argumenty a spustitelné příklady jsou ve vygenerované referenci (generated/cli.md, pod majordomus release); schopnosti, jejich routy a jejich případy měření výkonu v generated/capabilities.md, modul release. Nic z toho se zde neopakuje.

Verze se měří, netvrdí

O zvýšení verze při release se rozhoduje porovnáním veřejného povrchu posledního release s tímto stromem, ne slovy v commit zprávách. feat: s významem minor a fix: s významem patch popisuje, čemu autor věřil; neříká nic o tom, co volající stále může volat. Mezi v0.3.1 a 0.4.0 příkaz majordomus scope classify opustil příkazovou řádku a každá kontrola zůstala zelená, protože na povrch se nedívala žádná kontrola.

Plochou je registr schopností — jediná deklarace, jejímiž projekcemi jsou MCP, HTTP, OpenAPI a příkazová řádka — a docs/generated/registry.json se commituje při každém commitu a každém tagu, takže lze porovnat dva releasy, aniž by se sestavil jeden z nich. Co může volající držet, je identita veřejné schopnosti, její druh, nástroj a zdroj MCP, na které odpovídá, metoda a cesta HTTP, k nimž je svázána, cesta v příkazové řádce, která ji vyvolá, a obě její schémata.

something a caller held is gone    major implied   a caller who held it is broken
a contract narrowed under a caller major implied   a required field, a removed value, a type
something arrived                  minor implied   nothing broke, something is new
a contract widened                 minor implied   every existing caller still works
the surface is equal               nothing owed    most commits are here

Schémata se porovnávají jako kontrakty, v obou směrech, což je porovnání, které textový diff dvou schémat zkazí. Vstup je to, co volající posílá, takže přijímat víc je kompatibilní a žádat víc je breaking; výstup je to, co volající dostává, takže slibovat víc je kompatibilní a slibovat méně je breaking. Tatáž úprava — x se stane povinným — je minor ve výstupu a major ve vstupu. Popisy, tituly a příklady se normalizují pryč, než se cokoli porovná, takže přeformulování doc komentáře není release; změněné omezení, pro které komparátor nemá pravidlo, se počítá jako breaking, ne hádá, protože falešný major je spor, který lze vyhrát, a falešný patch je volající, který to zjistí tím, že mu to přestane fungovat.

majordomus release analyze                     # the tree against the last release
majordomus release analyze --explain           # every movement, and why each counts
majordomus release analyze --format json       # the canonical VersionPlan every surface renders
scripts/ci/version-matches-surface             # the gate: the same thing, mapping exit codes
majordomus release bump                        # raise the one authored version to the measured minimum

Existuje jeden engine a žije v apps/majordomus-cli/src/release/compat.rs, čte release/surface.rs. Kontrola je nad ním adaptér; stejně tak panel Cockpitu, HTTP trasa, nástroj MCP i zapisovatel. Do ADR 0051 existovaly dvě odpovědi — shellové porovnání v CI a odhad z předmětu commitu uvnitř release bump — a odhad vyhrával, protože zapisovatel běží dřív než kontrola.

Pod 1.0.0 je spodní mez minor release pro jakoukoli změnu povrchu, zaniklou či novou. Semantic versioning uděluje 0.y.z plošnou výjimku — cokoli se smí změnit — a Elm tuto výjimku odmítá tím, že každý balíček začíná na 1.0.0. Tento projekt si nezasloužil ani jednu odpověď, a proto bere nejsilnější signál, který 0.x má, místo aby kvůli jedinému odstranění požadoval 1.0.0. Až major dosáhne 1, posun končí a implikovaný bump je tím požadovaným.

Odstranění se jmenuje, ať je verdikt jakýkoli. Pod 1.0.0 neodmítne release a stále se uvádí jako zpětně nekompatibilní změna: volající, který držel to, co zmizelo, by se to jinak dozvěděl tím, že se mu to rozbije. Pravidlem je project.the-version-is-measured@2; kontrolou je version-surface, vybraná třídami cest rust, rust-generated a distribution, takže běží dřív, než změna přistane, a ne jen v plném plánu.

Kde je odstranění zapsáno

Ani jeden ze dvou dokumentů, které by ji držely, není autorský, takže odpověď není „napište ji do jednoho z nich“.

Záznam releasu ho dnes nemůže obsahovat vůbec. release/v1 (share/schemas/majordomus/release/release.v1.schema.json) je additionalProperties: false a nedeklaruje žádné pole pro zásadní změnu a záznam zapisuje workflow releasu z artefaktů, které publikovalo, a nikdo jiný (.ai/repo/releases/README.md) — takže nevydaná verze nemá záznam, do kterého by se cokoli dalo zapsat. Nejmenší pole, které by to uzavřelo, je návrh, ne změna provedená tady: změna schématu nepatří do změny dokumentace.

Changelog to dokáže, díky jedinému autorskému vstupu, který má. Nic v něm se nepíše ručně a jediný autorský text, který čte o změně, je zpráva commitu. release/commits.rs označí změnu jako zásadní podle type(scope)!: nebo trailer BREAKING CHANGE: v těle a release/changelog.rs takovou změnu vykreslí s úvodním **BREAKING**. Odstranění je tedy pojmenováno v předmětu commitu uvnitř vydání, které je přináší — v předmětu, protože to je text, který čtenář changelogu vidí; trailer jej jen označí a nevykresluje se.

Z compose_published plynou dva důsledky a oba jsou zamýšlené:

  • majordomus release changelog to říká okamžitě, pod ## Unreleased;
  • docs/generated/changelog.* ne, a nebude, dokud verze nevyjde. Potvrzený artefakt nese jen publikované release, protože soubor uvnitř commitu nemůže popsat commit, v němž je. Když pipeline zapíše záznam pro tuto verzi, změny oddílu jsou commity v <previous>..<this> — mezi nimi ten označený — a publikovaný changelog odstranění uvede, aniž by ho tam kdokoli psal.

Důsledek, který stojí za pojmenování, je, že značka musí být na commitu uvnitř release a nelze ji přidat k commitu, který už přistál. Odstranění zaznamenané až zpětně se proto vyjadřuje pozdějším commitem ve stejném okně, který říká, co odešlo. Právě to potřeboval v0.3.1..0.4.0: atom command scope classify opustil povrch v 427b73248d9, jehož předmět nic neznačil, a pojmenovává jej commit, který nese tuto sekci.

Mezera, kterou uzavírá

Každý jiný veřejný fakt v tomto repozitáři má jednu kanonickou deklaraci a sadu z ní odvozených projekcí — příkaz, schopnost, schéma, stránku. Release ne, a selhal dvěma různými způsoby.

Changelog neexistoval. To, co se ve verzi změnilo, žilo v poznámkách k release na GitHubu, mimo repozitář, který je vytvořil, psané tím, kdo release vydal, a poté nikým nečtené. Fakta, která by obsahoval, už byla ve stromě, nepřečtená:

  .ai/repo/releases/*.yaml   what shipped, when, from which commit   (kind release-record)
  .ai/repo/adrs/*.md         the decisions, dated                    (kind adr)
  git log <commit>..<commit> everything that has no object of its own

Nic verzi nezvyšovalo. Byla uvedena ve dvou souborech — apps/majordomus-cli/Cargo.toml a bin/majordomus (MJ_VERSION) — a scripts/release-version --check je porovnala a skončila s 10, když se lišily. Kontrola bez zapisovatele za sebou ověřuje lidskou paměť: umí říct, že se oba liší, a říct to může teprve poté, co někdo upravil jeden z nich a zapomněl na druhý. (Verze mezitím ztratila své druhé uvedení úplně; viz Verze.)

Žádná z mezer nepotřebovala nový zdroj pravdy. Changelog potřeboval spojení; verze potřebovala zapisovatele.

Z čeho se sekce skládá

Jedna sekce na záznam vydání, nejnovější první podle data, které záznam nese, s nevydanou prací vpředu:

flowchart LR
  record["release record"] --> meta["version, tag, date,<br>commit, artifacts"]
  adr["adr objects"] --> decisions["the decisions dated inside<br>this release's window"]
  log["git log A..B"] --> changes["the changes, as<br>conventional commits"]
  refs["issue · milestone"] --> resolved["the records those commits name,<br>resolved against the layer"]
  meta & decisions & changes & resolved --> section["one changelog section"]

Okno releasu je tentýž interval řečený ve dvou slovnících, které mají jeho dva zdroje. ADR nese datum a žádný commit; commit nese datum, které vrstva neindexuje. Rozhodnutími releasu jsou tedy ta datovaná v (previous release's date, this release's date] a jeho změnami commity v previous..this. Rozsah prvního releasu je vše až po jeho commit — klon, který tuto historii nenese, nedá nic a řekne to, místo aby ukázal prázdnou sekci.

Data se porovnávají jako řetězce, jimiž jsou: oba zdroje zapisují ISO-8601, kde lexikografické pořadí odpovídá chronologickému. published_at záznamu nese čas a date ADR ne, takže se porovnává podle dne, který mají společný.

Artefakty sekce jsou vlastním důkazem záznamu — cíl, jméno souboru a SHA-256, které scripts/release-record přečetlo ze souboru, který byl publikován. Changelog je kopíruje; nepočítá je a nesahá do sítě.

Nevydaná sekce

Vše po nejnovějším záznamu, jako <last release's commit>..HEAD. Otevírá dokument, když má nějaké změny, a chybí, když žádné nemá, protože prázdný nadpis „Unreleased“ neříká nic, co by čtenář mohl využít. Repozitář, který nikdy nepublikoval, nemá žádné záznamy a celá jeho historie je jedna nevydaná sekce — správná odpověď pro projekt před prvním release, ne chyba.

Conventional commits, čtené celé

Parsování je malé a záměrně úplné:

  feat(commands): one canonical command graph
  ^^^^ ^^^^^^^^   ^^^^^^^^^^^^^^^^^^^^^^^^^^^
  kind scope      subject

  feat(api)!: the route moved      `!` marks it breaking
  BREAKING CHANGE: <why>           so does this trailer, in the body

Předmět, který se nepodaří naparsovat, je druhu Other a přesto se objeví, se subjektem zachovaným celým, ne rozděleným na dvojtečce, která byla součástí věty. To je jediné návrhové rozhodnutí v parseru, o kterém stojí za to se přít, a jde opačně než většina generátorů changelogu: changelog, který mlčky zahazuje, co nedokáže klasifikovat, lže vynecháním a zahozené commity jsou přesně ty, kterým nikdo nevěnoval pozornost, když přistály. project.conventional-commits je od verze 2 blokující a hook commit-msg odmítne předmět, který se nenaparsuje, takže nové commity přicházejí konvenční. Historie stále drží 54 takových, které předcházejí politice — zaznamenaných v .ai/repo/commit-policy-baseline.txt, všechny publikované, žádný nepřepsatelný — a pro ně je parser shovívavý. Viz COMMIT.md.

Kindy a nadpisy, pod kterými se vykreslují, v pořadí, které čtenář changelogu chce — co je nové, co opraveno, co zrychleno, pak zbytek:

  feat      Added            docs           Documentation
  fix       Fixed            test           Tests
  perf      Performance      ci             Pipeline
  refactor  Changed          chore          Housekeeping
                             anything else  Other

Nadpis bez položek chybí; nic se neskrývá. Merge commity se nečtou vůbec (git log --no-merges), protože předmět merge popisuje integraci a jeho obsah už je v rozsahu.

Pořadí je v dokumentu, ne v každém rendereru

Sekce nenese plochý seznam změn. Nese groups — jednu na druh, který má vůbec nějakou změnu, každou s jejím kind, heading, pod kterým se zobrazuje, rank, podle kterého se řadí, a vlastním changes — a skupiny jsou při skládání dokumentu už v pořadí podle ranku:

{ "groups": [
  { "kind": "feat", "heading": "Added",         "rank": 0, "changes": ["…"] },
  { "kind": "fix",  "heading": "Fixed",         "rank": 1, "changes": ["…"] },
  { "kind": "docs", "heading": "Documentation", "rank": 4, "changes": ["…"] }
] }

To pořadí dřív existovalo na jediném místě, které projekce nemohla zasáhnout: ChangeKind::rank(), Rustovou funkcí, kterou volal renderer Markdownu. Web Rustovou funkci volat nemůže, a tak to, co dostal, seskupil abecedně, a tentýž changelog se četl ve dvou různých pořadích podle toho, který povrch ho ukázal. Nyní je pořadí v hodnotě. Každý renderer čte heading a rank, místo aby cokoli z toho rozhodoval — site/templates/changelog.html neseskupuje ani neřadí a renderer Markdownu prochází groups v pořadí, v jakém je dostal. Pořadí prezentace uvedené jednou a přenášené je jediný druh, který přežije projekci.

Pořadí patří samotným druhům a není souvislé: sekce bez refaktorů prostě nemá skupinu s pořadím 3. Absence skupiny neznamená nic jiného, než že se v rozsahu nestalo nic toho druhu, nikdy že se něco skrylo.

Kde lze fakt přečíst

Každá adresa, kterou changelog nese, je odvozena z about::REPOSITORY — vlastního repository manifestu crate, které kompilátor předává jako CARGO_PKG_REPOSITORY. Žádná z nich není literál, takže fork nebo přesun ponese každý odkaz s sebou a nikomu se nemusí říkat, kde projekt nyní žije:

flowchart LR
  section["section"] --> notes["notes_url<br>the published release, from<br>the record's own notes_url"]
  section --> compare["compare_url<br>the range against<br>the previous tag"]
  section --> tree["tree_url<br>the tree at that tag"]
  change["change"] --> curl["url<br>the commit"]
  decision["decision"] --> durl["url<br>the ADR file that<br>states the decision"]

forge() v changelog.rs rozpozná jediný tvar — https://github.com/<owner>/<repo> — a pro cokoli jiného nevrátí nic. Když nevrátí nic, nevzniknou žádné odkazy místo odkazů odhadnutých ze vzoru, který by mohl platit. Špatný odkaz nelze od správného odlišit, dokud se nenásleduje, takže čtenář bez odkazu ví, že žádný nemá, a čtenář s věrohodným špatným ne.

Dvě z adres sekcí nejsou prostě adresou tagu. Záznam, který jmenuje vlastní notes_url, si jej ponechá, protože publikovaný release je tam, kde záznam říká; záložní je stránka release u tagu. První release nemá předchůdce, se kterým by se porovnával, takže jeho compare_url je seznam commitů forge až po tag, ne rozsah. Nevydaná sekce porovnává poslední tag s master a ukazuje na tamní strom.

Decision.route bylo odstraněno ze stejného důvodu. Jmenovalo /decisions/<id>/ a nic takovou stránku nepublikuje: poctivým cílem rozhodnutí je soubor ADR v repozitáři, který nyní nese url. Vykreslení do Markdownu si zachovává prostý tvar, který mělo vždy — adresy jsou v hodnotě pro plochy, které je umějí využít, a stránka webu vykresluje každou z nich.

Dokument jmenuje svého vlastního producenta

produced_by je schopnost, která odpověděla — její id, příkazová řádka, která ji vykreslí, HTTP routa, nástroj MCP a zdroj MCP — vyčtená ze záznamu registru jedinou funkcí, capability::builtin::release::produced_by, kterou volá jak handler obsluhující dokument, tak generátor, který ho commituje. Commitnutý docs/generated/changelog.json a živá odpověď tak pojmenovávají tytéž povrchy a čtenář, který má jeden z nich, najde ostatní. Stránka, která odkazuje na tutéž hodnotu jinde, tato jména vyhodnocuje proti datovým sadám, které registr generuje (executable.json, cli.json), místo aby routu psala ručně: přesně to dělá stránka changelogu na webu, protože tvrzení registry a cli v site-check odmítají šablonu, která pojmenovává schopnost nebo routu příkazu ručně — a při prvním návrhu té stránky to také udělaly.

Co commit jmenuje, vyřešeno

Předmět a tělo commitu se prohledávají společně na id, která plán tohoto repozitáře používá — I a čtyři číslice pro úkol, M a tři pro milník — a každé nalezené id se pak vyhledá ve vrstvě. Id, které odpovídá tvaru a nejmenuje žádný objekt, se zahodí. Nese se reference s druhem, id a titulkem ze záznamu, na nějž se vyřešila, takže položka se čte jako úkol, k němuž patří, ne jako kód, který umí rozvinout jen jeho autor.

Polovina o rozlišení činí odvozování bezpečným. Samotné porovnávání vzorů by přidalo odkaz ke každému řetězci, který vypadá jako id, i k těm, kterým nic neodpovídá, a právě tomu se výše uvedené odkazy vyhýbají. Skenování čte kromě předmětu i tělo, protože commit, který se vysvětluje ve svém těle, je ten, který se nejvíc vyplatí propojit, a slovo, které pouze začíná tímto písmenem (Interesting, M1), id není.

Počty toho, co z toho plyne, zastarávají; změřte je místo toho:

  majordomus release changelog --format json \
    | jq '[.sections[].groups[].changes[].references[]?] | length'

Verze stromu a ta, kterou může čtenář získat

Jsou to dvě skutečnosti a web potřebuje obě. Verze stromu běží před nejnovějším releasem záměrně: politika je měřena podle plochy, takže práce, která nehýbe žádnou veřejnou plochou, nedluží bump a strom stojí tam, kam ho poslední bump nechal. 2026-09-15 strom deklaroval 0.7.0, zatímco nejnovější získatelný release byl v0.6.0 — správně v obou ohledech, a 55 commitů od sebe.

Nesprávné bylo, že odznak v navigační liště vykresloval project.version, číslo stromu, vedle instalačního příkazu. Návštěvník ho četl jako co dostanu a nemohl to dostat. Odznak se přitom právě změnil tak, aby se vykresloval při každé šířce místo od sm: výše, takže špatné číslo bylo učiněno nápadnějším a ta změna byla zaznamenána jako oprava.

Nic to nezachytilo a důvod stojí za zachování: scripts/ci/version-matches-surface se ptá, zda deklarovaná verze pokrývá to, co povrch dluží — deklarované versus dlužné. Nikdo se nezeptal zobrazené versus dodané. Obě otázky se týkají verze; položena byla jen jedna.

Web tedy nese release, který nabízí, odděleně od version stromu, a odznak vykresluje ten, ošetřeně: repozitář bez vydání neukáže žádný odznak, místo aby sáhl po verzi stromu, protože tento fallback je ta vada. Vlastní verze stromu zůstává viditelná každému, kdo audituje build, v /build.json.

Který release web nabízí, se zprvu četlo z nejnovějšího záznamu pod .ai/repo/releases/. To odpovídalo na zobrazeno versus dodáno pro strom, ne pro hodiny po release: záznam se dostane na master pull requestem, který release otevře po publikaci, a 2026-10-12 nabízela domovská stránka od 10:23Z v0.20.0, když /releases/latest.json už pojmenovával v0.21.0, dokud tento pull request neprošel vlastní validací. Nabídka se nyní odvozuje jednou, pomocí scripts/site-build, jako novější z nejnovějšího záznamu a releases/latest.json, který gh-pages už servíruje, do build.json jako release — každá stránka ji čte odtud (offered v base.html) a site/data/generated/project.json nenese žádný release vůbec. scripts/site-check odmítne sestavení, jehož domovská stránka, /build.json a servírované latest.json se rozcházejí, a strom, jehož dvě commitnuté projekce nejnovějšího release se rozcházejí; scripts/pages release-live drží živou domovskou stránku, latest.json a nejnovější release GitHubu u jedné verze, jakmile deploy.release_window prošlo. Pravidlo je project.the-site-offers-the-release-it-published; test/cases/1087 rozbíjí každý povrch.

scripts/site-check odmítne publikovaný strom, jehož nabízená verze nemá dokument release, takže to zastaví publikaci, místo aby to objevil čtenář. Je to omezeno na to, co web nabízí, ne na každý řetězec verze, který tiskne: první návrh prošel každou stránku a odmítl čtyři — v0.1.0, v0.2.0 a v0.4.0 z changelogu, kde pojmenování staré verze je prací té stránky. Každý z těch nálezů byl pravdivý a žádný nebyl tím, co kontrola tvrdila. test/cases/352 to drží, včetně stavu, který onen návrh nedokázal vyjádřit — repozitář bez jediného release nesmí být odmítnut.

Verze: napsána jednou, zapsána jedním příkazem

Verze se píše přesně na jednom místě, [package] version souboru apps/majordomus-cli/Cargo.toml, a každý jiný její výrok je odvozen (ADR 0085, project.release-is-a-projection v2):

  apps/majordomus-cli/Cargo.toml   authored    the one place; `release bump` writes it
  crate::VERSION                   compiled    everything the executable prints
  share/version.txt                projected   `majordomus generate`; bin/majordomus reads it
  apps/majordomus-cli/Cargo.lock   derived     cargo's record; the writer keeps it in step
  generator stamps, the changelog  generated   `majordomus generate`
  .ai/repo/releases/*.yaml         records     the release pipeline, after publication

Shellový nástroj nemůže číst manifest tam, kde je nainstalován — nainstalovaný strom nemá Cargo.toml — a proto čte share/version.txt, typovaný generovaný artefakt, který se dodává v každém archivu vedle něj; DISTRIBUTION.md pokrývá stranu distribuce. Na každý jazyk připadá jeden čtenář autority: release::version::declared v Rustu a scripts/release-version v shellu.

majordomus release version odpovídá, co manifest deklaruje, zda je projekce, kterou nástroj vypisuje, aktuální a zda někdo ručně zapsal verzi tam, kde žijí soubory samotného nástroje, nebo napsal označení verze do prózy. Končí s exit 10, když je cokoli špatně — stejný exit kód, který dává scripts/release-version --check — a je to to, co spouští kontrola version-authored-once. Nálezy pocházejí z release::version::diagnose a release analyze nese tytéž ve svém plánu:

diagnostikazávažnostkdy
version-stated-by-handchybapřiřazení jménu končícímu na version (shell, TOML, JavaScript nebo Python), člen version: nebo "version":, nebo majordomus X.Y.Z, zapsané ručně v bin/, lib/, scripts/ nebo share/ mimo generovaný artefakt
projection-stalevarováníshare/version.txt je pozadu za manifestem nebo chybí — stav, který každé zvýšení verze zanechá až do scripts/derive, odmítán generate --check
writers-disagreechybashare/version.txt je před manifestem nebo s ním nesouvisí — verze, kterou nezapisuje žádné odvození
version-label-in-prosechybaručně psaná próza jmenuje štítek verze — v, major a minor, bez patche, jako ten, který stránka omezení nesla dlouho poté, co se verze posunula — mimo dokumenty, o kterých .ai/repo/version-label-history.txt deklaruje, že jsou o minulé verzi (project.prose-states-no-version)
version-label-history-invalidchybazáznam historie neuvádí důvod, jmenuje cestu, která není sledovanou ručně psanou prózou, nebo jmenuje dokument, který už nenese žádný štítek

Jaká je příští verze, odpovídá release version s kontraktem. Její next je required_version téže analýzy, kterou release analyze dělá proti poslednímu release (ADR 0051): deklarovaná verze, když už kontrakt splňuje, jinak nejmenší, kterou kontrakt dovoluje, a decided_by: contract to říká. Obě poloviny měří od jednoho základu — nejvyšší verze, kterou vrstva zaznamenává — takže kontrakt a důkazy z commitů popisují totéž okno. Navýšení, které by znamenaly conventional commity od onoho release, se nese vedle jako důkaz — bump a commits_imply, verze, kterou by daly z posledního release, a ne z už deklarované verze, takže verze zvýšená pro okno se týmiž commity nikdy nezvýší znovu. Textová forma vypisuje next kontraktu a zvlášť commits imply <bump> (evidence; the contract decides).

Odpověď kontraktu je minimum, a když se změnilo jen chování za veřejnou hranicí, je tím minimem už vydaný release. next pak není nikdy poslední release: nesou-li commity cokoli, je to nejmenší release nad ním, patch, označený decided_by: contract_and_commits — ať subjekty tvrdí cokoli — a nenesou-li nic, next chybí a nic by se nevydalo. Nelze-li kontrakt změřit — žádný publikovaný release nenese registr, verze není sémantická verze a analýza by mohla jen hádat, nebo jakákoli jiná chybová diagnostika činí plán nespolehlivým (baseline, jejíž záznam a tag jmenují různé commity, ručně uvedená verze) — nerozhoduje nic: neměřitelná baseline se odmítne, nehádá se. next chybí, decided_by: undecided, důvod je v contract_unreadable a inference z commitů zůstává tam, kde vždy je, jako důkaz vedle, nikdy odpověď místo kontraktu. Cestou ven je explicitní cíl, release bump --level nebo --exact.

next z release version a verze, kterou release bump zapíše, když není pojmenován cíl, jsou jedna odpověď, daná jednou: obě ji berou z release::version::select — analýza a z ní rozhodnutý report — a zapisovatel zvedá přesně na next reportu (release::version::default_target). Plán je v obou správný pro jeden test: jakákoli chybová diagnostika (VersionPlan::has_errors) nechá report undecided s každou chybovou zprávou a je to plán, z něhož release bump odmítá zapisovat. Nemohou se rozcházet, protože nezbývá nic, o čem by kterákoli rozhodovala sama.

Kontrakt měří povrch registru schopností, jehož projekcí je každé rozhraní, takže behaviorální breaking change, kterou registr nevidí, potřebuje výslovně vyšší cíl: release bump --level major (nebo minor pod 1.0), pojmenovaný člověkem, který ví, co se změnilo.

Verze je sémantická verze: tři čísla, volitelná pre-release část a volitelná build metadata (1.0.0-alpha.1+build.5), řazená podle přednosti sémantické verze — 0.29.0 < 1.0.0-alpha.1 < 1.0.0-alpha.2 < 1.0.0-alpha.10 < 1.0.0-beta < 1.0.0, build metadata se ignorují. Pre-release je vždy jen explicitní cíl (release bump --exact 1.0.0-alpha.1); žádný se neodvozuje. Zvýšení pre-release přistane na finální verzi, k níž vede (1.0.0-alpha.1 zvýšená o major, minor či patch je 1.0.0), a pre-release spodní meze ji dosáhne (Version::reaches): se spodní mezí 1.0.0 se přijímá 1.0.0-alpha.2, takže řada alfa může pokračovat, zatímco nic nejde pod už deklarovanou verzi. Poslední release, od něhož plán měří, je nejvyšší zaznamenaná verze, včetně pre-release; stabilní ukazatel (docs/DISTRIBUTION.md) jí nikdy není.

Zastaralá projekce je záměrně varování, nikoli chyba: zapisovatel, který by odmítl vlastní neodvozený stav, by nemohl opravit navýšení, které právě provedl.

majordomus release bump je zapisovatel a nic nepočítá. Čte tentýž VersionPlan, který čtou release analyze, trasa HTTP, nástroj MCP i kontrola, a aplikuje jej:

flowchart LR
  plan["plan"] --> validate["validate"] --> apply["apply(plan)"]

Bez argumentu se verze stane next reportu: změřeným minimem — základní linií zvýšenou o to, co kontrakt vyžaduje, nebo deklarovanou verzí, když už ho splňuje — a patchem nad posledním releasem, když kontrakt nad ním nic nevyžaduje a commity nesou změny. Když kontrakt nelze změřit, není žádné next a bump bez argumentu se odmítne s důvodem reportu a exit 12, nic nezapíše; pojmenovaný cíl se zapíše stále, protože tak se vydává první release. --level major|minor|patch a --exact 1.2.3 jmenují vyšší verzi, než je tato, pro případy, kdy maintainer myslí víc, než kolik kontrakt udělal — hlášeno s původem, ne tiše. Ani jeden nesmí jmenovat nižší:

  required minor, --level patch    REFUSED, nothing written
  required minor, --exact 0.4.0    REFUSED, a version does not go down
  required minor, --level major    allowed; "required 0.6.0, selected 1.0.0, explicit override"

Přepsání, které by mohlo podstřelit, by nebylo přepsáním — byla by to díra, která z měření dělá dekoraci. --dry-run vypíše, co by se změnilo, a nic nezapíše.

Na zápisu záleží dvě vlastnosti:

  • Je bajtově úzký. Přepíše se jen řádek version uvnitř [package] manifestu a řádek version vlastního záznamu majordomus-cli v lock souboru — ten jeden řádek, který by přepsal cargo, držený v souladu, protože lock, který zůstane pozadu, shodí každý build --locked, včetně launcheru, který používají git hooky a server MCP. Závislost připnutá na stejné verzi zůstává nedotčena v obou souborech. share/version.txt a každé razítko generátoru nejsou věcí zapisovače: scripts/derive je staví a pak promítá a bump to říká.
  • Kontroluje vlastní práci. Po zápisu bump znovu přečte oba soubory a nesoulad ohlásí exitem 10 — takže bump, který se aplikoval jen napůl, zachytí to, co k tomu bylo postaveno, a ne release o tři commity později.

Nulová major verze není v aritmetice ošetřena zvláštním případem — Version::raised je aritmetika a nic jiného. Zda breaking change pod 1.0 stojí major, nebo minor, je otázka politiky a tento repozitář na ni odpovídá právě na jednom místě: compat::Policy, nesoucím id schématu majordomus/version-policy/v1 a režim určený samotnou základní verzí. Druhá odpověď v aritmetice je přesně ten tvar, který tento podsystém vznikl odstranit.

Verze, kterou crate deklaruje, je také pole current changelogu, takže provedené zvýšení a nepřegenerovaný changelog si odporují a generate --check to říká.

Který povrch co nese

příkazová řádkagenerateHTTP · MCPCockpit
changelogmajordomus release [changelog [VERSION]]docs/generated/changelog.{json,yaml,md}GET /api/v1/changelog · majordomus_changelog · majordomus://changelogpřes registr
report verzemajordomus release version—GET /api/v1/release/version · majordomus_release_versionpřes registr
analýza kompatibilitymajordomus release analyze—GET /api/v1/release/analysis · majordomus_release_analysispřes registr
zvyšování verzemajordomus release bump—zadrženozadrženo

Ten poslední řádek nic nekonfiguruje. release bump zapisuje sledované soubory, které command_graph/semantics.rs anotuje jako RepositoryMutation, a politika vystavení z ADR 0027 drží změny repozitáře mimo každý strojový povrch. Žádná schopnost jej nedeklaruje a majordomus commands explain executable.release.bump vypíše důvod, proč jej každý povrch zadržuje. Čtecí polovina jsou dvě schopnosti a příkazový řádek je vykresluje tak, že je provede, místo aby volal kód pod nimi, takže se terminál a API nemohou rozejít.

Žádná z čtecích schopností nedeklaruje expozici cli a je to záměr: majordomus release changelog je lokální příkaz, který schopnost vykresluje člověku v terminálu, a cli::LOCAL to říká jednou — RendersCapability("release.changelog") — vedle důvodu, proč je lokální. Schopnost, která by deklarovala tutéž cestu, by byl tentýž příkaz vyúčtovaný dvakrát, což tests/quality.rs odmítá jako OPERATION_CLASSIFICATION_CONFLICT. Tu jedinou deklaraci čte produced_by níže, takže dokument může jmenovat svou příkazovou řádku, aniž by ji crate uvedl podruhé.

Generovaný dokument je generovaný artefakt jako každý jiný — jedna hodnota zapsaná jako JSON pro program, YAML vedle a Markdown pro čtenáře, každá deklarující své schéma (majordomus/changelog/v1) a zdroj. To je smysl jeho generování: generate --check, spouštěné scripts/rust-check, si všimne, že changelog přestal popisovat strom, takže si to nikdo nemusí pamatovat.

Co nešlo přečíst

Changelog říká, co nemohl přečíst, místo aby to skrýval. Každé takové zjištění se objeví v diagnostics a v Markdownu pod závěrečným nadpisem „Co nešlo přečíst“:

zjištěnípříčina
záznam release nejmenuje žádný commitzáznam je malformovaný; jeho sekci nelze složit a vynechá se
pro verzi nebylo možné přečíst žádný commitklon tu historii nenese — mělký klon nebo přepsaný rozsah

git log, který selže, tu není chyba: nevrátí žádné commity a volající mezeru ohlásí. Díky tomu se changelog vůbec vykreslí v mělkém checkoutu v CI, kde by odmítnutí učinilo dokument nedostupným právě tam, kde ho čte stroj.

Vydávání

Samotný postup release — tag, pipeline, matice buildů, záznam napsaný z toho, co bylo publikováno, a obnova po špatném release — je DISTRIBUTION.md. Tento dokument vlastní jen verzi a changelog, který produkuje. Prvním krokem onoho postupu je majordomus release bump následované scripts/derive a po něm stále běží scripts/release-version --check: dokazuje, že shellový nástroj vypíše verzi, kterou deklaruje manifest, což generate --check — dokazující jen to, že soubor projekce je aktuální — nedokazuje.

Kdo spouští zapisovatele

Do 2026-09-15 nikdo. Měření fungovalo, kontrola fungovala, zapisovatel fungoval a test/cases/112 to všechno provedl proti skutečné fixture — a hledání v repozitáři našlo release bump jen uvnitř chybových zpráv radících člověku. Plocha se tedy mohla pohnout, kontrola by správně odmítla další pull request, který si toho náhodou všiml, a mezitím strom nesl verzi, kterou jeho vlastní kontrakt označoval za příliš nízkou, dokud se nikdo nepodíval.

Vlastní hlavička test/cases/112 jmenuje starší podobu téže vady: „zapisovač běží, když člověk zvýší verzi, a kontrola běžela potom, pokud vůbec.“ ADR 0051 přiměl kontrolu, aby souhlasila se zapisovačem. Nikdo pak nezařídil, aby zapisovač běžel — úplný, otestovaný, funkční mechanismus bez startéru, což je v CI nerozlišitelné od zapojeného, protože zelená fajfka na 112 říká může zvýšit verzi, nikdy někdo ji zvýší.

.github/workflows/version.yml je ten startér. Ptá se při každém pushi, který může pohnout povrchem, a podle hodin — běh zrušený nebo neúspěšný z nesouvisejícího důvodu nesmí otázku mlčky přeskočit — spustí release analyze a když se požadovaná verze liší od deklarované, spustí release bump, odvodí a otevře pull request. Nevznese druhý návrh pro verzi, která už navržena byla.

Netaguje a nevydává release. release.yml se spouští na tag v* a vytvoření toho tagu je rozhodnutí; automatizace, která by jej vytvořila, by udělala z rozhodnutí vedlejší efekt, podle plánu, bez toho, že by to kdokoli zvolil. test/cases/353 tvrdí přesně to a aserce je mutace, ne popis: varianta workflow s připojeným git tag je odmítnuta a jmenuje tag.

Od 2026-10-16 má toto rozhodnutí vlastníka a kadenci: role release pravidla project.the-cooperation-model-is-the-owners-word (ADR 0167) vydá release vždy, když master drží přistanou práci za posledním tagem, ne jednou na milník, a ověří, co web po něm servíruje. Pravidlo říká, který claim v meshi činí z relace tuto roli; tag stále čeká na scripts/ci/release-verdict.

Co to dokazuje

apps/majordomus-cli/src/release/commits.rsparser: konvenční tvary, oba zápisy breaking, neznámý typ malými písmeny a předmět, který není konvenční, zachovaný celý; a rozlišení: id, které vrstva drží, se stane referencí nesoucí název toho záznamu, id téhož tvaru, které nejmenuje nic, ne, jméno zmíněné dvakrát se nese jednou a slovo, které jen začíná tím písmenem, není id
apps/majordomus-cli/src/release/version.rskontrakt rozhoduje next a neměřitelný nerozhoduje nic — next chybí, undecided, s důvodem; next reportu a výchozí cíl zapisovače jsou jeden výběr; kontrakt, který žádný release nevyžaduje, nikdy neodpoví posledním release; odvození z commitů zvedá poslední release, ne deklarovanou verzi; zvýšení je totální funkce změn, zvyšování nuluje to, co nahrazuje, verze, která není sémantická, se odmítá, pre-release se řadí, zvyšuje a dosahuje podlahy tak, jak říká sémantické verzování, zápis se dotkne jen řádku manifestu a vlastní položky zámku, projekce projde obousměrně generovaným souborem, zastaralá projekce varuje a taková, kterou nic neodvozuje, se odmítá, a ručně zapsaná verze se najde tam, kde generovaná ne
apps/majordomus-cli/src/release/changelog.rsrozhodnutí patří k release, jehož okno obsahuje jeho datum, nevydané okno se otevírá po posledním release, časové razítko a datum se porovnávají na společném dni a skupiny dorazí k rendereru v pořadí podle rangu bez ohledu na to, v jakém pořadí commity přišly
test/cases/103_release_projection.shcelý povrch proti skutečnému spustitelnému souboru, v jednorázovém repozitáři se skutečnou historií: které commity spadají do kterého rozsahu, které rozhodnutí patří do kterého okna, záznam přidaný bez úpravy čehokoli jiného, předmět, který nenásleduje žádnou konvenci, převzatý místo zahozeného, fixture, jejíž repozitář není forge, který nástroj zná, nevytvářející odkaz místo uhodnutého, commit pojmenovávající jedno id, které vrstva drží, a jedno, které ne, a každý výše uvedený návratový kód
test/cases/743_release_version_answers_with_the_contract.shna fixture, jejíž commity říkají BREAKING a jejíž povrch jen rostl, je next příkazu release version požadovanou verzí příkazu release analyze, rozhodnutou smlouvou, přičemž major z commitů je uveden jako důkaz, a release bump --dry-run zvýší na stejnou 0.11.0; bez publikované základny je next nepřítomno, decided_by: undecided s důvodem, bump bez cíle se odmítne s exit 12 a pojmenovaný se přesto zapíše, manifest zvýšen na 0.11.0; verze zapsaná ručně v lib/ činí plán nespolehlivým, takže next je nepřítomno, decided_by: undecided se zprávou version-stated-by-hand, a release bump --dry-run odmítne s exit 12 a toutéž zprávou; verze již zvýšená na požadavek se nezvyšuje znovu; manifest bez řádku verze se nevydává za odpověď smlouvy a bump bez cíle jej odmítne; po publikovaném release s jediným fix: od té doby a nezměněným povrchem je next patch nad ním, nikoli samotný release, a release bump --dry-run zvýší na stejnou 0.11.1; a při deklarované verzi nad minimem odvození z commitů stále zvýší poslední release
test/cases/484_the_version_is_authored_once.shshellový nástroj neuvádí verzi; vypisuje autoritu, protože čte vlastní projekci, generovanou z manifestu a nikdy z MAJORDOMUS_SHARE; ručně upravenou projekci odmítne generate --check; zapisovatel zapisuje řádek manifestu a vlastní záznam zámku a nic jiného; verze zapsaná ručně v lib/ se odmítne — každá sekce nejdřív ukázána jako selhávající proti stromu před ADR 0085
scripts/ci/release-checkmanifest, který zapisovatel jmenuje, je ten, který čte release skript, shellový nástroj vypisuje, co deklaruje, při každém plánu, a ne jen při publikaci, zámek ho zaznamenává, neobjevil se žádný ručně vedený changelog, strom není pozadu za nejnovějším release, který vrstva eviduje, a každý publikovaný tag má záznam — nebo, nanejvýš na den, větev release/record-<tag> navrhující jej (DISTRIBUTION.md říká, proč je tag první); dokud tato větev neexistuje, tag, jehož běh release čeká nebo běží, nebo který nemá ještě ani běh, ani GitHub release v rámci téhož dne, je pending se jmenovaným během — zjišťováno dotazem na forge pomocí gh a tam, kde se forge nelze zeptat (žádný gh, žádný token, žádný známý repozitář GitHub), se tag neomlouvá
test/cases/3021_a_tag_does_not_freeze_the_queue.shhlava, která nemění žádný soubor release, projde, dokud je záznam tagu navržen, a tag jmenuje jako pending; vlastní větev záznamu projde jako zaznamenaná; větev záznamu bez záznamu, návrh po uplynutí odkladu a žádný návrh vůbec se odmítají
test/cases/3029_a_tag_in_flight_is_pending.shdokud neexistuje větev záznamu, je tag, jehož release běh je ve frontě nebo běží — nebo který nemá vypsaný běh a ještě žádný GitHub release, v rámci odkladu — čekající se jmenovaným během; ukončený běh, publikovaný GitHub release, forge, který nelze číst, a neznámý repozitář nechají nález stát, každý s poznámkou
kontrola version-authored-oncebin/majordomus-cli release version: projekce je aktuální a žádná verze se nepíše ručně tam, kde žijí soubory nástroje
scripts/rust-check (kontrola rust-check)generate --check: potvrzený changelog a share/version.txt stále popisují strom
scripts/release-version --checkshellový nástroj vypíše verzi, kterou deklaruje manifest — otázka, kterou generate --check nechává otevřenou

Behaviorální případ staví vlastní repozitář a nečte tento, protože každá otázka, kterou klade, je otázka o historii. Fixture se skutečnou historií je jediné, proti čemu lze tyto odpovědi ověřit.

  • ADR 0029 — rozhodnutí za tímto dokumentem a čtyři alternativy, které zamítlo
  • DISTRIBUTION.md — jak je nástroj balen, publikován a instalován, a pipeline release, která píše záznamy, jež toto čte
  • COMMANDS.md — model efektů a politika vystavení, které zadržují release bump
  • CAPABILITIES.md — registr, ve kterém jsou deklarovány obě čtecí schopnosti
  • DYNAMICITY.md — pravidlo vlastnictví, jehož je toto instancí

Prohledává názvy a popisy všech stránek, které tento web publikuje.