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.

Majordomus Mesh

mesh: jak se navzájem objevují běžící instance Majordomus — identita uzlu Ed25519, jedna podepsaná omezená obálka přes multicast, broadcast a rendezvous, jeden registr, důvěra odmítající neznámé, deklarace vypnutá ve výchozím stavu, model hrozeb a jak mesh provozovat a rozšiřovat

Vykresleno z docs/MESH.md — stejného Markdownu, který ukazuje GitHub.

Jak se runtimy Majordomus jednoho repozitáře — na jednom stroji či několika — navzájem nacházejí, rozhodují, zda budou spolupracovat, a sdílejí stav své práce. Rozhodnutí jsou ADR 0050 (objevování) a ADR 0067 (spolupráce); invarianty jsou pravidla project.mesh-is-observation-not-authority a project.mesh-cooperation-is-authenticated; implementace je apps/majordomus-cli/src/mesh/. Tento dokument je pohledem operátora a přispěvatele: co běží, co putuje, co je zaručeno a co ne, jak to provozovat a jak se to testuje.

Dvě věty řídí vše zde:

Objevení vytváří povědomí, ne autoritu.

Link se připouští handshakem a vše kooperativní je podepsaná událost jednoho žurnálu, kterou každý runtime skládá stejně.

Co to je a co to není

Je to způsob, jímž několik AI kódovacích relací — v několika terminálech, worktrees a na několika strojích — pracuje na jednom repozitáři bez kolizí: každý runtime vidí relace ostatních, výlučný nárok učiněný kdekoli odmítne překrývající se nárok všude, předávka napsaná na jednom stroji je převzata na jiném, review vyžádané na jednom je zodpovězeno na jiném a runtime, který zmizí, přestane všude sám cokoli držet.

Není to zámková služba se silnou konzistencí (při rozdělení sítě si mohou nárokovat obě strany; konflikt se po zahojení pojmenuje), synchronizátor souborů (zdroje vlastní git), nástroj vzdáleného provádění (nic, co peer pošle, nic nespustí), šifrovaný transport (ve spustitelném souboru není TLS) ani federace přes WAN (žádný průchod NAT; rendezvous a seedy potřebují dosažitelné adresy).

Tvar

flowchart TD
  decl["mesh declaration<br>.ai/repo/mesh/&lt;id&gt;.yaml<br>(enabled, trust, cooperation)"]
  identity["NodeIdentity<br>Ed25519 key per user and machine<br>node id = digest(key)"]
  subgraph discoveryLayer["discovery (ADR 0050) — candidates, never authority"]
    providers["providers: udp_multicast · udp_broadcast · rendezvous"]
    registry["MeshRegistry<br>one record per node × runtime"]
  end
  subgraph cooperationLayer["cooperation (ADR 0067)"]
    link["link handshake (link.rs)<br>signature · freshness · nonce · protocol ·<br>self · repository · trust"]
    table["peer-link table (cooperation.rs)<br>connecting · connected · degraded ·<br>unreachable · expired"]
    journal["Journal (journal.rs)<br>signed events (stream, seq), Lamport,<br>beats, marks"]
    fold["fold (state.rs)<br>sessions · claims · conflicts ·<br>handovers · reviews · digest"]
  end
  surfaces["mesh capability module<br>CLI · HTTP · OpenAPI · MCP · Cockpit"]
  board["peer board (peers.rs)"]
  decl --> discoveryLayer
  decl --> cooperationLayer
  identity --> discoveryLayer
  identity --> link
  providers --> registry
  registry -->|"trusted, same repository"| link
  decl -->|"seeds"| link
  link --> table
  table -->|"sync every heartbeat"| journal
  board -->|"projected each heartbeat"| journal
  journal --> fold
  registry --> surfaces
  table --> surfaces
  fold --> surfaces

Identita

identityco jmenujeodvozeno zpřežije
uzelklíč Majordomus strojeotisk veřejného klíče Ed25519 v $XDG_STATE_HOME/majordomus/node.jsonrestarty, změna adresy; ztrácí se se souborem
runtimeserver jednoho checkoutu na uzludigest id checkoutuserver se restartuje; dva worktrees jsou dva runtimy
instancejeden běh procesu runtimuentropie OS při startunic — restart je nová instance
proudco zapisuje události: node-runtime-instancetři výšenic — restart je nový stream téhož runtimu
sessionrelace jednoho workera v rámci proudupojmenováno volajícím (board-p2 pro relaci MCP, cokoli pro CLI)stream
repozitářrepozitář, na kterém se spolupracujedigest jeho kořenových commitů, nebo cooperation.repositoryklony, cesty, stroje

Adresa, port, jméno hostitele ani PID nikdy není identita. Dva worktrees jednoho stroje sdílejí klíč uzlu a jsou dvěma runtimy; restartovaný server si ponechá runtime a změní instanci, takže jej jeho peeři uvádějí jednou s počtem restarts.

Na identitě repozitáře stojí izolace. Cesta adresáře gitu — kterou server.status hashuje, aby rozlišil checkouty jednoho stroje — se mezi stroji liší a nemůže jí být. Kořenové commity sdílí každý úplný klon a žádný nesouvisející repozitář. „Kořen“ mělkého klonu je místo, kde byla jeho historie odříznuta, takže mělký klon musí deklarovat cooperation.repository a stejně tak fork, který nemá spolupracovat se svým původem; doctor říká, co platí.

Zjišťování

Zjišťování nachází kandidáty. Každý zaslechnutý datagram — multicast (preferovaný; skupina, port a TTL deklarovány), broadcast (deklarovaná záloha) nebo odpověď rendezvous — projde jednou ověřovací cestou v mesh::manager (velikost → tvar → verze → meze → zastaralost → podpis → přeskočení vlastního runtimu → politika důvěry) do jednoho registru klíčovaného uzlem a runtimem. Obálky mají nejvýše 1200 bajtů, jsou podepsané a nesou klíč, runtime, instanci, endpointy, transporty, identity repozitářů a verzi; nikdy tajemství, cestu ani obsah repozitáře.

Protokol objevování 2 přidal slot runtime. Čtenář verze 2 čte verzi 1 (jako nepojmenovaný runtime uzlu); čtenář verze 1 verzi 2 odmítne a započítá ji jako version, takže smíšená flotila ukazuje nesoulad místo toho, aby ztichla.

Multicastové sockety jsou navázány s SO_REUSEADDR a SO_REUSEPORT, takže skupinu slyší každý runtime na počítači. Server navázaný na 0.0.0.0 inzeruje adresy svých rozhraní, fyzických a overlay před kontejnerovými mosty; server navázaný na loopback inzeruje loopback, na který se dovolá jen jeho vlastní počítač.

Důvěra

O důvěře rozhoduje deklarovaná politika a výchozí stav je, že nikomu není důvěřováno:

policyplatný neznámý klíč jepoznámky
deny_unknown (výchozí)pozorováno, vypsáno, nedůvěřováno v ničems trust.allow, doporučený tvar pro flotilu
allowlistpozorováno; vypsané klíče jsou důvěryhodnétotéž co deny_unknown plus allow
tofudůvěřováno při prvním použitívývojové pohodlí pro síť, kterou ovládáte: pod TOFU je každému klíči v segmentu, který zná identitu repozitáře, důvěřováno a může propojovat, nárokovat a publikovat; každý výpis říká tofu a záznamy s důvěrou TOFU lze vytlačit

Klíč stažený z trust.allow ztrácí svá spojení, jakmile server změnu přečte: server uvede důvěru deklarace v platnost s každou novou generací repozitáře, ukončí spojení každého peera, kterému už nedůvěřuje, a zamítne jeho další hello untrusted — bez restartu (I2134). Vlastní klíč tohoto stroje je důvěryhodný jako sám sebe, což umožňuje propojit dva worktrees. Id uzlu, které se znovu objeví pod jiným klíčem, se zamítne pod každou politikou. Důvěra je předpoklad spojení, ne spojení: zjišťování žádné nepřipustí.

Pod deny_unknown a allowlist stroj, jehož vlastní klíč není v trust.allow, drží mesh uzavřený: nenaváže multicastový socket, nezaregistruje se u žádného hubu, nevytáčí žádný seed a neotevře žádné propojení a mesh status uvede důvod s klíčem k vypsání. Týká se to klonu přispěvatele veřejného repozitáře a nového stroje, který nikdo dosud nezapsal. Bez tohoto pravidla by se takový stroj ohlašoval ve své síti a vytáčel každý endpoint, který repozitář commituje, zatímco by ho každý peer odmítl. mesh doctor selže ve své kontrole trust s klíčem a nápravou, jeho kontrola runtime čte closed: … a briefing relace říká Mesh: CLOSED — …. Vypsání klíče a restart serveru ho otevře. tofu se z tohoto důvodu nikdy nezavře.

Která kopie deklarace je respektována

