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.

Pull requesty se integrují po jednom, každý proti master pozorovanému chvíli předtím

Pull requesty se integrují po jednom, každý proti master pozorovanému chvíli předtím

majordomus://adr/adr-0101navrženo integracegitgithubgovernancebezpečnost

odkazuje na

  • put_in_force apps/majordomus-cli/src/capability/builtin/integration.rs (soubor)
  • put_in_force apps/majordomus-cli/src/commands/prs.rs (soubor)
  • put_in_force apps/majordomus-cli/src/integration/classify.rs (soubor)
  • put_in_force apps/majordomus-cli/src/integration/drain.rs (soubor)
  • put_in_force apps/majordomus-cli/src/integration/exclusive.rs (soubor)
  • put_in_force apps/majordomus-cli/src/integration/forge.rs (soubor)
  • put_in_force apps/majordomus-cli/src/integration/mod.rs (soubor)
  • put_in_force apps/majordomus-cli/src/integration/relation.rs (soubor)
  • put_in_force apps/majordomus-cli/src/integration/retry.rs (soubor)
  • put_in_force apps/majordomus-cli/src/integration/tests.rs (soubor)
  • put_in_force apps/majordomus-cli/src/integration/wait.rs (soubor)
  • put_in_force docs/INTEGRATION.md (dokument)
  • put_in_force project.accumulation-is-measured@2
  • put_in_force project.integration-follows-the-current-master@3
  • put_in_force project.land-and-publish@1
  • put_in_force project.no-network-no-eval@1
  • put_in_force test/cases/720_integration_follows_the_current_master.sh (test)
  • put_in_force test/cases/740_integration_is_visible_where_a_person_looks.sh (test)
  • put_in_force test/cases/741_a_continuous_drain_stops_cleanly_and_alone.sh (test)
  • put_in_force test/cases/742_only_an_outage_is_asked_again.sh (test)
  • put_in_force test/cases/861_a_successor_that_landed_supersedes.sh (test)

odkazuje na něj

101. Pull requesty se integrují jeden po druhém, každý proti masteru pozorovanému chvíli předtím

Kontext

Dne 2026-09-30 bylo otevřeno sedmdesát pull requestů. Žádný nebyl připraven podle žádné poctivé definice a nic to neříkalo. Repozitář měl tři odpovědi na „co může přistát“:

  • příznak mergeable forge, který tento repozitář nemůže použít. Odvozené soubory řeší merge driver pro každý klon (merge=derived), který GitHub spustit nemůže, takže forge označí téměř každý pull request za konfliktní kvůli souborům, které generátor stejně přepíše;
  • scripts/land, které se gitu zeptalo na správnou otázku a pak sloučilo každý čistý pull request do jedné integrační větve. To je dávka naplánovaná ze seznamu, který její vlastní první merge zastaralí, a regrese dávky nejsou nikoho konkrétního;
  • integrační dávky sestavené ručně (A až E), ze stejného důvodu a za stejnou cenu: červenou dávku musel bisektovat člověk.

Mezitím je mechanika pevná. Každý merge posune odvozené artefakty, které nese každá jiná větev, takže každý jiný pull request přestane obsahovat master ve chvíli, kdy jeden přistane. Pull request, který neobsahuje master, nelze sloučit, aniž by odvozená data masteru zastarala. Povinná kontrola (ci) trvá hodiny.

Rozhodnutí

