25. Externí pracovní prostor není poskytovatel: pojem, postoj k závislostem a kde žije synchronizovaný obsah
Kontext
Operátor chce strojově čitelný přístup ke svým vlastním ChatGPT Projects — vyjmenovat je, číst jejich konverzace, synchronizovat přírůstkově a vidět výsledek přes povrchy, které tento repozitář už má. Oficiální pokrytí je neúplné, takže každá implementace sahá po ověřeném prohlížeči a pozorovaném soukromém provozu pro část, kterou dodavatel nedokumentuje. To je legitimní věc, kterou lze postavit nad vlastním oprávněným účtem, a šedá zóna není tím, o čem tento záznam je.
Tento záznam je o tom, že návrh, jak přichází, koliduje se třemi věcmi, které repozitář už rozhodl, a každá kolize je taková, kterou by relace jinak vyřešila tiše, mimochodem, uprostřed fáze, která měla být o něčem jiném.
První je slovo. provider je spotřebováno: ADR 0024 jej definuje jako nástroj, který pracuje v tomto repozitáři — bootstrap soubor, který čte, konfiguraci klienta, jež pro něj spouští sdílený server MCP, a hooky, jimiž předá Majordomus prompt — a share/providers.yaml je tabulka s agents, claude, codex, gemini a bb v ní. ChatGPT Project je opačný směr téže šipky: obsah držený jinde, který tento repozitář čte. Oba nesou jména po týchž dodavatelích a relace, jíž se řekne „rozšiř existující ekvivalentní invariant místo duplikace“, by je sloučila. Dvě sousední podstatná jména jsou spotřebovaná také. project je deklarovaný druh, majordomus.project/v1, a je to záznam tohoto repozitáře; source je třída kompilátoru znalostí v share/knowledge-sources.yaml.
Druhou je postoj k závislostem. majordomus-cli má jedenáct závislostí a manifest argumentuje pro každou: žádný crate pro YAML, protože YAML vrstvy je podmnožina, kterou definuje docs/SCHEMAS.md, jsonschema s default-features = false, protože nic se nesmí rozlišovat přes síť, tiny_http, protože loopback server je synchronní a není tu žádný async runtime ani framework. Nikde ve stromě není HTTP klient ani TLS. CDP je WebSocket nesoucí JSON-RPC a soukromé HTTP je TLS; přidat kterékoli do tohoto crate není volba crates, ale změna kategorie, a dopadá na jediný spustitelný soubor, který bin/majordomus-mcp staví na požádání — už podle ADR 0024 pomalejší než dvě sekundy, které se serveru MCP načtenému z nastavení dávají před prvním tahem.
Třetí je měnitelnost. Vše, co nástroj dnes dělá, je projekce souborů v repozitáři jen pro čtení. Synchronizace workspace je stavový síťový klient s trvalým úložištěm, který drží obsah, jejž repozitář nezapsal a který možná nesmí publikovat. ADR 0005 už vede hranici, kterou potřebuje — .ai/local/** je stav checkoutu, nikdy zdroj — ale nic v příchozím návrhu neříká, na které straně úložiště leží.
Audit provedený před přijetím tohoto záznamu zjistil dvě věci, které první čtení vrstvy chápe špatně, a obě mění odpověď.
discovery: state není sdílený mechanismus, za jaký se vydává. share/knowledge-sources.yaml má přesně jednoho čtenáře, lib/knowledge.sh:30, shellový kompilátor znalostí; dispatch je lib/knowledge.sh:99-103 a čtenář mj_kdisc_state je nerekurzivní ls -1 nad jedním adresářem (lib/knowledge.sh:130-146). Index v Rustu — ten, který krmí objects.list, každý zdroj MCP, každou trasu HTTP, OpenAPI a Cockpit — zná jediné zjišťování, DiscoveryKind::Vcs, sledované soubory přes index gitu (apps/majordomus-cli/src/discovery/mod.rs:33-37), a vylučuje .ai/local na třech nezávislých místech (app.rs:82-84, repository.rs:176-178, discovery/mod.rs:283-285). Třída stavu tedy kupuje viditelnost v majordomus knowledge a nikde jinde. Ani search a context ho nečtou: oba pevně zadávají své druhy záznamů a cesty (lib/search.sh:44-51, lib/context.sh:176-262). Jediným precedentem pro lokální stav, který se vůbec dostane ke schopnosti, je continuity, ručně psaný čtenář nad pevně zadaným STATE_DIR (apps/majordomus-cli/src/capability/builtin/continuity.rs:51) vystavený jako přesně jeden zdroj, který je, jeho vlastními slovy, poskytován a nikdy publikován.
Repozitář už prohlížeč ovládá a není to Rust. scripts/lib/ui-audit.mjs:262 i scripts/lib/cockpit-probe.mjs:436 volají chromium.launch({ channel: 'chrome' }) — Playwright nad systémově nainstalovaným Chrome, záměrně nikdy nad staženým — s playwright a axe-core jako kořenovými devDependencies, zapojenými do validate.yml pro úlohy site a cockpit, a čistě se přeskočí s definovaným návratovým kódem tam, kde Chrome chybí. Transportem Playwrightu k tomuto prohlížeči je CDP.
Rozhodnutí
Workspace není poskytovatel. Externí věc dostane vlastní podstatné jméno: workspace je tělo obsahu držené jiným dodavatelem, vůči kterému je operátor již ověřen a které tento repozitář čte. Kód, který se k němu dostane, je adaptér workspace; způsoby, jak se k němu dostane, jsou transporty. provider si ponechává význam, který mu dalo ADR 0024, a žádný adaptér toto slovo nepřebírá, ani žádný řádek v share/providers.yaml, ani žádné pole tabulky poskytovatelů. Že Claude Code je poskytovatel a Claude Project by byl workspace, je právě to rozlišení, nikoli nejednoznačnost, kterou je třeba uhladit.
Síťový kód nevstupuje do majordomus-cli a není ani druhým crate. Transport a synchronizace žijí ve vrstvě nástrojů Node, která už řídí Chrome: Playwright nad systémovým prohlížečem, postoj, který založilo scripts/lib/*.mjs a který CI už spouští. majordomus-cli z této práce nezískává žádnou závislost — ani async runtime, ani HTTP klient, ani TLS, ani klient CDP — a dokazuje to kontrola. apps/ zůstává jediným crate. Psát klient CDP v Rustu by znovu implementovalo, v jazyce s nejpřísnějším rozpočtem závislostí v tomto repozitáři, jedinou věc, kterou repozitář již má v funkční implementaci zapojené do CI.
Podíl CLI na subsystému je jedna čtecí schopnost. Je napsána ve tvaru, který založil continuity: ručně psaná čtečka nad adresářem stavu, jedna schopnost, obsluhovaná a nikdy nepublikovaná, bez docs/generated/ nebo projekce na web. Nic není zadarmo: pipeline druhů dosáhne jen na sledované soubory a nic pod .ai/local se neindexuje z konstrukce a třemi výslovnými pojistkami.
Synchronizovaný obsah je stav checkoutu. Obsah pracovního prostoru přistává pod .ai/local/workspaces/<workspace>/, což je přesně to, čím ho ADR 0005 už označuje: stav, nikdy zdroj, v gitignore na .gitignore:55 a hlídaný kontrolou mj_validate_ai_layout (lib/doctor.sh:431-436), která shodí pre-commit hook, pokud by tam byl někdy sledovaný soubor. Není vstupem derive, není vstupem webu a nikdy se neservíruje na veřejném povrchu.
Záměrně není deklarován jako třída v share/knowledge-sources.yaml. Získalo by se tím zviditelnění v jednom příkazu shellu, zaplaceno v nejhorší možné měně: kompilátor znalostí hashuje celý obsah každého souboru, který objeví (lib/knowledge.sh:80, lib/common.sh:465-470), a poté každý znovu parsuje, takže synchronizovaný korpus tisíců konverzací by učinil majordomus knowledge lineárním ve velikosti těla textu, který ten příkaz nemá důvod číst.
Jeho retence je vlastní. mj_validate_retention (lib/doctor.sh:303-316) jsou tři ručně psané stanzy nad účetní knihou, předávkami a checkpointy; nové úložiště nedostane strop ani řádku doctor, pokud se pro ně nenapíše. Pro toto je napsána, protože neomezené úložiště, které nic neměří, je vada, kterou tento repozitář u sebe stále nachází.
Obsah se stává výrokem samotného repozitáře, jen když jej člověk povýší do .ai/repo/, ve tvaru, jaký vrstva už používá pro rozhodnutí zaznamenané při práci a později sepsané jako ADR. Žádná synchronizace nezapisuje do sledovaného stromu.
Přihlašovací údaje se repozitáře nikdy nedotknou. Adaptér prohlížeče se připojí k již ověřenému profilu prohlížeče pojmenovanému konfigurací mimo strom. Žádná cookie, token ani autorizační hlavička se nekopírují do souboru, který repozitář může číst, a žádná se nedostane do fixtury, logu, snapshotu ani adresáře stavu.
Zamítnuté alternativy
Řádek v share/providers.yaml. Tabulka poskytovatelů ví o nástroji tři věci — jeho bootstrap, konfiguraci klienta, hooky — a žádná z nich neznamená nic pro korpus držený jinde. Naplnit tabulku členem, který neodpoví na žádnou z jejích otázek, stojí tabulku její smysl a kupuje sdílené slovo, které už bylo zdrojem zmatku.
Async, klient HTTP a crate CDP uvnitř majordomus-cli. Je to kratší cesta a utrácí vlastnost, kolem které byl crate postaven. Každá relace agenta platí sestavení, každý běh doctor platí linkování a argument v manifestu pro odmítnutí crate YAML přestává platit ve chvíli, kdy je ve stromu zásobník TLS kvůli nesouvisející funkci.
Druhý crate v Rustu v pracovním prostoru Cargo. Tento záznam jej navrhl před auditem a audit jej stáhl. Zachovává hranici závislostí, ale platí za ni dvakrát: ručně psaný klient CDP vedle fungujícího klienta Playwright a druhá binárka k sestavení, dodání a vysvětlení, pro subsystém, jehož polovinu s prohlížečem repozitář již umí provést.
Třída discovery: state pro synchronizovaný korpus. Čte se jako opětovné použití, které doktrína žádá, a není: jeden příkaz shellu je získá, žádný povrch ne, a kompilátor za tím příkazem hashuje a znovu parsuje každý bajt, který dostane.
Synchronizovaný obsah pod .ai/repo/. Není to tvrzení tohoto repozitáře, je lokální pro stroj, potenciálně velký a potenciálně soukromý a ADR 0005 už na tuto otázku obecně odpovědělo.
Samostatný repozitář. Kanonický model, schémata, druhy i každé rozhraní jsou zde; rozdělení by ve dvou repozitářích znovu vytvořilo duplicitu, kterou vrstva uvnitř jednoho zakazuje.
Důsledky
Příchozí plán mění tvar na třech místech. Jeho „kanonické jádro poskytovatele“ je modul Node vedle stávajících nástrojů prohlížeče, ne trait uvnitř binárky v Rustu a ne crate vedle ní. Jeho „jedna aplikační vrstva“ promítající korpus přes CLI, REST, OpenAPI, MCP a Cockpit není dostupná vůbec: strojovna, která by ji nesla, dosáhne jen sledovaných souborů, takže podíl CLI je jedna schopnost ve tvaru continuity a zbytek seznamu ploch plánu je věc, kterou by bylo třeba postavit záměrně, proti srsti, pro obsah, o kterém operátor neřekl, že má být veřejný. Jeho synchronizační engine dostane ručně napsaný limit retence, protože by si úložiště nikdy nevšimlo nic obecného.
Subsystém jsou tedy dvě poloviny ve dvou jazycích s adresářem mezi nimi: Node zapisuje .ai/local/workspaces/, Rust čte, a ani jeden nelinkuje druhý. Touto hranicí je souborový systém, tedy tatáž hranice, na které už žije continuity.
Distribuční model získává otázku, na kterou zatím nemá odpověď: nástroje Node jsou devDependency tohoto repozitáře, ne něco, co instalovaný nástroj nese, takže synchronizace workspace je nástroj operátora v checkoutu, ne funkce vydaného binárního souboru. Zda se to změní, zůstává otevřeno; rozhodne se, až bude co dodávat.
Kontrola musí ověřit, že seznam závislostí majordomus-cli nevyrostl, jinak je hranice větou, ne omezením.
Nic synchronizovaného není veřejné, dokud to člověk nepovýší, což znamená, že první užitečný výstup tohoto subsystému je viditelný pro vlastní nástroje operátora a pro nikoho jiného. To je zamýšlená asymetrie.