Pracovní strom smí důvěru zúžit a nikdy rozšířit. Když trunk drží commitnutou kopii deklarace na téže cestě, runtime checkoutu se řídí kopií pracovního stromu s odebraným každým rozšířením oproti trunku: mesh je zapnutý, jen když je zapnutá kopie trunku, politika není volnější než ta v trunku a každý klíč v trust.allow, každý rendezvous endpoint, seed a broadcast síť je takový, který uvádí i kopie trunku — nebo, u endpointu či seedu, loopback adresa, která nedosáhne na žádného jiného hostitele. Když trunk kopii nedrží (repozitář, který žádnou necommitnul, nebo adresář mimo git), pracovní strom se řídí tak, jak je zapsán, kromě tofu, které se respektuje jen z commitnuté kopie trunku.

Takže větev přispěvatele, checkoutnutá k review, nemůže přimět runtime tohoto checkoutu důvěřovat klíči svého autora ani poslat svou kartu na hostitele svého autora a necommitnutá úprava také ne; stroj nebo hub nabývá účinnosti, až když jeho řádek dosáhne trunku. mesh doctor selže na kontrole trust-root a jmenuje každý řádek, který tento checkout ignoruje, a server je při startu zaloguje. scripts/ci/mesh-check odmítne commitnutou deklaraci, která nastavuje tofu. Trunk je lokální větev, kterou repozitář jmenuje jako svůj trunk; lokální trunk za vzdáleným je starší důvěra, což je bezpečný směr.

Handshake

Dialer — runtime s nižším klíčem runtimu, nebo kterýkoli, když jen jeden může dosáhnout druhého, nebo jakýkoli runtime s deklarovaným seedem — postuje podepsaný hello na POST /api/v1/mesh/link/hello: svou kartu runtimu (klíč, runtime, instance, repozitář, název, verze, funkce, endpointy), 32hex nonce, časové razítko, rozsah svého link protokolu a, když dialer ví, koho volá, klíč runtimu, který chce zastihnout (to). Answerer ho přijme, jen když v pořadí, v němž to kód kontroluje:

  1. zpráva je do 900 KiB a má tvar hello;
  2. podpis Ed25519 se ověří pod doménou hello (podpis nad jiným druhem zprávy nelze nikdy přehrát jako hello);
  3. časové razítko je do ±300 s od jeho hodin;
  4. nonce má 32 hexadecimálních znaků;
  5. rozsahy protokolu se setkají (protokol spojení dnes 1..1; vybere se nejvyšší společná verze);
  6. peer není tento runtime;
  7. hello, který jmenuje svého příjemce, jmenuje tento runtime — kopie zachycená cestou k jinému runtime repozitáře je odmítnuta misaddressed;
  8. identita repozitáře peera se rovná jeho vlastní;
  9. klíč peera je důvěryhodný podle jeho politiky;
  10. nonce nebyl dosud zodpovězen. Kontroluje se záměrně jako poslední: cache proti opakování drží jen nonce důvěryhodných klíčů tohoto repozitáře, takže hello cizince ji nemůže ani zaplnit, ani vytlačit nonce skutečného peera.

Hello bez to — od seedu, prvního kontaktu nebo release před ním — je stále přijato a před replay na jiný runtime je chráněno jen cache nonce na runtime a oknem posunu hodin.

Poté odpoví podepsaným welcome — vlastní kartou, id linku, svými značkami, vráceným nonce — a volající na welcome uplatní stejné kontroly (podpis, vrácený nonce, časové razítko, protokol, self, repozitář, důvěra). Každé selhání na kterékoli straně je typované odmítnutí (malformed, oversized, signature, stale, replay, protocol_unsupported, self_link, misaddressed, repository_mismatch, untrusted, capacity; synchronizaci lze odmítnout také jako not_active nebo unknown_link, operaci jako feature_unsupported), započítané v refused_in/refused_out a vypsané s detailem na mesh.cooperation.

Přenos

Protokol linku posílá zprávy Signed přes LinkTransport. Transport sdíleného serveru je prosté HTTP/1.1 přes jediného minimálního klienta cratu (ADR 0032: žádná závislost na HTTP klientu, žádné TLS, žádný async runtime); unit testy staví runtime za transport v procesu, který běží stejným protokolem. Autenticitu zajišťuje protokol; důvěrnost síť — provozujte linky v privátní síti nebo overlay (tailnet, WireGuard).

Přítomnost, tepy a expirace

Dvě vrstvy, záměrně oddělené:

  • Linky jsou spojení tohoto runtime. Při každém tepu (cooperation.heartbeat_seconds, výchozí 5) dialer provede jedno kolo synchronizace. Link je connected s výměnou do dvou tepů plus jedné sekundy, degraded po této době, unreachable po polovině expirace a expired — zahozen — po cooperation.expiry_seconds (výchozí 30, nejméně tři tepy). Selhané kolo couvá exponenciálně, s horní mezí na expiraci, takže se vracející peer je znovu vytočen do jedné expirace; neznámé id linku (peer se restartoval) provede handshake okamžitě, do téhož řádku tabulky.
  • Streamy žijí, dokud jejich beat roste. Každý runtime zvyšuje svůj beat při každém heartbeatu; kola synchronizace nesou beat každého streamu s údajem, jak dávno odesílatel viděl, že vzrostl, takže runtime dosažený jen přes relay žije a zhavarovaný runtime přestane bít pro všechny najednou. Beat je podepsán klíčem svého původce, takže relay může beat nést, ale nikdy ho vyrobit, a stáří, které hlásí, je omezeno na expiraci. Čerstvost se počítá na vlastních monotónních hodinách každého runtime — žádné dvoje nástěnné hodiny dvou počítačů se neporovnávají. Znovu načtený žurnál obnoví události, ale ne čerstvost, takže restart nikomu neoživí vlastnictví.

Události, řazení a deduplikace

Vše kooperativní je událost žurnálu: session_opened, session_closed, claim_acquired, claim_released, handover_published, handover_consumed, review_requested, review_answered, review_withdrawn. Člověk nebo agent čte žurnál složený, ne surový, přes majordomus monitor (docs/MONITOR.md, ADR 0133). Zahazuje nezměněnou re-projekci relace nástěnky při každém heartbeatu, označuje, co jmenuje čtenáře, a stránkuje s plným kauzálním klíčem, ne jen s Lamportovým razítkem. Událost nese svůj proud, hustou sekvenci, Lamportovo razítko, nástěnné hodiny svého původu (informativně), identitu repozitáře, klíč původu, tělo a podpis Ed25519 nad kanonickým JSON všeho z toho.

Záruky přesně:

  • Identita: (stream, seq).
  • Autenticita od začátku do konce: relay předává bajty; příjemce ověřuje klíč původu a vazbu klíče na proud. Relay může událost zadržet; nemůže ji padělat ani změnit.
  • Doručení: alespoň jednou. Aplikace: idempotentní — duplikát se počítá a nic nemění. Ne přesně jednou.
  • Pořadí: v rámci streamu pořadí sekvence; předčasný příchod čeká (až 256 na stream) na svou mezeru. Napříč streamy fold řadí podle (lamport, stream, seq). Pořadí doručení nikdy nemění stav (property test promíchá a zduplikuje doručení).
  • Dopředná kompatibilita: událost druhu, který tento exekutor nezná, se uloží a přepošle, neinterpretuje se. Událost známého druhu mimo meze se zamítne.
  • Meze: 48 KiB na událost, 32 KiB na tělo předávky, 64 cest na tvrzení, 64 proudů na uzel. Proudy, které vypršely před více než 15 minutami a nic je už nepotřebuje, se zhutní pryč, když žurnál přesáhne 20 000 událostí nebo 768 proudů, a vlastní takové proudy uzlu, když jich drží 48 — každý worktree stroje podepisuje klíčem stroje a každý restart serveru je nový proud, takže by stroj jinak naplnil svou kvótu pouhým během a jeho pozdější běhy by byly odmítnuty. Co zhutnění ponechává a jak je zhutněný proud, který se vrátí, znovu vyslyšen, je Zhutnění.

Synchronizace stavu

Replikace je porovnání značek, ne záplava. Požadavek na synchronizaci nese značky volajícího (na proud: nejvyšší pořadí, úder, stáří úderu) a události, které chybí posledním značkám odpovídajícího; odpověď nese značky odpovídajícího a události, které chybí značkám požadavku. Jedno kolo replikuje oběma směry. Runtime, který se připojí pozdě — nebo po rozdělení sítě znovu — dostane v prvních kolech přesně to, co jeho značkám chybí, což je po kompakci živý stav plus to, co kompakce ponechává, ne neomezená historie. Jakmile se značky shodují, kola nenesou žádné události: replikace se zastaví, nezacyklí.

Události kola dostanou to, co zbývá z limitu zprávy 900 KiB poté, co se serializuje zbytek zprávy — především její značky — a nikdy víc než 600 KiB. Značky žurnálu na mezích jeho proudu váží stovky kilobajtů a při pevných 600 KiB událostí vedle nich zpráva překročila mez a byla odmítnuta jako příliš velká, každé kolo, takže replikace přestala bez vysvětlení (I2139). Velikost se kontroluje na zprávě tak, jak je podepsána, kde se sestavuje, volajícím i odpovídajícím stejně; událost, která by ji přenesla přes mez, čeká na další kolo.

mesh.state skládá žurnál: každý runtime se stejnými událostmi a stejnými verdikty živosti spočítá stejný stav a stejný digest.

Přečtení stavu jednou odpovědí