Integrace pull requestů je typovaný subsystém spustitelného souboru (crate::integration) s jedním kanonickým stavem, jednou klasifikací a jedním executorem.

  1. Jeden kanonický stav. Jeden PullRequestAssessment na otevřený pull request: co forge pozoroval, jaký je jeho head vůči masteru, jeho dispozice, důvody, důkazy, riziko, překryvy a master a head, proti nimž bylo rozhodnuto, s okamžikem, kdy forge pozoroval. Příkazová řádka, HTTP routa, nástroj MCP a Cockpit vykreslují tuto hodnotu a nic jiného. Jeho slovník je typovaný, takže žádný klient neparsuje prózu. Důvod je ReasonCode, jehož drátová podoba je řetězec code nebo code:payload, jaký vždy byla: stacked_on:#N, base_is:BRANCH, draft, label:NAME, auto_merge_armed, merge_commit_not_allowed, superseded_by:#N, successor_open:#N, successor_not_landed:#N, successor_unread:#N, head_reachable_from_master, merge_changes_nothing, patch_ids_upstream, only_derived_artifacts_differ, relation_unknown:WHY, conflicts_on:COUNT, depends_on:#N, review:STATE, review_policy_unread, required_check_failed, behind_master:COMMITS, fork_head, required_checks:STATE, no_required_checks, required_checks_unread, dependency_cycle:#N, dependency_closed_unmerged:#N, dependency_unread:#N, declarations_unread a u připraveného contains_master s required_checks_passed nebo required_checks_skipped. Trail, pole řetězců v OpenAPI a Cockpit čtou stejné řetězce jako dřív a kód, který nese starší trail a tento seznam ho nejmenuje, se uchová doslova. Důkaz má typovaný druh, s drátovými slovy, která měl (required_checks, review, relation_to_master, dependency, label, draft, base, plus required_check, jeden na každý povinný kontext, auto_merge kdykoli má forge zapnutý auto-merge, repository_settings když nastavení nepovolují žádný merge commit, a supersession, jeden na každého deklarovaného nástupce, jeden na každou deklaraci, kterou učinil někdo neoprávněný, a jeden, když nebyly odkazy pull requestu přečteny celé; freshness je rezervováno), a zdroj: pozorování forge v jeho okamžiku, nebo git na pojmenovaném masteru a headu.

  2. Vztah k masteru je vztah gitu. git merge-tree --write-tree s ovladači tohoto klonu a git check-attr merge pro to, které cesty jsou odvozené, z vlastních .gitattributes masteru. Konflikt na odvozené cestě patří regeneraci, ne člověku. Výsledek je cachován dvojicí SHA, které jsou neměnné. Rozhoduje se přesně na headu, který posouzení jmenuje jako vyhodnocený: head, který v klonu není, protože se během obnovy posunul, je unknown, nikdy vztah toho, co drží právě stažený ref. Selhané čtení atributu je unknown, nikdy „nic není odvozeno“. Klíčem cache je jen dvojice plných id commitů a unknown se nikdy necachuje. Kde by merge konfliktoval nebo změnil jen odvozený výstup, ptá se se i git cherry: když je každý commit, který head má a master nemá, na masteru už jako shodný patch, je vztah patch_ids_upstream, změna přistála a master ji přerostl. Částečná shoda jej nikdy nedává, ani head s vlastním merge commitem v tom rozsahu (rozlišení merge nemá patch k porovnání), ani čistý merge, který mění autorské cesty, kde master postrádá něco, co head nese, například přistálou změnu, kterou master od té doby revertoval. Vedle vztahu, ze stejné dvojice commitů a cachované pod stejným klíčem, je to, co merge mění podle druhu (ChangeShape): každá autorská cesta, kterou mění nebo na které konfliktuje, odvozené cesty a verze, kterou head deklaruje v manifestu crate, když zvyšuje tu, kterou deklaruje jeho merge base. Varianty vztahu zůstávají, jaké jsou; tvar je to, co čtou překryvy a riziko, takže konfliktní head se překrývá s ostatními přes celou svou změnu, ne jen přes cesty, na kterých konfliktuje. Překryv jmenuje svůj druh: version_bump, když oba zvyšují verzi, release, když oba mění záznam pod .ai/repo/releases/, a authored pro sdílené autorské cesty. Riziko je high s paths_unknown, když je vztah unknown, nikdy „jen dokumentace“; zvýšení verze (version_bump:V), sdílené (overlapping_version_bump:#N) a sdílený release (overlapping_release:#N) jsou faktory high; a .ai/repo/releases/ nahrazuje odvozené share/version.txt, které autorské cesty nikdy neobsahovaly.

  3. Patnáct dispozic, rozhodovaných v jednom pořadí. Jsou to ready (dráha připravená); needs_refresh, waiting_for_checks, waiting_for_review a waiting_for_dependency (čekající); needs_repair a conflicting (oprava); redundant, superseded a possibly_redundant (úklid); a draft, blocked, unsafe, other_base a unknown (zadržené). Seznam je hodnota za běhu (PullRequestDisposition::ALL), takže povrch, který jmenuje dispozice dráhy, je odvozuje, místo aby je kopíroval. K ready se dospěje, jen když pull request míří na základ, není draft, nenese štítek, který jej zadržuje, nemá aktivní auto-merge, nemá deklarovaného nástupce, už není na masteru, repozitář povoluje merge commit, obsahuje aktuální master, má přistálé deklarované závislosti, splňuje politiku posouzení a na svém aktuálním headu prošly všechny požadované kontroly. To jsou kontroly, kladené v tomto pořadí: base, draft, label, auto_merge, supersession, relation_to_master, merge_method, dependency, review, no_failing_check, freshness, required_checks. Štítky, které zadržují, jsou jedna tabulka, LABEL_POLICY, každý s účinkem; politika ji kopíruje a klasifikátor čte tuto kopii a žádný jiný seznam štítků neexistuje. Dnes je každý účinek hold, porovnáváno bez ohledu na velikost písmen: do-not-merge, do not merge, blocked, hold, on-hold, wip, manual-merge. Žádný štítek nevyjímá pull request z obnovení executoru (rozhodnutí vlastníka D11). Pull request, na kterém má forge aktivní auto-merge, je unsafe (auto_merge_armed): forge by jej sloučil sám, mimo executor a proti masteru, jaký bude, a obnovení by mu jen dalo head k merge, takže nad ním nic nepůsobí, dokud jej člověk nezruší; fronta jmenuje každý takový pull request v diagnostice. Executor slučuje jen merge commitem, protože ovladač odvozených souborů řeší merge a squash nebo rebase by přehrál commity, které nikdy neviděl. Metoda merge je proto merge, když to nastavení repozitáře dovoluje merge commit (allow_merge_commit), a jinak žádná; bez ní je každý pull request, o kterém vztah nerozhodl, blocked (merge_commit_not_allowed, s důkazy repository_settings) a fronta říká proč. Kontrola se klade po relation_to_master, protože uzavření práce, která už je na masteru, nezávisí na nastavení merge: pull request redundant zůstává redundant, za svým důvodem říká merge_commit_not_allowed a prs cleanup --apply jej stále uzavře; stejně tak superseded. Nikdy se nevrací ke squash ani rebase. Každá kontrola se klade bez ohledu na to, co odpověděly ostatní. gates posouzení vypisuje každou s tím, zda prošla, a jeho reasons drží zjištění každé selhávající kontroly ve stejném pořadí. Dispozice je ta první selhávající kontroly, takže draft, který také konfliktuje a jeho kontrola selhala, je draft a říká všechny tři. Selhaná požadovaná kontrola se klade před aktuálností a čekající po ní: head za masterem potřebuje obnovení, které jeho kontroly spustí znovu, ledaže nějaká kontrola už selhala. Kontrola, která selže jen proto, že selhala dřívější, například aktuálnost při konfliktu merge, selže bez vlastního důvodu. Požadované kontroly jsou ochrana větve základu a pravidla (rulesets), která se na něj vztahují, dohromady; pokud nelze přečíst kterékoli z nich, nic není připraveno. Kontrola vázaná na aplikaci (app_id v ochraně, integration_id v ruleset) je jen běh kontroly té aplikace. Stav commitu stejného jména jím není a není jím ani běh kontroly jiné aplikace s tímto jménem: ten kontrolu ani nesplní, ani nezpůsobí selhání, ani ji nenechá čekat. Kontext, který dva zdroje váží na dvě aplikace, se vyžaduje od obou. Když je kontext vázán, obnovení čte aplikaci každého běhu kontroly z GraphQL rollupu forge (checkSuite.app.databaseId); gh pr list ji nenese. Běh kontroly vázaného kontextu, jehož aplikace nebyla přečtena (head se mezi dvěma čteními posunul, jeho sada kontrol nejmenuje žádnou aplikaci nebo jeho kontexty se nevešly na čtené stránky), činí tuto kontrolu unknown (required_checks:unknown), nikdy ne prošlou, a fronta říká, které pull requesty takovou nesou; čtení zapisovatele, které selže, způsobí selhání obnovení. Vazba jmenuje aplikaci, ne workflow: každé workflow repozitáře zapisuje jako tatáž aplikace, včetně toho, které definuje head samotného pull requestu, takže vazba drží jinou aplikaci venku a nedokazuje, které workflow běželo. (Upraveno 2026-10-07: dříve se aplikace nikdy nečetla, protože gh pr list ji nehlásí, a prošel jakýkoli běh kontroly daného jména.) Z hlášení kontextu jeho stav „čeká“ určí jedno stále běžící a jinak je verdiktem nejnovější dokončené hlášení, takže selhání, které opravil nový běh, prošlo. Požadovaná kontrola, která čeká, chybí nebo je nečitelná, nebo kterou napsal kdokoli jiný než aplikace, na kterou je vázána, neprošla, a neprošla ani při přeskočení, ledaže politika přeskočení daného kontextu povoluje. Základ, který nevyžaduje žádnou kontrolu, o headu nic nedokazuje: prázdná množina po úspěšném čtení je unknown (no_required_checks), nikdy připraveno (rozhodnutí vlastníka D5). Politika posouzení se čte z ochrany a rulesets stejným způsobem (schválení, vlastníci kódu, zastaralé zrušení) a schválení se počítá jen na commitu, na kterém bylo dáno: schválení dřívějšího headu jsou stale a dost jich na headu, kdy ještě dluží vlastník kódu, je code_owners_pending. Rozhodnutí posouzení forge předchází politice: REVIEW_REQUIRED je čekající posouzení i tam, kde politika žádné nevyžaduje, protože to může pravidlo, které tato politika neuvádí. Závislost je deklarována jen řádkem, který začíná Depends on, Stacked on, Requires nebo Land after a číslem; stejná slova uprostřed věty jsou próza. Splněna je jen merge: závislost uzavřená bez merge drží pull request blocked (dependency_closed_unmerged:#N), závislost, kterou forge nedokáže vysvětlit, je unknown (dependency_unread:#N) a deklarované závislosti, které tvoří cyklus mezi otevřenými pull requesty, blokují každého člena (dependency_cycle:#N). Head, který obsahuje head jiného otevřeného, je odvozená závislost: důkaz, nikdy blokace. Pull request je naskládán jen na větev tohoto repozitáře, nikdy na větev forku stejného jména. Nástupce se deklaruje stejně, stejným parserem: řádek těla pull requestu, který začíná Superseded by #N, jmenuje N jeho nástupcem, a řádek těla N, který začíná Supersedes #M, jmenuje N nástupcem M, z druhé strany. Kontrola supersession se klade před relation_to_master, protože pull request, jehož nástupce přistál, obvykle konfliktuje s tím, co nástupce přinesl, a je nahrazen, ne konfliktní. Deklarace se počítá jen od toho, komu to repozitář dovoluje: pull request, jehož tělo ji nese, je větev tohoto repozitáře a jeho autor je OWNER, MEMBER nebo COLLABORATOR, v obou směrech, testováno na autorovi těla, které mluví. Cizí, forku nebo ta, jejíž příslušnost nebyla přečtena, je důkaz (possible_supersession, jmenující, kdo to řekl) a nemění žádnou dispozici. Nástupce přistál, když git najde jeho head nebo jeho merge commit v masteru, ať to forge nazývá jakkoli: sloučený squashem i uzavřený poté, co jeho head přenesla dávka, oba přistály. Oprávněný nástupce, který je stále otevřený, pull request zadržuje (successor_open:#N, waiting_for_dependency): nesmí přistát, dokud může přistát jeho nástupce, a jakmile nástupce přistane, je uzavřen, ne sloučen. Ten, který přistál, jej činí superseded (superseded_by:#N) a posouzení jmenuje N v superseded_by; uzavřený bez merge jej uvolní k jeho vlastnímu vztahu; sloučený někam, co master neobsahuje, jej nechá possibly_redundant (successor_not_landed:#N) pro člověka; nečitelný je unknown (successor_unread:#N). Pull request, jehož křížové odkazy nebyly přečteny celé, je unknown (declarations_unread) před čímkoli z toho: nepřečtená deklarace se nikdy nepovažuje za žádnou. Z několika nástupců rozhoduje ten, který přistál, pak ten stále otevřený, pak ten sloučený jinde, pak ten nepřečtený.

  4. Jeden merge najednou a žádný plán ho nepřežije. Krok exekutoru nepřijímá žádný plán. Pozoruje forge, rozhodne, pozoruje znovu a jedná, jen pokud druhé rozhodnutí jmenuje stejný master a head a stále říká ready. drain je smyčka nad tímto krokem. Neexistuje proměnná, ve které by seznam kandidátů mohl merge přežít. Merge se poté dokazuje tam, kde přistál: první-rodičovský následník rozhodnutého masteru musí být tento merge a rozhodnutý head jeho druhým rodičem; cokoli jiného je unexpected_master, i když forge označí pull request jako sloučený. Strana této záruky na forge je required_status_checks.strict ochrany; exekutor hlásí základ bez ní a nastavit ji neumí (D8). Neověřený merge zadrží každé další vyprázdnění, dokud ho člověk nepotvrdí (D7); přerušený merge se před dalším rozhodnutím ověří, nikdy neopakuje.

  5. Obnovení je samostatný, omezený akt. Když není nic připraveno, drain --refresh přivede master do prvního pull requestu, který ho potřebuje: merge commit s odvozeným driverem a čerstvým derive, pushnutý jako prostý fast-forward, dokud origin stále obsluhuje pozorovanou hlavu, nikdy vynucený a nikdy přepis. Přesně to už předepisuje project.land-and-publish a prs repair <n> --apply je tentýž akt pro jeden pull request, který člověk pojmenuje, pod stejným zámkem a stopou; jeho dry run rozhoduje offline ze zaznamenaného pozorování. Pipeline je jedna hluboká: dokud obnovený pull request čeká na své kontroly, žádný další se neobnovuje, protože sloučení prvního by druhý zase posunulo za a zmařilo jeho běh CI. Drží ji jen běh, který executor spustil: povinná kontrola hlavy, kterou událost refreshed zaznamenala jako pushnutou, dokud je pending nebo chybí. Chybějící se počítá, protože agregátní kontrola vzniká až po dokončení jobů, které potřebuje, ale drží jen po omezenou dobu po pushi (čtyři hodiny), aby kontrola, která se nikdy nepřihlásí, nezmrazila každé obnovení. Kontrola na hlavě, kterou pushnul autor, nedrží nic.

  6. Cleanup vyžaduje víc než merge. Uzavírají se jen dvě dispozice, a jen s --apply. Pull request redundant se uzavře na silný důkaz gitu: jeho head je předek masteru (head_reachable_from_master), jeho merge nemění žádný soubor (merge_changes_nothing), nebo každý commit, který má a master nemá, je na masteru jako stejný patch (patch_ids_upstream). Pull request superseded se uzavře, protože přistál nástupce, který pro něj deklaroval vlastník, člen nebo spolupracovník; uzavírací komentář jmenuje tohoto nástupce a netvrdí, že uzavřená práce je na masteru. Slabý důkaz se nikdy neuzavírá: ten, který se liší jen v odvozeném výstupu, nebo jehož nástupce byl sloučen někam, kam master nesahá, je possibly_redundant a ponechá se člověku. Stáří, sdílené cesty a podobné titulky nejsou důkaz. Stopa zaznamenává uzavření jako closed_redundant nebo closed_superseded.

    Změna drátového formátu (rozhodnutí vlastníka D2, dodáno s minorem 0.13). Před ní slovo superseded znamenalo to, co nyní znamená redundant: silný vztah k masteru, uzavíraný úklidem s akcí closed_superseded. Silný případ byl přejmenován na redundant, jeho uzavření na closed_redundant a superseded nyní znamená jen výslovně deklarovaného nástupce, který přistál, s closed_superseded jako jeho uzavřením. Klient, který superseded porovnával, aby našel práci už na masteru, nyní porovnává redundant. Každý starší řádek se čte dál: řádek stopy s closed_superseded se parsuje a jeho důvody (head_reachable_from_master, merge_changes_nothing) říkají, který význam nesl, protože uzavření deklarovaného nástupce vždy nese superseded_by:#N. Vztah k masteru si ponechává své slovo, superseded (merge_changes_nothing), což je fakt gitu, ne disposice.

    Doplněno 2026-10-07 (rozhodnutí D1 až D5 z auditu K2/K3; navrženo, vlastník je může přepsat). D1 Deklarace je autorizována asociací pull requestu, který ji nese, na straně forge (OWNER, MEMBER, COLLABORATOR, stejný repozitář), v obou směrech; neznámá je neautorizovaná. D2 Autorizovaná deklarace drží nahrazený pull request, dokud se nevyřeší jeho nástupce: pokud přistál, je superseded a lze jej zavřít; pokud byl zavřen bez přistání, je uvolněn. Neautorizovaná je pouze possible_supersession důkaz a nic nedrží ani nezavírá. D3 Přistání je fakt gitu — head nebo merge commit nástupce je předkem základu — takže se počítá i nástupce, který se dostal na master v dávce; MERGED forge důkaz pouze vyslovuje. D4 Deklarace o otevřeném pull requestu se čtou z jeho vlastních křížových odkazů (stránkovaně) a z jeho vlastního těla, nikdy z omezeného hledání mezi zavřenými pull requesty; částečné čtení tento pull request drží (declarations_unread) a selhané čtení způsobí selhání pozorování. D5 Pravidlo má verzi 3 a jmenuje všechny čtyři důvody uzavření: předky, merge bez změn, každou záplatu v upstreamu (což odrážka verze 2 nikdy nejmenovala, ačkoli kód podle ní zavíral) a autorizovanou deklaraci, jejíž nástupce přistál. Před touto změnou držel a zavíral #M Supersedes #M jakéhokoli těla, zavřené deklarace pocházely z 200 nejnovějších výsledků hledání a schéma pozorování mělo verzi 3; nyní má 4.

    Dodatek k D3, 2026-10-07 (rozhodnutí R1; zúžení, které může vlastník zvrátit). Fakt z gitu je důvěryhodný jen o head, který nikdo nemohl přesunout, aby ho vyrobil. Útok: otevřený #1 vlastníka říká Superseded by #7, kde #7 je pull request z forku přispěvatele; #1 je na něm podle D2 zadržen. Autor forku force-pushne větev #7 na libovolný starý commit masteru a #7 zavře. Jeho head je pak předkem masteru a D3 v původním znění to označilo za přistálé: prs cleanup --apply zavřelo #1, ačkoli na masteru nebyla žádná práce z #7. Nástupce, o kterém forge nehlásí, že byl sloučen, se tedy počítá jako přistálý jen tehdy, když jeho head žije v tomto repozitáři a je předkem báze (cesta batch-landed); nástupce z jiného repozitáře zavřený nesloučený nikdy nepřistane, ať ukazuje head kamkoli, a uvolní zadržení stejně jako nástupce zavřený bez přistání. Nástupce, o kterém forge hlásí, že byl sloučen, přistane, když jeho head nebo jeho merge commit je předkem báze, fork nefork. Kde žije head vyřešeného nástupce, se čte spolu s ním a nepřečtené je fork. Ve stejném duchu nástupce, o kterém forge říká, že nemění žádný soubor, nepřinesl masteru nic a nikdy nepřistane, čímž se uzavírá stejný trik s větví tohoto repozitáře resetovanou na master.

    Rozhodnutí z 2026-10-07 o D4 a o tom, co se počítá jako deklarace. R3 Čtení deklarací, na které forge neodpoví, shodí celou obnovu (exit 12) a nezaznamená žádné pozorování: to zaznamenané před ním zůstane, jak bylo. Jen zkrácené čtení je podržení u pull requestu. R4 Řádek uvnitř citace markdownu (>), ohraničeného bloku kódu nebo komentáře HTML není deklarací jeho autora; D1 vložilo veškerou důvěru do toho, čí tělo mluví, takže se počítá jen to, co toto tělo uvádí. Závislost si ponechává shovívavé čtení: může pull request jen nechat čekat. R5 Otevřený deklarující, jehož příznak repozitáře čtení deklarací neuvedlo, se čte jako mezirepozitářový a nic neautorizuje.

    Zbytky, 2026-10-07 (známé, přijaté, k opětovnému posouzení vlastníkem). R2 Zkrácení drží a cizí osoba ho může způsobit: limit stránek počítá každý křížový odkaz na pull request a zmínka z úkolu nebo z jiného repozitáře se zahodí až poté, co byla projita po stránkách. Kdo napíše více zmínek o owner/repo#N, než se přečte (padesát stránek po sto), drží #N jako unknown (declarations_unread) při každém obnovení; žádný příkaz ho neuvolní a rozhoduje člověk. To je blokace a nikdy uzavření, stojí cizí osobu jednu napsanou zmínku na kus a zbytek fronty se hýbe dál. Žádný mechanismus uvolnění se zde nevymýšlí. R6 MEMBER je slovo forge pro člena organizace bez ohledu na jeho přístup k tomuto repozitáři a vyhodnocuje se při pozorování, ne při napsání těla. Bývalý spolupracovník, který je stále členem, může upravit tělo svého starého pull requestu v tomto repozitáři na Supersedes #M: je-li ten pull request otevřený, drží #M, a byl-li dávno sloučen, #M je superseded a uzavíratelné. D1 jmenuje MEMBER a platí; osobní repozitář nemá členy a není vystaven.

  7. Jeden exekutor na základní větev. Výhradní lease pod společným git adresářem: výhradní flock na jeho souboru, držený po dobu života exekutora, s držitelem zaznamenaným vedle pro toho, kdo čte. O držiteli rozhoduje jádro, takže exekutory spuštěné současně nemohou vyhrát oba, a držitel, který skončí, i pádem, ho okamžitě uvolní; živý držitel není nikdy převzat, jakkoli starý je jeho záznam. Mez zastarání je to, co hlásí pozorovatel, ne licence k převzetí lease. Pozorovatelé ho nikdy nepřebírají.

    Lease chrání jeden klon: každý worktree sdílí jeho společný adresář gitu a druhý klon na jiném stroji má svůj vlastní. Repozitář chrání mesh (ADR 0067), když ho běží sdílený server tohoto checkoutu: před lease si exekutor vezme výhradní nárok mesh na integration/<repository>/<base> jako relaci svého checkoutu, proti které se každý propojený runtime připouští. Exekutor druhého stroje je odmítnut s exit 12, jmenující držící relaci a co o svém účelu řekla, dříve než si vezme lease nebo jedná. Klíč nároku se zapíše do záznamu lease a uvolní po lease. Mesh, který zde neběží, neodmítne nic: chrání samotný lease a záznam, prs brief i Cockpit to říkají („per-clone guard only“). Nárok, který exekutor zanechal při ukončení, je vlastní relací jeho checkoutu, kterou mesh této relaci nikdy neodmítne; další exekutor tam vezme lease a zbylý nárok uvolní, takže pád stojí ostatní stroje čekání, nikdy nárok, který nikdo nemůže uvolnit.

  8. Síť je deklarována, ne tolerována. prs refresh, prs drain, prs cleanup a prs repair --apply spouštějí GitHub CLI a git fetch. Každý jiný povrch vykresluje zaznamenané pozorování s jeho stářím. SECURITY.md jmenuje výjimku. Kromě otevřených pull requestů pozorování čte u každého otevřeného pull requestu asociaci jeho autora a pull requesty, které ho zmiňují (gh api graphql, stránkované; zmínka je deklarace, jen když tělo onoho pull requestu říká Supersedes), a každého nástupce nebo závislost, kterou některý otevřený jmenuje a která není otevřená (gh pr view), a stahuje jejich hlavy, aby git mohl říct, zda nástupce přistál poté, co přestal být uváděn jako otevřený.

  9. Každý úkon se zaznamenává. Výběry, rozhodnutí o zastaralosti, pokusy o merge, merge s masterem před a po, odmítnutí, obnovení a uzavření jdou do stopy jen k připojování, kterou obsluhuje schopnost integration.events. Stopa patří repozitáři, ne checkoutu: jeden soubor pod společným adresářem gitu, <git-common-dir>/majordomus/integration/events.jsonl, vedle lease, takže každý worktree zapisuje a čte tentýž. Každý úkon se připojí dříve, než se provede, a úkon, který stopa nemůže zaznamenat, se neprovede.

  10. Třídy selhání a opakování. Každý neúspěšný čin se zaznamená se svou třídou: stale, conflict, new_failing_check, review_revoked, transient, policy_violation, verification_failed nebo unreadable. Prvních šest patří jednomu kandidátovi a odvod pokračuje dalším; poslední dvě patří repozitáři nebo exekutoru a odvod se zastaví. Kandidát, kterého forge odmítl mergovat nebo jehož obnovení selhalo, se drží stranou jako needs_repair (executor_merge_refused, executor_refresh_failed), dokud jsou odmítnuty jeho head a master — složeno ze stopy, nikdy z plánu — takže jedna odmítnutá změna neblokuje frontu. Přechodné selhání nezdržuje nic. Forge se ptá znovu jen při výpadku (crate::integration::retry, případ 742), merge nikdy; souvislý odvod vyčká až CONTINUOUS_TRANSIENT_LIMIT po sobě jdoucích výpadků, každý zaznamenaný jako observe_failed, než skončí.

  11. Průběžný režim je ohraničená smyčka ohraničených odtoků. prs drain --continuous drží lease po celý svůj běh a opakuje jeden ohraničený odtok za cyklus, takže každé akci stále předchází čerstvé pozorování a žádný plán nepřežije merge. Mezi cykly čeká interval uvnitř INTERVAL_SECONDS (30 až 900 s: dolní mez šetří forge, horní drží čekajícího workera hluboko uvnitř LEASE_STALE_AFTER), po jednosekundových dílcích, takže signál je respektován do sekundy po dokončení probíhajícího kroku. Zastaví se na signál, na merge, který nešlo ověřit, a po CONTINUOUS_TRANSIENT_LIMIT po sobě jdoucích výpadcích. Zkušební běh nikdy není průběžný.

  12. Čekání je fakt a vyhladovění se ukazuje, nevykupuje. Stopa zaznamenává, kdy se pull request stane použitelným k akci a kdy jím přestane být, a při každém výběru použitelné pull requesty, které byly přeskočeny; crate::integration::wait to sloučí na to, jak dlouho každý čekal a jak často byl zvolen jiný. Pull request přeskočený STARVING_AFTERkrát je označen jako vyhladovělý. Pořadí se tím nemění: jeho rozhodování při shodě už preferuje starší ze dvou jinak stejných kandidátů a dlouhé čekání nikdy není důvodem sloučit dříve něco méně bezpečného.

  13. Rollout je po etapách a stopa je jeho záznam. Etapa 1 je zkouška nasucho proti skutečnému repozitáři (prs refresh, prs status, prs drain --dry-run), která nic nepřesune. Etapa 2 je jeden omezený merge (prs drain --max 1), etapa 3 malé omezené drainy (--max 3, pak --max 5). Etapa 4, kontinuální režim, je odmítnuta (exit 10, před lease, base či forge), dokud stopa nedrží ROLLOUT_MERGES_BEFORE_CONTINUOUS (pět) ověřených mergí od posledního merge, který nešlo ověřit; takový merge začíná počítání znovu, ať byl potvrzen, nebo ne, protože záznam, který ukončuje, byl důkazem. Výchozí hodnoty jsou bezpečné: prs status, plan a explain čtou; drain mergeuje, jen když je o to požádán; cleanup nic neuzavře bez --apply.

Důsledky

Propustnost omezuje povinná kontrola: jeden pull request přistane na jeden běh CI jeho obnovené hlavy. To je skutečná cena mechaniky tohoto repozitáře a plánovač ji dělá viditelnou, místo aby ji schovával do dávky. Paralelní spekulativní obnovy — merge queue — jsou pozdější rozhodnutí a potřebují nejprve důkazy z tohoto.

scripts/land je smazáno. scripts/force-merge-all, které inventář automatizace už určil ke smazání, se maže s ním. scripts/ci/backlog-check --remote měří backlog přes majordomus prs.

Zamítnuté alternativy

  • Důvěřovat mergeable forge. Je tu špatně v obou směrech: změřeno CONFLICTING na čistých mergích a MERGEABLE na skutečném konfliktu.
  • Sloučit každý zelený pull request postupně z jednoho seznamu. Každý merge seznam zneplatní. O druhém merge by se rozhodovalo proti masteru, který už neexistuje.
  • Dávky. Vyměňují přiřaditelnost za propustnost a červená dávka stojí člověka bisekci. Zůstávají rozhodnutím člověka, ne executoru. Dne 2026-10-16 vlastník toto rozhodnutí učinil: role přistání přistává kombinované tops (ADR 0167) a zde popsaný executor se nemění.
  • Sloučit pomocí --admin. Odmítnutí ochrany větve je odpověď, ne překážka.
  • Zavírat pull requesty podle stáří nebo podobnosti. Ani jedno není důkaz, že práce přistála.

Doplnění, 2026-10-15: merge, který provede GitHub, hlídají kontroly, ne strict (I1409)

D8 nechalo required_status_checks.strict vlastníkovi a nazvalo to polovinou ochrany na straně forge. I1409 položilo užší otázku, kterou to nechává otevřenou: když forge provede merge na master, který se posunul poté, co běžel ci pull requestu, co odmítne zastaralý odvozený strom dřív, než se publikuje? Změřeno 2026-10-15, ochrana vyžaduje ci s strict: false a enforce_admins: false. Odpovědí jsou tři kontroly, každá na stromě, který posuzuje, a žádná z nich není strict.

  1. Před mergem požadovaná ci. Běh pull requestu si stáhne refs/pull/<n>/merge, merge, který by forge v tu chvíli provedl, a jeho úloha site tam spouští scripts/generate-site-data --check a jeho úloha rust tam spouští majordomus generate --check (.github/workflows/validate.yml, úloha site, krok derived site data in sync).
  2. Mezi tímto během a sloučením je to vlastní textové slučování forge. Forge nespouští žádný vlastní merge driver. Když je merge.derived nedefinováno, git sáhne po textovém slučování, což je přesně to, co říká .gitattributes. Každý odvozený strom zaznamenává jedno source_hash v site/data/generated/source.json a tento hash pokrývá deklarované vstupy webu: 1497 cest včetně projekcí registru docs/generated/{registry,graph,openapi, cli,web,changelog}.json. Dvě strany, z nichž každá změnila kanonický vstup, změnily ten samý řádek, takže forge hlásí pull request jako konfliktní a nesloučí ho. Ukazuje to holý repozitář bez nakonfigurovaného driveru: git merge-tree --write-tree masteru c0e3585dc8 s hlavami #1159 a #1149, oba na GitHubu CONFLICTING, uvádí mezi konflikty site/data/generated/source.json. Pokud kanonické vstupy změnila jen jedna strana, sloučené vstupy jsou vstupy této strany a odvozená data jsou jejich projekcí (případ 3048 drží nesouvisející merge aktuální).
  3. Po merge, před publikací, mergnutý commit. Push na master spouští validate.yml na merge commitu, se stejnými kontrolami site a rust, a pages.yml, jehož job deploy spouští scripts/pages build. Tento příkaz končí s kódem 10 na zastaralých datech webu (cmd_current) a job ho spouští před krokem publish. Merge masteru c0e3585dc8 commitoval GitHub. Na něm validate run 38635619021 (job 116098148462) prošel derived site data in sync a pages run 38635619039 (job 116105040387) spustil build v 21:07:39Z a publish v 21:09:30Z. Zastaralá data webu jsou odmítnuta na build, takže gh-pages drží poslední aktuální commit a kontrola pages-live jmenuje dluženou publikaci.

Rozhodnutí. Ochranou na straně serveru je kontrola čerstvosti v čase CI, na třech výše uvedených místech. Nejsou to povinné kontroly strict a není to nová kontrola v čase merge. Dokumenty tvrdí přesně to: .githooks/pre-commit chrání merge, který commitne člověk, a výše uvedené kontroly chrání ten, který commitne forge. Žádný dokument nesmí tvrdit, že hook chrání merge na GitHubu.

Rejected:

  • strict: true jako stráž. Je to nastavení repozitáře a akt vlastníka (D8, mimo rozsah I1409). Znovu by spouštělo ci pro každý otevřený pull request pokaždé, když se master pohne, což je propustnost, kterou tento záznam už oceňuje na jedno přistání na běh. Nepokryl by ani merge --admin, protože administrátoři nejsou vynucováni.
  • Merge fronta. Je to „pozdější rozhodnutí“ z Důsledků výše. Potřebuje nejdřív důkazy tohoto ADR a pro odvozená data se nedluží, protože bod 2 už nutí forge odmítnout jedinou kombinaci, ve které by textový merge mohl pro web udělat chybu.
  • Znovu odvozovat v buildu Pages. Non-scope I1409: generování není deterministické podle stromu, takže by build vytvořil třetí odpověď.

Zbytek, uvedený, ne zamlčený. Odvozený artefakt mimo deklarované vstupy webu, například generovaná oblast AGENTS.md nebo dovednost pod .claude/skills/, mohou změnit dva pull requesty dotýkající se různých řádků a forge může tyto změny textově čistě sloučit do výsledku, který není projekcí. Bod 1 ten merge nevidí a bod 2 ty soubory nepokrývá. Až generate --check bodu 3 na běhu push to odmítne jako první: master zčervená dřív, než se na něj kdokoli spolehne. Žádný z těch bajtů web nepublikuje.

Odkud to pochází

kanonický soubor
.ai/repo/adrs/0101-pull-requests-are-integrated-one-at-a-time-against-the-current-master.md
identity
adr-0101
dostupné také jako
Zdroj MCP majordomus://adr/adr-0101 · majordomus entity show adr/adr-0101 · GET /api/v1/entity?uri=majordomus://adr/adr-0101
v Cockpitu
/cockpit/objects/adr/adr-0101

Tato stránka je generována z výše uvedeného souboru pomocí scripts/generate-site-data; úprava stránky nic nemění, úprava souboru změní všechna rozhraní. Výše uvedené vztahy jsou hrany docs/generated/graph.json, které spustitelný program zapisuje ze stejného indexu.

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