50. Objevování peerů v mesh je pozorování založené na poskytovateli s autentizovanou identitou uzlu
Kontext
ADR 0003 dalo jednomu repozitáři jeden sdílený server, nalezený přes soubor lease, a ADR 0035 naučilo nástroj, že každý checkout repozitáře drží jeden a server.status je spojuje přes git worktree list. Vše, co běžící Majordomus vidí, vidí přes souborový systém a loopback rozhraní: dva stroje, na nichž běží stejný repozitář, se navzájem nevidí a operátor, který provozuje flotilu (notebook, desktop, build board), je koordinuje ručně.
Nástěnka peerů (peers.rs) už spolupráci modeluje správně — tvrzení, překryvy, uchování —, ale její rozsah je jeden proces. Chybí vrstva pod ní: které další běžící instance Majordomus vůbec existují napříč procesy, které souborový systém nedokáže vyčíslit.
Cokoli, co na tuto otázku odpovídá, slyší pakety ze sítě a LAN je nepřátelský vstup. Repozitář má také stálá omezení, na která musí toto rozhodnutí odpovědět, ne je erodovat: SECURITY.md říká „jen lokálně, nic neopouští stroj“ s přesně jednou deklarovanou výjimkou; seznam „Intentionally Absent“ v docs/DESIGN.md odmítá registry pojmenovaných workerů a neomezená úložiště; a ADR 0032 drží klienty HTTP, TLS a asynchronní runtimy mimo crate.
Rozhodnutí
Subsystém mesh (apps/majordomus-cli/src/mesh/) s těmito nosnými pravidly:
- Identita je klíč. Uzel je pár klíčů Ed25519 vytvořený jednou pro uživatele a stroj, uložený pod
$XDG_STATE_HOME/majordomus/node.json(nebo~/.local/state/...), režim 0600, nikdy uvnitř repozitáře. Id uzlu je otisk veřejného klíče: nárokovat si id znamená držet klíč. Názvy hostitelů, IP adresy, porty a PID jsou běhové atributy, nikdy identita. Běh procesu je id instance; restart ponechá uzel a změní instanci. - Jeden protokol. Kompaktní, verzovaná, omezená, podepsaná obálka JSON (
protocol.rs), stejná na každém přenosu. Parser odmítá dřív, než začne věřit — velikost, tvar, verze, meze, zastaralost, teprve pak podpis — a na žádném vstupu nikdy nespadne (ověřeno property testy). Oznámení nese pouze veřejná fakta: klíč, instanci, endpointy, názvy přenosů, digesty repozitářů (stejné 32hex digesty, kteréserver.statusuž poskytuje — digest neodhaluje žádnou cestu) a verzi. Nikdy tajemství, nikdy obsah repozitáře, nikdy prostředí. - Poskytovatelé pozorují; manažer rozhoduje. Mechanismus objevování implementuje jeden kontrakt (
provider.rs): start, stop, status a surové bajty nahoru kanálem. Manažer (manager.rs) vlastní jedinou verifikační cestu a jediný registr; poskytovatelé nic neparsují a nedrží žádné peers. UDP multicast (preferovaný), UDP broadcast (deklarovaná záloha, ve výchozím stavu vypnutá) a rendezvous se dodávají nyní; mDNS, Tailscale, Consul nebo cokoli jiného později je jen další implementace téhož kontraktu a žádný povrch se nemění, když přijde — dokládá to syntetický poskytovatel v testech. - Jeden registr.
MeshRegistrydeduplikuje podle id uzlu napříč zdroji: uzel zaslechnutý na multicastu a vrácený rendezvous je jeden záznam se dvěma pozorováními. Omezeno (256 uzlů; důvěryhodné záznamy se kvůli místu nikdy nevyhazují), chráněno proti replay po instancích, přítomnost vyprchá po 60 s, záznamy vyprší po 15 min. V paměti, zaniká s procesem. CLI, HTTP, OpenAPI, MCP a Cockpit jsou jeho projekce a nedrží nic vlastního. - Objevení vytváří povědomí, ne autoritu. Výchozí politika důvěry je
deny_unknown: platný neznámý uzel je zaznamenán, viditelný a nedůvěryhodný pro nic.allowlistdůvěřuje deklarovaným klíčům;tofuje vývojové pohodlí, které každý výpis jako takové jmenuje. A důvěra podmiňuje jen označování a pozdější vyjednávání: není žádné vzdálené provedení, které by důvěryhodný uzel mohl získat, protože tento spustitelný soubor žádné nemá. - Ve výchozím stavu vypnuto, zapnuto deklarací. Žádný socket se neotevře, dokud repozitář necommituje objekt
mesh(druhmesh, schémamesh/v1) senabled: true. PostojSECURITY.mdje zachován jako výchozí; mesh je výslovné, revidované, verzované rozhodnutí operátora, přesně jako vazba objektudeploymentmimo loopback. Aktivace probíhá při startu sdíleného serveru, po lease a routeru: discovery nikdy nezpožďuje servírování a poskytovatel, který nemůže nastartovat, je stavFailed, ne selhaný server. - Každý server Majordomus je místo setkání.
mesh.registerpřijme podepsanou obálku, ověří ji přesně jako datagram a odpoví obálkami, které drží — každá ověřitelná od začátku do konce vlastním podpisem, takže otrávené místo setkání může uzly zadržet, ale nemůže žádný vymyslet. „Mothership“ je libovolný uzel, na který deklarace ukazuje; lokální registr zůstává jedinou pravdou a selhání místa setkání prodlouží backoff a nic dalšího nezastaví. Klient jede na stávajícím šedesátiřádkovém HTTP/1.1 klientovi mostu MCP: „žádná závislost na HTTP klientovi, žádné TLS, žádné async“ z ADR 0032 platí beze změny.
Vůči seznamu „Intentionally Absent“, bod po bodu
- Žádní démoni: mesh běží uvnitř sdíleného serveru, který už ADR 0003 zdůvodnilo, na stejném životním cyklu — začíná s klientem a umírá s posledním peerem. Žádný nový proces neexistuje.
- Žádné registry pojmenovaných pracovníků: registr meshe drží runtime strojů, ne pracovníky, role ani persony; je omezený, v paměti a zaniká s procesem. Nástěnka peerů zůstává jediným povrchem spolupráce.
- Žádné neomezené úložiště jen pro připojování: 256 záznamů, 15minutová retence, čítače. Nic nepřetrvává kromě jednoho souboru identity.
- Pouze lokálně, bez telemetrie: ve výchozím stavu beze změny. Datagram meshe vznikne teprve poté, co člověk potvrdí
enabled: true, a nese pak veřejná fakta uvedená výše, podepsaná, do síťového segmentu, který deklarace určuje. Nic se nikdy neposílá třetí straně; neexistuje žádný koncový bod, který by volal domů.
Zamítnuté alternativy
- Rozšíření modulu
peers. Peer relace (klient MCP jednoho procesu) a uzel mesh (runtime jiného stroje) jsou různé věci s různými životními cykly; jedno slovo pro obojí je způsob, jak vzniká pět protichůdných pravd. Nástěnka zůstává; mesh jí dodává kontext, ne řádky. - mDNS/DNS-SD nyní. Vlekl by závislost nebo druhou implementaci protokolu kvůli přínosu, který multicastový provider již dává na týchž segmentech. Zůstává to na jednu implementaci smlouvy providera daleko.
- Centrální služba mothership. Jediný povinný rendezvous je jediný bod selhání a kanonická databáze pravdy o peerech někde jinde — obojí výslovně zamítnuto. Slouží kterýkoli uzel; endpointy jsou seznam; objevování v LAN funguje i s nulou z nich.
- HMAC se sdíleným tajemstvím. Tajemství, které drží každý uzel, je tajemství, které protokol nesmí nikdy vysílat, a přesto je každý uzel musí nějak dostat; podpisy veřejným klíčem takový kanál nepotřebují a z falšování dělají problém držení klíče.
- Nepodepsané objevování s důvěrou při připojení. Nepodepsané ohlášení může otrávit výpisy a řídit pozdější spojení; podpis u zdroje stojí jednu závislost a odstraní celou třídu problému.
Důsledky
- Crate získává svou první kryptografickou závislost,
ed25519-dalek(jen podpisy), agetrandompro dva okamžiky, kdy je potřeba entropie. Obě nesou své zdůvodnění vCargo.toml. - Poprvé existuje soubor stavu pro každého uživatele (
node.json). Jeho ztráta je ztráta identity uzlu — nový klíč je nový uzel a seznamy povolených je třeba aktualizovat.mesh.identityamesh doctoruvádějí cestu. - Model důvěry autentizuje uzly, ne repozitáře: pozorován může být každý držitel platného klíče. Autorizace zůstává tam, kde vždy byla — nikde, protože mesh nic neuděluje a povrch HTTP, který inzeruje, je táž projekce jen pro čtení, jakou vždy servíroval. Kdo později postaví vzdálené operace, musí přinést vlastní autorizační rozhodnutí; toto ADR vylučuje zdědění takového z objevování.
- Multicast nepřekračuje segmenty; provozovatelé s routovanými flotilami musí deklarovat rendezvous endpointy (prostý HTTP — předpokládá se soukromá síť nebo overlay jako tailnet; v tomto programu záměrně není žádné TLS).
mesh.status,mesh.nodes,mesh.identity,mesh.doctoramesh.registerjsou schopnosti registru jako každá jiná: typované, měřené, promítnuté do každého transportu z jedné deklarace, a pravidloproject.mesh-is-observation-not-authoritydrží hranice.