Fold si drží svou historii — každou relaci, která kdy byla otevřena, každý claim, který byl kdy vzat — a 2026-10-10, s dvanácti runtimy, byl celek 449 kB z mesh.state a 313 kB z mesh.peers; stránka 80 událostí žurnálu měla 83 000 znaků, z toho 30 jedna relace, která se znovu otevírala. Odpověď, kterou klient pracovníka nemůže zobrazit, není odpověď. Obě schopnosti proto berou jeden vstup (apps/majordomus-cli/src/mesh/page.rs, I2414):

VstupHodnotyCo dělá
viewsummary (výchozí), fullShrnutí počítá relace a nároky podle postavení, dává každému runtimu jeho živost, počty a nejnovější živý nárok a vypisuje otevřené žádosti o review a předávky, které nikdo nevzal — nejvýše 24 od každého, zbytek se počítá, každý záměr oříznutý na 160 znaků. full je skládání.
claimsall (výchozí), liveJen nároky, které stále platí; jejich překryvy je následují.
afterLamportovo razítkoJen relace, claimy a předávky, které se po něm pohnuly.
sessionklíč nebo holé idJen to, co ta relace drží, otevřela nebo převzala.
runtime<node>-<runtime> nebo id uzluPouze ten runtime, nebo každý runtime toho stroje.
kindsessions, claims, overlaps, handovers, reviewsJen ta kolekce; ostatní odpovídají prázdně.
limitpočetNejvýše tolik z každé kolekce, nejnovějších podle Lamportova razítka, v pořadí skládání.
opentrueJen to, co je stále otevřené: aktivní relace, živé nároky, žádosti o revizi, na které nikdo neodpověděl, předávky, které nikdo nevzal.
matchingtextPouze to, co to zmiňuje, bez ohledu na velikost písmen: nárokovaná cesta, větev, úkol, pull request, slovo záměru nebo předmětu.

digest v každé odpovědi je vždy celého skládání, nikdy stránky: je to to, co dva runtime porovnávají. majordomus mesh state a mesh peers berou stejný vstup jako --full, --live-claims, --after, --session, --runtime, --kind, --limit, --open a --matching. Server starší než tento vstup jej odmítne jako neplatný; čtenáři v tomto repozitáři, kteří skládání potřebují (claimy integračního leasu, poradce mesh-review), se zeptají znovu bez něj a odpověď je stejná.

Tvrzení a leases

Claim je rozsah — cesty relativní k repozitáři — držený relací, exclusive (výchozí) nebo advisory. Cesty se potkávají podle peers::claims_meet, predikátu samotné nástěnky peerů: apps potkává apps/majordomus-cli; app ne.

  • Claim drží, dokud není uvolněn, jeho relace je otevřená a tep jeho streamu stoupl v rámci expirace. To je lease: obnovuje ho tep držitele a na každém runtime sám vyprší, když držitel přestane tepat. Žádná samostatná tabulka leasů neexistuje.
  • Přijetí: mesh.claim odmítne výhradní tvrzení, které se střetne s živým výhradním tvrzením jiné relace známé tomuto runtime — claim_conflict, jmenující tvrzení, s nimiž se střetává.
  • Souběh: dvě výlučná tvrzení učiněná tam, kde ani jeden runtime neviděl druhý (partition, nebo stejný heartbeat), vstoupí obě do žurnálu. Fold na každém runtime jmenuje jednoho vítěze — nejnižší (lamport, stream, seq) získání — a druhé uvádí jako conflicted s klíčem vítěze. Nikdy „vyhrává poslední paket“.
  • Uvolnění: nárok uvolní jen aktuální běh držitele (jinak not_own); nárok mrtvého držitele místo toho vyprší.
  • Poradní claimy hlásí překryvy a nic neodmítají. Oznámení z nástěnky peerů se promítají jako poradní claimy oznamující relace MCP.

Role jsou nároky

Způsob práce repozitáře umisťuje relace do rolí: ta, která přistává, ta, která vydává release, lanes, které opravují konfliktní větve, a workeři. Role se drží držením jejího exkluzivního claimu, takže podléhá všemu výše: druhé relaci je odepřena, dokud je držena, přesune se, když je claim uvolněn, a uvolní se, když její držitel přestane bít. Které role existují a jaký rozsah si každá nárokuje, je pravidlo project.the-cooperation-model-is-the-owners-word (ADR 0167), jediný záznam o tom; tento dokument tabulku nekopíruje. Obrazovka Mesh v UI (/ui/mesh) se ptá mesh.state na živé claimy a kreslí, kdo teď kterou roli drží.

Předávky

majordomus handover zapíše záznam pod .ai/local/state/handovers/ checkoutu. mesh handover publish (nejnovější záznam, nebo --path pod tím adresářem — nic jiného se nikdy nečte) jej změní v událost žurnálu: úloha, větev, head a čas z jeho front matter, dané --issue/--milestone a tělo v Markdownu, identifikované digestem těla. Nepřenáší se žádná cesta publikujícího stroje. Na jiném runtime mesh handover consume <id> --session <s> zaznamená spotřebování a zapíše předávku jako záznam stejného schématu do adresáře předávek toho checkoutu — jednou, bez ohledu na to, kolikrát se spotřebuje — jmenující lokální repozitář a mesh:<origin runtime> jako jeho worktree, takže majordomus handover --resolve na téže větvi ji najde jako předávku jiného checkoutu.

Zhušťování

Every server restart is a new stream, so a journal that kept everything would grow without bound and a machine would fill its 64-stream quota by running. Compaction drops the streams nothing needs any more. Each runtime runs it on its own, every sixty heartbeats (five minutes at the defaults), and only when its journal holds more than 20 000 events or 768 streams or when one node holds 48 streams or 15 000 events — then that node's streams alone are due. A node's own crowding matters because the journal-wide bound is no larger than one node's quota of 20 000 events: a machine alone fills its quota before the journal is over its bound, and past the quota every event of that machine is refused, a claim's release among them. A stream can go only when it has been silent for longer than the expiry plus 15 minutes, measured on the local clock from the last beat heard or, for a stream never heard beating, from when the journal learned of it (so the streams a restart reloads are not taken for dead at once). A runtime never drops its own stream. Of the streams that could go, one stays while:

  • it published a handover nobody has taken — for at most a day past those 15 minutes (HANDOVER_RETENTION), counted on the local clock or from the handover's signed publication time, whichever lapses first: the local count restarts with every server, so on its own a machine whose servers never live a day would keep a dead stream for ever. A handover is continuity meant to outlive the run that wrote it; the day bounds how much of its node's quota a machine's untaken handovers can hold. A handover somebody has taken keeps nothing. A lapsed one is still its author's record, and mesh handover publish offers it again under the same id.
  • drží převzetí předávky nebo odpověď na review, které zůstává. Kdo převzal předávku a kdo odpověděl na review, jsou události streamu přebírajícího. Zahození tohoto streamu, zatímco předávka nebo review zůstává, by způsobilo, že dosud nabízená předávka vypadá nepřevzatá a zodpovězené review znovu otevřené. Ponechání jednoho streamu může ponechat stream, který na ně odpověděl, takže se to aplikuje, dokud se nic nemění.

Kdo co převzal a kdo na co odpověděl, čte fold, jako je čte každý povrch. Žádost o revizi sama nic nedrží: žádá něco po relaci, která ji vytvořila, a jakmile je runtime této relace dávno mrtvý a nic jiného její proud nedrží, žádost zaniká s ním. Kompakce tedy zapomíná ukončené relace a nároky, převzaté předávky a žádosti toho, co zahazuje — nikdy nárok, který drží, ani relaci, která je otevřená, nikdy předávku, která je stále nabízena, a nikdy nezůstane, kdo co převzal nebo na co odpověděl. Property test to drží nad náhodnými historiemi mrtvých i živých běhů.

Žádost o review končí jedním ze tří způsobů a fold říká kterým:

  • answered: verdikt vynáší jiná relace.
  • withdrawn: runtime, který se ptal, ji ukončí pomocí mesh review withdraw (done, void nebo superseded). Může to udělat kterákoli relace tohoto runtime, protože relace MCP je jedno spojení a tazatel, který se znovu připojí, je další relace téhož runtime. Žádný jiný runtime to nesmí. Poznámka je vlastní result žadatele a nikdy není odpovědí.
  • expired: nikdo neodpověděl a žádající runtime přestal vysílat heartbeat.

Vypršení je čtení živosti jako u nároku, takže žádný runtime nečte hodiny jiného. Runtime, který usne a znovu zabuší, čte open znovu, a uzavření dotazující se relace požadavek neukončí, protože požadavek na lane přežije mnoho spojení. Konzument, který bere jen požadavky open (ReviewState::is_waiting), například příjem lane v executoru, proto vypršelé přeskakuje: měl by je pojmenovat, ne tiše zahodit. Požadavek může vedle jednořádkového předmětu nést body do 8 KiB: důkazy, příkazy a otázku. Runtime starší než pole čte požadavek bez jeho těla a drží review_withdrawn jako neprůhlednou událost, kterou přeposílá, ale nezapracovává.

Náhrobky. Zahozený stream zanechá náhrobek: sekvenci, které dosáhl, inzerovanou jako jeho značku, aby mu peer, který stream stále drží, neposílal znovu, a tep, při kterém byl naposledy slyšen. Opožděná kopie toho, co se drželo, nebo událost, kterou stream zapsal po zhutnění, nic nemění; značka stoupne nad pozdější událost, takže relay, který ji drží, ji přestane nabízet. Náhrobky žijí v paměti: restartovaný runtime žádné nemá, může mu být zhutněný stream poslán znovu a zahodí ho znovu, jakmile je tam ticho tak dlouho, jak musí být ticho u kteréhokoli streamu. Náhrobek se říká jen peeru, jehož značky ukazují, že ten stream drží: komukoli jinému neříká nic a čtyři tisíce, které plný žurnál smí držet, by jen zatěžovaly každou zprávu.

Zhutněný stream, který se vrací. Notebook, který spí déle než expirace a 15 minut, je jeho peery zhutněn pryč; probudí se jako tentýž proces, tedy jako tentýž stream, a píše dál. Čerstvý beat od něj — podepsaný jeho klíčem, z důvěryhodného původu, v rámci expirace a nad beatem, který drží jeho náhrobek — náhrobek zvedne: značka streamu spadne na nic nedrženého a peeři mu stream pošlou znovu od první události. Od první události, ne od značky náhrobku, protože to, co stream řekl, než byl zhutněn, je s ním opět živé: claim, který převzal a nikdy neuvolnil, platí znovu v okamžiku, kdy tepe, a runtime držící jen část za značkou by připustil claim, který s ním koliduje, a viděl by uvolnění claimů, které nikdy neznal. Původ svůj stream nikdy nezhutňuje, takže celý stream je vždy k dispozici ke stažení; návrat stojí jedno opětovné stažení, omezené kvótou uzlu a rozpočtem událostí kola. Relay přehrávající starý beat, jakkoli čerstvý tvrdí, že je, nezvedne nic.

Digesty při kompaktaci runtimů. Každý runtime rozhoduje sám, kdy je kompaktace na řadě, takže dva runtimy mohou po nějakou dobu držet různé sady mrtvých proudů: jeden zapomněl ukončené relace a claimy mrtvého proudu nebo převzatou předávku, které druhý stále uvádí. digest tyto záznamy pokrývá, takže mesh state a mesh verify mohou vypsat různé digesty na dvou runtimech, které se shodují na všem živém — držené a kolidující claimy, otevřené relace, stále nabízené předávky s jejich příjemci, zodpovězené revize — nejvýše po dobu jednoho rozvrhu, když jsou na řadě oba, a tak dlouho, dokud na řadě není jeden z nich. Značky tím nejsou dotčeny: náhrobek oznamuje pořadí, kterého jeho proud dosáhl, takže mesh verify a jeho converged, které porovnává značky, platí. Digesty porovnávejte mezi runtimy, které obě kompaktaci už provedly, nebo porovnávejte živé claimy.

Relace a nástěnka peerů

Nástěnka peerů zůstává lokálním pohledem stroje (ADR 0003, ADR 0044). Při každém heartbeatu ji spolupráce promítne: připojená relace MCP se stane relací meshe board-<peer> se svým klientem, intentem, checkoutem, větví a headem; každé oznámení se stane poradním claimem; změněné oznámení uvolní a znovu claimne; odpojená relace se zavře. Volání MCP majordomus_mesh_claim bez session jedná za vlastní relaci board-<peer> volajícího. Větev a head se čtou při změně relace, nikdy při heartbeatu.

Relace poskytovatele je na meshi dřív, než worker cokoli řekne (I2734). Startovní událost poskytovatele otevře <provider>-<provider session id> — claude-code-<uuid> pro Claude Code — na sdíleném serveru checkoutu s jeho klientem, větví a headem, a s úlohou a jejím názvem, dokud je nějaká aktivní; briefing vypíše toto id pod řádek Mesh:, takže pod ním worker může claimovat (majordomus_mesh_claim s tímto session). Obnovení nebo zhutnění to řekne znovu; koncová událost je zavře, čímž skončí jeho claimy na všech propojených runtime najednou. Id je vlastní id poskytovatele, takže cokoli, co drží payload poskytovatele, jmenuje tutéž relaci. Bez připraveného serveru nebo při neběžícím meshi se nic neotevře a událost není dotčena.

Úloha je na meshi, dokud pracuje (I2288). majordomus start nárokuje rozsah úlohy jako poradní nárok relace meshe task-<id>, s názvem úlohy jako záměrem a jejím úkolem, a klíč nároku uchovává v záznamu úlohy jako mesh_claim; finish tu relaci uzavře, čímž skončí její nároky na každém propojeném runtime. Každý stroj vidí, na čem úloha pracuje, a hlásí překryv; výhradní nárok před stavbou zůstává vlastním aktem pracovníka. V repozitáři s povolenou deklarací meshe start nejprve zajistí sdílený server checkoutu — čerstvý propojený worktree žádný nemá, dokud ho něco nespustí, protože tam nenastala žádná startovní událost poskytovatele — se stejným omezeným serve ensure, jaké běží při startovní události a při vstupu do shellu, pod stejnými přepínači (session.ensure_server_on_start, MAJORDOMUS_RUNTIME=off), a na stderr řekne, když nějaký spustí. Když nárok přesto nevznikne (runtime vypnutý, zastaralý nebo chybějící spustitelný soubor, server, který nezkonvergoval), start neselže: ohlásí WARN mesh s uvedením důvodu a nápravy. Bez povoleného meshe start nárokuje jen přes server, který už odpovídá.

Toto tvrzení se vynucuje (I2289, pravidlo majordomus.task-on-the-mesh). V repozitáři, jehož sledovaná deklarace říká enabled: true, check ohlásí aktivní úlohu, jejíž záznam nenese mesh_claim pod mesh, a finish --outcome completed ji odmítne, přičemž odmítnutí se zaznamená se jménem pravidla. Stav lze opravit bez ukončení úlohy: majordomus serve ensure, pak majordomus check, který vyhradí rozsah běžící úlohy, jakmile server odpovídá, a zaznamená klíč; finish také nejdřív vyhradí znovu. finish --check, kontrola před pushem, je otázka a nic nevyhrazuje. partial, blocked, no_match a failed se neodmítají a repozitář bez zapnutého mesh se neposuzuje.

Na úpravu lze zeptat se na její tvrzení dřív, než nastane (I2735). S session.edits_require_claim: true v politice hook poskytovatele před nástrojem (.claude/hooks/majordomus-edit-guard, pro Edit, Write, MultiEdit a NotebookEdit) spustí majordomus guard hook, který skončí kódem 2 — odmítnutím poskytovatele — když cestu nepokrývá ani rozsah aktivní úlohy, ani živé mesh tvrzení zapisující relace (<provider>-<session id>, id, které otevřela událost start); pojmenuje cestu a nápravu: majordomus start "<task>" --scope <path>, nebo majordomus mesh claim <path> --session <id> --advisory. Posuzuje se checkout, ke kterému soubor patří, takže relace spuštěná v jednom checkoutu, která edituje soubor jiného, je posuzována podle úlohy a tvrzení toho druhého. Lokální stav, cesta mimo checkout, repozitář bez povoleného mesh a server, který neodpovídá, jsou propuštěny. Stejný úsudek je guard.edit přes MCP a HTTP.

Hook zatím není zapojen a dokud nebude, edits_require_claim z politiky v Claude Code nic neodmítá (I3010). majordomus capture install zapisuje záznamy UserPromptSubmit, SessionStart, SessionEnd a PreCompact v .claude/settings.json a žádný záznam PreToolUse; skript hooku a guard.edit existují, ale žádná úprava se k nim nedostane. Jeho zapojení (nejprve poradní) a poté odmítání nezabrané úpravy jsou práce na generovaném pluginu, která následuje po alfě. Do té doby si hlídač zapojí ručně ten, kdo jej chce: přidejte tento záznam pod "hooks" v .claude/settings.json (sdíleno se všemi, kdo v checkoutu pracují) nebo v .claude/settings.local.json (jen tento stroj) a spusťte novou relaci Claude Code, která své hooky čte při startu:

"PreToolUse": [ { "matcher": "Edit|Write|MultiEdit|NotebookEdit",
  "hooks": [ { "type": "command", "command": "${CLAUDE_PROJECT_DIR}/.claude/hooks/majordomus-edit-guard" } ] } ]

Hook se ptá bin/majordomus-cli a nikdy ho nesestavuje: kde tento spustitelný soubor chybí, je každá úprava propuštěna.

To, co relace sdílí, jsou metadata — záměr, úkol, úloha, milník, větev, head, špinavost, identifikátor revize kontextu. Těla kontextů se nereplikují; předávka je odkazovaný artefakt, který peer zhmotní.

Selhání a obnova

událostco dělá který runtimeco konverguje
proces peera je zabitjeho linky přejdou degradováno → nedosažitelné → vypršelo; jeho proud přestane bít; jeho relace a claimy vypršípři restartu je to týž runtime, nová instance; znovu naváže spojení do jednoho řádku tabulky a synchronizuje; claimy jeho předchozího běhu zůstávají prošlé
síť se rozdělíkaždá strana nechá expirovat streamy druhé a může převzít, co druhá drželapři hojení po jednom kole v každém směru; souběžné výhradní nároky se vyřeší na jednoho jmenovaného vítěze; otisky se shodují
peer se restartuje, zatímco je propojenodpovídající strana odmítne staré id spojení jako unknown_link; vytáčející strana řekne hello znovuřádek tabulky se zachová, restarts počítá
one machine's dead streams fill its node's event quotaevery later event of that machine is refused as capacity — claims, releases, closes; mesh verify's rejections check fails naming capacity; the node is due for compaction at three quarters of its quota, and a dead stream's untaken handover lapses by its signed publication time, so a restart does not renew itonce the dead streams go, the refused events are offered again and accepted; the release arrives and the claim reads released everywhere (I2825)
two processes speak for one checkout (a server older than v0.27.0 that never left the mesh when another took its lease)every peer that hears both replaces one link with the other each round, and unknown_link: … previous instance repeats; mesh verify's instances check fails naming both instancesstopping the process the lease does not name ends the flapping; the lease's holder is the checkout's one runtime
relay zemře v řetězci A–B–CA a C si navzájem nechají vypršet proudy kvůli ztraceným úderůmkdyž se B vrátí, značky se znovu vyrovnají a nic se neduplikuje
nepřátelský nebo poškozený vstuptypované odmítnutí, započítané, vypsané; runtime dál obsluhujedo žurnálu nevstupuje nic
pád utrhne soubor deníku, nebo disk odmítne připojenípřipsání zapíše chybějící nový řádek jako první a synchronizuje; selhané připsání se počítá (write_failures) a jeho operace odpoví "durability": "not_durable" — nikdy není potvrzeno jako trvalé a mesh claim, mesh session open a ostatní zápisy vypíšou NOT DURABLE; řádek, který se při opětovném načtení nečte, se počítá (unreadable) a mesh doctor selže na své kontrole journal; kompaktace zapíše synchronizovaný soubor a přejmenuje jej přes starý; soubor patří jen vlastníkovi (0600)každá úplná událost se načte znovu, událost po utržené řádce se čte a událost, která nedorazila na disk, se stále replikuje z paměti a od peerů
soubor žurnálu větší než mez opětovného načtení (64 MiB)opětovné načtení čte do meze, do posledního celého řádku, a zbytek nechá na disku: je spočten (unread_bytes), jmenován kontrolou mesh doctor journal a zkopírován bajt po bajtu do souboru, který kompakce zapisujeudálosti v mezích se načtou znovu; zbytek tento runtime ani neskládá, ani nepředává, dokud se soubor neodsune a peeři jej znovu nenaplní

Odpověď operace říká, co dosáhlo disku: durability je synced, když byla událost připojena k souboru deníku a jeho data synchronizována před odpovědí, memory, když deník podle konfigurace nemá soubor, a not_durable, když zápis selhal. „Synced“ je synchronizace dat souboru deníku operačním systémem (fdatasync na Linuxu, fsync na macOS, který nevyprazdňuje vlastní cache disku), ne příslib o úložišti pod ním.

Bezpečnostní model

Proti čemu se mesh brání a jak:

hrozbaanswer
zfalšovaný inzerát, hello, welcome, sync nebo událostEd25519 nad kanonickými bajty pod doménami na zprávu; id uzlu se odvozuje z klíče, nikdy se nenese
přehraný hello nebo sync roundcache nonce; rostoucí čítač na spojení; sekvence na instanci pro inzeráty a události; hello, které jmenuje svého příjemce, odmítá misaddressed každý jiný runtime (I2132)
runtime jiného repozitářerepository_mismatch při handshaku, repository odmítnutí při příjmu
neznámý nebo nedůvěryhodný klíč v sítipozorováno discovery, odmítnuto untrusted při handshaku; jeho předávané události odmítnuty untrusted při ingestu
klíč stažený z trust.allow, pak restartsoubor žurnálu je vstup: znovu načtená událost prochází kontrolami a rozhodnutím o důvěře jako přijatá a co je odmítnuto, se hlásí podle důvodu (untrusted); jen klíč tohoto stroje nepotřebuje povolení
relay, který mění nebo vymýšlí událostipodpis původu selže u každého spotřebitele
důvěryhodný klíč razící události na konci rozsahu Lamportových hodinudálost o více než 2^32 před hodinami příjemce je odmítnuta clock_ahead (MAX_LAMPORT_LEAD), takže hodiny rostou nejvýše o tolik na uloženou událost. Razítko nastavené nízko, aby vyhrálo spor o nárok, není odmítnuto: Lamportova razítka jsou vlastním tvrzením jejich zapisovatele
zahlceníomezené datagramy a omezená fronta jich do registru (1024; záplava nad ni se zahodí a počítá jako overflow v mesh.status), zprávy (900 KiB), události, proudy (1024, nejvýše 64 na uzel), události na uzel (20 000), čekající události (256 na proud, 4096 celkem), peery (256), cíle vytáčení (8 na uzel, přítomné runtimy před zastavenými), vypsaná zamítnutí (128), registr (256; vytlačeny nejsou jen povolené záznamy)
zfalšovaný nebo přehraný report o živostitepy podepisuje jejich původ a počítá se jen vyšší podepsaný tep; předané stáří se omezí na expiraci; proud vzniká ze značky jen tehdy, když se značka ověří, její původ je důvěryhodný a její tep čerstvý
větev nebo necommitnutá úprava, která přidá klíč, hub, seed nebo tofuplatí deklarace pracovního stromu s odstraněným každým rozšířením z potvrzené kopie kmene; mesh doctor jmenuje každý ignorovaný řádek (trust-root) a mesh-check odmítne potvrzený tofu (I2135)
zde spotřebovaná nepřátelská předávkakaždé pole front matteru je při příjmu jednořádkové; název souboru záznamu zachovává jen číslice časového razítka, hex a [A-Za-z0-9_-] a cesta mimo adresář předávek se odmítá
hostitel v síti, který posílá claimy, relace, předávky nebo review k podpisu tímto uzlempožadavek z adresy, která není loopback, spustí jen schopnost, která čte nebo jejíž vstup je podepsaná zpráva (mesh.link.hello, mesh.link.sync, mesh.register); každá další routa odpoví 403 forbidden dřív, než běží její handler (ADR 0126)
webová stránka řídící server (DNS rebinding)požadavek měnící stav z prohlížeče se přijme jen z vlastního původu serveru a jen když je adresován IP literálem nebo localhost
registrace, která mapuje meshodmítnutá registrace se odpovídá samotným odmítnutím; přijatá se dozví hub a důvěryhodné uzly vlastních repozitářů, nikdy pozorované cizince ani jiný repozitář (I2133)
pozdravy od cizíhocache přehrání a seznam odmítnutí drží jen to, co poslal důvěryhodný klíč tohoto repozitáře, nebo jsou omezeny
zastaralý peer držící rozsah navždyclaimy žijí, jen dokud tep držitele roste
publikování libovolných souborůpublikace předávky čte jen .ai/local/state/handovers/
budoucí protokolprotocol_unsupported s oběma rozsahy, ne zahozené spojení

Proti čemu se nebrání: pozorovatel v síti vidí, že Majordomus běží, jeho oznámení a — u propojených peerů — záměry relací, rozsahy nároků, předměty review a těla zveřejněných předávek, protože nic není šifrováno; provozujte to v privátní síti nebo overlay. Držitel povoleného klíče uzlu je tím uzlem. Kompromitovaný důvěryhodný peer může pod svou identitou zveřejňovat zavádějící nároky a předávky — jsou mu připsány a vyprší s ním, ale do té doby se jim věří. Výchozí stav — žádná deklarace, nebo enabled: false — existuje pro prostředí, kde je zveřejnění přítomnosti nepřijatelné.

Konfigurace

# .ai/repo/mesh/majordomus.yaml
schema: mesh/v1
kind: mesh-declaration
id: majordomus
enabled: true                 # nothing opens without it
multicast: { enabled: true, group: 239.255.77.77, port: 7741, ttl: 1, interval_seconds: 15 }
broadcast: { mode: disabled }
rendezvous: { endpoints: [], interval_seconds: 60 }
trust:
  policy: deny_unknown
  allow:                      # `majordomus mesh identity` on each machine
    - 5b0f…e1
    - 9a41…07
cooperation:
  enabled: true               # default once the mesh is enabled
  heartbeat_seconds: 5
  expiry_seconds: 30          # at least three heartbeats
  seeds: [http://10.0.0.12:8741]   # dial these whether or not discovery hears them
  # repository: my-project    # required for a shallow clone; separates a fork

Deklarace je jediné místo, kde se chování meshe konfiguruje; žádné proměnné prostředí pro něj neexistují. Aby jej mohly vytáčet jiné stroje, server musí naslouchat za loopbackem: majordomus serve --host 0.0.0.0 (nebo objekt nasazení). Server na loopbacku může stále vytáčet ven a linka, jakmile je vytočena, replikuje oběma směry.

--host trvá tak dlouho jako ten jeden proces. Pro stroj, který má být vždy dostupný, exportujte MAJORDOMUS_HTTP_HOST=0.0.0.0 ve startovacím souboru shellu: není to konfigurace meshe — pojmenovává rozhraní, na které se váže každý lokální server tohoto stroje, ať ho spustí kdokoli — a MCP.md říká, co zpřístupňuje.

Linked worktree tuto proměnnou nepřebírá: jeho vlastní server zůstává na loopbacku, pokud --host neřekne jinak, protože tvář stroje v síti je server primárního checkoutu. Worktree nepotřebuje vlastní server ani pro mesh. majordomus mesh v checkoutu bez běžícího serveru se zeptá připraveného serveru téhož repozitáře — nejprve primárního — a jmenuje ho na stderr (asking the server of <checkout> at <url>). Jediný příkaz, který tam odmítá, je mesh handover consume bez --no-materialize, protože by se předávka zapsala do jiného checkoutu.

Mesh tohoto repozitáře

Tento repozitář provozuje vlastní mesh: .ai/repo/mesh/majordomus.yaml je potvrzen s enabled: true, takže každý server každého jeho worktree oznamuje, naslouchá a propojuje se. To je zrevidované rozhodnutí tohoto repozitáře, ne výchozí chování nástroje: kostra, ze které nový repozitář začíná, deklaraci nenese a nic se tam neotevře, dokud ji jeho vlastní operátor nepotvrdí (záruka mesh-off-by-default).

deklarováno
multicast239.255.77.77:7741, TTL 1 — jen místní segment; sockety jsou svázány s SO_REUSEADDR a SO_REUSEPORT, takže skupinu slyší každý server na stroji
broadcastvypnuto
rendezvous hubyjetson (192.168.100.30, tailnet 100.92.246.32), lundra (192.168.100.10, tailnet 100.65.22.118) a dva MacBooky Pro vlastníka (tailnet 100.120.245.51 a 100.108.237.68), port 8791, každých 30 s
seedyžádné
spoluprácevýchozí hodnoty: tep 5 s, expirace 30 s
důvěradeny_unknown s pěti klíči: 641bdb94 (MacBook Pro vlastníka), 5d81b5c9 (druhý MacBook Pro vlastníka), aaba18ea (iMac vlastníka), 9d652b2c (jetson), 25c9758f (lundra)

Komu se důvěřuje. Pěti strojům vlastníka a nikomu jinému. Worktrees jednoho stroje sdílejí klíč toho stroje a propojují se jako on; dva stroje se propojí, jen když jsou uvedeny oba klíče, proto je na seznamu i vlastní klíč MacBooku. Jakýkoli jiný klíč na segmentu je pozorován, není mu důvěřováno v ničem a při pokusu o spojení je odmítnut untrusted.

Kde jsou huby. Hub je server tohoto repozitáře na jetsonu, lundře nebo na jednom ze dvou MacBooků Pro vlastníka, naslouchající mimo loopback na portu 8791. jetson a lundra jsou uvedeny svou adresou v LAN i v tailnetu; MacBook jen adresou v tailnetu, protože jeho adresa v LAN je pronájem z DHCP, který se mění — první MacBook byl deklarován na 192.168.100.93 a router mu od té doby přidělil .91, takže jej nějakou dobu žádný Mac nedosáhl. Multicast nemůže přejít tailnet a firewall macOS jej příchozí zahazuje, takže huby jsou způsob, jakým se stroje na různých segmentech navzájem nacházejí. Každý server se registruje u každého hubu, který dosáhne, jedno vlákno na hub s mezí 5 sekund na požadavek. Hub, který neodpovídá, se ptá méně a méně často — 30sekundový interval se při každém selhání zdvojnásobí, až osmkrát — a nic jiného nezdržuje: server, jehož všech šest adres hubů je nedostupných, startuje, objevuje a linkuje přes multicast a odpovídá na požadavky přesně jako server bez hubů (mesh status ukazuje poskytovatele rendezvous running s sent 0).

Co putuje. To, co říká bezpečnostní model výše: oznámení na místním segmentu a k hubům; k propojeným runtimům strojů vlastníka metadata relací, rozsahy nároků, předměty review a těla zveřejněných předávek. Nic není šifrováno; každá adresa hubu je v privátní síti nebo tailnetu a test/cases/491_the_mesh_is_on_here.sh odmítne deklaraci, která uvádí veřejnou.

Přidání stroje. Spusťte tam majordomus mesh identity, přidejte klíč pod trust.allow s komentářem pojmenovávajícím stroj a jeho id uzlu do řádku důvěry v tabulce výše a commitněte obojí; majordomus mesh doctor na tom stroji projde kontrolou trust, jakmile je klíč uveden, a do té doby uvádí nápravu. Nový hub je server spuštěný pomocí majordomus serve --host 0.0.0.0 --port 8791 na tom stroji s adresami přidanými pod rendezvous.endpoints; poté tam sudo majordomus mesh firewall apply, protože hub, jehož firewall hostitele zahazuje port 8791, je hub, u kterého se nikdo nemůže zaregistrovat (Firewall hostitele).

Huby jako služby. Na lundra je hub uživatelská jednotka systemd, ~/.config/systemd/user/majordomus.service, spouštějící release spustitelný soubor vyhrazeného checkoutu (~/dev/prismatic-majordomus-hub, odpojený na trunku) s serve --host 0.0.0.0 --port 8791 --idle 0, Restart=always a sdílením tohoto checkoutu; aktualizace je tam fetch a checkout trunku, just build-release a systemctl --user restart majordomus. Na MacBooku Pro vlastníka hub spouští nainstalovaný release z vyhrazeného worktree (~/dev/prismatic-majordomus-hub, odpojeného na trunku): majordomus serve --repo ~/dev/prismatic-majordomus-hub --host 0.0.0.0 --port 8791 --idle 0, spuštěno ručně, protože checkout, ve kterém člověk pracuje, už spouští svůj vlastní sdílený server a druhý je odmítnut. Agent launchd stejného tvaru — ~/Library/LaunchAgents/dev.majordomus.hub.plist s RunAtLoad, KeepAlive a těmito argumenty — má nainstalovat operátor a instalátor, který zapisuje kteroukoli jednotku, je stále dlužen jako typovaná schopnost (ADR 0059).

Huby následují release (ADR 0161). Hub je uzel jako každý jiný: s follow: stable drží svůj nainstalovaný Majordomus na verzi, kterou releases/latest.json jmenuje, restartuje se na ni pod svým správcem služeb, když není připojena žádná relace nebo po omezené odkladné lhůtě, zkontroluje svou připravenost a živé kolo mesh verify a sám se vrátí na předchozí verzi, když cokoli z toho selže, přičemž na nástěnce jmenuje uzel, verzi a selhavší kontrolu. Typovaná schopnost zapisuje jednotku systemd a agenta launchd (I2722). Dokud to nepřistane, role release po každém releasu upgraduje huby ručně a každou verzi přečte zpět.

Vypnutí. Commitněte enabled: false; server spuštěný poté nic neotevře, mesh status řekne proč a briefing řekne Mesh: off, as declared. Tím se ADR 0059 zvrací, takže je to samostatné rozhodnutí: případ 494 odmítne vypnutou deklaraci, dokud se rozhodnutí a případ nezmění s ní.

Zda to server drží. Deklarace říká, co má běžet; sdílený server rozhoduje, co běží, a obojí se může rozejít — 2026-09-12 běžely tři stroje s nezapsaným enabled: true, zatímco zapsaný soubor říkal false, a ten, kdo se k němu vrátil, byl potichu sám (ADR 0059). Proto mesh doctor končí kontrolou runtime, která je verdiktem serveru:

runtime říkákdyverdikt
active as <node> since …: n of m provider(s) running; …server aktivoval meshdrží; selže, jen když selhaly všechny deklarované transporty
off, as declared — …deklarace je vypnutá a server nechal mesh vypnutýplatí
the declaration is enabled and this server's mesh is not active — <reason>server nemohl, nebo nechtěl aktivovat povolenou deklaraciselže, s dopadem a restartem, který následuje po opravě důvodu
not decided in this process: …v dotázaném procesu neběží žádný serverplatí: absence, ne verdikt

majordomus mesh doctor se ptá běžícího serveru tohoto checkoutu na jeho report, pokud jej obsluhuje — verdikt je serveru — a běží ve vlastním procesu, pokud ne; při jakékoli neúspěšné kontrole skončí kódem 10. majordomus run mesh.doctor je vlastní kontrola samotného vyvolávajícího procesu, pod jeho vlastním adresářem stavu, a nikdy se neptá serveru.

Začátek relace to říká. Briefing, který vloží startovní událost poskytovatele, nese jeden řádek pod řádkem serveru, kdykoli repozitář sleduje deklaraci a server je připraven:

Shared server: ready http://127.0.0.1:8741 pid 123 version 0.10.0
Mesh: active — 2 of 2 provider(s) running; 2 node(s) known, 2 trusted, 2 present
Mesh: off, as declared
Mesh: DECLARED ENABLED BUT NOT ACTIVE — identity: …/majordomus/node.json: Is a directory (os error 21)

(jedno ze tří). Sledovanou deklaraci, kterou index nemůže přečíst, je Mesh: DECLARATION NOT READ — …. Řádek je mesh doctor spustitelného souboru vykreslené knihovnou háčků, která neposílá žádný vlastní požadavek (SECURITY.md); bez připraveného serveru žádný řádek není a řádek serveru výše říká proč.

Co to drží. test/cases/491_the_mesh_is_on_here.sh: commitnutá deklarace je ta zrevidovaná, jak ji čte spustitelný soubor, vlastní kontrola prochází nad tímto repozitářem, stroj, jehož klíč není uveden, drží mesh zavřený a nikoho nevolá, dva worktrees pod jedním uvedeným klíčem se propojí a stroji, jehož klíč chybí, se to řekne. test/cases/494_the_mesh_is_declared_and_held.sh: deklarace je sledovaná, povolená, deny_unknown, na allowlistu má alespoň tolik klíčů, kolik strojů jmenuje řádek důvěry výše, a jmenuje hub — takže přidání stroje je klíč pod trust.allow a id uzlu v tom řádku, v jednom commitu — a jednorázový repozitář dokazuje každou odpověď briefingu a exit 10 přes startovní událost. Huby se spouštějí ručně; instalátor, který jeden spustí jako službu, je dlužen jako typovaná schopnost nebo workflow Rhai (ADR 0059).

Firewall hostitele

Deklarace říká, kdo se smí linkovat; firewall hosta říká, co se vůbec dostane k socketu, a obojí se mlčky rozchází. 2026-10-08 lundra, deklarovaný hub, provozovala ufw s default deny a bez pravidla pro port 8791 nebo multicastovou skupinu: jádro zahazovalo každou registraci a každé oznámení z LAN (187 řádků [UFW BLOCK] za týden, 109 za poslední hodinu), zatímco mesh doctor hlásil, že platí každá kontrola — protože každá kontrola, kterou provedl, byla faktem procesu a žádná nebyla faktem hosta. Takže přijetí je odvozený požadavek s verdiktem, mesh.firewall (apps/majordomus-cli/src/mesh/firewall.rs):

odvozeno zpravidlo
multicastmulticast.enabled, .group, .portUDP na port skupiny z libovolného zdroje — TTL ho drží na segmentu
broadcastbroadcast.mode, .port, .networksUDP na port, z deklarovaných sítí
hubkaždá položka rendezvous.endpoints, jejíž adresa je jednou z adres tohoto strojeTCP na tento port, ze sítí flotily
odkaz--port, když server tohoto checkoutu naslouchá mimo loopbackTCP na tento port, ze sítí flotily

Sítě flotily jsou obklopující soukromé rozsahy každé adresy, kterou deklarace jmenuje (huby a seedy): 192.168.x.0/24, 10.0.0.0/8, 172.16.0.0/12, 100.64.0.0/10 pro tailnet — a vlastní rozsahy tohoto stroje, když žádné nejmenuje. Veřejná adresa neodvozuje žádnou síť a případ 491 ji v deklaraci odmítne dřív. Bez sítě, ze které by se povolovalo, se pro port TCP ani broadcast neplánuje žádné pravidlo: z jakéhokoli zdroje se povoluje jen multicastová skupina a otevření portu každé adrese zůstává aktem operátora. Stroj, který není hub, se zapnutým multicastem potřebuje právě jedno pravidlo: port skupiny.

majordomus mesh firewall vypíše plán, backend (ufw, nftables, aplikační firewall macOS, nebo none), příkazy, které tam plán zavedou, vlastní slovo firewallu o něm — present, missing, inactive, nebo unobservable bez roota — a co kernel zalogoval jako zahozené směrem k těmto portům za posledních pět minut, což lze číst bez roota na stroji, jehož uživatel je v adm; skončí s exit 10, když je pozorováno chybějící pravidlo nebo byl zalogován drop. sudo majordomus mesh firewall apply tyto příkazy spustí a znovu se zeptá firewallu, takže verdikt potom je verdikt firewallu; odmítne, aniž by cokoli spustil, bez roota, bez backendu, nebo když je firewall neaktivní, protože pak nic nefiltruje a žádné pravidlo se nedluží. Každý příkaz, který spustil, je hlášen s vlastním exitem, ok nebo ne, a slovy nástroje vedle něj. U nftables se pravidla přidávají do řetězce input tabulky inet filter, který plán předpokládá a nevytváří: hostitel, který filtruje v jiné tabulce, dostane odmítnutí od nft pro každý příkaz a verdikt missing. Každé pravidlo, které zapíše, nese komentář majordomus mesh: …, takže jej lze odlišit od pravidel operátora a odstranit jako sadu. Nabízí se pouze na příkazové řádce: spouští privilegovaný nástroj hostitele, což nesmí nic dosažitelné přes HTTP nebo MCP. Kontrola firewall doktora, mezi kontrolami procesu a verdiktem runtime serveru, je týž report jako jeden řádek.

$ majordomus mesh firewall
backend     ufw (linux)
plan        2 rule(s) from 100.64.0.0/10, 192.168.100.0/24
  multicast  udp/7741 to 239.255.77.77 from any — …
  hub        tcp/8791 from 100.64.0.0/10, 192.168.100.0/24 — …
observation unobservable: ufw status needs root; run `sudo majordomus mesh firewall` to observe the rules
blocked     7741: 81, 8791: 28 (kernel log, last 300 s)
commands    what `sudo majordomus mesh firewall apply` runs:
  ufw allow in proto udp to 239.255.77.77 port 7741 comment 'majordomus mesh: multicast discovery'
  ufw allow in proto tcp from 100.64.0.0/10 to any port 8791 comment 'majordomus mesh: rendezvous hub'
  ufw allow in proto tcp from 192.168.100.0/24 to any port 8791 comment 'majordomus mesh: rendezvous hub'
verdict     the kernel logged 109 inbound datagram(s) or connection(s) toward the mesh's ports dropped in the last 5 minutes, and the firewall could not be asked without root: run `sudo majordomus mesh firewall apply`

Čím není. Správce firewallu: povoluje porty mesh z privátních sítí flotily a nedotýká se žádného jiného pravidla, nikdy nerozšíří zdroj na veřejný rozsah a nikdy nic neodstraní. Na macOS aplikační firewall povoluje spustitelný soubor, ne port, takže tamní plán je povolit příchozí spustitelný soubor serveru, a stroj s vypnutým aplikačním firewallem nepotřebuje nic. Kde se nenajde žádný backend, nic se nepozoruje a kontrola platí jako absence, jako runtime v procesu, v němž žádný server neběží.

Zaznamenáno, 2026-10-08. Na lundra byl výše uvedený report pořízen jako vlastník, pak jako root (missing, obě pravidla, 77 a 28 zahozených paketů v okně), pak sudo majordomus mesh firewall apply přidal tři pravidla (Rule added, Rule added, Rule updated — pravidlo pro LAN hub bylo ručně přidáno o čtvrt hodiny dříve), po čemž report jako root ukázal present bez zahozeného paketu v okně a mesh nodes hubu, který dosud nikoho nevypsal, vypsal oba Macy v LAN jako přítomné a důvěryhodné, každý slyšený multicastem a registrovaný přes rendezvous.

Provoz

majordomus mesh doctor            # prerequisites, the host firewall, and the server's runtime verdict; exits 10 on a failed check
majordomus mesh firewall          # what the host firewall must admit, whether it does, and the commands; exits 10 when it does not
sudo majordomus mesh firewall apply   # admit it; needs root, refuses without
majordomus mesh identity          # this machine's key, for trust.allow
majordomus mesh status            # discovery: providers, tallies, refusals
majordomus mesh nodes             # discovery: one row per node × runtime
majordomus mesh peers             # machines → runtimes → active sessions, each link's state; --full for all
majordomus mesh peer <runtime>    # one runtime; exits 10 when unknown
majordomus mesh state             # counts, each runtime's newest claim, open reviews, untaken handovers, digest
majordomus mesh state --full --live-claims   # every held claim; --after, --session, --runtime narrow
majordomus mesh events --after 0  # the journal
majordomus mesh verify            # a live round with every dialed peer, convergence; exits 10 on failure
majordomus mesh session open --session s1 --client codex --issue '#184'
majordomus mesh claim apps/majordomus-cli --session s1 --issue '#184'   # exits 10 on claim_conflict
majordomus mesh release <claim-key>
majordomus mesh handover publish --issue '#184'
majordomus mesh handover consume <id> --session s2
majordomus mesh review request feature/x --session s1 --reviewer <runtime>
majordomus mesh review answer <request-key> --session s2 --verdict approved
majordomus mesh review withdraw <request-key> --session s1 --note 'superseded by the rebuilt head'

Každý příkaz má --format json, který vypíše odpověď schopnosti beze změny.

HTTP a OpenAPI. GET /api/v1/mesh, /nodes, /identity, /doctor, /firewall?port=, /cooperation, /peers, /peer?runtime=, /state, /events?after=&limit=; POST /api/v1/mesh/register, /verify, /sessions, /sessions/close, /claims, /claims/release, /handovers, /handovers/consume, /reviews, /reviews/answer, /reviews/withdraw, /link/hello, /link/sync. Schémata jsou v /openapi.json, generovaná z deklarací schopností.

MCP. majordomus_mesh, _nodes, _identity, _doctor, _firewall, _register, _cooperation, _peers, _peer, _state, _events, _verify, _session_open, _session_close, _claim, _release, _handover_publish, _handover_consume, _review_request, _review_answer, _review_withdraw. Agent si nárokuje práci pomocí majordomus_mesh_claim místo vymýšlení koordinačního kanálu; bez session volání jedná za vlastní relaci agenta na nástěnce.

Cockpit. /cockpit/mesh: tento uzel, kooperace (runtime, identita repozitáře, endpointy, heartbeat a expirace, protokol, čítače, digest), stroje → runtimy (odznak živosti, „poslední heartbeat před Ns“, stav spojení, počty prací) → relace (záměr, úkol, větev, claimy), konflikty claimů, předávky, revize, odmítnutí kandidáti, poskytovatelé objevování a uzly. Akce spouštějí stejné schopnosti z jejich stránek Schopnosti.

Řešení problémů

  1. mesh doctor na každém počítači. Neúspěšná kontrola declaration, identity, trust, repository, udp nebo multicast pojmenovává svůj dopad a nápravu; neúspěšná kontrola firewall patří hostiteli: pravidlo, které mesh potřebuje, je pozorováno jako chybějící, nebo kernel zalogoval, že firewall zahazuje provoz k portům meshe — sudo majordomus mesh firewall apply; neúspěšná kontrola runtime patří serveru: deklarace je povolena a její mesh není aktivní, a proč. Hub, u kterého se registrují všechny ostatní počítače a který nikoho neuvádí, je případ firewallu, dokud mesh firewall neřekne jinak.
  2. mesh status — discovery. Žádné uzly: multicast se mezi stroji neroutuje; deklarujte cooperation.seeds nebo rendezvous. Rostoucí odmítnutí signature: rozbitý nebo nepřátelský odesílatel. Odmítnutí version: peer běží se spustitelným souborem starším než protokol discovery 2.
  3. mesh peers / mesh.cooperation — odkazy a odmítnutí. repository_mismatch: runtime servírují různé repozitáře, nebo je jeden shallow klon či fork s jinou identitou — porovnejte řádek repozitáře mesh doctor na obou. untrusted: přidejte klíč do trust.allow. protocol_unsupported: upgradujte starší spustitelný soubor. Odkaz uvízlý v connecting s last_error „connection refused“: peer naslouchá na loopbacku.
  4. mesh verify — živé kolo s každým vytočeným peerem a zda oba po něm drží stejné značky. NOT converged hned po dávce událostí je jedno kolo zpoždění; trvalé s journal pending > 0 je peer přeposílající částečný proud. Dva runtime vypisující různé digesty při konvergenci jsou kompakce v různých rozvrzích (Kompakce), ne divergence čehokoli živého.
  5. Logy serveru nesou runtime_id, peer, link_id, session_id, repository_id a odmítnutí code u každé události linku.

Testování a důkazy

úroveňcokderuns
unit & propertydedup žurnálu, mezery, nepřátelské události, tepy; zhutnění nic živého nemění, ohraničuje držení předávky a znovu slyší vracející se stream; pořadí skládání/nezávislost na duplicitách a exkluzivita; odmítnutí handshaku; relay tří runtime; sladění po rozdělení; projekce nástěnky; materializace předávky; identita repozitáře; plán firewallu z deklarace, jeho vykreslení pro každý backend a posouzení výstupu samotného firewallucargo test --lib meshCI rust
firewall hostitelecommitnutá deklarace odvozuje port skupiny; doctor nese firewall; žádná deklarace nepotřebuje nic; deklarace jmenující tento stroj hubem odvozuje pravidlo hubu jen ze svého soukromého rozsahu; apply odmítne bez root, nic nespouští; odpoví nástroj MCPtest/run.sh 495_the_firewall_admits_the_meshCI suite
dva runtime v procesuskutečný protokol linku přes transport v procesu: runtime zhutněný, zatímco spal, je znovu vyslyšen celý; příjemci a odpovědi přežijí své běhy; převzatá předávka nenechá svého zastaveného vydavatele nikdecargo test --test mesh_compaction, test/run.sh 493_mesh_compaction_keeps_what_is_liveCI rust, suite
integrace více procesůoddělené procesy majordomus serve s oddělenými klíči přes TCP: spolupráce dvou runtime (relace, claimy, konflikty, review, předávka, parita CLI, verify), izolace repozitářů, nedůvěryhodný klíč, expirace SIGKILL a restart, tři runtime v řadě, nepřátelské zprávy přes HTTP, dva worktree pod jedním klíčemcargo test --test mesh_cooperationCI rust, macos
síťová laboratořčtyři kontejnery Linuxu na jednom mostě dockeru: multicastové objevování bez seedů, plný mesh handshake, izolace repozitářů, claim a konflikt napříč uzly, předávka napříč uzly, konvergence tří uzlů, síťový split a hojení, pád a restart procesu, mesh verify v uzlutest/mesh-lab/run → target/mesh-lab/evidence.jsonCI mesh-lab
fyzické strojepostup nížeručnízaznamenáno při běhu

Fyzické ověření na více strojích

Na dvou strojích A a B v jedné síti, každý s checkoutem téhož repozitáře sestaveným ze stejného commitu:

# on each machine
majordomus mesh identity                      # note the public key
# in the repository, on both: enabled: true, trust.allow: [keyA, keyB],
# cooperation.seeds on B: [http://<A's address>:8741]
majordomus serve --host 0.0.0.0 --port 8741 --idle 0 &
majordomus mesh doctor
# A and B
majordomus mesh peers                         # each lists the other: connected, trusted
majordomus mesh claim apps --session a1       # on A
majordomus mesh claim apps/x --session b1     # on B: exits 10, claim_conflict naming A's claim
majordomus handover --derive | …              # on A, then:
majordomus mesh handover publish              # on A
majordomus mesh state                         # on B: the handover is listed
majordomus mesh handover consume <id> --session b1   # on B: a local record
majordomus handover --resolve                 # on B, on the handover's branch
# failure: stop A's server with kill -9, watch B's `mesh peers` go unreachable → expired and
# A's claim expire; start A again and watch it reconnect with restarts: 1
majordomus mesh verify                        # on both: ok, converged

Zaznamenaný běh, 2026-09-15. MacBook Pro (Darwin arm64, node 9ce70581…) a lundra (Linux x86_64, node 94713da7…) na jedné LAN /24, každý s vlastním checkoutem, vlastním stavovým adresářem a deklarovaným cooperation.repository, s allowlistem klíče toho druhého, seedy oběma směry, heartbeatem 2 s a expirací 10 s. Změřeno: Mac slyšel lundru přes UDP multicast hned při prvním pollu; lundra vytočila Mac a link byl připojen (round trip 11 ms); claim zapsaný na Macu byl na lundře držen o 500 ms později a překrývající se exkluzivní claim lundry byl odmítnut s 422; předávka publikovaná na lundře byla spotřebována do checkoutu Macu; oba hlásily jeden digest; server lundry byl zabit pomocí kill -9 a Mac po 10 s link expiroval s claimem lundry expired: its runtime stopped beating, načež Mac mohl ten rozsah claimnout; lundra se restartovala jako týž runtime a znovu se připojila, vypsána jednou s restarts: 1; mesh verify prošlo na obou (lundra: round trip 9 ms, zkonvergovalo). Dřívější pokus téhož večera selhal, protože krok zastavení v postupu osiřel první server Macu ($! jmenoval subshell), který držel prázdný allowlist a odmítal každý link jako untrusted — odmítnutí bylo vidět na obou stranách a mesh verify nyní selže, když je runtime odmítnut jako nedůvěryhodný, protokolově nekompatibilní nebo špatně podepsaný, a když deklarace jmenuje seedy a žádný peer není připojen, místo aby hlásilo ok.

Záruky a co zaručeno není

Zaručeno a testováno: ověřená spojení s typovanými odmítnutími; izolace repozitářů; výlučné claimy napříč runtimy s deterministickým řešením konfliktů; živost a vypršení bez shody hodin; opětovné připojení bez duplicitních peerů; konvergence přes relé, kde je každá událost držena jednou; doručení nejméně jednou s idempotentní aplikací; předávky a review napříč runtimy; komprese, která nezmění nic živého a znovu uslyší vracející se proud; jeden stav na každém povrchu.

Nezaručeno: doručení právě jednou; silná konzistence či linearizovatelná tvrzení; dostupnost tvrzení během rozdělení sítě nad rámec pohledu každé strany; důvěrnost na drátě; provoz přes WAN, průchod NAT nebo směrovaný multicast; kompatibilita s programy staršími než protokol zjišťování 2 a protokol spojení 1; replikace čehokoli kromě metadat relací, tvrzení, předávek a revizí.

Jak ji rozšířit

Nový mechanismus objevování implementuje mesh::provider::MeshProvider a nepotřebuje žádnou jinou změnu. Nový transport implementuje mesh::link::LinkTransport uvnitř src/mesh/. Nový druh kooperativní události je varianta mesh::journal::EventBody, případ mesh::state::fold a schopnost modulu mesh; starší runtime ji přepošlou nedotčenou. Změna zpráv linku zvyšuje LINK_PROTOCOL_MAX a ponechává předchozí verzi čitelnou, dokud se flotila nepřesune, nebo je to záměrné porušení zaznamenané v ADR.

Tvrzení, která tento dokument definuje

Každý odkazuje na vlastní stránku s implementací, testem a místy použití.

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