From a28da1734b639ff222a8b96e90efd134159d92b0 Mon Sep 17 00:00:00 2001 From: jochen Date: Wed, 7 Oct 2026 19:58:39 +0200 Subject: [PATCH] Graduate research 034: the mesh in domains, one word per thing, checked ADR 0244 sorts the mesh's concepts into ten domains (ADR 0006's contexts carry over as domains), keeps machine and node as distinct words, and makes the glossary the authority with every retired word on its replacement's Not: line. To-be 49 draws the domains; the glossary is reorganised by them, its two contradictions removed and the missing words added. words.py now fails on a retired word in running prose and on a word defined twice, so the rule is enforced rather than believed; the 63 documents it failed on are reworded here, and research 034 is kept as the record of the words it studied. --- 00-META/checks/README.md | 20 + .../checks/__pycache__/words.cpython-314.pyc | Bin 0 -> 17364 bytes 00-META/checks/words-allowed.md | 16 + 00-META/checks/words.py | 278 ++++++++ 00-META/effect.md | 2 +- 00-META/glossary.md | 619 +++++++++++++----- 00-META/mission.md | 2 +- 00-META/process/03-issues.md | 2 +- 00-META/process/06-writing-a-module.md | 6 +- 00-META/repos.md | 2 +- .../01-evidence.md | 2 +- .../02-options.md | 2 +- .../034-the-mesh-in-domains/00-overview.md | 5 +- ...in-domains-and-one-word-names-one-thing.md | 234 +++++++ 02-DECISIONS/README.md | 1 + 03-DESIGN/00-as-is/01-mesh-and-transport.md | 4 +- .../00-as-is/05-runtime-and-installation.md | 2 +- 03-DESIGN/00-as-is/07-knowledge.md | 6 +- .../09-interfaces-and-observability.md | 2 +- 03-DESIGN/00-as-is/10-module-catalogue.md | 2 +- 03-DESIGN/00-as-is/11-the-lab.md | 2 +- 03-DESIGN/00-as-is/12-the-seats.md | 4 +- 03-DESIGN/00-as-is/13-the-console.md | 20 +- .../00-as-is/15-the-agent-and-its-licences.md | 4 +- 03-DESIGN/01-to-be/00-work-breakdown.md | 6 +- 03-DESIGN/01-to-be/01-end-to-end-testing.md | 18 +- 03-DESIGN/01-to-be/02-scenario-declaration.md | 12 +- 03-DESIGN/01-to-be/04-lab-installation.md | 2 +- 03-DESIGN/01-to-be/05-the-node-host.md | 82 +-- 03-DESIGN/01-to-be/06-the-controller.md | 17 +- 03-DESIGN/01-to-be/07-the-foundation.md | 39 +- 03-DESIGN/01-to-be/08-connectivity.md | 73 +-- 03-DESIGN/01-to-be/09-the-node-lifecycle.md | 108 ++- 03-DESIGN/01-to-be/10-delivery.md | 13 +- 03-DESIGN/01-to-be/11-a-board.md | 4 +- 03-DESIGN/01-to-be/12-a-module-repository.md | 27 +- .../13-credentials-and-their-rotation.md | 12 +- 03-DESIGN/01-to-be/15-the-agent-session.md | 8 +- 03-DESIGN/01-to-be/16-module-coverage.md | 8 +- 03-DESIGN/01-to-be/17-raising-a-mesh.md | 8 +- 03-DESIGN/01-to-be/18-building-a-module.md | 18 +- 03-DESIGN/01-to-be/19-the-module-protocol.md | 4 +- 03-DESIGN/01-to-be/20-writing-a-module.md | 8 +- .../01-to-be/21-the-installation-in-full.md | 11 +- 03-DESIGN/01-to-be/25-the-bus-on-nats.md | 32 +- 03-DESIGN/01-to-be/26-the-seats.md | 11 +- .../27-a-module-requires-the-mesh-resolves.md | 22 +- 03-DESIGN/01-to-be/28-building-the-bus.md | 60 +- .../29-a-node-has-operator-accounts.md | 8 +- .../30-the-mesh-updates-itself-on-a-push.md | 28 +- .../31-a-module-declares-its-fail2ban-jail.md | 4 +- .../01-to-be/32-what-a-module-declares.md | 27 +- .../01-to-be/33-the-tools-the-mesh-answers.md | 20 +- 03-DESIGN/01-to-be/34-the-console.md | 58 +- 03-DESIGN/01-to-be/35-reading-the-record.md | 10 +- .../36-the-operators-agent-on-a-machine.md | 42 +- .../01-to-be/37-the-operators-machine.md | 34 +- .../38-building-the-operators-machine.md | 46 +- .../39-the-anthropic-licence-manager.md | 4 +- ...operators-agent-and-its-licence-manager.md | 18 +- ...-the-shell-and-the-accounts-environment.md | 16 +- .../42-the-machines-modules-in-order.md | 2 +- .../45-a-core-that-cannot-fail-silently.md | 86 +-- .../46-the-conversation-with-the-operator.md | 14 +- .../47-delivery-from-commit-to-delivered.md | 4 +- .../48-a-module-says-how-it-is-healthy.md | 8 +- 03-DESIGN/01-to-be/49-the-mesh-in-domains.md | 145 ++++ 03-DESIGN/01-to-be/README.md | 9 +- .../00-report.md | 10 +- .../00-report.md | 4 +- .../00-report.md | 4 +- AGENTS.md | 9 +- README.md | 2 +- merge-check.sh | 6 +- 74 files changed, 1735 insertions(+), 723 deletions(-) create mode 100644 00-META/checks/__pycache__/words.cpython-314.pyc create mode 100644 00-META/checks/words-allowed.md create mode 100644 00-META/checks/words.py create mode 100644 02-DECISIONS/0244-the-mesh-is-described-in-domains-and-one-word-names-one-thing.md create mode 100644 03-DESIGN/01-to-be/49-the-mesh-in-domains.md diff --git a/00-META/checks/README.md b/00-META/checks/README.md index 07e6d4e5..03570189 100644 --- a/00-META/checks/README.md +++ b/00-META/checks/README.md @@ -4,6 +4,8 @@ python3 00-META/checks/records.py structure: links, citations, supersession, topics python3 00-META/checks/index.py the reading order in 02-DECISIONS/README.md is current python3 00-META/checks/index.py --write regenerate it +python3 00-META/checks/words.py the glossary's retired words are not used, and no word is defined twice +python3 00-META/checks/words.py --list tools the words the catalogue's copy must list ``` Non-zero exit on any problem, so it can be a gate rather than a report. @@ -60,3 +62,21 @@ allocate the same one). And a core issue — one opened from 2026-10-07 whose `l repository — resolves only with `replay:` (an id in mesh-lab's replays register) or `replay-none:` saying why none is possible ([ADR 0237](../../02-DECISIONS/0237-a-change-is-judged-against-the-mesh-that-runs-before-it-merges-on-the-build-seat.md)); it failed on a resolved core issue carrying neither before it passed. `python3 00-META/checks/cycle.py` + +## words.py + +One word per thing, checked ([ADR 0244](../../02-DECISIONS/0244-the-mesh-is-described-in-domains-and-one-word-names-one-thing.md)). +It reads the glossary's *Not:* lines (the retired words, each with its scope) and *Identifier until +renamed* lines, and fails on a retired word or a bare identifier in running prose — what is left once code, +quotations, struck-through text, link targets, comments and frontmatter are taken out — in `00-META/`, +`03-DESIGN/`, `AGENTS.md`, `README.md`, and research and issues dated from 2026-10-07. Decision records are +never checked. It also fails when a glossary head word heads two entries or is also retired. +[`words-allowed.md`](words-allowed.md) names a document that could not be reworded at once, with a date, or +a graduated research effort kept as written. With `MESH_CATALOG_DIR` set it compares the catalogue's copy +of the tools' retired words (`retired-words`) with the glossary. + +It failed on something real before it passed: 581 uses of retired words in 63 documents on its first run, besides the glossary itself and research 034 — +"the host" for the node-engine in 30 designs, "control plane" in 10, the glossary's own entry for the tool runner — +and two glossary contradictions (the "console", the deprecated broker's seat), fixed in the change that +added it. Homonyms (*plan*, *gate*, *tier*, *ask*, *store*, *record*, *check*) are not checked by it: a +word list cannot tell one sense from another, so they are reviewed. diff --git a/00-META/checks/__pycache__/words.cpython-314.pyc b/00-META/checks/__pycache__/words.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..bf10f7c30c3e821923c95ba2a69bbd557694a913 GIT binary patch literal 17364 zcmb7rdr(_fn&-XJ)mwmg8T{m$hd~%He#ABy+d~7kF*YUwi`bR{83|A7%p$4cU6 zQZ>_Hr^a~VX_Ibm2X8WCGM%lsYi6t6TU#~Gbap4bTe~eHY>3*so!OnLt^Q{!v3sg3 znLl=a-?_S1g2+zxg3i77-1GR(Ip6y`XKQYbmBaNv|KvZv9CUKr|E3r9GNm4#zMbW4%+qk#=SM+r6d(tJK@44z0+>I{Kse~LQXz37gilH# zR-6GvUwrD^bo%@#GZMfuD}^P$^1W6ZA_(fo!g6#-{9ltd8Bmg69a5cmE)b$c_W~%{ zFI@}_Q-4@9Yimdhq?<*b-e53%2@4kp$@qZfV!+4|fJ(JPVK$YbKPdZwQvEb0)bfW$ z!qRXGRCVTQL1{_UcZQ^xF=dH{#x4wrHDL)QLVjmms`U(L>S)HLiBJd_tuD!k=w*1q znpP4O)qUaNiE&I&gXmgO2hd$gK^gRqM6o;=JR8)xV0iev3}T_xp%LPl<*n}G!bCXg z$H%Du6^1eN?j-~e$GsA@pdey>bW93QjE=E#b%gTg#i&;r^+!?UNZ;{e=xLllq=omb z?qoy)%8z@aQ7o@l0=jt5`+*)4(Pk_{y2tZ=e`F=$v5y3vH?vDx6)%V|=wICt;fylf zSksKf4+bs*B{An2Yu|k#dE-4V2iS}5kFfiZ<@9#8U`Bh3tX>ISL>d9~i2yXRquF zC^riH3DCR~u~PGC_K>1#Rfh|p12xFu$jxOxnXbA8w`(5_`x{BmqH5b z6_sVGVPu&qAr7Sop`Jl7m`T_D&fhDfVIOdSZJ zaviN_8lX3b$r|wnf`sls9L8-JkiNjkh+mRHt!*i(ppuH*EUHqVPHfy+-`>&NdANI< zSaU86W(r=5YM{*4!yVmyy^s%#MqM4P?Z-RthBXXc@?Mtr0W`@k`@PcenAJZ5MnFUo zhz7h-KMH}u3D0C1I0V)lVtGJpWRDQ8A99^I-XL}pn}(zU3~imgohQ0`w-Mg^{KEm6 z2u<=2lZex>->R%427bwl5kpo*F=$$RjI&i#N2sh24LdbAXiAwGZzvRwis$@jnVMo8 zFICzb8clKQk?@4V#(|BD)dD9-&HykB&PoiC#_pBFA;zs3Q1vMmN)vX@&$uWPF+__b z7Z?p(79(EZYXCS&O{Jg<2F_E@DS3{M{@@74L$nvU43Qk#sVx{o26-Fnu_1E#yOa?# zslwVF(={~23RMKiPy=FRYG!698N-TMcQo$UUBA7tzHv`%#U4zzH+7J%)phiTb+0~*o?BC8c5;S7{NI`PUpecj&HW`Oi`ycYZ*eT zLuhprghqXeXS{GJ{~GRb*ZCM7*76;{ijJVWN2^6ch3 zMm1Medn-66-|ZBVy0DxSBHrj&(j@tV>_KpyIML_iCF)luK(-`zZyO7b`?tLu#;k_6 zLC1L6KOB{}g~AuZuWTE;ur1r9V`Yq0neV7k|R zzI!>Z`1-5UuU>oo4|>kpj9mODEKOg_H1PY5)gLu zZgpbytTHsFs7zcoDU85)Na`ei(x^z6B!4_<7=?}%0nrJhjU@$Q%SpW-1^IEAhP-8q zsH|o(KSQM&gxVbRNo6Q-7(e+ml1Xmamd8kF&yua;Q(MJnd4vhXj%gna9_J;*E^Of@#?iJ2&{afkh(Vs;V=9bCU->zLZwVh~jX8X0~xKKGK zAdxI+ei%RPqN&7^2~XbyB7pyB3!bJ#b)q6%0~g^lmVr=n)dd<%wcy|m`8W>Ctu0nI z>UHZf${*plOMpV!m3_%v+LEE|2DEMHjz%-$YnCdc|HW6bNxG_NuXw4p!dQRV&(Cz`WmQ&N&NF{ULPg*lz{Oj)w zqjd$0=B_3M#{M;K^G6i(WKEE0q6GN6(&L?Y3*!>+No5rw4G@080_t7?M@0upnszI^_XxHx6J2| z6v!4x3ZxC8KDhvrdYF|-TL#EUK9DpJv=G*GOY&okOQ$Rwx-lt`G^@Ihv>TnAz)u!H zZIj$T+463huN|0dU(R(*9)9d7U2=#E4sovNkz>o0@Ob^^cMiOLV7~Iv`fbx)Q|-$o z>u<$w#OA6VmDJ4WmmMXya&P3$>F-+ZSl+eGpZsuOe&C)zUe@x+vHz)_D|I5-(Dy_bgQH zp5t#-->Ckf6ORk|dmftiTs2VTiqCBYbH#C{Rebft!j-o3Up+B$1$$*XX5*K9X?u(L zHHNXa$f=`u<^ORlL50)z;Kff3?b5CxxRgxOS*&750fOCU?EJ`rC;x1wH# zGy?$af}G=Bx*p=ktFAOltjqY~(r3P@Oj9Er9t0!Ae66st!a+YX%Km!zOZ;$| zL}5h3D-azXBeMujg<+75;zJ|kR6ddCNBNdA+Wq1=s3zx`Q6!FCjzB?dh~?HC3^;2J zwroD*xm17F88e>o$n|Hp#w;}lQ&~=fv<0wBq!T8M@Jo4t_a-1V$M#z1P;z6 z`AE_bA;Btx6+_)gVY&&rDr~Tj1SZN2;U|-Ga+3SJqWW(0o#yvi686SBEwkP?UDr=t zJvDRT&pdZpCOhKxM$$ip%~jSTf5JJ#!6 zf}3+0+|UU#F`DPpdFo&F#bw~R%#x&rxXdSbXnL9;_f@jK)2c?6*oMcB$zxZx$VY?D zt#=s-Yu$RN7tqHd!ncjZEqHQMO=6~0_#<4`W`zs5VKz7|v9lz`n2U}{FXs1U?r_L1 z#Y;&R|4N4IWPbE#j6)* ze78I1b|vy^t_mdE)_iWuo9z0ghEAvqmLxp=3DeM_*TF<`a2@=iHt5UvA{9jR&u3KG z7*yJ800EX_7(jVVAZ4_eg_t1r1XT#BcaY7$fNTsIH8U9zhCx?0So%BMdX0RH-%>wA z+f`Zw$r@D{RQ9E4_N2jkG+V%<1tL8}Gwe#&R9k@fRcFKGHmU9Tz&yYXO=*pKFSK+Y zW`6_M^*Va_&hz#yqst;n`a4vJR2bDeW*okNh}muKA;MX8rS*1o6scL+fEnX@pxms! z+{PF-sCZo!uBCWdDfk0% z&zy@&NG{XgTE_wd;jIcKh6xM4l6DX3d7%u6IuV==8jh*jWE)O~ce7;%0kp?77u)qh(H* z_a_Q>O|`3e2W}jgt6VCq{R_k zt)n-N{?z?9?*GsiFZ}PrJOx)^5{1?Ai1nSY24FI zpv+0KY%~0%pn)&HT}{tst*1Q`85~gee04vYeVkUTp8WQ9um2$u4g&+ zx)gqqWh@?z>tu{VSk)|Ev(0}0?q_i|%*)9Xmx-nBPgNe{(YTgsu4Sr*2{#-+HPy8>LGxq6i~fSmxf~d1wuwe2ENd3G>4-r#2%vrwODQ9+r~^7BFNETInSe zK{jZcC>)+zg^*3iUO~qBEK1?`4XQyOS@NOGP5k7)K|*f6oXPgz=9f$vR?=@3lmgIi z%9nm%`oL`ElCA7hTiLSRalLE0Yu2!6-}ITi=<()?yCwg&WWMOHHZIsVPPNY(s6gj* z=bK%i&)JEe(go|G>%n{-xdGEMG0s1lCr zY%*Cbm+f0x!uOD3yy{ASg-b)o@Y}U#YfJ4X?OTi(pbwIA%`UgbMFs>i_eLQV!Q%`A zTpgLOsjG)%<*Li2aWHK?6us#R^pMr{HTq6BlCF=^{EFV5TRPh)U3yiQL0h`awxl1x zyDO@__LQqn={wAQ!EIJ?OdCrTn+LuJ_tjS#6PP|_aGTg2{FmKLJ2btu4ElT*4Cekf zm|p4p|7ueQolR{Mx*%)Yvcs&7l(miJh0dn}odKxZ=s|DFrQ(1(#-6R}m-O9j>Zwn^ zsZUzX6vneYLkibc=q}~s72v3{#&)g_NH>eGrDNl-Dcd$)OYJ7Y>)2)SwIq-Kp(KAL zRyg4Cy}Il5dZas2X=SXanYn^ljN40FX%O!hV*)w3Vs>#1!FX&DA+-x{5(58hzd}fy zASS@JCMofDM82IR8A5}J3Nj%S9nsW!?AVF^j`pOPnu-R-{nBZCOd6tLpZ7ACLgdY)lehkU`XnN2m}(lP|V#{dZLv&{GIBztKX@6yY9W{2mOh4FD%)1e`?#koL4aQdfdF>vAyW} z@#*7B_Ob9!fY4FH|0yy>P4KM#&G?;&I9lcN~6f%bl9I);yd4=E1qD zdHyeJmdamPD1YJJ@KSm6r{&EMfy?W~x%_{#mpQmA66+i88DOBBf1dlZ-1~B(v2Cfb zbD^>GlZr%Rcf99h+};1M=cSn!rt~ClU%C29yc89d@^*ikw|g0bIWc|W*M}1)PbJO_ z#5W8s+J}!f9m6sXE@;xB+iEtk%{<*i;MP4RKRSzo;#g8Bj0ZOaYw>jzGU9A zVBRtpy&Jm|d-t^^^R6#Txcse5xt-b{7fQc+g7+i53~J|onOAxw*SI1EOI|9b>|RC6 zlwDmqD5sK{5+>`fToG2$xj8U;!%5?Wo_VjKwy|<C=t-=vUj!~_9gJaqwWnp469!LJKb88tN)g8&-Ugh@=^Z-jEpD%Tat~)1Nh{|nK z+d}DohaU6X_U8$H2<9xJ-)(i-R7Od&>#JZ)+mcPS?*HIo6|k1PwE)3B5*Mf-bSpEe0M(H8}MxJ|6r%iZD|6dN?|d~k?iC0 zXrfyYvTiH8o89(i5|oxqp{s>@TT-w=L1cnTA2CJ@S~$`Sp=Fl{YKdkxR!bYlwB4z+ z-%Qz?N;`8`ZB&Fo0tX3Lv^BO4I|{0N&0>{ATlcDDu}1Y{I+@q@YhAM#bC8${hOyHN z5l(Dk6DLT=NeQTN_&HWeIcypsu#&p53-5Ez0>;u6uW1C?%-&#rPsM#fGm|t^m@@$P zgCxOzO;j$g$dd3lP*#Nq`WFpODDaO)I;1$U_vS3mc#0(P08Rf zif=b%YbiIyagxIgNvd0g&%*SR2+O?EB|_J@KvdE~Rb z2N&(9e_?w6$b9RA7d|<@&~|Dur+UhJvt%~^t#z}f5;@hsFimzW*iSDDmiWn`M2>gi z8UVCA({c)c7D6gC(P0`K1*^0ML&C7o~ z_Wt)5^ZsZ`_|3-h>El!FGsDZpCAZGpID7J?i)>Xjm2Lcj~@&@6hn_3 z;iookO*P`Zn=0Uvaa4R!z?Id!6Mj4VXwx=wH0h_#{-$U%BFK)ysaNMJALVUHb+Xhy zkQgG_7#ClDR?XpHhjO|5}C%$^RnaerDf0fMf$`B&lZQG${;|B#uJ~FI1 z%&nAwBo~+jw6kS{yGOT;%p+dm4G{sZ4}4jDsT?F?=g^j>3iV92nesf*`})`rV)~GAQ(!P;~7}b0()C zy^3aI1>fAa+!qs-jn&$&2ev1cZN5#B10s$@@CsoQCqUqZ(eO^nWi%|verTpNk&>Gd za9(h(L7Cj1ES>^lQz*@@P@1v@TbjZ5U*jiJD=9)_z6}=cpvcP0(=T5OvY^VF`%=o( z-_`uGJ-(rH(S8)@JtbVTd}%=t4`2y6tWKLliC>sf#(rY>X6NJrw& zr!^Y=s))XTW>;P5Er*&6!7czu4n1Sh@AGU%6;W_)_Wl%d7_)~Cl zouimlM2I!LFYTZxgWCwM8MPa(hagyWWp%w`4~43UM0D#p53~a1!g?aotZIf?Hi5$3 zlHnFFO;=R8iZ%vd6+%96d1QCMx->V5S|b%C0+pc#S(2+RwFl_W9SW_Je^czSGs>1&kxOraGjcwRWCNVgE>oXzW$P2Vjc{Z{j0cU_lgaJ@&`)Yx ztu5(TeF7nN279-}wiCyDT3sFO&Eo3s#1Sd94ojLH@uFQ}+Svr0ieRS@QYWR@u?*U> z&tmb|g%!(5@lEAaghZBzWJi+ZE4pJX&uc4HF?FB=$H}xE{a#Z4Wa-b*Ozi0DCRX$( zrcOtQ*eg2DbUsyCY^GBc@>qBx=)+C`?A5Q5W4b!gnajKuNs~WxF@PgG()S5gguZdU zMDbe_?nxj)PH{yLLM2TndOIX3EHQ4vc#(9IN|2;T$%mvxn=_@Ie5%L?lLE~jwzx_X zxzTZog^7t3E^w% zo1m;=X{H%+GU3)J~+HIIJPi277vUk215@Wp?F?6ZVr=Z>-_3T z4p-#nSyKSxU;oKg@OzF2Xv>c7_=|lH9ewe?TwD7l%g1X4%Gso+bhE94(|=rNY2T;+ z_ywMx_Zg6`;NKKeWYSML51f{MDz8zu4PYnE3iqvxZ{XRP&?w{7)CRlK+j<9ddx0Q6 zxZEkgwbF%p9Nd_+GnbCF0b&8S*DU|^KDp!0v*ZG8wYk zZGcQJQn=&5)(k^2* zpG+oi%@{F_L+i(7%BZOYCNuIh*L_Bw=0e}8e?}je)l4heX>R@!x#5D4I6)vAvj1g_4cGV!zftDyFI4DCPGKkFgV-aNE*m zT`5EfscZ_9{SiGehD^FMvs4*_bXt=FPG(-ru&$vvD5{`DN@0?hr5{iZsoGLA5}CqZ zwAo3y6#ZNn_u_ELU3?*KE88&rV7cYB@79v-dw8~L*<_n)`;*HvFU=fXG;Mmke$#C9 z$4zq=ZttHFW?uZEX?}Fduv}et&$L**=caXb&!VH^Ke`usPcODhNPo5KlkM?=!MHCF zKOcz?UsxEp@DDFOcoAPdZI=@JWyCYhV5n?Al_8P4Y0+HvS)Sv!wgSf9!;9t!ULWVJ zpFNz&+xo%y{lkfd7a!)mxY#!k9~?>ajXum9ojmfJoZ=~-+VTFG8Mb8Zn*U?UWA!WF zljrxo`})J&U5n-ys3Dl;*3ri~j+v5cp4&TTUrnsv`a$o#;-8+5+xPy9p0_m-V{ZOO zoBgY&8xd!bd70$+M@2d9`>Y>rF(OTx2M1}!1_!Z=)~p_QidoZV4`UYXv|-Gm9W`|F zhnzJ@(;$U-2M48>(X0|Xpujk^`pdF%rpFvYP=L-~u{BSc5Ikn*L(nZnkWtl0Rmm!2 z?iI;JPiHA1qeNOuNd+YoWm0y;)zT9MXq0d@xCFN`FaTUZ;e3hJst>mhrG}BIrLL-9sJ`wp@=Ca*pS} z6smaPX*bXDj^A(=_HX^%Wck6RKgKq@k_A)Atd4etzhi&fKIgjIf2aTM;GMyHFD0rD zJP;oE5~W=U)3Hg|e&VEg*}8So1Ov}pG%0*;**|IgT;1Ed`@Zh)gkJ~=+kTX?*sia; zx-MR{7pI#hjmtTC*H2$P9WOopiS+B}ucC>ZzDet{J@@+2t4C*EOV}$XEz1BW_C_pT zP&a?>L*M(pglXrb@WjaT)lbYie&Z9Xfv_$TIEKKDs^0iW|lEhpHgVvG8<%Yx|#N8UIx-8WNl UtL8?{wSh;%2H>)=j!ozP1G!-J6951J literal 0 HcmV?d00001 diff --git a/00-META/checks/words-allowed.md b/00-META/checks/words-allowed.md new file mode 100644 index 00000000..824a7624 --- /dev/null +++ b/00-META/checks/words-allowed.md @@ -0,0 +1,16 @@ +# Words allowed — documents `words.py` does not hold to the glossary's retired words + +Read by [`words.py`](words.py) ([ADR 0244](../../02-DECISIONS/0244-the-mesh-is-described-in-domains-and-one-word-names-one-thing.md)). +One row per document. *Until* is the date by which it is reworded — past it, the row fails like the words +themselves — or `kept` for a graduated research effort, which records what was said and keeps its words +the way a decision record does. A row whose document no longer uses a retired word fails too, so this +list only shrinks. + +| Document | Until | Why | +|---|---|---| +| `01-RESEARCH/034-the-mesh-in-domains/00-overview.md` | kept | the effort that studied these words; its findings quote and propose them as they stood before ADR 0244 | +| `01-RESEARCH/034-the-mesh-in-domains/01-the-concepts-in-use.md` | kept | as above: the inventory of the words in use | +| `01-RESEARCH/034-the-mesh-in-domains/02-the-domains.md` | kept | as above: the proposed domains, in the words of the day | +| `01-RESEARCH/034-the-mesh-in-domains/03-the-clashes.md` | kept | as above: every clash names the words that clashed | +| `01-RESEARCH/034-the-mesh-in-domains/04-how-the-glossary-is-checked.md` | kept | as above: the check's own examples | +| `01-RESEARCH/034-the-mesh-in-domains/05-a-glossary-by-domain.md` | kept | as above: the proposed glossary, with the entries of the day | diff --git a/00-META/checks/words.py b/00-META/checks/words.py new file mode 100644 index 00000000..d0276070 --- /dev/null +++ b/00-META/checks/words.py @@ -0,0 +1,278 @@ +#!/usr/bin/env python3 +"""One word per thing, checked (ADR 0244). + +The glossary (00-META/glossary.md) is the authority on the mesh's words. It names the words it +retired on one fixed kind of line, so a program can read them: + + *Not:* ~~control plane~~, ~~overlay~~ (hq) + +A struck word with no scope is retired everywhere; `(hq)` retires it in this repository's prose only; +`(tools)` in the descriptions of the mesh's tools only. And it names code that still carries an old +name until that code is renamed: + + *Identifier until renamed:* `mesh-host` — the repository, binary and service unit + +An identifier is allowed inside a code span and nowhere else. + +What is enforced: + + retired no retired word of scope `hq` (or none), and no identifier, in running prose of a + checked document. Running prose is what is left once code blocks, code spans, block + quotes, text in quotation marks, struck-through text, link targets, HTML comments and + frontmatter are taken out: a quotation keeps the words it quotes, a link target is a file + name, and an identifier lives in a code span. + unique no head word (a bolded word opening a glossary entry) heads two entries, and no head + word is also struck through on a *Not:* line. + tools list with `--list tools` it prints the words retired in the tools' descriptions, which is the + copy the catalogue's own check keeps (mesh-catalog `retired-words`). With + MESH_CATALOG_DIR set to a checkout of the catalogue it also fails when that copy differs. + +Checked documents: 00-META/, 03-DESIGN/ (both layers), AGENTS.md and README.md, always; a research +effort initiated, or an issue opened, on or after FROM. Never 02-DECISIONS/: a decision record keeps the +words it was written with. + +A document the check fails on that cannot be reworded in the change that found it is named, with a date +and a reason, in words-allowed.md beside this file. An entry past its date fails like the word itself, and +an entry for a document that no longer needs it fails too. `kept` instead of a date is allowed only for a +graduated research effort, which is a record of what was said, like a decision record. + + python3 00-META/checks/words.py + python3 00-META/checks/words.py --list tools +""" + +import datetime +import os +import re +import sys + +ROOT = os.path.normpath(os.path.join(os.path.dirname(__file__), "..", "..")) +GLOSSARY = "00-META/glossary.md" +ALLOWED = "00-META/checks/words-allowed.md" + +# The day the rule began (ADR 0244): research and issues from it on are held to it. +FROM = "2026-10-07" + +ALWAYS = ("00-META/", "03-DESIGN/", "AGENTS.md", "README.md") + +NOT_LINE = re.compile(r"^\s*(?:[-*]\s+)?\*Not:\*(.*)$", re.M) +STRUCK = re.compile(r"~~([^~]+)~~(?:\s*\((hq|tools)\))?") +IDENT_LINE = re.compile(r"^\s*(?:[-*]\s+)?\*Identifier until renamed:\*(.*)$", re.M) +CODE_SPAN = re.compile(r"`([^`]+)`") +HEAD = re.compile(r"^\s*-\s+\*\*([^*]+)\*\*", re.M) + + +def rel(path): + return os.path.relpath(path, ROOT) + + +def read(path): + with open(os.path.join(ROOT, path), encoding="utf-8") as handle: + return handle.read() + + +def frontmatter_field(text, name): + if not text.startswith("---\n"): + return None + end = text.find("\n---", 4) + m = re.search(r"^%s:\s*(\S+)" % name, text[4:end], re.M) + return m.group(1) if m else None + + +def glossary(): + """The retired words with their scope, the identifiers, and the head words.""" + text = read(GLOSSARY) + retired = [] + for line in NOT_LINE.findall(text): + for word, scope in STRUCK.findall(line): + for one in word.split(" / "): + retired.append((one.strip(), scope or "all")) + identifiers = [] + for line in IDENT_LINE.findall(text): + identifiers += [i.strip() for i in CODE_SPAN.findall(line)] + heads = [] + for head in HEAD.findall(text): + heads += [h.strip() for h in head.split(" / ")] + return retired, identifiers, heads + + +def pattern(word): + """A whole-word, case-insensitive match; a space in the word matches a space, a line break or a hyphen.""" + parts = [re.escape(p) for p in word.split()] + return re.compile(r"(?i)(?", # comments + r"(?m)^\s*>.*$", # block quotes + r"`[^`\n]+`", # code spans + r"\]\([^)\s]*\)", # link targets + r"~~[^~\n]+~~", # struck through: a word named as retired + r"\"[^\"\n]*\"", # "quoted" + r"\u201c[^\u201d]*\u201d", # curly double quotes + r"\u2018[^\u2019\n]*\u2019", # curly single quotes + ] + if glossary_file: + steps[:0] = [NOT_LINE.pattern, IDENT_LINE.pattern] + for step in steps: + text = re.sub(step, blank, text) + return text + + +def research_and_issues(): + """The research efforts and issue reports dated on or after FROM, file by file.""" + out = [] + for folder, key, first in (("01-RESEARCH", "initiated", "00-overview.md"), ("04-ISSUES", "opened", "00-report.md")): + base = os.path.join(ROOT, folder) + for effort in sorted(os.listdir(base)): + head = os.path.join(base, effort, first) + if not os.path.isfile(head): + continue + date = frontmatter_field(read(rel(head)), key) + if not date or date < FROM: + continue + for name in sorted(os.listdir(os.path.join(base, effort))): + if name.endswith(".md"): + out.append("%s/%s/%s" % (folder, effort, name)) + return out + + +def checked_documents(): + docs = [] + for entry in ALWAYS: + path = os.path.join(ROOT, entry) + if os.path.isfile(path): + docs.append(entry) + continue + for base, dirs, files in os.walk(path): + dirs.sort() + for name in sorted(files): + if name.endswith(".md"): + docs.append(rel(os.path.join(base, name))) + return docs + research_and_issues() + + +def allowed(): + """words-allowed.md: one row per document — | `document` | until | why |. + + `until` is a date, after which the allowance fails like the words themselves, or `kept` for a + document that records what was said and must keep its words: only a research effort that has + graduated may be kept, because once it has become a decision it is a record like one. + """ + out, problems = {}, [] + if not os.path.isfile(os.path.join(ROOT, ALLOWED)): + return out, problems + today = datetime.date.today().isoformat() + for line in read(ALLOWED).splitlines(): + cells = [c.strip() for c in line.strip().strip("|").split("|")] + if len(cells) != 3 or not cells[0].startswith("`"): + continue + doc, until, why = cells[0].strip("`"), cells[1], cells[2] + if not why: + problems.append("%s: the allowance for %s gives no reason" % (ALLOWED, doc)) + if until == "kept": + overview = os.path.join(os.path.dirname(doc), "00-overview.md") + status = frontmatter_field(read(overview), "status") if os.path.isfile(os.path.join(ROOT, overview)) else None + if not doc.startswith("01-RESEARCH/") or status != "graduated": + problems.append("%s: %s is kept, but only a graduated research effort may be" % (ALLOWED, doc)) + continue + elif not re.match(r"^\d{4}-\d{2}-\d{2}$", until): + problems.append("%s: the allowance for %s has neither a date nor `kept`" % (ALLOWED, doc)) + continue + elif until < today: + problems.append("%s: the allowance for %s ran out on %s — reword it" % (ALLOWED, doc, until)) + continue + out[doc] = until + return out, problems + + +def check_retired(retired, identifiers): + problems = [] + allowance, problems_allowed = allowed() + problems += problems_allowed + words = [(w, pattern(w), "retired") for w, scope in retired if scope in ("all", "hq")] + words += [(i, pattern(i), "an identifier, allowed only in a code span") for i in identifiers] + used = set() + for doc in checked_documents(): + text = prose(read(doc), glossary_file=(doc == GLOSSARY)) + for word, rx, why in words: + for m in rx.finditer(text): + if doc in allowance: + used.add(doc) + continue + line = text.count("\n", 0, m.start()) + 1 + problems.append("%s:%d: %r is %s (glossary)" % (doc, line, m.group(0).replace("\n", " "), why)) + for doc in allowance: + if doc not in used: + problems.append("%s: %s no longer uses a retired word — remove its allowance" % (ALLOWED, doc)) + return problems + + +def check_unique(retired, heads): + problems = [] + seen = {} + for head in heads: + key = head.lower() + if key in seen: + problems.append("%s: %r heads two entries" % (GLOSSARY, head)) + seen[key] = True + for word, _ in retired: + if word.lower() in seen: + problems.append("%s: %r is a head word and also retired" % (GLOSSARY, word)) + return problems + + +def tools_list(retired): + return sorted({w for w, scope in retired if scope in ("all", "tools")}, key=str.lower) + + +def check_copy(retired): + catalogue = os.environ.get("MESH_CATALOG_DIR") + if not catalogue: + print("NOT COMPARED: MESH_CATALOG_DIR is not set, so the catalogue's copy of the list was not read") + return [] + path = os.path.join(catalogue, "retired-words") + try: + with open(path, encoding="utf-8") as handle: + copy = [l.strip() for l in handle if l.strip() and not l.startswith("#")] + except OSError as err: + return ["the catalogue's copy of the retired words cannot be read: %s" % err] + want = tools_list(retired) + if sorted(copy, key=str.lower) != want: + return ["the catalogue's retired-words differs from the glossary: it should list %s" % ", ".join(want)] + return [] + + +def main(argv): + retired, identifiers, heads = glossary() + if argv[1:2] == ["--list"]: + scope = argv[2] if len(argv) > 2 else "tools" + words = tools_list(retired) if scope == "tools" else sorted({w for w, s in retired if s in ("all", scope)}) + print("\n".join(words)) + return 0 + if not retired: + print("words: the glossary names no retired word on a *Not:* line — the check would pass on anything") + return 1 + problems = check_unique(retired, heads) + check_retired(retired, identifiers) + check_copy(retired) + for p in problems: + print(p) + if problems: + print("words: %d problem(s)" % len(problems)) + return 1 + print("words: %d retired words and %d identifiers, none in running prose; %d head words, each once" + % (len(retired), len(identifiers), len(heads))) + return 0 + + +if __name__ == "__main__": + sys.exit(main(sys.argv)) diff --git a/00-META/effect.md b/00-META/effect.md index 58f21cce..d4050ae6 100644 --- a/00-META/effect.md +++ b/00-META/effect.md @@ -11,7 +11,7 @@ Imagine the mesh works as intended. What is different? An agent says what it wants — from a terminal, a phone, a message — and the mesh takes it from there. It works out which nodes are involved, does the work, and returns a -result. Nobody opens a console, recalls which node holds what, or follows a runbook +result. Nobody opens a terminal, recalls which node holds what, or follows a runbook written months ago. The interface is intent. The mesh handles the rest. diff --git a/00-META/glossary.md b/00-META/glossary.md index 035e8b89..12eb289d 100644 --- a/00-META/glossary.md +++ b/00-META/glossary.md @@ -1,175 +1,498 @@ -# Glossary — the words this repository uses, and the ones it stopped using +# Glossary — the words of the mesh, by domain, and the ones it stopped using One name per thing. This page is the authority; where an older record says something else, that -record is being superseded, not this page. It exists because the terms kept drifting in -conversation — control plane / controller / master / hub for one thing, substrate / foundation for -another — and a mesh you cannot name precisely is a mesh two people describe differently. +record keeps its words and this page says how to read them. It exists because the words kept drifting +in conversation — "control plane", "controller" and "master" for one thing, "substrate" and +"foundation" for another — and a mesh you cannot name precisely is a mesh two people describe differently. The rule and +its check are [ADR 0244](../02-DECISIONS/0244-the-mesh-is-described-in-domains-and-one-word-names-one-thing.md); +the domains are drawn in [to-be 49](../03-DESIGN/01-to-be/49-the-mesh-in-domains.md). -## The mesh and its machines +## How to read this page -- **node** — a machine in the mesh. There are 0..n of them, and each runs the node-engine. A node is - just a machine that has joined; being one implies nothing about what it runs. -- **operator account** — the login name of the person who works on a node, stated on the node - record; empty for a machine nobody logs into. Everything the mesh places under a person's home is - resolved against this account's home and owned by it - ([ADR 0181](../02-DECISIONS/0181-the-operator-account-is-a-node-fact-and-a-home-is-a-placement-root.md)). - Not "the user" (ambiguous with a module's own account) and not a name a definition carries. -- **control-node** — the one node that also holds the `mesh-controller` seat. There is exactly one - per mesh. "control-node" is not a separate kind of machine — it is a node that additionally runs - the controller (and, today, the foundation). Lose it and the other nodes keep running what they - were last told; they simply cannot be told anything new. -- ~~master / slave~~, ~~hub / peer~~ — not used. The relationship is *controller and nodes*, and no - node is subordinate: a node applies declarations on its own and survives the control-node dying. +A **domain** is an area of the mesh that owns a set of concepts: inside it each concept has one word, +and the domain decides what that word means. Other domains use the word as it is defined here and never +change its meaning. Every word is defined once, in the domain that owns it; a section lists the words +it uses from other domains under *Uses*, with no second definition. -## What runs the mesh +Two fixed lines follow an entry where they apply, and `00-META/checks/words.py` reads them: -- **controller** — the component that decides what each node should be, holds the mesh's records, - and tells nodes over the broker. Replaces **"control plane"** (borrowed from networking's - control-plane/data-plane, and opaque here). -- **mesh-controller** — the module that runs the controller. It **claims** the `mesh-controller` - seat at mesh scope, which is what makes it singular. Replaces the module name **`mesh-control`**. -- **node-engine** — the program on every node that applies what the controller declares: it receives the - node's declaration, writes the files, runs the services and containers, and reports what it did. It - is the engine, not a module: it owns no file's content, and every file it writes belongs to the module - that declared it. Replaces **"host agent"**, **"the host"** and **`mesh-host`**. *Agent* is avoided - because the word already means two other things here, the build agent and the operator's coding - agent. The code still carries the old name (the `mesh-host` repository, its binary and service - unit) until the rename is made there; a record written before the rename keeps the old name. - (The git repository has been renamed `mesh-control` -> `mesh-controller` on the forge; the module, - container and image it produces are `mesh-controller`.) -- **foundation** — the store and the broker, raised at genesis before any module system exists. - Replaces **"substrate"** (a biology metaphor that landed for no one). The foundation is not a - third thing beside the store and broker — it *is* those two, named together. -- **store** — the one postgres server. It holds the controller's own context databases - (`inventory`, `identity`, `licences` — a context owns its store, [ADR 0008](../02-DECISIONS/0008-a-context-owns-its-store.md)) - and every module's own database. One server, many databases — never one shared "mesh database". +- *Not:* followed by struck-through words — the words this entry replaced. A struck word with no scope + is retired everywhere; *(hq)* retires it in this repository's prose only; *(tools)* in the + descriptions of the mesh's tools and seat verbs only. A retired word may be quoted, never used. +- *Identifier until renamed:* followed by a name in a code span — code that still carries an old name. + It may stand in a code span and nowhere else until the code is renamed, and then the line goes. + +Nothing else on this page is struck through. A word that means different things in different domains +is listed under [Homonyms](#homonyms), with the qualified form each domain uses. + +## Words every domain uses + +- **mesh** — the whole: the nodes, the modules assigned to them, the controller that decides and the + records it keeps. One mesh per operator; a second mesh is a second everything. +- **domain** — an area of the mesh that owns a set of concepts and the one word for each, as described + in the section above ([ADR 0244](../02-DECISIONS/0244-the-mesh-is-described-in-domains-and-one-word-names-one-thing.md)). + ADR 0006 called seven of them the controller's *contexts*; they carry over as domains under the same + names, except *observability*, which is now **Health and repair**. Where a domain's records live in + the controller, the domain owns that store alone ([ADR 0008](../02-DECISIONS/0008-a-context-owns-its-store.md)). + *Context* in that sense is not used in new writing; the word stays ordinary English, so this is + checked by review, not by `words.py`. +- **machine** — any computer: hardware, a virtual machine, before, during or outside its membership of + the mesh. Prose about the computer itself — its disks, its kernel, the network it sits on, what was on + it before the mesh — says machine. +- **node** — a machine the mesh has adopted and owns. A machine becomes a node when it joins + ([ADR 0004](../02-DECISIONS/0004-a-node-and-how-it-joins.md)), and from then on it runs the node-engine + and is either **adopted** (the mesh holds what it found there as found) or **converged** (the mesh made + it what it is) — [ADR 0100](../02-DECISIONS/0100-a-node-in-use-is-adopted-before-it-is-converged.md). + Prose about a member of the mesh says node. A node is just a machine that has joined; being one + implies nothing about what it runs. +- **module** — the unit the mesh assigns: one named thing, defined by its manifest, that a node runs — + a service, a container, files, packages, its own code — and the tools it serves + ([ADR 0040](../02-DECISIONS/0040-what-a-module-is.md)). Everything configurable on a node is a module. +- **seat** — a named role at a scope (node / site / mesh), held by a module assignment, from a **closed + set** the mesh defines: a claim naming a seat outside the set is refused. A seat may **deliver a + provision**, and its holder is then the mesh's answer for it when several modules provide it + ([ADR 0126](../02-DECISIONS/0126-a-module-declares-its-own-seats.md)). The set, with who holds each + seat, is the overview of what a mesh has ([26 — The seats](../03-DESIGN/01-to-be/26-the-seats.md)). + A seat carries **verbs** — the tools every holder must serve. +- **operator** — the person who runs the mesh, and the one the mesh talks to. + *Not:* ~~master~~ (hq) +- **person** — any human, as against the mesh acting unattended: *a person's word* releases a held + delivery, *a person* deletes a consumer's data. The operator is one; the word says that a human, not + the mesh, acted. + +## Module — what a module is and declares + +**Purpose.** To say what one module is, in the one document every other domain reads. +**Recorded in** the catalogue, one manifest per module. **Upstream of** every other domain; its words +are a published vocabulary, fixed by the manifest's schema. **Decided by** ADR 0040, 0126, 0174, 0188, +0207, 0236. +**Uses:** module, seat, provision (Provisioning), data class (Data), health (Health and repair). + +- **manifest** — the file a module is defined by (`module.json`): what it is, what it provides and + requires, the seats it claims, the resources it declares, its tools, its data and its health. Say + manifest in prose, not the file's name. + *Not:* ~~module definition~~ (hq) +- **resource** — one thing a manifest declares a node must have: a file, a directory, a service, a + container, a package, a bundle, an archive. A module **declares** resources; the verb *declares* is + this domain's and means what a manifest states. +- **claim** — a module taking a spot on a seat: `claims: [{name, scope}]` in a manifest. A mesh-scoped + exclusive claim is how the mesh says "there is one of me". A foundation seat is named after the role + it guards: the `mesh-controller`, `postgres` and `nats` modules claim the `mesh-controller`, + `mesh-store` and `mesh-broker` seats ([ADR 0079](../02-DECISIONS/0079-the-foundation-seats-are-named-after-their-servers.md), + [ADR 0116](../02-DECISIONS/0116-the-bus-is-built-in-five-steps.md)). +- **setting** — a value a manifest declares and an assignment gives, one of the two ways a node varies + a module ([ADR 0174](../02-DECISIONS/0174-a-node-varies-a-module-through-settings-and-kept-regions-never-an-edit.md)). + What a "flavor" once varied is a setting or a separate module. + *Not:* ~~flavor~~ +- **kept region** — a marked block in a managed file the mesh writes *into*, where the operator's own + lines survive every send and are given back when the module goes (ADR 0174). The other way a node + varies a module. +- **bundle** — the artifact a module's own code is built into — its tools, a seat's implementation, a + daemon — in any language the mesh has a toolchain for; never an image. A **tools bundle** speaks MCP to + the tool runner. One module may declare several + ([ADR 0188](../02-DECISIONS/0188-a-modules-own-code-is-bundles-in-any-language-and-a-tools-bundle-speaks-mcp-to-the-runtime.md)). +- **tool** — one capability a module serves through the tool runner, addressed `.`. A + seat's **verb** is the same thing carried by a seat rather than a module. +- **mesh-sdk** — the library a module's own code is written against, including the harness that serves a + tools bundle to the tool runner. The harness is part of it, not a library of its own. + *Not:* ~~tools-sdk~~ +- **invokes** — the manifest word for the tools a module calls, `.` each or `*` for every + one. A grant on the publish side and nothing else; a module that declares none calls nothing. +- **upgrade policy** — how a module's new builds reach its nodes: `roll` (one node first, judged, then + the rest), `together`, or `record` (register the build, send it nowhere) + ([ADR 0236](../02-DECISIONS/0236-a-build-is-judged-on-its-first-machine-and-put-back-by-something-other-than-itself-and-so-it-rolls-out-unattended.md)). + The controller's verb `upgrade` shows it. + +## Core — the mesh's own machinery + +**Purpose.** To keep the controller, the bus, the store and every node's engine running and agreed, so +every other domain has something to run on. **Recorded by** the controller and the store. +**Upstream of** every domain but Module; the others use the bus and the store as they are. +**Decided by** ADR 0005, 0006, 0067, 0106, 0141, 0175, 0227, 0229. +**Uses:** node, module, seat. + +- **controller** — the component that decides what each node should be, holds the mesh's records, and + tells nodes over the bus. The relationship is *controller and nodes*, and no node is subordinate: a + node applies its declaration on its own and survives the control-node dying. + *Not:* ~~control plane~~, ~~slave~~ (hq), ~~mesh-control~~ (hq) +- **mesh-controller** — the module that runs the controller. It claims the `mesh-controller` seat at + mesh scope, which is what makes it singular. +- **control-node** — the one node that also holds the `mesh-controller` seat. There is exactly one per + mesh. It is not a separate kind of node — it is a node that additionally runs the controller (and, + today, the foundation). Lose it and the other nodes keep running what they were last told; they simply + cannot be told anything new. +- **node-engine** — the program on every node that applies what the controller declares: it receives + the node's declaration, writes the files, runs the services and containers, and reports what it did. + It is the engine, not a module: it owns no file's content, and every file it writes belongs to the + module that declared it. *Agent* is avoided because that word means a coding agent here. A record + written before the rename keeps the old name. + *Not:* ~~host agent~~, ~~the host~~, ~~node host~~ + *Identifier until renamed:* `mesh-host` — the repository, its binary and its service unit +- **tool runner** — the one program on each node that loads every assigned module's tools bundle and + serves every tool and every held seat's verb on the subjects the memberships issue; the node-engine + supervises it as a process beside the services it runs, never a container + ([ADR 0175](../02-DECISIONS/0175-one-tool-runtime-per-node-serves-every-modules-tools-on-the-host-side.md), + which called it "node tools"). Its mode on loopback is the mesh MCP server. + *Not:* ~~node tools~~, ~~tool runtime~~ (hq) + *Identifier until renamed:* `node-tools` — the module, its unit and its bus account +- **foundation** — the store and the bus, raised at genesis before any module system exists. The + foundation is not a third thing beside the store and the bus — it *is* those two, named together. + *Not:* ~~substrate~~ +- **genesis** — raising the foundation and the controller on an empty mesh, by the one installation + done by hand ([ADR 0067](../02-DECISIONS/0067-genesis-is-a-pivot.md)). +- **store** — the one postgres server. It holds the controller's own databases (`inventory`, + `identity`, `licences`, each owned by one domain, ADR 0008) and every module's own database. One server, + many databases — never one shared mesh database. Bare *store* means this and nothing else + (see [Homonyms](#homonyms)). - **bus** — the mesh's own nervous system: NATS, one per mesh, carrying every link the mesh has — - control, declarations, builds, events, tool calls - ([ADR 0106](../02-DECISIONS/0106-the-bus-is-nats.md)). A module reaches it by requiring - `mesh-bus` ([ADR 0128](../02-DECISIONS/0128-the-mesh-bus-is-required-not-ambient.md)); one that - does not require it has no account on it. Held by the `mesh-broker` seat, which is named after - the *role* rather than the server, so the server can change without the seat doing so. -- **the deprecated broker** — the lavinmq module. It was the mesh's bus and is not any more. It - keeps running as an **ordinary provider** of the `amqp` provision, for modules that need a - message broker of their own the way something needs a database - ([ADR 0127](../02-DECISIONS/0127-amqp-is-a-provision-not-the-bus.md) (superseded by [ADR 0131](../02-DECISIONS/0131-everything-on-the-mesh-speaks-to-the-broker-seat.md))) — no seat, not foundation, - never raised at genesis, and a mesh that never installs it is complete. + control, declarations, builds, events, tool calls ([ADR 0106](../02-DECISIONS/0106-the-bus-is-nats.md)). + A module reaches it by requiring `mesh-bus` ([ADR 0128](../02-DECISIONS/0128-the-mesh-bus-is-required-not-ambient.md)); + one that does not require it has no account on it. Held by the `mesh-broker` seat, which is named + after the role rather than the server, so the server can change without the seat doing so. The `nats` + module holds it. +- **the deprecated broker** — the lavinmq module. It was the mesh's bus and is not any more. It keeps + running as an **ordinary provider** of the `amqp` provision, for modules that need a message broker + of their own the way something needs a database + ([ADR 0131](../02-DECISIONS/0131-everything-on-the-mesh-speaks-to-the-broker-seat.md)) — it claims no + seat, is not foundation, is never raised at genesis, and a mesh that never installs it is complete. + Say *the deprecated broker*, not *the compatibility broker* (it serves the mesh's own modules, not only + the predecessor's) and not *the AMQP broker* (naming it after a protocol invites describing the bus by + contrast with it, which is backwards). +- **layer** — one of the four levels the mesh is built in, from the bottom: the node-engine, the + foundation, the controller, the surfaces. The records' `topic: the tiers` and `repos.md` say *tier* for + this; new prose says layer (see [Homonyms](#homonyms)). +- **lease** and **epoch** — which controller may send (the lease, held in the bus and renewed), and the + order of what it sent (the epoch, which a node reads before it accepts a declaration) + ([ADR 0229](../02-DECISIONS/0229-the-cores-order-is-a-lease-the-store-remembers-and-an-epoch-a-machine-is-sent-once-it-reads-one.md)). - Say *the deprecated broker*, not "the compatibility broker" (it serves the mesh's own modules, - not only the predecessor's) and not "the AMQP broker" (naming it after a protocol invites - describing the bus by contrast with it, which is backwards: the bus is the mesh's nervous - system and this is a module). +## Placement — what runs on which node -## What the mesh stores and serves +**Purpose.** To decide, send and apply what each node runs, and to say why. +**Recorded by** the controller's `inventory` database. **Upstream of** Provisioning, Connectivity, Data +and Health and repair. **Decided by** ADR 0100, 0126, 0176, 0181, 0207, 0221. +**Uses:** node, machine, module, seat, setting (Module), manifest (Module). -- **package** — what code resolves when it is **compiled**: an npm/cargo/pypi dependency, by - **version**. Served by the **package-registry** (gitea). Only a builder talks to it. -- **artifact** — anything a build produces and the mesh delivers to a machine by **digest**: an - `image`, a mirrored `upstream` image, a `bundle` of the module's own code, an `archive`. Served by - the **artifact-store**, an OCI registry that holds every kind as content-addressed blobs +- **assignment** — a module put on a node, with the settings that node gives it. +- **scope** — where a seat or a claim holds: `node`, `site` or `mesh`. `node` is also the scope's + value in a manifest. +- **capacity** — how many holders a seat takes. A capacity-1 seat is exclusive. A higher-capacity seat + is a **bench**: several holders coexist. A **replicated** bench has one holder per node, each on record + and each answering the same, like `mesh-dns-resolver` + ([ADR 0223](../02-DECISIONS/0223-the-mesh-has-two-resolvers-and-a-machine-lists-only-them.md)). A + **kinded** bench has holders that are different modules, each claiming one **kind**, and a verb's + subject carries the kind; `channel` and `intake` are the only ones (ADR 0234). +- **installed / holding** — a module may be assigned (its package installed, its files placed) without + holding the seat its family declares; *holding* is being the one — the login shell, the display session + — on that node ([ADR 0176](../02-DECISIONS/0176-the-login-shell-is-a-node-seat-and-execute-is-its-contract.md)). + The module that holds a seat is its **holder**. +- **depends on a seat** — a module needing a seat held on its node by some module, without holding it. + Derived from the resources it declares, never stated: a `service` depends on `node-service-manager`, a + `package` on `node-package-manager`, a `container` on `node-container-runtime` + ([ADR 0207](../02-DECISIONS/0207-a-module-depends-on-the-node-seats-that-apply-its-resources.md)). + Not a claim: a module claims a seat it holds and declares resources. +- **declaration** — what the controller sends one node: every resource of every module assigned there, + composed. The noun is this domain's; what a manifest says is that it *declares* (Module), and a manifest + is never called a declaration. The controller's verb `plan` previews a node's declaration. +- **send** — the controller giving a node its declaration. The controller's verb `push` asks for a + send (of one node, or of every node); prose says send. A git push and a phone's push notification are + other things (see [Homonyms](#homonyms)). +- **apply** — the node-engine making its node match its declaration; **reconcile** is doing so again + until nothing differs. +- **operator account** — the login name of the person who works on a node, stated on the node record; + empty for a node nobody logs into. Everything the mesh places under a person's home is resolved + against this account's home and owned by it + ([ADR 0181](../02-DECISIONS/0181-the-operator-account-is-a-node-fact-and-a-home-is-a-placement-root.md)). + Not a name a manifest carries. + +## Provisioning — one module serving another + +**Purpose.** To resolve which module serves a provision for which consumer, and to wire the two. +**Recorded by** the controller. **Upstream of** Data; downstream of Module, Placement and Identity. +**Decided by** ADR 0027, 0084, 0138, 0225, 0230. +**Uses:** module, seat, credential (Identity and access). + +- **provision** — a service one module `provides` and others `require`; the mesh resolves a provider + and wires the two with an endpoint and a credential. A provision is a service you offer, a seat is a + role you occupy, and the two meet where a seat delivers a provision: occupying the seat is what makes + a module *the* provider of it. +- **provider** and **consumer** — the module that provides a provision, and the module that requires + it. Which of several providers serves a consumer is resolved + ([ADR 0084](../02-DECISIONS/0084-which-provider-serves-a-consumer.md)). +- **pin** — a person's choice of provider for a consumer, which resolution then respects. +- **endpoint** — where a consumer reaches its provider; an assignment binds it and says how far it + reaches ([ADR 0138](../02-DECISIONS/0138-an-assignment-binds-an-endpoint-and-says-how-far-it-reaches.md)). +- **retire** — a provider stops serving a consumer the mesh no longer asks for; what it kept is deleted + only by a person ([ADR 0230](../02-DECISIONS/0230-a-consumer-the-mesh-stops-asking-for-is-retired-and-deleted-only-by-a-person.md)). + +## Identity and access — who may do what + +**Purpose.** To issue, hold, rotate and check the credentials and grants by which modules, nodes, agents +and the operator act. **Recorded by** the controller's `identity` and `licences` databases and the vault. +**Upstream of** Provisioning, Change and delivery, and Operator and conversation. +**Decided by** ADR 0085, 0113, 0183, 0225, 0234. +**Uses:** module, node, operator, consumer (Provisioning). + +- **credential** — anything that proves an identity: a password, a key, a token. A **pair credential** + is the two ends of one credential, the consumer's and the provider's. +- **secret** — a credential or other value the vault keeps sealed and the node-engine unseals into a + file at apply ([ADR 0085](../02-DECISIONS/0085-a-secret-is-a-provision.md)). +- **vault** — the module that owns every secret (`mesh-vault`, + [24 — The secrets vault](../03-DESIGN/01-to-be/24-the-secrets-vault.md)). +- **grant** — what an identity may call or reach, bounded by the provisions it requires + ([ADR 0225](../02-DECISIONS/0225-a-consumers-identity-is-bounded-by-the-provision-it-requires.md)). +- **bus account** and **membership** — a module's or a node's account on the bus, and the subjects it + is issued. The bus server calls its accounts *users*; the mesh says bus account. +- **rotate** — replacing a credential without a consumer holding one the provider does not know. +- **licence** — a model-access account the licence manager holds and **binds** to a consumer, the + coding agent on a node ([ADR 0183](../02-DECISIONS/0183-the-anthropic-licence-manager-is-a-module-and-hands-tokens-to-the-agent-over-the-bus.md)). + Taking a consumer off a licence is **unbinding** it; the licence manager's verb for it is still + `release`, an identifier to rename. +- **proof** — what an authorising answer carries: a verified sender, a one-time code, a security key's + touch (ADR 0234). How much proof a kind of ask needs is its **assurance level**. + +## Change and delivery — a commit on its way to the nodes + +**Purpose.** To take one commit from its pull request to every node that should run it, and to put it +back when it fails. **Recorded by** the `mesh-delivery` module (deliveries) and the controller (the +planner, the walk). **Downstream of** Module, Core, Health and repair and Data. +**Decided by** ADR 0162, 0218, 0236, 0237, 0238, 0239. +**Uses:** module, node, declaration and send (Placement), health (Health and repair), person. + +- **merge check** — the two statuses a pull request carries before it may merge: the **merge gate** + (`mesh/merge-gate`, the build seat's composed check of the modules the change touches) and the + **repository check** (`mesh/repo-check`, the repository's own `merge-check.sh`) + ([ADR 0238](../02-DECISIONS/0238-a-commit-is-the-build-at-hand-one-commit-one-change-plan-checked-off-the-trunk-and-published-only-on-it.md)). +- **build seat** — the seat whose holder builds modules, `node-build-agent`; its holder on a node is the + **builder**. Any node may hold the seat, so there is no build node by nature + ([ADR 0237](../02-DECISIONS/0237-a-change-is-judged-against-the-mesh-that-runs-before-it-merges-on-the-build-seat.md)). + *Not:* ~~build machine~~ +- **build queue** — the controller's queue of builds to run; one entry in it is a **build request**. + The queue's verbs still say *ask* for an entry; the conversation's ask is another thing + (see [Homonyms](#homonyms)). +- **package** — what code resolves when it is **compiled**: an npm/cargo/pypi dependency, by **version**. + Served by the **package-registry** (gitea). Only a builder talks to it. +- **artifact** — anything a build produces and the mesh delivers to a node by **digest**: an `image`, a + mirrored `upstream` image, a `bundle` of the module's own code, an `archive`. Served by the + **artifact store**, an OCI registry that holds every kind as content-addressed blobs ([ADR 0156](../02-DECISIONS/0156-an-artifact-is-what-a-build-produces-and-the-store-is-named-for-its-scope.md)). - Every node pulls from it. An image is one kind of artifact, and a module is not an image. -- These are two protocols, not one store being weak — see [ADR 0075](../02-DECISIONS/0075-two-stores-and-which-provides-what.md). + An image is one kind of artifact, and a module is not an image. A package and an artifact are two + protocols, not one store being weak ([ADR 0075](../02-DECISIONS/0075-two-stores-and-which-provides-what.md)). +- **catalogue** — what the mesh holds of every module, at which commit; and the repository the + modules live in. `mesh-catalog` and `catalog_modules` are identifiers and keep their spelling. + *Not:* ~~catalog~~ (hq) +- **delivery** — one commit in one repository on its way to the nodes, from its pull request's head + being announced to delivered, failed, superseded or stopped: one pull request, one status, one note + ([ADR 0239](../02-DECISIONS/0239-a-delivery-is-owned-by-the-mesh-delivery-module-and-runs-from-commit-to-delivered.md)). + Its states are one table (`proposed`, `checked`, `ready` or `rejected`, `published`, `delivering`, + `held`, and the final four). Not *change* (a diff). *Pipeline* is the predecessor's build-and-deploy + mechanism, which the as-is designs describe; it is never a word for a delivery. + *Not:* ~~deployment~~ (hq) +- **delivery group** — two or more deliveries sharing a pull request head branch name across the mesh's + repositories, delivered as one unit in an order declared (`after:`) or inferred by the planner. One + level: a group holds deliveries, never groups. Its state is derived from its members, never set. +- **delivery plan** — what a delivery does to the mesh: its **build plan** (the modules it moves and + their dependents, in tiers), its **deploy plan** (per node, what it receives and what waits for a + person) and its verdict (the composed nodes, the replays). Computed by the controller's planner from a + diffset; the controller's verb `delivery-plan` shows it. *Deploy* is used in this one place. + *Not:* ~~change plan~~, ~~release plan~~ +- **mesh-delivery** — the module that owns deliveries and delivery groups, holding the mesh-scoped seat + of the same name. It records and decides; the controller sends, judges and rolls back when it is asked. +- **walk** — the controller's sending of one trunk commit's builds across nodes, tier by tier, one node + first and judged at the first-node gate, then the rest (ADR 0236). A primitive the delivering stage + asks for, not an object anyone manages. A **tier** is one step of a walk: a module, then the modules + built against it. The controller's verb `plans` lists the walks. Walking is not a noun of its own: + a module *rolls out* by its `roll` policy. + *Not:* ~~rollout~~ (hq) +- **first-node gate** — the judgement on a delivery's first node: healthy three times, no witness put it + back, no condition raised since the send (ADR 0236 §2). Not *the gate* bare (see [Homonyms](#homonyms)). +- **release** — a person's word that a held delivery goes on; the verb `mesh-delivery.release`. Used in + this sense only. +- **rollback** — putting a node back on the build it ran before, by something other than the build being + judged (a witness, ADR 0236). +- **the lab**, **scenario**, **replay** — a real mesh raised to run a change against before it reaches + nodes; what a lab run declares; and a recorded failure run again to prove a fix (ADR 0016, ADR 0237). -## How modules relate to the mesh +## Health and repair — what is wrong, and putting it right -- **seat** — a named role at a scope (node / site / mesh), held by a module assignment, from a - **closed set** the mesh defines: a claim naming a seat outside the set is refused. A seat may - **deliver a provision**, and its holder is then the mesh's answer for it when several modules - provide it ([ADR 0126](../02-DECISIONS/0126-a-module-declares-its-own-seats.md) (superseding [ADR 0110](../02-DECISIONS/0110-a-seat-is-a-module-assignment-from-a-closed-set.md))). - The set, with who holds each seat, is the overview of what a mesh has - ([26 — The seats](../03-DESIGN/01-to-be/26-the-seats.md)). A seat has a **capacity**: a - capacity-1 seat is exclusive (one holder); a higher-capacity seat is a **bench** (several holders - coexist). The first bench is `mesh-dns-resolver`, a *replicated* mesh seat: one holder per machine, - each on record and each answering the same names ([ADR 0223](../02-DECISIONS/0223-the-mesh-has-two-resolvers-and-a-machine-lists-only-them.md)). - The second sort is the **kinded** bench: holders are different modules, each claiming one **kind**, - and a verb's subject carries the kind. `channel` and `intake` are the only ones - ([ADR 0234](../02-DECISIONS/0234-the-mesh-holds-a-conversation-with-its-operator.md)). +**Purpose.** To notice what is wrong with the mesh, say it once, repair what may be repaired unattended, +and record what was done by hand. **Recorded by** the controller's condition store. +**Upstream of** Change and delivery (the first-node gate) and Operator and conversation (a condition +becomes a message). **Decided by** ADR 0227, 0231, 0240. +**Uses:** node, module, node-engine (Core). + +- **health** — a module's or a core component's statement of how it is alive and ready, which the + node-engine judges ([ADR 0240](../02-DECISIONS/0240-a-module-says-how-it-is-healthy-and-the-node-engine-judges-it.md)). +- **probe** — one look at one thing's health, run by whoever owns the verdict; what a probe returns is a + **finding**, before it becomes a condition. +- **signal** and **watchdog** — something that must keep happening (a heartbeat, a report after a send), + and the watcher that raises a condition when it stops (to-be 45 §3). +- **condition** — one open fact about something the mesh owns that is wrong, with a key and a severity; + raised and cleared only by observation + ([ADR 0231](../02-DECISIONS/0231-a-healer-acts-on-what-observation-raised-and-only-observation-says-it-worked.md)). + A wrapped dashboard's alerts are its own; the mesh's notion is a condition. + *Not:* ~~alert~~ (hq) +- **self-check** — the controller's own examination of the mesh, run on demand and on a schedule + (to-be 45 §4). Its verb is `doctor`, an identifier; prose says self-check. + *Not:* ~~doctor~~ (hq) +- **healer** — a registered response to one condition kind: its **repair** (the ordinary path again), + its **budget**, its back-off and its brake. A healer may not withdraw, delete or recreate data. +- **drill** — something broken on purpose to see the mesh raise and clear its condition; a drill is + never counted as a repair. +- **hand-act** — a repair a person made by hand, recorded so the mesh knows it happened. +- **witness** — what rolls a core component back when its new build does not become healthy: never the + component itself (to-be 45 §8). + +## Data — what the mesh keeps, and getting it back + +**Purpose.** To know every item of data a module holds, how precious it is, and to keep it recoverable. +**Recorded by** the manifests' `data` declarations and every node's `node-backup` seat. +**Upstream of** Change and delivery and Health and repair. **Decided by** ADR 0232, 0233, 0235. +**Uses:** module, node, provider and consumer (Provisioning), person. + +- **data class** — how precious an item of data is, ranked by the operator: `irreplaceable`, + `valuable`, `rebuildable`, `cache`; and `none` for a provision that keeps nothing of anybody's + ([ADR 0233](../02-DECISIONS/0233-a-module-declares-the-data-it-holds-and-the-mesh-protects-and-watches-it-from-that.md)). +- **backup** — a copy kept elsewhere by the node's `node-backup` holder; **restore point** — one backup + at one moment; **restore** — bringing it back beside the live data, never over it. +- **stream snapshot** — the bus's own backup of each stream, taken by the module holding `mesh-broker` + ([ADR 0235](../02-DECISIONS/0235-the-bus-is-backed-up-by-its-own-snapshot-of-each-stream.md)). +- **sticky binding** — a consumer's tie to the data a provider keeps for it, which moves only by a + person ([ADR 0232](../02-DECISIONS/0232-a-binding-to-a-consumers-data-moves-only-by-a-person.md)). + +## Connectivity — how nodes and modules reach one another + +**Purpose.** To make every node and module reachable by name where it should be, and unreachable where +it should not. **Recorded by** the controller. **Upstream of** Provisioning and Health and repair. +**Decided by** ADR 0007, 0117, 0138, 0223, 0226. +**Uses:** node, machine, assignment and endpoint. + +- **private network** — the mesh's own encrypted network between its nodes, on which every node has an + address and a mesh name ([ADR 0226](../02-DECISIONS/0226-the-private-network-is-assigned-by-its-own-name-and-the-proxy-names-its-public-issuer.md)). + The network the machine sits on without it is the **underlay**. + *Not:* ~~overlay~~ (hq) +- **anchor** and **hub** — the roles a node plays for the private network: the anchor is reachable from + outside and every node reaches it; a hub relays for nodes that cannot reach each other directly. +- **resolver** — a seat holder that answers the mesh's names; a node lists only the mesh's resolvers + (ADR 0223). **uplink** — a node's connection to the outside network, a seat + ([ADR 0117](../02-DECISIONS/0117-a-machines-uplink-is-a-seat.md)). **hostname** — a node's own name, + a seat. +- **proxy** and **public name** — the module that answers a public name and forwards it to an + endpoint on the private network. +- **packet filter** — what the mesh enforces on a node about which packets pass, the + `node-packet-filter` seat and its verb `rules`. A **found firewall** is a program the mesh found on a + machine and keeps retired or in force; *firewall* says that, and a wrapped program's firewall is its own. +- **intrusion prevention** and **ban** — refusing a source that misbehaved, and the refusal itself. +- **reach** — how far an assignment's endpoint may be reached from: the node, the private network, or + the outside (ADR 0138). + +## Operator and conversation — the person the mesh works for + +**Purpose.** To let the mesh and its operator talk: tell, ask, answer, and act only on an answer it can +trust. **Recorded by** the router and the controller (authorising asks). +**Downstream of** Health and repair, Change and delivery, and Identity and access. An authorising +answer acts in whichever domain asked, through that domain's own verb; the conversation owns the +asking, never the act. **Decided by** ADR 0152, 0175, 0234. +**Uses:** operator, person, proof (Identity and access), release (Change and delivery). + +- **mesh MCP server** — the endpoint on a node's loopback through which every agent and person on that + node reaches the mesh: the MCP server named `mesh`, with its five tools `mesh_overview`, + `mesh_machine`, `mesh_search`, `mesh_describe` and `mesh_call`. It is the tool runner's loopback mode, + not a module of its own. "Console" suggested a terminal or a shell, and the module `mesh-console` it + once named no longer exists; "the tool bridge" and "the brain" named the predecessor's program. + *Not:* ~~console~~ (hq), ~~mesh-console~~, ~~tool bridge~~ - **channel / intake** — the two kinded benches the mesh talks to its operator through: `channel` - sends, `intake` turns what arrives into one envelope. A holder of either declares **capabilities** - from the fixed vocabulary `channel-capabilities/1`. Not "notifier" (that is the desktop's node seat, - one holder of kind `desktop`) and not "bot" (that is one service's account). -- **ask** — a request for the operator's input, of a declared kind (yes-no, one-of, text, number, date, - acknowledge). An **authorising ask** is one whose answer performs an action; the controller holds it - and checks its **proofs** (a verified sender, a TOTP code, optionally a security key's touch) - ([ADR 0234](../02-DECISIONS/0234-the-mesh-holds-a-conversation-with-its-operator.md)). + sends, `intake` turns what arrives into one envelope. A holder of either declares **capabilities** from + the fixed vocabulary `channel-capabilities/1`. Not *notifier* (that is the desktop's node seat, one + holder of kind `desktop`) and not *bot* (that is one service's account). +- **router** — the module that holds the `operator-channel` seat, orders the channels and checks every + sender ([ADR 0234](../02-DECISIONS/0234-the-mesh-holds-a-conversation-with-its-operator.md)). +- **ask** — a request for the operator's input, of a declared kind (yes-no, one-of, text, number, + date, acknowledge). An **authorising ask** is one whose answer performs an action; the controller + holds it and checks its proofs (ADR 0234). - **operator message / input** — what arrives on `intake`, once the router has checked the sender against the controller's list of the operator's identities: a **trusted** one is an operator message, addressed to an agent by `@name` or thread or else to the **responder** (`@mesh`, the router's own participant, which answers from read verbs); anything else is **untrusted input** — data, never - instructions ([ADR 0234](../02-DECISIONS/0234-the-mesh-holds-a-conversation-with-its-operator.md)). + instructions. - **reference** — an opaque token in a message's words standing for a detail the content rule keeps out - of them (a path, an address); opened with `detail` only on a `private` channel or at the console. -- **claim** — a module taking a spot on a seat. `claims: [{name, scope}]` in a manifest. A - mesh-scoped exclusive claim is how the mesh says "there is one of me". A foundation seat is - named after the server it guards: the `mesh-controller`, `postgres` and `lavinmq` modules claim - the `mesh-controller`, `mesh-store` and `mesh-broker` seats ([ADR 0079](../02-DECISIONS/0079-the-foundation-seats-are-named-after-their-servers.md)). -- **depends on a seat** — a module needing a seat held on its node by some module, without holding - it. Derived from the resources it declares, never stated: a `service` depends on - `node-service-manager`, a `package` on `node-package-manager`, a `container` on - `node-container-runtime` ([ADR 0207](../02-DECISIONS/0207-a-module-depends-on-the-node-seats-that-apply-its-resources.md)). - Not a claim: a module **claims** a seat it holds and **declares** resources. Nothing claims a - package. -- **provision** — a service one module `provides` and others `require`; the mesh resolves a provider - and wires the two with an endpoint and a credential. A provision is a service you offer, a seat - is a role you occupy, and the two meet where a seat delivers a provision: occupying the seat is - what makes a module *the* provider of it. + of them (a path, an address); opened with `detail` only on a `private` channel or through the mesh MCP server. +- **agent** — a coding agent: the operator's, or one the mesh runs. Not the node-engine, and not the + build seat's holder, whose seat name `node-build-agent` is an identifier to rename. -## The surfaces +## The record — how this repository works -- **console** — the module (`mesh-console`) that puts the mesh's tools in front of whoever is on a - machine: an MCP endpoint on the machine's loopback for an agent, the same endpoint for a person. It - is assigned like any module, holds a credential the mesh minted, and calls tools under a grant its - manifest declares (`invokes`). Loopback is the authority boundary: whoever is on the machine owns the - mesh there ([ADR 0152](../02-DECISIONS/0152-the-operators-surface-is-a-module-the-console.md)). - Not "the tool bridge", "the brain" or "the MCP server" — those name the predecessor's program or a - protocol, and the console is a module. -- **invokes** — the manifest word for the tools a module calls, `.` each or `*` for - every one. A grant on the publish side and nothing else; a module that declares none calls nothing. +Not a domain of the mesh but of the way it is built, listed because its words meet the mesh's. +**Recorded in** this repository. **Decided by** ADR 0019, 0080, 0244. -## How a change reaches the machines +- **research effort**, **graduation**, **hand-off**, **playbook** — an investigation in `01-RESEARCH/`; + its closing into a decision and a design; a design given to a code repository; a documented workflow + in `00-META/process/`. +- **decision record** — one numbered file in `02-DECISIONS/`, never rewritten: **superseded** by a later + record, or corrected by a marked **progressive insight**. Always qualified in this repository: bare + *record* has other meanings (see [Homonyms](#homonyms)). +- **design** — a document in `03-DESIGN/`: **as-is** (what runs) or **to-be** (what is being built). +- **issue** — a report in `04-ISSUES/` of something wrong with the mesh at the level of design or + governance; an **incident** is a past event that taught a rule. +- **hq check** — one of `00-META/checks/`, run by `merge-check.sh` as this repository's check. -- **delivery** — one commit in one repository on its way to the machines, from its pull request's head being - announced to delivered, failed, superseded or stopped: one pull request, one status, one note - ([ADR 0239](../02-DECISIONS/0239-a-delivery-is-owned-by-the-mesh-delivery-module-and-runs-from-commit-to-delivered.md)). - Its states are one table (`proposed`, `checked`, `ready` or `rejected`, `published`, `delivering`, `held`, - and the final four). Replaces **release plan** as a concept: the controller's plan is now the *walk* of one - delivery's trunk commit across machines, its record and nothing more. Not "change" (a diff), not - "pipeline" (to-be 10 retires it), not "deployment" (one stage of it). -- **delivery group** — two or more deliveries sharing a pull request head branch name across the mesh's - repositories, delivered as one unit in an order declared (`after:`) or inferred by the planner. One level: - a group holds deliveries, never groups. Its state is derived from its members, never set. A lone delivery - needs none. -- **delivery plan** — what a delivery does to the mesh: its build plan (the modules it moves and their - dependents, in tiers), its deploy plan (per machine, what it receives and what waits for a person) and its - verdict (the composed machines, the replays). Computed by the controller's planner from a diffset. ADR 0238 - called it the **change plan**; that word is retired. -- **mesh-delivery** — the module that owns deliveries and delivery groups, holding the mesh-scoped seat of the - same name. It records and decides; the controller sends, judges and rolls back when it is asked. -- **walk** — the controller's sending of one trunk commit's builds across machines, tier by tier, one machine - first and judged at the gate (ADR 0236). A primitive the delivering stage asks for, not an object anyone - manages. +## Homonyms + +A word below means different things in different domains. In a governing document it is never bare; +it is always the qualified form. **Checked by review, not by `words.py`:** a word list cannot tell one +sense from another, so the reviewer looks for the bare word in the diff. Where a homonym is settled by +renaming one sense, the old sense moves to a *Not:* line and becomes mechanical. + +| Word | Sense | Say | Domain | +|---|---|---|---| +| plan | what the controller would send one node | that node's **declaration** (verb `plan`) | Placement | +| | what a delivery would do to the mesh | **delivery plan** | Change and delivery | +| | the sending of one commit across nodes | **walk** (verb `plans`) | Change and delivery | +| | a step a person starts, like the bus's | **planned step** | Core | +| push | the controller giving nodes their declarations | **send** (verb `push`) | Placement | +| | a git push | **git push** | The record | +| | a phone notification | **push notification** | Operator and conversation | +| release | a person letting a held delivery go on | **release** | Change and delivery | +| | taking a consumer off a licence | **unbind** (verb `release`, to rename) | Identity and access | +| gate | the judgement on a delivery's first node | **first-node gate** | Change and delivery | +| | the pull request status | **merge gate** | Change and delivery | +| | a failed step stopping its module's later steps (ADR 0136) | *a failed step holds its module* | Placement | +| tier | a level of the mesh | **layer** | Core | +| | a step of a walk | **tier** | Change and delivery | +| | how much proof an ask needs | **assurance level** | Identity and access | +| ask | a request for the operator's input | **ask** | Operator and conversation | +| | an entry in the build queue | **build request** | Change and delivery | +| store | the one database server | **store** | Core | +| | the OCI registry | **artifact store** | Change and delivery | +| | a node's own copy of its last declaration | **last declaration** | Placement | +| record | a numbered decision | **decision record** | The record | +| | the knowledge base agents search first | **the record** (the `records` module) | Operator and conversation | +| | the upgrade policy that sends nowhere | `record` | Module | +| check | a pull request's two statuses | **merge check**, **merge gate**, **repository check** | Change and delivery | +| | a delivery group's verdict | **composed check** | Change and delivery | +| | the controller's own examination | **self-check** | Health and repair | +| | a file in `00-META/checks/` | **hq check** | The record | +| agent | a coding agent | **agent** | Operator and conversation | +| | the build seat's holder | **builder** | Change and delivery | +| the user | an account of a wrapped program, or of the bus server | **its account**, **bus account** | Identity and access | +| | a human | **operator** or **person** | words every domain uses | +| firewall | what the mesh enforces | **packet filter** | Connectivity | +| | what was found on a machine | **found firewall** | Connectivity | +| deploy | the per-node part of a delivery plan | **deploy plan** | Change and delivery | +| | a wrapped program's own deploy | its own word | — | ## How this page is kept -A new name for an existing thing lands here first, in the same change that introduces it in code. A -record under `02-DECISIONS/` keeps whatever word it was written with — those are immutable — so a -term retired here may still appear there, and the mapping above is how to read it. - -## The operator's machine - -- **node tools** — the one tool runtime per node, a host-side process the host supervises, that loads - every assigned module's tools bundle and serves every tool and held seat's verb on the subjects the - memberships issue; its serving mode on loopback is what was called **the console** - ([ADR 0175](../02-DECISIONS/0175-one-tool-runtime-per-node-serves-every-modules-tools-on-the-host-side.md)). - Replaces **"console"** as the module's name; *console* remains the word for the person's end of it. -- **bundle** — the artifact a module's own code is built into — its tools, a seat's implementation, a daemon — in any language the mesh has a toolchain for, interpreted or compiled; never an image. One module may declare several ([ADR 0188](../02-DECISIONS/0188-a-modules-own-code-is-bundles-in-any-language-and-a-tools-bundle-speaks-mcp-to-the-runtime.md)). -- **kept region** — a marked block in a managed file the mesh writes *into*, where the operator's own - lines survive every push and are given back when the module goes - ([ADR 0174](../02-DECISIONS/0174-a-node-varies-a-module-through-settings-and-kept-regions-never-an-edit.md)). - One of the two ways a node varies a module; the other is a **setting**. -- **installed / holding** — a module may be assigned (its package installed, its files placed) without - holding the seat its family declares; *holding* is being the one — the login shell, the display - session — on that node ([ADR 0176](../02-DECISIONS/0176-the-login-shell-is-a-node-seat-and-execute-is-its-contract.md)). -- ~~flavor~~ — not used. What a flavor varied is a setting or a separate module. +A new word for an existing thing lands here first, in the domain that owns it, in the same change that +introduces it in code. A word moves to another domain only with a decision record. A retired word goes +on a *Not:* line and nowhere else on this page, and `words.py` then fails on it in running prose. A +record under `02-DECISIONS/` keeps whatever word it was written with — those are immutable — so a word +retired here may still appear there, and the *Not:* lines are how to read it. +**How it is checked** ([ADR 0244](../02-DECISIONS/0244-the-mesh-is-described-in-domains-and-one-word-names-one-thing.md)): +`python3 00-META/checks/words.py`, run by `merge-check.sh` on every pull request, fails on a retired +word or a bare identifier in running prose in `00-META/`, `03-DESIGN/`, `AGENTS.md`, `README.md`, and +research and issues dated from 2026-10-07 — code spans, quotations and link targets excepted — and on a +head word that heads two entries or is also retired. The words retired with no scope or with *(tools)* +are copied into the catalogue as `retired-words`, whose own repository check holds the descriptions of +the mesh's tools to them; a change here that retires such a word changes that copy in the same delivery. +Homonyms are checked by review. diff --git a/00-META/mission.md b/00-META/mission.md index 9ebddd3b..0ccb4780 100644 --- a/00-META/mission.md +++ b/00-META/mission.md @@ -11,7 +11,7 @@ updated: 2026-08-22 An agent states an intent — in words, from wherever they already are — and the mesh carries it out. It takes the request in, works out what it means, does the work across -whichever nodes it needs, and returns a result. No console to open, no runbook to follow, +whichever nodes it needs, and returns a result. No terminal to open, no runbook to follow, no remembering which node holds which thing. Not automation, which does what it was told to do in advance. Self-control: the mesh diff --git a/00-META/process/03-issues.md b/00-META/process/03-issues.md index 7258d52c..e9457a45 100644 --- a/00-META/process/03-issues.md +++ b/00-META/process/03-issues.md @@ -43,7 +43,7 @@ incident someone must **clear**. ruled out. Move `status:` to `diagnosing`, then `located` once the owner is known. 3. Resolve. Set `status: resolved`, fill `fixed-by:`, and if the root cause was a design gap, run playbook [02](02-graduation.md) and fill `amended-design:`. **A core issue** — its `located-in` - names the controller, the node-engine, the node tools, the SDK, or the catalogue's bus, forge or build + names the controller, the node-engine, the tool runner, the SDK, or the catalogue's bus, forge or build agent — resolves with `replay:`, the id of its replay in mesh-lab's replays register, proved to fail on the commit before the fix and pass on it, or with `replay-none:` saying why none is possible ([ADR 0237](../../02-DECISIONS/0237-a-change-is-judged-against-the-mesh-that-runs-before-it-merges-on-the-build-seat.md); `cycle.py` checks it). diff --git a/00-META/process/06-writing-a-module.md b/00-META/process/06-writing-a-module.md index b97d9c63..5f47e4f4 100644 --- a/00-META/process/06-writing-a-module.md +++ b/00-META/process/06-writing-a-module.md @@ -52,7 +52,7 @@ Three questions, answered from the machine: the container env-file: [/var/lib/postgres/superuser.env] ``` - The host fills the hole on the machine, which is the only place both halves exist — the mesh + The node-engine fills the hole on the machine, which is the only place both halves exist — the mesh discarded the value ([credentials and their rotation](../../03-DESIGN/01-to-be/13-credentials-and-their-rotation.md)). A **provisioner** is the exception: it reads a password file, so it mounts the `.secret` @@ -89,7 +89,7 @@ Six attempts, one real bug. Recorded because the ratio is the lesson: **the mesh time and the scaffolding was not.** - A shape existed in the language and no host implemented it, so every declaration carrying one - was refused whole — correctly, and the host said exactly that. **Nobody was reading the host's + was refused whole — correctly, and the node-engine said exactly that. **Nobody was reading the node-engine's log.** Read it first; it is the only place that says why a machine did nothing. - A blind find-and-replace renamed a provision in quotes and missed the same word bare. - A command was tested only for the invocations that should fail, so it rejected every real one @@ -99,7 +99,7 @@ time and the scaffolding was not.** ## Rules -- **Read the host's log before theorising.** A declaration that was sent and not applied says so +- **Read the node-engine's log before theorising.** A declaration that was sent and not applied says so there and nowhere else. - **A failing test is kept, not skipped.** It is the reproduction. - **Never rotate during an adoption.** Rotation is a separate act, afterwards, deliberately. diff --git a/00-META/repos.md b/00-META/repos.md index ca343c5e..7aa319d6 100644 --- a/00-META/repos.md +++ b/00-META/repos.md @@ -30,7 +30,7 @@ one-for-one — `mesh-catalog`, `mesh-sdk` and `mesh-tools` exist where the tabl | Repository | Tier | Holds | |---|---|---| -| `mesh-host` | 0 | **exists.** The node host — one statically linked binary, requiring nothing present ([ADR 0005](../02-DECISIONS/0005-the-node-host.md)) | +| `mesh-host` | 0 | **exists.** The node-engine — one statically linked binary, requiring nothing present ([ADR 0005](../02-DECISIONS/0005-the-node-host.md)) | | `mesh-foundation` | 1 | the four pinned services, as declarations | | `mesh-controller` | 2 | **exists.** The controller and its contexts — one of seven built ([ADR 0006](../02-DECISIONS/0006-the-substrate-and-the-control-plane.md)) | | `mesh-surfaces` | 3 | tools, web, cli | diff --git a/01-RESEARCH/033-split-dns-with-a-vpn-client/01-evidence.md b/01-RESEARCH/033-split-dns-with-a-vpn-client/01-evidence.md index dea4a565..4e8db227 100644 --- a/01-RESEARCH/033-split-dns-with-a-vpn-client/01-evidence.md +++ b/01-RESEARCH/033-split-dns-with-a-vpn-client/01-evidence.md @@ -15,7 +15,7 @@ nothing restarted. The corporate network's domains and addresses are not reprodu only DNS path. - **The mesh's names are not in `/etc/hosts`** any more (ADR 0148 moved every name to the resolvers): with the resolver file replaced, no mesh name resolves on the machine itself, nor in any container. -- **Five containers**: two on the host network (they read the machine's file as it is, at each lookup), +- **Five containers**: two on the node-engine network (they read the machine's file as it is, at each lookup), three on bridges (they read it, or the runtime's embedded resolver copies its servers, when they start). ## The VPN client diff --git a/01-RESEARCH/033-split-dns-with-a-vpn-client/02-options.md b/01-RESEARCH/033-split-dns-with-a-vpn-client/02-options.md index 36f38d75..7b3f3e2a 100644 --- a/01-RESEARCH/033-split-dns-with-a-vpn-client/02-options.md +++ b/01-RESEARCH/033-split-dns-with-a-vpn-client/02-options.md @@ -24,7 +24,7 @@ domains as routing domains. resolved sends each question to the link whose domai rest to the default route. - **R1–R3 met**, by routing rather than by listing: one answer per name. -- **Containers cannot reach resolved's stub on `127.0.0.53`.** A container on the host network can; one +- **Containers cannot reach resolved's stub on `127.0.0.53`.** A container on the node-engine network can; one on a bridge cannot — it reads the machine's file and gets an address that is its own loopback. resolved can listen on further addresses (`DNSStubListenerExtra=`): on the machine's private mesh address, which ADR 0223 already relies on being reachable from containers on a holder. The machine's file then lists diff --git a/01-RESEARCH/034-the-mesh-in-domains/00-overview.md b/01-RESEARCH/034-the-mesh-in-domains/00-overview.md index 7509b9dd..5400ad6b 100644 --- a/01-RESEARCH/034-the-mesh-in-domains/00-overview.md +++ b/01-RESEARCH/034-the-mesh-in-domains/00-overview.md @@ -1,5 +1,5 @@ --- -status: active +status: graduated initiated: 2026-10-07 touches: - 00-META/glossary.md @@ -8,6 +8,9 @@ touches: - 02-DECISIONS/0008-a-context-owns-its-store.md - 03-DESIGN/01-to-be/06-the-controller.md - the descriptions of every module's tools and every seat's verbs in the catalogue +became: + - 02-DECISIONS/0244-the-mesh-is-described-in-domains-and-one-word-names-one-thing.md + - 03-DESIGN/01-to-be/49-the-mesh-in-domains.md --- # 034 — The mesh in domains diff --git a/02-DECISIONS/0244-the-mesh-is-described-in-domains-and-one-word-names-one-thing.md b/02-DECISIONS/0244-the-mesh-is-described-in-domains-and-one-word-names-one-thing.md new file mode 100644 index 00000000..8b4fd64c --- /dev/null +++ b/02-DECISIONS/0244-the-mesh-is-described-in-domains-and-one-word-names-one-thing.md @@ -0,0 +1,234 @@ +--- +topic: how we work +status: accepted +date: 2026-10-07 +deciders: jochen +reconstructed: false +extends: 02-DECISIONS/0006-the-substrate-and-the-control-plane.md +--- + +# 244. The mesh is described in domains, and one word names one thing + +## Context + +The operator, 2026-10-07: *"I wanted to develop our nox-mesh domain driven. Meaning every concept should +fit into some domain and we try to come up with a common knowledge base/glossary/jargon for our +application. We kind-of do this already I think, yet sometimes, you return different words for existing +concepts."* + +Two terms from domain-driven design are used below. A **domain** (in the literature, a *bounded +context*) is an area of the system inside which every word has exactly one meaning, and which owns the +concepts that meaning describes. A **ubiquitous language** is the set of words a domain uses the same way +in conversation, in documents and in code. + +[Research 034](../01-RESEARCH/034-the-mesh-in-domains/00-overview.md) measured the drift on 2026-10-07: + +- **The glossary's rule was checked by nothing.** It had 33 entries and said *one name per thing*. It + retired "control plane" on 2026-09-16; three weeks later 29 occurrences stood in 12 to-be designs, the + documents that tell somebody what to do. It retired "the host" for the node-engine on 2026-10-05; 34 + to-be designs and four tool descriptions the running mesh serves still said it, and so did the + glossary's own entry for node tools. +- **The glossary contradicted itself twice.** It defined the console as a module and, further down, said + node tools had replaced that name; it said the deprecated broker holds no seat and, in the entry for + *claim*, that it claims `mesh-broker` (the `nats` module claims it, ADR 0116). +- **It lacked the words in use.** At least 40 words used in more than ten decision records each — + *module*, *manifest*, *machine*, *provider*, *condition*, *gate*, *tier*, *operator* among them — had no + entry. +- **Twenty clashes**: two words for one thing (a *synonym*), or one word for several things (a + *homonym*). *Plan* meant four things, three of them on one seat's verbs. +- **The mesh already had domains under another name.** [ADR 0006](0006-the-substrate-and-the-control-plane.md) + named seven *contexts* of the controller — inventory, config, connectivity, provisioning, delivery, + observability, identity — and [ADR 0008](0008-a-context-owns-its-store.md) gave each its own store. The + words the mesh grew since (seat, delivery, condition, ask, data class) were never sorted into them. + +This serves the mission directly: an agent states an intent and the mesh carries it out +([`mission.md`](../00-META/mission.md)). An agent answering from these documents repeats whichever word it +read last, and one that meets two words for one thing assumes two things. + +The operator answered the research's questions on 2026-10-07, and those answers are decisions 2, 3 and 5 +below. + +## Decision + +### 1. The mesh is described in ten domains, and a domain replaces ADR 0006's context + +Every concept of the mesh belongs to exactly one domain, which owns its word and its meaning. Another +domain may use the word as defined and never changes its meaning. The ten, most upstream first (a domain +is *upstream* of another when the other depends on its concepts and not the other way round): + +| Domain | Owns | From ADR 0006 | +|---|---|---| +| **Module** | what a module is and declares: manifest, resource, claim, setting, kept region, bundle, tool, verb, invokes, upgrade policy | — | +| **Core** | the mesh's own machinery: controller, control-node, node-engine, tool runner, foundation, store, bus, genesis, layer, lease and epoch | — | +| **Placement** | what runs on which node: assignment, scope, capacity, bench, holder, declaration, send, apply | inventory, half of config | +| **Provisioning** | one module serving another: provision, provider, consumer, pin, endpoint, retire | provisioning | +| **Identity and access** | who may do what: credential, secret, vault, grant, bus account, licence, proof | identity, half of config | +| **Change and delivery** | a commit on its way to the nodes: merge check, build seat, package, artifact, catalogue, delivery, delivery plan, walk, first-node gate, release | delivery | +| **Health and repair** | what is wrong, and putting it right: health, probe, condition, self-check, healer, drill, hand-act | observability, renamed | +| **Data** | what the mesh keeps: data class, backup, restore point, stream snapshot | — | +| **Connectivity** | how nodes reach one another: private network, resolver, uplink, proxy, packet filter, reach | connectivity | +| **Operator and conversation** | the person the mesh works for: the mesh MCP server, channel and intake, router, ask, operator message | — | + +Beside them, **the record** holds this repository's own words (research effort, decision record, design, +issue, playbook, hq check), because they meet the mesh's. + +**"Domain" replaces ADR 0006's "context"** for this sense. ADR 0006's seven contexts carry over as +domains under their names, except *observability*, which becomes **Health and repair**: the mesh never +built alerts, it built conditions, and half of what the domain owns is repair. ADR 0008's rule now reads +with *domain*: where a domain's records live in the controller, it owns that store alone. ADR 0006 and +0008 are not rewritten; they keep their word, and the glossary says how to read it. A domain is a +partition of words and records, not of the catalogue: one module may serve several domains, as research +005 and ADR 0009 already found for modules. + +### 2. A machine is any computer; a node is a machine the mesh has adopted and owns + +The operator: *"a node is a mesh-adopted/owned machine"*. Both words stay, with distinct meanings. A +**machine** is any computer. A **node** is a machine the mesh has adopted and owns; a machine becomes a +node when it joins (ADR 0004), and is then adopted or converged (ADR 0100). Prose about members of the +mesh says node; prose about the hardware, or about the computer before or outside its adoption, says +machine. + +This reverses the research's proposal to make *machine* the one word. The code that now misfits is listed +in §6 for a later rename and is not renamed here. + +### 3. One word per thing; the glossary is the authority; a retired word is listed in its entry with its scope + +[`00-META/glossary.md`](../00-META/glossary.md) is organised by domain and defines every word once, in the +domain that owns it. A word another word replaced is named **in that word's entry**, on one fixed line — +*Not:* followed by the struck-through word — with its **scope**: none (retired everywhere), *(hq)* +(retired in this repository's prose only) or *(tools)* (retired in the descriptions of the mesh's tools +only). Code that still carries an old name is named on an *Identifier until renamed* line and may stand +only in a code span. Nothing else in the glossary is struck through. + +Scopes exist because vendor words are not drift. A module wrapping a program describes that program's +objects in its own words — an identity provider's *users*, a media manager's *releases*, a router's +*firewall* — so a word that is also a common vendor word is never retired in the tools' scope. + +A **homonym** is listed in the glossary's homonym table with the qualified form each domain uses, and is +never bare in a governing document. A word list cannot tell one sense from another, so homonyms are +**checked by review**; where one sense is settled by renaming, the old sense moves to a *Not:* line and +becomes mechanical. + +### 4. The words this record settles + +| Clash | Decided | Retired | +|---|---|---| +| the program on every node | **node-engine** | "the host", "host agent", "node host"; `mesh-host` an identifier until the code rename | +| the program that serves every module's tools on a node | **tool runner** (ADR 0175's "node tools") | "node tools", "tool runtime" (hq); `node-tools` an identifier until renamed | +| the loopback endpoint every agent and person on a node reaches the mesh through | **the mesh MCP server** — the MCP server named `mesh` and its five tools | "console" (hq: it suggests a terminal or shell, and the module `mesh-console` no longer exists), "mesh-console", "tool bridge" | +| the module library | **mesh-sdk**; its tool-serving harness is part of it | "tools-sdk" | +| plan | **declaration** (what the controller sends one node), **delivery plan** (what a delivery does), **walk** (one commit sent across nodes); the bus's **planned step** stays | "change plan", "release plan" | +| moving a change | **send** (the controller giving a node its declaration; the verb `push` asks for one), **delivery**, **walk**, the policy **`roll`**, **release** (a person letting a held delivery go on, this sense only), **unbind** (a licence), **upgrade policy**; *deploy* only in **deploy plan** | "rollout" as a noun (hq), "deployment" (hq) | +| the controller | **controller** | "control plane", "master" and "slave" (hq), "mesh-control" (hq) | +| the foundation | **foundation** | "substrate" | +| the build role | **build seat**, held on a node by the **builder** | "build machine" | +| an open fact about something wrong | **condition**; a probe's result before it is one is a **finding** | "alert" (hq) | +| the controller's examination of the mesh | **self-check** (the verb `doctor` is an identifier) | "doctor" in prose (hq) | +| the mesh's own network | **private network** | "overlay" (hq) | +| a manifest | **manifest** | "module definition" (hq) | +| the catalogue | **catalogue**; `mesh-catalog` is an identifier | "catalog" (hq) | +| a setting's predecessor | **setting** or a separate module | "flavor" | + +Decided as the research proposed, and checked by review because the word is ordinary or has vendor +senses: **packet filter** for what the mesh enforces and **found firewall** for a program found on a +machine; **operator** or **person** for a human, never *the user*, which is a wrapped program's or the +bus server's account; **first-node gate** and **merge gate**, never *the gate*; **layer** for a level of +the mesh, **tier** for a step of a walk, **assurance level** for how much proof an ask needs; **build +request** for an entry in the build queue, *ask* staying the operator's; **store** for the database +server only, **artifact store** and **last declaration** for the others; **decision record** always +qualified in this repository; **agent** for a coding agent only. *Pipeline* is not retired: it names the +predecessor's mechanism, which the as-is designs describe, and is never a word for a delivery. + +### 5. The rule is checked, in this repository and in the catalogue + +- **`00-META/checks/words.py`**, run by `merge-check.sh` beside `records.py`, `index.py` and `cycle.py`, + reads the glossary's *Not:* and *Identifier* lines and fails on a retired word of scope *hq* or none, + or an identifier, in **running prose**: what is left once code blocks and spans, block quotes, text in + quotation marks, struck-through text, link targets, comments and frontmatter are taken out. A quotation + keeps the words it quotes; a link target is a file name. It also fails when a head word heads two + entries or is also retired (**uniqueness**). +- **It covers** `00-META/`, both layers of `03-DESIGN/`, `AGENTS.md` and `README.md` from now on, and + research efforts initiated and issues opened on or after 2026-10-07. Decision records are never + checked: they keep their words. +- **A document it fails on** that cannot be reworded in the change that found it is named in + `00-META/checks/words-allowed.md` with a date by which it is reworded; an entry past its date, or for a + document that no longer needs it, fails. A graduated research effort may instead be **kept**, because it + records what was said; research 034 is, since every clash it found names the words that clashed. +- **The catalogue keeps a copy** of the words retired with scope none or *(tools)*, as `retired-words`, + and its own repository check (`mesh/repo-check`, its `merge-check.sh`) fails when the text a module's + tools show an agent — their descriptions, and the notes and errors they answer with — uses one. A + copy rather than an artifact the catalogue reads: it fails loudly in the right place and adds no + dependency to a catalogue merge. `words.py` compares the copy with the glossary when a checkout of the + catalogue is named to it (`MESH_CATALOG_DIR`); a change that retires a tools word changes both. + +Every part failed on something real before it passed: `words.py` on 581 uses in 63 documents, and the catalogue's +check on tool descriptions saying "the host". + +### 6. What is not renamed here + +Code is renamed by the repositories that own it, each with its own change. The misfits, for that later +work: + +- the mesh MCP server's tools: `mesh_machine`, and an overview listing *machines*, where the + members of the mesh are nodes; the controller's `nodes` verb answering *"Every machine the mesh + knows"*; +- `mesh-host` (the node-engine's repository, binary and unit), `node-tools` (the tool runner's module, + unit and bus account), `mesh-console` wherever code still names it, and the `--console` flag of the + person's client; +- `node-build-agent` (the build seat), the licence manager's verb `release` (unbind), the controller's + verbs `plan` (a declaration), `doctor` (the self-check), and `queue`, `cancel` and `clear`, whose + descriptions call a build request an *ask*; +- the nftables module's tool `firewall_rules`, which serves the packet filter. + +## Options rejected + +- **Machine as the one word, node only as an identifier** — the research's proposal, and what the newest + records already said. Rejected by the operator: a machine and a node are different things, and the + difference (adopted and owned by the mesh, or not) is one the mesh acts on. +- **Node everywhere** — cheaper in code, but it leaves no word for the computer before it joins, which the + joining, adoption and lab designs all need. +- **Widening "context" to mean a domain** — keeps ADR 0006's word, but *context* is ordinary English in + every other sentence, and the research found it used for a store-owning part of the controller. Two + meanings for the word that is supposed to hold one meaning per word. +- **Keeping both "context" and "domain"**, the research's tentative proposal (a domain holds contexts) — + rejected as the effort's own synonym: only three contexts have a store, and the domain owns it anyway. +- **A list of retired words in a file of its own in this repository** — a second source that drifts from + the glossary. The *Not:* lines are the list. +- **The list as an artifact the catalogue reads at check time** — one source, but one more thing that must + be up for a catalogue merge. The checked copy is preferred. +- **A probe in the self-check comparing the served descriptions with the list** — the descriptions an + agent sees are the served ones, which may lag the catalogue's trunk; worth having once the first check + has run, not before. A second place for the same rule. +- **A warning-only mode** — a warning blocks a merge on the mesh's repositories the same as a failure + (issue 293), so none is available. +- **Retiring homonyms by list** (*plan*, *gate*, *tier*, *ask*, *store*, *record*, *check*) — the word is + right in one sense and wrong in another; a list would cry wolf, and a check that cries wolf gets + suppressed (`00-META/checks/README.md`). +- **Retiring "the user", "firewall" and "pipeline" mechanically** — each has a correct use the check + cannot tell apart (a bus server's users and an SSH user CA; a found firewall; the predecessor's + pipeline). They are homonyms, reviewed. +- **Rewriting research 034 in today's words** — it is the record of the words as they stood; it is kept. +- **The reverse rule** (a to-be design defining a word in the glossary's form must find it in the + glossary) — proposed by the research; not built here, because a bolded word followed by a dash is also + how designs emphasise a term they do not define, and the first run would need its own measuring. + +## Consequences + +- **The glossary is rewritten by domain**, with the missing words added, the two contradictions removed + and every retired word on a *Not:* line. The domains are drawn in + [to-be 49](../03-DESIGN/01-to-be/49-the-mesh-in-domains.md). +- **The governing documents are reworded in the same change**: the node-engine, the tool runner, the + mesh MCP server, the controller, the private network and the build seat by their words, in 63 documents. + These are wording fixes, not changes of meaning; where a sentence quoted an older record, it is now in + quotation marks. +- **To-be 34 and as-is 13 describe a module, `mesh-console`, that no longer exists.** Their wording is + fixed here; their substance is an as-is fact to update when the tool runner's serving mode is written + up, not a word. +- **A change that retires a word** edits the glossary's entry, rewords what `words.py` then finds, and — + for a word in the tools' scope — changes the catalogue's copy and the descriptions it then finds. +- **Tool descriptions are not yet held to *node* and *machine*.** Both words are correct, in different + senses; which one a description needs is a reviewer's call. The catalogue's check holds them only to the + retired words. +- **The controller's, the node-engine's and the tool runner's own verb descriptions** are not in the + catalogue, so its check does not see them; a check in those repositories is the same few lines and is + left to them. diff --git a/02-DECISIONS/README.md b/02-DECISIONS/README.md index 7549f3f2..57b6e3f0 100644 --- a/02-DECISIONS/README.md +++ b/02-DECISIONS/README.md @@ -383,5 +383,6 @@ python3 00-META/checks/index.py fail if stale - **0080** — [The development cycle is checked, not trusted](0080-the-development-cycle-is-checked.md) - **0081** — [A decision nothing cites is not yet in the chain](0081-a-decision-nothing-cites-is-not-yet-in-the-chain.md) - **0153** — [The record is read by a module the mesh assigns, and the console lists it](0153-the-record-is-read-by-a-module-and-the-console-lists-it.md) +- **0244** — [The mesh is described in domains, and one word names one thing](0244-the-mesh-is-described-in-domains-and-one-word-names-one-thing.md) diff --git a/03-DESIGN/00-as-is/01-mesh-and-transport.md b/03-DESIGN/00-as-is/01-mesh-and-transport.md index 6e4848ef..75b57c6d 100644 --- a/03-DESIGN/00-as-is/01-mesh-and-transport.md +++ b/03-DESIGN/00-as-is/01-mesh-and-transport.md @@ -103,8 +103,8 @@ restarts. Nothing re-discovers on a schedule. ## Names and reachability -Nodes address each other by names that resolve on the mesh's own overlay, not on whatever the -underlying network provides. A node's mesh name is its overlay address; its public name, if it +Nodes address each other by names that resolve on the mesh's own private network, not on whatever the +underlying network provides. A node's mesh name is its private network address; its public name, if it has one, is a separate fact used by things outside the mesh. Two lessons are embedded in that separation, both learned the expensive way. A name resolved diff --git a/03-DESIGN/00-as-is/05-runtime-and-installation.md b/03-DESIGN/00-as-is/05-runtime-and-installation.md index cf061823..778a21fd 100644 --- a/03-DESIGN/00-as-is/05-runtime-and-installation.md +++ b/03-DESIGN/00-as-is/05-runtime-and-installation.md @@ -69,7 +69,7 @@ is what runs today. ## Supervision -Services run under the host's init system via a templated unit, one instance per module. It is +Services run under the machine's init system via a templated unit, one instance per module. It is a thin layer: the unit starts and stops a container group. Whether the mesh keeps this, drops the per-module layer, containerises the daemons, or writes diff --git a/03-DESIGN/00-as-is/07-knowledge.md b/03-DESIGN/00-as-is/07-knowledge.md index 7f25af60..15f4c307 100644 --- a/03-DESIGN/00-as-is/07-knowledge.md +++ b/03-DESIGN/00-as-is/07-knowledge.md @@ -12,8 +12,8 @@ decisions: # Knowledge **The mesh keeps no knowledge store.** What it knows is what its modules answer, and the way a person -or an agent asks is the console's tool list on the machine they sit at -([13 — The console](13-the-console.md)). This document used to describe two stores; it is rewritten +or an agent asks is the mesh MCP server's tool list on the machine they sit at +([13 — The mesh MCP server](13-the-console.md)). This document used to describe two stores; it is rewritten because neither exists from the mesh's side, and an as-is document that describes what is gone is a brochure. @@ -36,7 +36,7 @@ merge the forge announces and every ten minutes — and answers over the bus: wh written, one document whole, what a folder holds, and where the checkout stands, each naming the commit it read. Which repository it reads is a setting on its assignment; the module names no mesh. -It is listed by the console beside every other tool, with a description that says to search the +It is listed by the mesh MCP server beside every other tool, with a description that says to search the literal words of a symptom before forming a hypothesis. That is what [ADR 0025](../../02-DECISIONS/0025-the-design-record-is-read-not-copied.md) meant by *beside everything else*, in a mesh with no store to be beside diff --git a/03-DESIGN/00-as-is/09-interfaces-and-observability.md b/03-DESIGN/00-as-is/09-interfaces-and-observability.md index 6f08b04a..125cdf20 100644 --- a/03-DESIGN/00-as-is/09-interfaces-and-observability.md +++ b/03-DESIGN/00-as-is/09-interfaces-and-observability.md @@ -14,7 +14,7 @@ How the mesh is reached, and how anyone can tell what it is doing. ## Capabilities are the primary interface -The mesh's primary interface is not a web console. It is a set of **capabilities**, exposed to +The mesh's primary interface is not a web dashboard. It is a set of **capabilities**, exposed to a session and callable in language. A capability is contributed by a module and is available on any node, wherever it actually diff --git a/03-DESIGN/00-as-is/10-module-catalogue.md b/03-DESIGN/00-as-is/10-module-catalogue.md index 57de7608..cdb8c8f5 100644 --- a/03-DESIGN/00-as-is/10-module-catalogue.md +++ b/03-DESIGN/00-as-is/10-module-catalogue.md @@ -56,7 +56,7 @@ the unit of one piece of software, because that is the only granularity the modu offers. This is the same failure [ADR 0001](../../02-DECISIONS/0001-mesh-brokers-nodes-host-agents-think.md) -names for the platform core — *boundaries drawn by deployment accident rather than by domain* — +names for the platform core — "boundaries drawn by deployment accident rather than by domain" — appearing outside it, at four times the scale. The core is being recomposed; the flat level is addressed in principle by [ADR 0009](../../02-DECISIONS/0009-modules-and-the-graph.md), which diff --git a/03-DESIGN/00-as-is/11-the-lab.md b/03-DESIGN/00-as-is/11-the-lab.md index 9ebff58e..935021b4 100644 --- a/03-DESIGN/00-as-is/11-the-lab.md +++ b/03-DESIGN/00-as-is/11-the-lab.md @@ -46,7 +46,7 @@ scenario as one state. ## What it does not do, and why that matters -**`place:` is refused.** A scenario can declare that a node host is placed on a machine; the +**`place:` is refused.** A scenario can declare that a node-engine is placed on a machine; the lab names the gap and refuses rather than raising a scenario that silently lacks what it declared. Nothing can be placed because tier 0 does not exist yet. diff --git a/03-DESIGN/00-as-is/12-the-seats.md b/03-DESIGN/00-as-is/12-the-seats.md index 5ec52093..0f739cee 100644 --- a/03-DESIGN/00-as-is/12-the-seats.md +++ b/03-DESIGN/00-as-is/12-the-seats.md @@ -25,7 +25,7 @@ have; nothing else can add to it. Five seats deliver a provision: the mesh's store delivers the relational database, the mesh's broker the message transport, the artifact-store seat the registry, and two more the package registry for one ecosystem and the git service. The remaining nine — the controller and catalogue seats, and the -node-scope ones for the build machine, the DNS port, the packet filter, intrusion prevention, the +node-scope ones for the builder, the DNS port, the packet filter, intrusion prevention, the private network, the resolver's configuration and the showcase — mark a role without answering for anything a consumer requires. @@ -74,7 +74,7 @@ mesh carrying one from before the set existed says so. A module's repository is either a URL, recorded and cloned exactly as given, or a path on the forge holding the git seat, recorded as that path plus the seat. The clone URL is composed from wherever -the holder runs at the moment of building, so the build machine is never told an address that could +the holder runs at the moment of building, so the builder is never told an address that could go stale. Before this, a self-hosted forge's scheme, host and port were written into every module built from it, and moving the forge made every record stale at once — noticed when a rebuild failed to clone. diff --git a/03-DESIGN/00-as-is/13-the-console.md b/03-DESIGN/00-as-is/13-the-console.md index c45170bd..9d2e8564 100644 --- a/03-DESIGN/00-as-is/13-the-console.md +++ b/03-DESIGN/00-as-is/13-the-console.md @@ -10,7 +10,7 @@ decisions: - 02-DECISIONS/0154-the-meshs-own-verbs-are-the-controller-seats-tools.md --- -# The console, as it runs +# The mesh MCP server, as it runs **The mesh's tools reach a person through a module the mesh assigned to their machine.** Since 2026-09-30 a workstation that is a node can be assigned `mesh-console`; the mesh mints a bus account @@ -22,36 +22,36 @@ endpoint. Nothing on the machine holds a credential a person had to carry. ## What it answers `initialize`, `tools/list`, `tools/call`, over HTTP, one JSON body per request, no session and no event -stream. `tools/list` is what the running modules answered: every tool runtime built on or after that day -serves a `tools` verb for its module, and the console asks the catalogue for the roster and each module +stream. `tools/list` is what the running modules answered: every tool runner built on or after that day +serves a `tools` verb for its module, and the mesh MCP server asks the catalogue for the roster and each module for its tools. A module that did not answer is named in the list's `_meta.notAnswering`. On the day it shipped that was 36 of 51 modules — those that serve no tools at all, and those whose rebuilt runtime the mesh records rather than rolls out — and 62 tools from the rest. -`tools/call` reaches any tool by `.`, listed or not. The console's grant is `*`, so what it +`tools/call` reaches any tool by `.`, listed or not. The mesh MCP server's grant is `*`, so what it may call is every tool on the mesh; its account may publish nothing else and subscribes nothing. ## The mesh's own verbs *Since 2026-09-30 evening ([ADR 0154](../../02-DECISIONS/0154-the-meshs-own-verbs-are-the-controller-seats-tools.md)).* -The console asks the `mesh-controller` seat's `tools` verb beside the modules and lists every role's +The mesh MCP server asks the `mesh-controller` seat's `tools` verb beside the modules and lists every role's tools as `.` — `mesh-controller.status`, `mesh-controller.push` and the other ten. A call to `.` reaches the seat when the prefix is a seat declaring that verb, and the module -otherwise; `seat:.` says so outright. When the control plane does not answer, the list +otherwise; `seat:.` says so outright. When the controller does not answer, the list names `mesh-controller (seat)` as not answering and carries the modules' tools regardless. The `mesh-controller` *module* is always named as not answering: it serves no module tools, only its seat's. ## Around it - **`invokes`** in a manifest is the grant. It is composed into the bus's user list exactly as a - person's account is; the console is the only module that declares it. + person's account is; the mesh MCP server is the only module that declares it. - **`module check …`** on the controller's binary judges a manifest with no mesh: the strict parse, every per-manifest problem, and the rules between the manifests given. It prints what it cannot - judge without a store rather than refusing. The console's own manifest was the first thing checked + judge without a store rather than refusing. The mesh MCP server's own manifest was the first thing checked with it, and the whole catalogue passes. - **The person's client remains.** `operator issue` and `mesh tools|call|mcp` with a credential file still work, for a machine that is not a node and for a mesh not yet able to assign anything. - `mesh tools --console ` goes through a running console with no credential; it is covered by the + `mesh tools --console ` goes through a running mesh MCP server with no credential; it is covered by the runtime repository's tests and was not exercised on the live mesh. ## What shipped bent @@ -61,4 +61,4 @@ names `mesh-controller (seat)` as not answering and carries the modules' tools r operator did not pass. The rebuild-on-merge matched the URL anyway. - Modules whose upgrade policy is *record* — the forge among them — answered `tools` only once something pushed their rebuilt runtime; until then they are listed as not answering while still - callable. That is the policy doing what it says, not a fault of the console. + callable. That is the policy doing what it says, not a fault of the mesh MCP server. diff --git a/03-DESIGN/00-as-is/15-the-agent-and-its-licences.md b/03-DESIGN/00-as-is/15-the-agent-and-its-licences.md index 3bf29dbd..606cdf91 100644 --- a/03-DESIGN/00-as-is/15-the-agent-and-its-licences.md +++ b/03-DESIGN/00-as-is/15-the-agent-and-its-licences.md @@ -17,7 +17,7 @@ a port or a bus credential of its own. Live since 2026-10-04, on all four machin ## The agent module on each machine -- **Writes the agent's managed directory**: the tool servers — the console as `mesh`, plus servers +- **Writes the agent's managed directory**: the tool servers — the mesh MCP server as `mesh`, plus servers registered through the module — the mesh's settings, and the instruction file. The tool-server list is exclusive by the vendor's rule: a server not in it does not load on that machine. - **Keeps registered tool servers in its state**, one key per registration for every machine or for one; @@ -46,7 +46,7 @@ a port or a bus credential of its own. Live since 2026-10-04, on all four machin One subscription account was adopted from the control node's own login on its first start; the other three machines, logged in to the same account with older logins, were bound to it without their logins being exchanged. A forced rotation reached all four machines within seconds. Two faults were found and -fixed during the rollout: a machine reporting an already-adopted account later was never bound, and a +fixed while it rolled out: a machine reporting an already-adopted account later was never bound, and a seat verb named with an underscore was refused by the builder. ## How it is checked diff --git a/03-DESIGN/01-to-be/00-work-breakdown.md b/03-DESIGN/01-to-be/00-work-breakdown.md index 72fbb87e..db20a023 100644 --- a/03-DESIGN/01-to-be/00-work-breakdown.md +++ b/03-DESIGN/01-to-be/00-work-breakdown.md @@ -95,7 +95,7 @@ anyway, because a dependency can restart long after everything was applied. shape widens what a compromised controller can express, so [ADR 0029](../../02-DECISIONS/0029-a-network-is-a-shape-because-an-action-cannot-be-undone.md) records why this one is worth it: an `action` could create a network and **nothing could ever -remove it**, because an action leaves no footprint the host can undo. The vocabulary is nine. +remove it**, because an action leaves no footprint the node-engine can undo. The vocabulary is nine. **Three tasks in a row that were already possible.** Both were written from the design rather than from the code, which is the review's finding arriving in the plan: *a claim here is counted, not @@ -128,7 +128,7 @@ does not say.** survive every step**. A data folder may move; it may never be lost. **One thing was found by asking this and is fixed** -([ADR 0030](../../02-DECISIONS/0030-data-outlives-the-mesh-that-declared-it.md)): the host deleted +([ADR 0030](../../02-DECISIONS/0030-data-outlives-the-mesh-that-declared-it.md)): the node-engine deleted a directory and everything under it when the directory stopped being declared, which happens when a module is unassigned or a manifest is edited to move a data folder — the exact operation this plan needs. A directory holding anything the mesh did not put there is now kept and reported. @@ -340,7 +340,7 @@ once, and maintain for a year. The modules are the input; a person reads what on writes what it declares tomorrow. **It also changes what "safe" means for the system being retired.** A fix to it has to be safe on -its own, because there is no careful rollout to sequence it into: the thing is being switched off +its own, because there is no careful walk to sequence it into: the thing is being switched off by hand, not managed into retirement. A change needing three steps in the right order is a change that will be half-applied. diff --git a/03-DESIGN/01-to-be/01-end-to-end-testing.md b/03-DESIGN/01-to-be/01-end-to-end-testing.md index 23e1381a..f02c1a38 100644 --- a/03-DESIGN/01-to-be/01-end-to-end-testing.md +++ b/03-DESIGN/01-to-be/01-end-to-end-testing.md @@ -42,9 +42,9 @@ not the first one built** ([ADR 0016](../../02-DECISIONS/0016-the-lab.md)). | | **Bootstrap scenario** | **Full scenario** | |---|---|---| -| Contains | virtual machines, the host binary, a pinned foundation bundle | a complete mesh: forge, coordinator, delivery, modules | -| Verdict from | what the host reports about the state it reconciled | a pipeline result ending in verify | -| Exercises | the node host and the foundation | the controller and everything above it | +| Contains | virtual machines, the node-engine binary, a pinned foundation bundle | a complete mesh: forge, coordinator, delivery, modules | +| Verdict from | what the node-engine reports about the state it reconciled | a pipeline result ending in verify | +| Exercises | the node-engine and the foundation | the controller and everything above it | | Exists to | **develop the mesh** | **test what runs on it** | The bootstrap scenario is a **strict subset**: same virtualisation, same networking, same @@ -350,7 +350,7 @@ from the existing system has run against any of it yet. ### A node is a system container An OS userspace with its own init, its own network interface, its own filesystem, sharing -the host kernel. +the lab machine's kernel. **A node's job is to run containers**, so modelling a node *as* an application container inverts the thing being modelled: it forces nested containers through a privileged daemon or @@ -369,14 +369,14 @@ test file — when a question needs a kernel to answer it, or when the point is ### The network -Two segments and an overlay, because some module behaviour is only visible across a real +Two segments and a private network, because some module behaviour is only visible across a real network boundary: - **wan** — a published node holds an address here, and an authoritative resolver maps its name to it, so a public touchpoint is real enough to exercise routing, virtual hosts and certificates - **local** — behind translation, as a home network is -- **the overlay** — the mechanism production uses; the mesh addresses peers by mesh name and +- **the private network** — the mechanism production uses; the mesh addresses peers by mesh name and never learns which segment anyone is on A node can be moved between segments or detached entirely, mid-test. @@ -420,14 +420,14 @@ receipt written before it recorded a given fact, which claims nothing rather tha **The run rebuilds what it tests.** The suite consumes artifacts from other repositories, and an artifact rebuilt from memory is one rebuilt sometimes. A stale binary reporting success against rules that have since changed is the same fault wearing different clothes. This covers the module -runtimes a bed's scenario stocks as well as the host and the control plane +runtimes a bed's scenario stocks as well as the node-engine and the controller ([issue 075](../../04-ISSUES/075-a-stocked-runtime-image-is-never-rebuilt-by-the-run/00-report.md)): each is compared against the module's source and what it is built on, and rebuilt where older, missing or uncommitted. *How it is checked:* unit tests on what a bed stocks and when it is stale; a run with an image removed rebuilds it before the bed passes. **The general rule, which outlives this suite:** *silence and success must never look alike.* -It is the same rule the host follows about a service that does not exist +It is the same rule the node-engine follows about a service that does not exist ([ADR 0004](../../02-DECISIONS/0004-a-node-and-how-it-joins.md)) — absence must be distinguishable from a failure to answer — applied to coverage instead of to a machine. @@ -472,7 +472,7 @@ runner needs is the one the mesh should keep. **Module verification becomes worth writing**, because it is the thing that gives a developer a verdict, not just a stricter deploy. -**Host-borrowing ends.** Today's tooling starts providers on the host's own init system and +**Host-borrowing ends.** Today's tooling starts providers on the machine's own init system and reads credentials from host paths, because there is nowhere else to put a mesh. Once there is, a workstation stops being collateral. diff --git a/03-DESIGN/01-to-be/02-scenario-declaration.md b/03-DESIGN/01-to-be/02-scenario-declaration.md index 29c8c5c5..7affc14c 100644 --- a/03-DESIGN/01-to-be/02-scenario-declaration.md +++ b/03-DESIGN/01-to-be/02-scenario-declaration.md @@ -244,7 +244,7 @@ too thin. It carries three facts, and all three are load-bearing: carrier NAT; omitting it means mappings never expire, which no real gateway does. **`segments[].mtu`** — the largest packet the segment carries, defaulting to 1500. Lower values -reproduce tunnelled and PPPoE paths. This matters because an overlay adds its own header: a +reproduce tunnelled and PPPoE paths. This matters because a private network adds its own header: a tunnel over a 1400-byte path establishes a connection and then silently drops large packets, which is the shape of fault this whole effort exists to stop shipping. @@ -297,7 +297,7 @@ The same machine, the same identity, three positions in one run: at home where i reach it directly, on a foreign network where it can only dial out and its apparent address belongs to a router it does not control, and asleep. -Whether the overlay survives that, re-forms, and is noticed to have changed endpoint is +Whether the private network survives that, re-forms, and is noticed to have changed endpoint is **observed**, never arranged ([ADR 0016](../../02-DECISIONS/0016-the-lab.md)). @@ -347,13 +347,13 @@ assert: ``` `module:` and `assert:` are meaningless in a bootstrap scenario and absent from one. A -bootstrap scenario's verdict comes from what the host reports about the state it reconciled, +bootstrap scenario's verdict comes from what the node-engine reports about the state it reconciled, not from an assertion runner — which is why assertion execution is second in the build order, not first. ## What a scenario deliberately cannot say -- **Overlay addresses, the hub, peer configuration.** Outcomes, not inputs +- **Private network addresses, the hub, peer configuration.** Outcomes, not inputs ([ADR 0016](../../02-DECISIONS/0016-the-lab.md)). - **What a machine is in mesh terms** — server or workstation, its site, its names. Mesh configuration, established by the mesh. @@ -541,7 +541,7 @@ cannot yet express. ### What is deliberately absent -Nothing here mentions overlay addresses, which node is the hub, who peers with whom, any name, +Nothing here mentions private network addresses, which node is the hub, who peers with whom, any name, or any certificate. Research 004 recorded all of those for this topology, and **a scenario must not state them** ([ADR 0016](../../02-DECISIONS/0016-the-lab.md)): they are what the mesh does, and a scenario that supplied them would be certifying its own work. @@ -634,7 +634,7 @@ is the one real absence, and it is exactly the double-NAT case. - **`user` and `edge` profiles have no scenario.** A lab machine is always privileged, so the two profiles that exist for unprivileged and phone-like participation cannot be exercised. - Either the lab grows a way to run the host unprivileged, or those profiles are developed + Either the lab grows a way to run the node-engine unprivileged, or those profiles are developed against something that is not a virtual machine. This is the largest gap. - **Where `place:` gets its artifacts from.** Before the mesh is self-hosting these come from outside; afterwards from the mesh itself. The declaration should not have to care, which diff --git a/03-DESIGN/01-to-be/04-lab-installation.md b/03-DESIGN/01-to-be/04-lab-installation.md index 1bbd5c82..35454437 100644 --- a/03-DESIGN/01-to-be/04-lab-installation.md +++ b/03-DESIGN/01-to-be/04-lab-installation.md @@ -81,7 +81,7 @@ was done* is not evidence. Worth stating, because it looks like an exception and is not. -The node host is the one thing installed by hand on a machine +The node-engine is the one thing installed by hand on a machine ([research 006](../../01-RESEARCH/006-mesh-from-scratch/code-skeleton.md)): everything else arrives through it. The lab is the same shape on a workstation — installed once, by hand, and then everything about the mesh is developed inside it. diff --git a/03-DESIGN/01-to-be/05-the-node-host.md b/03-DESIGN/01-to-be/05-the-node-host.md index 499d3442..fc95481e 100644 --- a/03-DESIGN/01-to-be/05-the-node-host.md +++ b/03-DESIGN/01-to-be/05-the-node-host.md @@ -24,7 +24,7 @@ decisions: - 02-DECISIONS/0005-the-node-host.md --- -# The node host +# The node-engine Tier 0. The one thing ever installed by hand, and the only thing that changes a machine. @@ -33,10 +33,10 @@ Tier 0. The one thing ever installed by hand, and the only thing that changes a A **statically linked binary that requires nothing to be present** — copy it onto a machine and run it, and that is the whole installation ([ADR 0005](../../02-DECISIONS/0005-the-node-host.md)). Written in Go, because the -job is system-level and because the host shares no code with any other tier. +job is system-level and because the node-engine shares no code with any other tier. A single binary with one job: **apply declared state on this machine** -([ADR 0005](../../02-DECISIONS/0005-the-node-host.md)). Overlay +([ADR 0005](../../02-DECISIONS/0005-the-node-host.md)). Private network membership, packet filtering, packages, services, containers and filesystems are not six concerns it carries; they are six instances of the one. @@ -130,7 +130,7 @@ the link; never asked downward. - It has no listening surface. - **It does not manage its own unit.** It manages `service` resources and its own unit is one — the temptation is obvious and it ends with a host stopping itself half way through an apply, - leaving a machine with nothing running to fix it. The installation owns the host; the host owns + leaving a machine with nothing running to fix it. The installation owns the node-engine; the node-engine owns everything else. **How it is installed, enrolled, run, upgraded and retired is @@ -141,36 +141,36 @@ is the component; that one is what happens to it. *2026-09-22, [ADR 0100](../../02-DECISIONS/0100-a-node-in-use-is-adopted-before-it-is-converged.md).* A declaration says whether the node is adopted, and which of its modules have been **taken**. *Found* is a file at a declared path, or a container at a declared -name, that the host's store has no record of writing. On an adopted node the host keeps what it +name, that the node-engine's store has no record of writing. On an adopted node the node-engine keeps what it found for any module not yet taken: it records a found file's original content before anything else, and it reports the file or container as held — a report that says what it holds, so an adopted node never reads as converged. Once the module is taken, its resources converge like any other. What is held is never removed, even when its module is unassigned, and a held file or container that changes while held is reported as changed by something else, not reverted or -restarted. The host also +restarted. The node-engine also converges a new resource, the **opening** — a port made reachable through the firewall it found ([08-connectivity](08-connectivity.md)) — and reports which firewall it found. This is the -companion the host's ownership rule needed: *never touch what you did not create, unless adoption +companion the node-engine's ownership rule needed: *never touch what you did not create, unless adoption made it yours — and while the node is adopted, not until its module is taken.* *How it is -checked:* unit tests hold the host to keeping a found file and container, converging them once +checked:* unit tests hold the node-engine to keeping a found file and container, converging them once taken, never removing a held file and reporting one that changed; the adoption bed asserts a found file byte for byte unchanged until its module is taken. -**What the host says of a found container, and what it removes** — revision, 2026-10-01 +**What the node-engine says of a found container, and what it removes** — revision, 2026-10-01 ([ADR 0163](../../02-DECISIONS/0163-taking-a-module-over-is-a-comparison.md)). Its report of a held container carries the image and the image's creation date, the networks it is on and the other -containers on each, its mounts and its published ports — the facts a take compares. The host compares +containers on each, its mounts and its published ports — the facts a take compares. The node-engine compares every field it writes before calling a container current, volumes and paths included; its record keeps a resource's former targets, removes a container or file it wrote under a name the declaration no longer names, never removes what was found, and reports what runs on the machine that it neither wrote nor holds. *How it is checked:* ADR 0163's table. -**What the host joins, keeps and raises for a take** — revision, 2026-10-02 +**What the node-engine joins, keeps and raises for a take** — revision, 2026-10-02 ([ADR 0163](../../02-DECISIONS/0163-taking-a-module-over-is-a-comparison.md), rules 4, 6 and 7). A container may name networks it also joins once it runs — the found network a per-machine setting keeps for a taken container while a neighbour still resolves it there; joined after the run, part of the container's spec, refused when it cannot be joined. A declaration may name the modules the mesh left -out of it because a stored setting cannot compose with the module's definition: the host keeps what it +out of it because a stored setting cannot compose with the module's definition: the node-engine keeps what it wrote and holds for a left-out module and says so, where absence used to read as removal. And genesis raises the bootstrap forge under the forge module's container name, with the module's image digest and its data directory, so the module holds it by the found rule; the network is the one difference a take @@ -179,7 +179,7 @@ host test keeps a left-out module's record and hold and removes an absent module holds the installer's constants to the module's manifest where the catalogue is checked out beside it. **What filters the machine, and the found firewall kept retired** — revision, 2026-10-02 -([ADR 0168](../../02-DECISIONS/0168-a-converged-machine-is-filtered-by-the-mesh-alone.md)). The host +([ADR 0168](../../02-DECISIONS/0168-a-converged-machine-is-filtered-by-the-mesh-alone.md)). The node-engine reports, with every apply, every table and legacy chain that refuses traffic and whose it reads it as — the mesh's, the found firewall's, the container runtime's own, a ban, or other — and, converged, whether the firewall it was found with is in force and who retired it. It retires that firewall on every @@ -191,14 +191,14 @@ step was skipped. *How it is checked:* ADR 0168's table. keeps its mode and owner, a unit present with no record keeps its state and boot setting, a container that would mount found data is not created, and an action run in a held container waits for the cutover. **A file the machine shares is written into, never over** -([ADR 0102](../../02-DECISIONS/0102-the-mesh-writes-into-a-shared-file-never-over-it.md)): the host sets the mesh's keys in the object already there, +([ADR 0102](../../02-DECISIONS/0102-the-mesh-writes-into-a-shared-file-never-over-it.md)): the node-engine sets the mesh's keys in the object already there, keeps every other key, adds its members to a list already there rather than replacing it, records what each of its keys held and which members it added, and gives them back when the file is undeclared. Such a file replaces nothing, so it is never held. And on any node, before -the host writes over a file it has no record of making, it keeps the original once and names +the node-engine writes over a file it has no record of making, it keeps the original once and names where; if it cannot keep it, it does not write. A service that re-reads its configuration is **reloaded** for what it names in `reload-on`, never restarted. *How it is -checked:* unit tests hold the host to each of these, and the adoption bed asserts the runtime's +checked:* unit tests hold the node-engine to each of these, and the adoption bed asserts the runtime's own settings survive adoption and a container without a restart policy keeps running. ## Where a declaration comes from @@ -208,7 +208,7 @@ One behaviour, two sources | Situation | Source | |---|---| -| no mesh reachable | `foundation.lock` — the pinned bundle the host carries | +| no mesh reachable | `foundation.lock` — the pinned bundle the node-engine carries | | mesh reachable | the controller, over the link | **The first node is not a different kind of node.** It is a node whose mesh is not up yet. It @@ -216,23 +216,23 @@ applies the bundle it carries, the controller comes up on top of it, and from th takes declarations like every other node. Its specialness is temporary and self-erasing. **A joining node does the minimum to be reachable and nothing else** — an identity, an address, -one peer — and then stops deciding. It does not compute the overlay; it needs one peer to reach +one peer — and then stops deciding. It does not compute the private network; it needs one peer to reach the mesh, and the full peer set arrives derived. ## What a declaration is Settled by [ADR 0005](../../02-DECISIONS/0005-the-node-host.md). -**JSON**, because the host has no dependencies to spend and the standard library carries no +**JSON**, because the node-engine has no dependencies to spend and the standard library carries no YAML. **An ordered list of typed resources**, each with a stable identity — the order is stated -rather than derived, because deriving it would be the host deciding the thing most likely to +rather than derived, because deriving it would be the node-engine deciding the thing most likely to differ from what the controller intended. **Unknown is refused, never skipped.** An unknown version, type or field refuses the whole declaration. A host that skipped what it did not understand would apply most of it and report success. -**Complete for what the host owns, and only that.** It removes what it previously applied and +**Complete for what the node-engine owns, and only that.** It removes what it previously applied and is no longer declared — a fact it holds, from the store, rather than an inference — and never removes anything it did not create. @@ -243,11 +243,11 @@ without one, applying the bundle it carries, has nothing to check against. Staged so each stage is verifiable in the lab before the next exists. -**1 — profile and inventory.** The host runs on a machine, detects what it can do, and reports +**1 — profile and inventory.** The node-engine runs on a machine, detects what it can do, and reports what it is. No controller, no declarations, no network. Verifiable immediately: the lab's `place:` gains its first implementation, and a raised scenario finally contains something. -**2 — apply, from the bundle.** The host applies `foundation.lock` with no mesh present. This is +**2 — apply, from the bundle.** The node-engine applies `foundation.lock` with no mesh present. This is the first node's path, and it is the claim the skeleton's Move 1 rests on and has never proved: that one host can raise the foundation alone. @@ -260,7 +260,7 @@ ready; the current bundle simply does not. **All of them are built:** |---|---|---| | `directory`, `file` | **built** | no machine dependency at all | | `service` | **built** | running/stopped **and** enabled/disabled at boot — a unit started but not enabled stops being true at the next reboot | -| `package` | **built** | present, never upgraded, and **never uninstalled** — the host cannot know what else needs it, so dropping one is *forgotten*, not *removed* | +| `package` | **built** | present, never upgraded, and **never uninstalled** — the node-engine cannot know what else needs it, so dropping one is *forgotten*, not *removed* | | `container` | **built** | pinned by digest ([ADR 0006](../../02-DECISIONS/0006-the-substrate-and-the-control-plane.md)); identified by a label carrying a digest of the declaration that made it, because a runtime normalises what it is given and that is indistinguishable from drift | | `action` | **built** | bundle-only ([ADR 0005](../../02-DECISIONS/0005-the-node-host.md)); verify is mandatory and is the idempotency check as well as the read-back | @@ -285,7 +285,7 @@ unqualified name still means *my own*, so the common case reads as it always did **An action's verify is the definition of what the action is for**, and the action's own idea of being finished must be the same one. *Written 2026-08-31, after this went wrong.* If an action waits on one test and its verify reads back another, the two can disagree — and then the action -succeeds into a state its own verify rejects. The host says so accurately and uselessly: *the +succeeds into a state its own verify rejects. The node-engine says so accurately and uselessly: *the action ran without error and its own verify still fails.* It is intermittent, it reads as a slow machine, and the remedy people reach for is a longer timeout, which cannot help. [04-ISSUES/017](../../04-ISSUES/017-an-action-succeeded-into-a-state-its-verify-rejects/00-report.md) @@ -323,7 +323,7 @@ Each decision above owes a test: | Decision | What asserts it | |---|---| -| 0037 — the host never queries the mesh database | no database client in the dependency tree; a dependency-direction lint failing on an upward import | +| 0037 — the node-engine never queries the mesh database | no database client in the dependency tree; a dependency-direction lint failing on an upward import | | 0038 — one behaviour, two sources | the same code path raises a first node and joins a second | | 0039 — a node holds no shared credential | a raised node's store contains no credential to any service | | 0036 — disconnection is a situation | a node cut off and returned reconciles without being re-adopted | @@ -366,7 +366,7 @@ nobody can write against without reading the code.* | `overlay` | the private network can be joined | | `graphical-session` | a display server **is running** — state | | `seat` | hardware where one **could** run — and assignment needs this one, not the row above | -| `privileged` | the host can change the machine | +| `privileged` | the node-engine can change the machine | **`seat` and `graphical-session` are the pair worth reading twice**, because collapsing them is the obvious economy and it is wrong in both directions: a machine with a seat and no session can @@ -379,7 +379,7 @@ A version string proves a binary is on disk, which records as false in the way that matters: the package was installed and the daemon was not running. -**Never reported and reported nothing stay different.** One machine has not run the host yet; the +**Never reported and reported nothing stay different.** One machine has not run the node-engine yet; the other ran it and can do nothing. Both refuse everything that requires a capability, and the remedies are not remotely alike. @@ -391,10 +391,10 @@ reported to be distinguishable from one that reported an empty list.* - **Whether one host can raise the foundation alone.** Move 1 assumes it. Stage 2 tests it, and if it is false the tier boundary moves. -- **What the host carries versus what it finds.** It manages `wg`, `nft`, `pacman`, `docker`; +- **What the node-engine carries versus what it finds.** It manages `wg`, `nft`, `pacman`, `docker`; it does not contain them, and how it obtains one it lacks is undecided — [04-ISSUES/007](../../04-ISSUES/007-an-installed-package-is-not-a-capability/00-report.md). -- **Six vocabularies.** Zero dependencies, but the host must still know what a peer, a rule, a +- **Six vocabularies.** Zero dependencies, but the node-engine must still know what a peer, a rule, a package, a unit, a container and a dataset *are*. Nothing has measured that surface, and it is the residue of the question [`host-size.md`](../../01-RESEARCH/006-mesh-from-scratch/host-size.md) answered. @@ -433,7 +433,7 @@ of the reason to manage a machine. otherwise, which would silently remove every group that makes a login able to use the machine. A machine's own groups are not the mesh's to know about. - **An archive is pinned by digest, checked before a single file is written.** This is the one - place the host reaches out on its own — everywhere else it holds one outbound connection and + place the node-engine reaches out on its own — everywhere else it holds one outbound connection and fetches nothing — so the only thing making those bytes safe to unpack is that they hash to what was declared. - **An entry naming a path outside the archive is refused, not sanitised.** Rewriting it to land @@ -443,31 +443,31 @@ of the reason to manage a machine. silently incomplete. **A partial host does archives and refuses users**: an archive needs a filesystem and a way to -fetch; a user needs a user database the host is allowed to write. +fetch; a user needs a user database the node-engine is allowed to write. ## A machine becomes the last thing it was told Every declaration is complete, so applying an old one is never wrong, only wasted — and under a flurry of pushes a machine spent minutes becoming things the mesh had moved past ([issue 031](../../04-ISSUES/031-a-machine-becomes-each-thing-it-was-told-in-turn/00-report.md)). -So the host looks at what is already waiting before it applies anything: it holds a small window +So the node-engine looks at what is already waiting before it applies anything: it holds a small window of unacknowledged declarations, applies the newest, and sets the rest aside — each **reported as superseded**, naming the one applied instead, because silence would read as a machine that ignored an instruction and "applied" would be a lie. Applying stays one at a time; only seeing is not. **Checked** by the link's unit tests on the drain, and by the genesis bed's settle wait, which counts on a node catching up to the newest declaration rather than the oldest. -## The host delivers its own successor +## The node-engine delivers its own successor -*2026-09-29, from a change to the host that could reach no machine — +*2026-09-29, from a change to the node-engine that could reach no machine — [issue 142](../../04-ISSUES/142-the-host-is-the-one-thing-the-mesh-does-not-deliver/00-report.md), settled by [ADR 0141](../../02-DECISIONS/0141-the-host-delivers-its-own-successor.md).* -A merge builds every changed module and the control plane, and the result reaches the machines running -it with nobody asking. The host was the exception: not a build target, named by no declaration, and +A merge builds every changed module and the controller, and the result reaches the machines running +it with nobody asking. The node-engine was the exception: not a build target, named by no declaration, and identical on every machine because somebody had copied it there. -The supervision needed for this was already right. A clean exit from the host means it has stood aside, +The supervision needed for this was already right. A clean exit from the node-engine means it has stood aside, and the launcher's next turn runs whatever is on disk. Consecutive failed starts are counted, a rollback happens at the limit, and a second failure halts with the machine named rather than the binary. What was missing was smaller than it looked: nothing told the running host a successor was waiting, and @@ -503,13 +503,13 @@ that runs. ## A service is still running a moment later, 2026-10-02 [ADR 0184](../../02-DECISIONS/0184-a-service-the-mesh-asked-to-run-is-still-running-a-moment-later.md). -The host has always read a unit back after acting on it, because a service manager accepting a +The node-engine has always read a unit back after acting on it, because a service manager accepting a command says the transaction was accepted and nothing about the process. The read raced the failure: a daemon that refuses the configuration the mesh just wrote exits a fraction of a second after the -manager returns, and one look sees it alive. So the host looks twice, with a pause between, and a +manager returns, and one look sees it alive. So the node-engine looks twice, with a pause between, and a unit that was running and is not any more fails its resource by name. A unit still coming up reads as running at both looks and is accepted; a service asked to stop is not waited on. No command for this reaches a machine. A module declaring *how to test my configuration* was weighed -and refused: the link carries no actions, and a verification command is one. The host is checking +and refused: the link carries no actions, and a verification command is one. The node-engine is checking the state it was told to establish, which is what it is for. *How it is checked:* ADR 0184's table. diff --git a/03-DESIGN/01-to-be/06-the-controller.md b/03-DESIGN/01-to-be/06-the-controller.md index ea1e7cb7..390087c1 100644 --- a/03-DESIGN/01-to-be/06-the-controller.md +++ b/03-DESIGN/01-to-be/06-the-controller.md @@ -26,7 +26,7 @@ This document defines it. It does **not** design the contexts inside it; those a > **The controller is everything that needs to know about more than one node.** That is the whole test, and it is not arbitrary — it follows from -[ADR 0005](../../02-DECISIONS/0005-the-node-host.md). The host applies and +[ADR 0005](../../02-DECISIONS/0005-the-node-host.md). The node-engine applies and does not decide *because deciding needs knowledge the machine does not have*. So the line falls exactly there: @@ -35,12 +35,11 @@ exactly there: | write this file, with this content, with this mode | the **host** — one machine | | which nodes should run the store | the **controller** — needs every node | | is this unit running | the **host** — one machine | -| which peers belong in this node's overlay | the **controller** — needs every node | +| which peers belong in this node's private network | the **controller** — needs every node | | what does this machine have installed | the **host** reports; the controller **records** | | has this node been unreachable for a week | the **controller** — nobody else is watching | -A useful consequence: **anything a single machine could answer alone is not the control -plane's.** If it needs no second node, putting it here is a mistake, and the tier rule will not +A useful consequence: **anything a single machine could answer alone is not the controller's.** If it needs no second node, putting it here is a mistake, and the tier rule will not catch it because the dependency direction is still correct. ## What is inside it @@ -53,7 +52,7 @@ each one earning its place by the test above rather than by being ours: |---|---|---| | **inventory** | nodes, modules, assignments, versions | that *is* the mesh-wide fact | | **config** | settings, secrets, and deriving them onto nodes | it derives **onto nodes** | -| **connectivity** | overlay, resolution, exposure, filtering, certificates — **specified in full in [`08-connectivity.md`](08-connectivity.md)** | who peers with whom; which node is reachable | +| **connectivity** | private network, resolution, exposure, filtering, certificates — **specified in full in [`08-connectivity.md`](08-connectivity.md)** | who peers with whom; which node is reachable | | **provisioning** | resource grants between modules | consumer and provider may be on different nodes | | **delivery** | source to artifact to node | it targets nodes | | **observability** | health, logs, metrics, alerts | *unreachable for a week* is nobody else's to notice | @@ -97,7 +96,7 @@ because each one alone reads like a detail: | | | |---|---| -| [ADR 0005](../../02-DECISIONS/0005-the-node-host.md) | the host never queries the mesh database | +| [ADR 0005](../../02-DECISIONS/0005-the-node-host.md) | the node-engine never queries the mesh database | | [ADR 0004](../../02-DECISIONS/0004-a-node-and-how-it-joins.md) | a node holds its own identity **and nothing else** — the shared database credential every node carries today is the exposure this exists to remove | | [ADR 0008](../../02-DECISIONS/0008-a-context-owns-its-store.md) | a context is granted only what it **exclusively** owns: no shared writes, no read-only roles | | [ADR 0006](../../02-DECISIONS/0006-the-substrate-and-the-control-plane.md) | there is no single mesh database, and nothing reads one | @@ -174,8 +173,8 @@ volume genuinely argues against a relational store. ## What it is not -- **Not the thing that changes machines.** It decides; the host applies. It never reaches into a - node except through the host. +- **Not the thing that changes machines.** It decides; the node-engine applies. It never reaches into a + node except through the node-engine. - **Not a surface.** Tier 3 is how people and agents reach it. It has one interface; the surfaces are what speak to that interface. - **Not the foundation.** It *runs on* tier 1 — PostgreSQL, LavinMQ, an OCI registry @@ -192,7 +191,7 @@ module needs, granted the same way. That is the circularity the tiers exist to resolve rather than hide: the controller cannot provision its own database, because it is not running yet. So its **store** is raised from the -bundle the host carries, before there is a controller to ask +bundle the node-engine carries, before there is a controller to ask ([ADR 0004](../../02-DECISIONS/0004-a-node-and-how-it-joins.md), [research 011](../../01-RESEARCH/011-the-module-graph/worked-provider.md)). diff --git a/03-DESIGN/01-to-be/07-the-foundation.md b/03-DESIGN/01-to-be/07-the-foundation.md index 82a05ede..4ab06e41 100644 --- a/03-DESIGN/01-to-be/07-the-foundation.md +++ b/03-DESIGN/01-to-be/07-the-foundation.md @@ -42,10 +42,9 @@ gap applied: the word was load-bearing and unpinned. > **The foundation is what the controller consumes and cannot grant itself.** -Every module that needs a database asks the controller's provisioning for one. The control -plane needs a database too — and it cannot ask itself, because it is not running yet. That +Every module that needs a database asks the controller's provisioning for one. The controller needs a database too — and it cannot ask itself, because it is not running yet. That circularity is not an awkwardness to work around; it *is* the definition. Anything on the wrong -side of it must be raised some other way, and the other way is the bundle the host carries +side of it must be raised some other way, and the other way is the bundle the node-engine carries ([ADR 0004](../../02-DECISIONS/0004-a-node-and-how-it-joins.md)). The test, applied: @@ -103,7 +102,7 @@ cannot obtain it* is. ## What the foundation is not -- **Not tier 0.** The host raises the foundation; it is not part of it. The host carries the +- **Not tier 0.** The node-engine raises the foundation; it is not part of it. The node-engine carries the declaration that brings the foundation up, and depends on nothing. - **Not the controller.** These are services with no knowledge of the mesh. A store does not know what a node is. @@ -151,9 +150,9 @@ foundation exists to start, and it is in the bundle for the same reason they are to fetch it with yet. It also carries seven actions, a package and a service. **Why pinned:** the bundle is applied when no mesh exists, so nothing can resolve a version, ask -a registry, or check a constraint. What the host carries must already be exact. +a registry, or check a constraint. What the node-engine carries must already be exact. -**Why references and not payload:** the bundle names images by **digest** and the host fetches +**Why references and not payload:** the bundle names images by **digest** and the node-engine fetches them ([ADR 0006](../../02-DECISIONS/0006-the-substrate-and-the-control-plane.md)). A first node is a real machine with a network; the sealed case is the lab, and the lab places images itself. @@ -167,7 +166,7 @@ up ([ADR 0088](../../02-DECISIONS/0088-the-foundation-filters-before-anything-li default, keep loopback, replies, ping, ssh, the bus and the registry, and the container runtime's networks through the forward chain. It is written into the same table the filter module derives, so that module replaces it wholesale once it can. Until it does, the machine admits nothing else — -not the overlay hub's port, which is derived from the hub's endpoint — so the filter module is +not the private network hub's port, which is derived from the hub's endpoint — so the filter module is assigned to the control-node before a hub is placed there, as genesis does; a lab bed that raises the foundation without genesis must do the same, and says so by waiting for the hub's port in the ruleset the machine loaded. **Checked** by the installer's bundle test (order and rules) and by @@ -227,7 +226,7 @@ is a *package*, not a container. already has one keeps it. On a machine with none, the controller names the package, because what it is called differs per system. It is: -- what the host's capability detection already reports, and the first use of that report by +- what the node-engine's capability detection already reports, and the first use of that report by something other than a person; - **adopted rather than installed** when the machine already has one with configuration somebody chose ([research 012](../../01-RESEARCH/012-the-minimum-viable-node/00-overview.md)); @@ -238,21 +237,19 @@ So the bootstrap uses four shapes: **package**, **container**, **service** and * *counted from `foundation-first-node.lock`, which is the only bundle there is*. It had said six, adding `file` and `directory`, which this bootstrap never asks for. -All four are built, as are the host's other five +All four are built, as are the node-engine's other five ([`05-the-node-host.md`](05-the-node-host.md) stage 2), so nothing in this bootstrap is blocked -on the host any longer — which is the claim that mattered, and it was true either way. +on the node-engine any longer — which is the claim that mattered, and it was true either way. **Steps 2 and 3 happen before there is a mesh to do them**, which is why provisioning is part of the bootstrap rather than a service consumers use later. They are **actions** the bundle -declares and the host runs -([ADR 0005](../../02-DECISIONS/0005-the-node-host.md)) — so the -host's vocabulary grows by one shape rather than by one resource type per foundation service. +declares and the node-engine runs +([ADR 0005](../../02-DECISIONS/0005-the-node-host.md)) — so the node-engine's vocabulary grows by one shape rather than by one resource type per foundation service. ## Open - ~~**Whether identity is the fifth.**~~ **Closed 2026-08-31** by - [ADR 0031](../../02-DECISIONS/0031-the-control-plane-authenticates-nobody.md): the control - plane delegates authentication to nothing, so identity is an ordinary module. With the object + [ADR 0031](../../02-DECISIONS/0031-the-control-plane-authenticates-nobody.md): the controller delegates authentication to nothing, so identity is an ordinary module. With the object store gone ([ADR 0028](../../02-DECISIONS/0028-the-substrate-supplies-the-control-plane-and-nothing-else.md)) the foundation is three, and no member is conditional. - ~~**Whether the bus must precede the controller.**~~ **Resolved** by @@ -273,11 +270,11 @@ host's vocabulary grows by one shape rather than by one resource type per founda - ~~**Whether the host can do step 2.**~~ **Resolved** by [ADR 0005](../../02-DECISIONS/0005-the-node-host.md). A service running on this machine is part of this machine, so the scope was never in question — the real - question was whether the host must learn what a database is, and it must not. The bundle - declares an **action**; the host runs it and verifies it, and what a database means stays with + question was whether the node-engine must learn what a database is, and it must not. The bundle + declares an **action**; the node-engine runs it and verifies it, and what a database means stays with the module that provides one. - **Whether one host can raise all three.** The claim under stage 2 of - [the node host](05-the-node-host.md), never proved. If it is false, the tier boundary moves. + [the node-engine](05-the-node-host.md), never proved. If it is false, the tier boundary moves. - ~~**The vault as the fourth piece.**~~ **Closed 2026-09-21** by [ADR 0085](../../02-DECISIONS/0085-a-secret-is-a-provision.md) as amended: genesis makes the operator key before anything is minted, raises the store and broker with credentials it made @@ -325,11 +322,11 @@ username. Recorded in ADR 0004 as the fifth thing a token carries. The bundle carries three images and one of them is the controller, *because there is nothing to fetch it with yet*. That reasoning holds and its conclusion changes: the controller is carried as a **binary** reference rather than an image reference, pinned by digest exactly as before. Nothing about the bundle's -shape moves — it names a thing and the host fetches it — and the container runtime stops being something -genesis must raise before the control plane can exist. It still raises one, for the store and the broker, +shape moves — it names a thing and the node-engine fetches it — and the container runtime stops being something +genesis must raise before the controller can exist. It still raises one, for the store and the broker, which is where somebody else's software belongs. -The mesh's own components — the host, the controller, the catalogue, the builder, the vault — are +The mesh's own components — the node-engine, the controller, the catalogue, the builder, the vault — are delivered as binaries into directories named for their versions, by the mechanism [ADR 0141](../../02-DECISIONS/0141-the-host-delivers-its-own-successor.md) describes. Third-party software stays a container. The split is not about isolation; it is about who built the thing. diff --git a/03-DESIGN/01-to-be/08-connectivity.md b/03-DESIGN/01-to-be/08-connectivity.md index af44f2d9..17de2df6 100644 --- a/03-DESIGN/01-to-be/08-connectivity.md +++ b/03-DESIGN/01-to-be/08-connectivity.md @@ -62,7 +62,7 @@ decisions: # Connectivity One of [the controller's](06-the-controller.md) ten contexts, and the one with the most -moving parts: **overlay, resolution, exposure, filtering, certificates.** +moving parts: **private network, resolution, exposure, filtering, certificates.** It is written as a whole because the five are one design. They share inputs, they must agree, and every one of them today is computed in a different place by a different module from a different @@ -75,10 +75,10 @@ node* — to each responsibility: | | needs to know | whose | |---|---|---| -| **overlay** — who peers with whom, at what address | **every node**, and which of them can be dialled | controller | +| **private network** — who peers with whom, at what address | **every node**, and which of them can be dialled | controller | | **resolution** — which name is which node | **every node** | controller | | **exposure** — which public name reaches which container | **which node is publicly reachable** ([ADR 0007](../../02-DECISIONS/0007-connectivity.md)) | controller | -| **filtering** — which port is open, to whom | what is assigned here, and the overlay's shape | controller decides, host applies | +| **filtering** — which port is open, to whom | what is assigned here, and the private network's shape | controller decides, host applies | | **certificates** — who may present which name | which name belongs to which node | controller | **Not one of the five can be answered by a machine on its own.** That is the whole reason this is @@ -135,7 +135,7 @@ settings, and absent from a machine nobody gave it to. > why the bundle was built. **Three rather than one, because WireGuard is one VPN of several.** Naming the module after the -job — `networking` — and putting WireGuard inside it is the retired *flavor* idea wearing a +job — `networking` — and putting WireGuard inside it is the retired "flavor" idea wearing a generic name: the second VPN has nowhere to go. So a module is named for what it *is* and declares what it *does*, and `networking` is the third row — requirements and no files ([ADR 0009](../../02-DECISIONS/0009-modules-and-the-graph.md)). @@ -165,7 +165,7 @@ into whatever it runs, which is why swapping Traefik for something else touches publishes through it. See [ADR 0009](../../02-DECISIONS/0009-modules-and-the-graph.md) for the other direction — handing a credential *back* — which is the larger half and is not built. -**What is still not a module, and why that is correct.** The host needs none of this. It has an +**What is still not a module, and why that is correct.** The node-engine needs none of this. It has an address and a route before the mesh exists — that is the machine's own networking — and the broker's address is carried in the token rather than resolved ([ADR 0004](../../02-DECISIONS/0004-a-node-and-how-it-joins.md)). **The one connection that @@ -186,10 +186,10 @@ The one thing to get right, because everything else depends on it: 7 routes and certificates once this node has something to expose ``` -**Step 1 runs on the underlay and never on the overlay.** This is the circularity that must not -be created: the overlay is configured by the mesh, so a link that required the overlay could +**Step 1 runs on the underlay and never on the private network.** This is the circularity that must not +be created: the private network is configured by the mesh, so a link that required the private network could never be established on a new node. The link stays on the underlay permanently — it is -outbound-only and carries its own identity, so it needs nothing the overlay provides. +outbound-only and carries its own identity, so it needs nothing the private network provides. **Nothing before step 3 can resolve a mesh name**, which is why the token carries an *address* ([ADR 0004](../../02-DECISIONS/0004-a-node-and-how-it-joins.md)). Today this is @@ -204,7 +204,7 @@ a broker node whose address moves invalidates every token issued for it. *2026-10-02.* **The order changes at step 1: the tunnel comes first, from the token** ([ADR 0169](../../02-DECISIONS/0169-a-machine-joins-through-the-tunnel-and-the-bus-is-never-public.md)). -The circularity above is real, and it is broken differently. The overlay is configured by the mesh, +The circularity above is real, and it is broken differently. The private network is configured by the mesh, except for the one peer a joining machine needs, and the token carries that peer. So the sequence becomes: @@ -224,14 +224,14 @@ including one that is joining, so it is never opened to the internet. The precon hub's tunnel must be dialable by every node, at a stable address.** That port answers nothing to a key it does not know. -**Whether the link should later move onto the overlay, with the underlay as fallback, is +**Whether the link should later move onto the private network, with the underlay as fallback, is [open](../../02-DECISIONS/0007-connectivity.md).** It is a decision rather than a derivation: the gain is which network carries bytes, not what an attacker can reach, since the link is already encrypted against a pinned fingerprint. -## 1 — The overlay +## 1 — The private network -**What is decided:** the peer graph. For every node: its overlay address, which peers it holds, +**What is decided:** the peer graph. For every node: its private network address, which peers it holds, which of those it may dial, and which must dial it. **Inputs, all declared:** @@ -247,7 +247,7 @@ which of those it may dial, and which must dial it. **Keys.** Each node generates its own keypair. **The private key never leaves the machine**; the public key is published to the mesh. This is already true and it is already right — it is [ADR 0004](../../02-DECISIONS/0004-a-node-and-how-it-joins.md)'s *a node holds its own -identity* applied to the overlay, and it means the controller computes a graph it cannot +identity* applied to the private network, and it means the controller computes a graph it cannot itself impersonate. **Shape: a hub, with direct peering between co-located nodes.** @@ -263,7 +263,7 @@ preference: there is no failover.** A more specific route to a dead endpoint bla not fall back to the general one. So a node whose location changes gets exactly one path, because two paths would mean one of them silently swallowing traffic. -**What the host receives:** an interface configuration and a peer list, as files. It does not +**What the node-engine receives:** an interface configuration and a peer list, as files. It does not compute them, and after this it holds no credential to the mesh's database. ### Four things the lab found, none of them visible from the mesh's own state @@ -286,7 +286,7 @@ files were right, the services were up, and every node reported success. path, and the direct route is more specific than the hub's, so it wins and blackholes. This document's own warning, arriving in its implementation: *a more specific route to a dead endpoint blackholes; it does not fall back to the general one.* -- **The container runtime closes the door the overlay needs.** Docker sets the FORWARD policy to +- **The container runtime closes the door the private network needs.** Docker sets the FORWARD policy to DROP, so a hub with `ip_forward` enabled still carries nothing between its spokes. The foundation at tier 1 silently breaks the network at tier 2, and nothing in either tier's state says so. The hub inserts its own rule above those chains and removes it on the way down. @@ -301,10 +301,10 @@ each was found within minutes of a real machine trying it. | | resolves to | certified by | |---|---|---| -| **internal names** | overlay addresses | the **mesh CA** | +| **internal names** | private network addresses | the **mesh CA** | | **public names** | whatever the outside world must reach | a **public authority** | -A node's mesh name is its overlay address. Its public name, if it has one, is a separate fact +A node's mesh name is its private network address. Its public name, if it has one, is a separate fact used by things outside the mesh — and the separation carries two lessons that were learned expensively enough to be worth restating: @@ -314,7 +314,7 @@ expensively enough to be worth restating: - **A node must not pin its own public name locally.** The duplicate record breaks resolution of that name for everything else that needs it. -**What the host receives:** what to ask, not what to answer. The mesh has **two resolvers**, each +**What the node-engine receives:** what to ask, not what to answer. The mesh has **two resolvers**, each holding every node's internal domain; a node lists both and nothing else ([ADR 0194](../../02-DECISIONS/0194-the-mesh-has-one-resolver-and-every-node-asks-it-for-the-meshs-names.md), [ADR 0223](../../02-DECISIONS/0223-the-mesh-has-two-resolvers-and-a-machine-lists-only-them.md)). @@ -364,8 +364,7 @@ nothing is copied.** The resolver is a machine-level process rather than a conta circular is being asked for. It was gated on a container being able to reach the resolver from any of the runtime's networks ([issue 110](../../04-ISSUES/110-a-container-on-the-runtimes-own-network-cannot-reach-the-resolver/00-report.md)), -and landed the day that did, 2026-09-30: the controller writes no mesh name into a container and the -host's digest carries only what the module declared for itself. +and landed the day that did, 2026-09-30: the controller writes no mesh name into a container and the node-engine's digest carries only what the module declared for itself. The paragraph below states the old boundary, and 0148 deliberately gives it up: a container somebody started by hand resolves the same names as everything else, because the resolver answers the machine, @@ -590,7 +589,7 @@ it and hands back the public name. Ordinary vocabulary — the mirror of a database grant, where the consumer supplies a target and receives a name rather than supplying nothing and receiving credentials. -**A workload on an unreachable node is proxied by a reachable one, across the overlay.** Which is +**A workload on an unreachable node is proxied by a reachable one, across the private network.** Which is the case is a mesh-level fact, which is the fourth reason exposure is controller work. ### What was built @@ -681,7 +680,7 @@ program that reads it, and another proxy may implement the same file. ## 4 — Filtering -**Derived from what is assigned here, and from the overlay's shape** — a node's open ports are a +**Derived from what is assigned here, and from the private network's shape** — a node's open ports are a consequence of what runs on it and who must reach it, not an independent declaration to keep in step by hand. That is true of a converged node; an adopted one keeps the firewall it was found with until it converges (below). @@ -692,7 +691,7 @@ is removed rather than implemented: five manifests carry it today, it is referen and it is the clearest instance in the repository of *an unenforced rule is indistinguishable from a wrong one, and costs more, because people believe it.* -**Unknown keys are refused** — the discipline the host's declaration parser already has +**Unknown keys are refused** — the discipline the node-engine's declaration parser already has ([ADR 0005](../../02-DECISIONS/0005-the-node-host.md)), and the one manifests lack. `scope:` survived because nothing rejected it. @@ -746,7 +745,7 @@ tmpfiles — is shaped that way. Until there is one, a module ships a unit that the better shape: how a machine enforces rules is a fact about the machine, and the mesh has no business depending on what a distribution happens to package. -**One rule is derived from the overlay's shape rather than from what is assigned: a hub's own +**One rule is derived from the private network's shape rather than from what is assigned: a hub's own listening port.** A hub accepts inbound connections from every node at other sites; a machine that is not a hub dials out and needs nothing open, because a reply to a flow it started is already accepted. The two want different rules on an *identical module*, so `listens` — a static field — @@ -799,7 +798,7 @@ declares as **openings**: a port, from where, on the incoming path or the forwar published container port is forwarded, and a firewall that filters only incoming traffic never sees it. The controller derives them from what the filter would be derived from, each from where the filter would admit it: the assigned modules' `listens`, the hub's port, the bus and the registry -from anywhere; the store's port and the broker's management port from the private network. The host converges each opening through +from anywhere; the store's port and the broker's management port from the private network. The node-engine converges each opening through the found firewall in that firewall's own terms, marks it as the mesh's, removes only what it marked, and re-checks every opening on each reconcile so a reload or a reboot does not lose it for longer than one reconcile. An opening is state, not a command, so it travels over the link like any @@ -869,9 +868,9 @@ named in any setting, and each reaches outward afterwards — which fails agains where the same flip cut them off, and is how it was written; a network made *after* the last declaration needs no new filter; a declared port is reachable from off the private network and an undeclared one is not; no address of a machine's own networks appears in a rendered filter, asserted on the text; and a -machine reporting no outward link is refused in the control plane with its existing filter left alone. +machine reporting no outward link is refused in the controller with its existing filter left alone. -### A converged machine is filtered by the mesh alone, and the host says what else refuses +### A converged machine is filtered by the mesh alone, and the node-engine says what else refuses *2026-10-02, [ADR 0168](../../02-DECISIONS/0168-a-converged-machine-is-filtered-by-the-mesh-alone.md), from [issues 143](../../04-ISSUES/143-converging-does-not-retire-the-firewall-it-found/00-report.md) and @@ -884,9 +883,9 @@ runtime's user hook — legacy iptables on one machine, invisible to a reader of forwarded path, refused ports the mesh declared open, and carried an allowance every module reaching another by the machine's own name relied on. -**Convergence is a state the host keeps.** Every converged apply reads whether the found firewall is in +**Convergence is a state the node-engine keeps.** Every converged apply reads whether the found firewall is in force; enabled again, it is retired again and said; the record says whether the mesh disabled it or -found it inactive, and a skipped step is said. **The host reports what filters the machine**, every +found it inactive, and a skipped step is said. **The node-engine reports what filters the machine**, every apply, adopted or converged: every table and legacy chain that refuses, with an owner — the mesh's, the found firewall's, the runtime's own plumbing, a ban, or *other*, which is where the runtime's user chain's refusals go. **The mesh says which:** `node show` lists them; `status` names a converged machine @@ -904,7 +903,7 @@ predicate. Live: the home server's record names the predecessor's chain as *othe names the machine until the chain is removed by hand. *2026-10-02, [ADR 0170](../../02-DECISIONS/0170-the-firewall-seat-serves-its-verbs.md):* removing what -the host reports as *other* is reached through the packet filter seat's `remove` verb, an operator's act +the node-engine reports as *other* is reached through the packet filter seat's `remove` verb, an operator's act by name on the bus; the seat also serves `rules` and `reload`, and its holder's runtime declares the `NET_ADMIN` capability on the machine's network. See design 33. @@ -920,7 +919,7 @@ adopted then enables nothing. The rollback path ADR 0100 kept on disk is given u | | issued by | for | |---|---|---| | **public names** | a public ACME authority | anything outside the mesh reaches | -| **internal names** | the **mesh CA** | node-to-node, over the overlay | +| **internal names** | the **mesh CA** | node-to-node, over the private network | **The split is not collapsed, including in the lab.** A single-CA lab would hide any bug living in the split, so the lab runs its own ACME issuer on its public segment and keeps the mesh CA @@ -973,7 +972,7 @@ extracted bundles — and stopping it, which is what being unassigned does, take and refreshes them again. Not the controller's business, because being on the private network is what makes the authority *reachable* and is not the same fact as having a reason to *verify* a mesh name; and because where anchors live and which command refreshes them is one operating -system's difference, which is the host's half of the mesh +system's difference, which is the node-engine's half of the mesh ([ADR 0005](../../02-DECISIONS/0005-the-node-host.md)). *How it is checked:* on a machine holding the module a plain client verifies an internal HTTPS name with no bundle argument, and on one without it the same fetch fails to find an issuer — both @@ -997,7 +996,7 @@ valid certificate and there is nothing to keep in step. **A machine with no name inside the mesh is refused**, not given a certificate for nothing. A certificate for a name nothing resolves is a certificate nothing can check. -**And the key is stored in the format a server reads** — PKCS#8 PEM, not the host's own encoding. +**And the key is stored in the format a server reads** — PKCS#8 PEM, not the node-engine's own encoding. That is not an implementation detail of whoever writes the file: the file exists *because something else reads it*, so the format is the interface ([04-ISSUES/014](../../04-ISSUES/014-a-key-that-is-present-and-unusable/00-report.md)). @@ -1125,14 +1124,14 @@ The list is worth having in one place, because it is most of the argument: hub is declared rather than elected, and non-co-located paths stop while co-located direct peers and every already-assigned workload keep running. The recovery path is restore, and its deadline is certificate renewal. -- **Renumbering the overlay.** Made *possible* by declaring the hub rather than inferring it from +- **Renumbering the private network.** Made *possible* by declaring the hub rather than inferring it from an address, but no procedure exists, and a graph delivered node by node has an ordering problem while it is half-applied. - ~~**Revoking a route** when a module is unassigned.~~ **Resolved** 2026-08-31 — see §3. The file a proxy is given is the whole truth about who has a route, so a route does not outlive the module that asked for it. - **IPv6.** [ADR 0007](../../02-DECISIONS/0007-connectivity.md) makes - it expressible; nothing here says the overlay or the resolver handle it. + it expressible; nothing here says the private network or the resolver handle it. - **Reporting declared-versus-observed.** ADR 0007 makes the disagreement detectable and does not say who looks or what they are told. - **Composing a route name from a label and a node's domain.** @@ -1169,7 +1168,7 @@ anything but a person restoring it by hand. Undeclaring the private network does before code; this section names the shape only.* Every link the mesh has rides NATS subjects; durability is JetStream's; a module's account is a -NATS account with permissions derived from `emits`/`consumes`, declared as configuration the host +NATS account with permissions derived from `emits`/`consumes`, declared as configuration the node-engine writes and the server reloads. The sdk's contract is unchanged. The adopted AMQP broker stays as the predecessor's compatibility broker until its last client is gone. Built in the lab beside the -migration; cut over in one rollout after the core; the person's client is designed on it. +migration; cut over in one switch-over after the core; the person's client is designed on it. diff --git a/03-DESIGN/01-to-be/09-the-node-lifecycle.md b/03-DESIGN/01-to-be/09-the-node-lifecycle.md index 760c3935..b3b217d6 100644 --- a/03-DESIGN/01-to-be/09-the-node-lifecycle.md +++ b/03-DESIGN/01-to-be/09-the-node-lifecycle.md @@ -29,7 +29,7 @@ decisions: How a Linux machine becomes a node, stays one, and stops being one. -[`05-the-node-host.md`](05-the-node-host.md) describes the host as a component. This describes +[`05-the-node-host.md`](05-the-node-host.md) describes the node-engine as a component. This describes it as something that runs for years on a machine somebody else also uses — which is where the questions that were not being asked live. @@ -44,7 +44,7 @@ questions that were not being asked live. | State | Has | Can | |---|---|---| | **unmanaged** | nothing of ours | — it is a Linux machine | -| **hosted** | the host, no identity | apply a local file, apply its bundle | +| **hosted** | the node-engine, no identity | apply a local file, apply its bundle | | **enrolled** | identity, link, store | everything; this is *a node* | | **disconnected** | identity, store, no link | hold its machine in the last state it was told | @@ -116,7 +116,7 @@ asked to *start this at boot* and *start it again if it exits*, and nothing else expressible in OpenRC, runit, s6 and an Android `init.rc`, so porting this file is transcription rather than design. -**`Restart=always` and not `on-failure`**: the host restarts onto a new binary by exiting +**`Restart=always` and not `on-failure`**: the node-engine restarts onto a new binary by exiting *cleanly*, so a supervisor that only restarts on failure would leave every upgraded node stopped, having successfully upgraded. @@ -124,14 +124,13 @@ having successfully upgraded. lives in the launcher, where it can be tested — `OnFailure=` in a unit file can only be read and hoped for, and it is the one thing that has to work on a machine where nothing else does. -**The package owns this file. The host never does.** It manages `service` resources, and its own +**The package owns this file. The node-engine never does.** It manages `service` resources, and its own unit is a service — the temptation is obvious and it ends with a host stopping itself half way -through an apply, leaving a machine with nothing running to fix it. A declaration naming the -host's own unit is **refused**, and that refusal is a test rather than a convention. +through an apply, leaving a machine with nothing running to fix it. A declaration naming the node-engine's own unit is **refused**, and that refusal is a test rather than a convention. -The line to hold: **the installation owns the host; the host owns everything else.** +The line to hold: **the installation owns the node-engine; the node-engine owns everything else.** -At this point the host is running and **doing nothing**. It has no identity, so there is nobody +At this point the node-engine is running and **doing nothing**. It has no identity, so there is nobody to link to and nothing to apply. It answers `profile`, `inventory` and `version`, and waits. --- @@ -150,29 +149,29 @@ join once. **The fourth is the one this document listed three of.** A node connects to the broker and takes instruction from the controller behind it, and those are two different identities. Pinning only the broker would make the controller's authority *transitive* — a compromised broker could then -forge declarations, which, since the host applies whatever the link delivers, is the whole machine. +forge declarations, which, since the node-engine applies whatever the link delivers, is the whole machine. So the transport is verified once at connect, and **each declaration is verified by its signature, every time**. What happens, in order: -1. the host dials the broker at the address in the token, **over the underlay**; +1. the node-engine dials the broker at the address in the token, **over the underlay**; 2. it checks the broker's certificate against the pinned fingerprint — *before* sending anything; 3. it presents the one-time secret **and its own public key**, which the mesh records; 4. it reports its `profile` and `inventory` upward; 5. the controller decides what this machine should be, and sends a declaration; -6. the host applies it, reads back, and reports. +6. the node-engine applies it, reads back, and reports. **Step 4 is the one that is easy to miss and is what makes step 5 possible.** The controller cannot decide what a machine should run without knowing what it *can* run — a graphical session, a container runtime, an architecture. The profile is not a diagnostic; it is the input. -**The node computes nothing about the mesh.** It needs one peer to reach; the whole overlay is +**The node computes nothing about the mesh.** It needs one peer to reach; the whole private network is derived centrally and pushed down ([ADR 0004](../../02-DECISIONS/0004-a-node-and-how-it-joins.md), [`08-connectivity.md`](08-connectivity.md)). -### The first declaration is the overlay, and nothing else +### The first declaration is the private network, and nothing else **The mesh makes a node reachable before it makes it useful.** Step 5 is not one declaration carrying everything the node will ever run. It is two, in order: @@ -184,13 +183,13 @@ then everything else — packages, containers, services, files Three reasons, and the third is the one that matters when something goes wrong: -- **It is forced.** A node cannot join the overlay before contacting the mesh, because its +- **It is forced.** A node cannot join the private network before contacting the mesh, because its address and peer set are *assigned* — it generates a keypair, publishes the public half, and - receives the rest ([`08-connectivity.md`](08-connectivity.md)). So the overlay is the first + receives the rest ([`08-connectivity.md`](08-connectivity.md)). So the private network is the first thing the mesh can give it, and it should be. - **It is what [ADR 0004](../../02-DECISIONS/0004-a-node-and-how-it-joins.md) already says:** *a joining node does the minimum to be reachable, and nothing else.* -- **It is the way back in.** Once the overlay is up, the node is reachable over it — by SSH, by +- **It is the way back in.** Once the private network is up, the node is reachable over it — by SSH, by anything. If a later declaration breaks the machine, there is a route to it that does not depend on the mesh's control path working. **Sending a large first declaration risks a node that is broken and unreachable at the same time**, and those two failures are much worse @@ -202,14 +201,14 @@ Worth stating plainly, because the two rules read as a contradiction and are not | | | |---|---| -| **every node reaches every other node** | over the overlay — SSH, services, ordinary traffic. This is the point of having one | +| **every node reaches every other node** | over the private network — SSH, services, ordinary traffic. This is the point of having one | | **every node consumes from the broker** | its own queue, over its own outbound connection ([ADR 0002](../../02-DECISIONS/0002-nodes-communicate-over-a-broker.md)) | -| **nothing dials a node to control it** | the host has no inbound control surface ([ADR 0004](../../02-DECISIONS/0004-a-node-and-how-it-joins.md)) | +| **nothing dials a node to control it** | the node-engine has no inbound control surface ([ADR 0004](../../02-DECISIONS/0004-a-node-and-how-it-joins.md)) | **[ADR 0004](../../02-DECISIONS/0004-a-node-and-how-it-joins.md) is about the control channel, not about network reachability.** What it forbids is a listening thing that accepts -instructions and changes the machine. A node being reachable on the overlay — the whole purpose -of the overlay — is untouched by it, and so is a person opening a shell on it. +instructions and changes the machine. A node being reachable on the private network — the whole purpose +of the private network — is untouched by it, and so is a person opening a shell on it. The distinction is *who can tell this machine what to be*: only the controller, only over the link the node opened, only in declarations of known shape. @@ -233,7 +232,7 @@ nox-mesh-host enrol --token Step 1 is the bootstrap from [`07-the-foundation.md`](07-the-foundation.md): a container runtime, then PostgreSQL, then the database, then the schema, then the controller. It needs no identity -because nothing is being asked of anyone — the host is applying a declaration it already +because nothing is being asked of anyone — the node-engine is applying a declaration it already carries, to the machine it is already on. **After step 3 the first node is not special in any way**, which is the property `adopt.sh` and @@ -247,7 +246,7 @@ used months later on node two. ## Two kinds of host -Everything above assumes a machine with an init that runs the host at boot. Not every machine +Everything above assumes a machine with an init that runs the node-engine at boot. Not every machine has one ([ADR 0005](../../02-DECISIONS/0005-the-node-host.md)). | | **resident** | **episodic** | @@ -296,7 +295,7 @@ converged, as before The operator says a node is adopted — at genesis for the control-node, in the enrolment token for the others — and the controller records it and says so in every declaration, with the modules **taken** on that node. On an adopted node what is found is held until its module is taken -([05-the-node-host](05-the-node-host.md)): assigning a module prepares it, taking it is its +([05-the node-engine](05-the-node-host.md)): assigning a module prepares it, taking it is its cutover. The firewall found there stays in force and the mesh opens what it needs through it ([08-connectivity](08-connectivity.md)). **Converging is one act per node, previewed**: it refuses while an assigned module still holds a found container; otherwise it lists what is reachable on the @@ -340,7 +339,7 @@ over a take's digest, staleness and secrets. A candidate machine is not empty. It has a package manager, probably a container runtime, configuration somebody chose. [ADR 0005](../../02-DECISIONS/0005-the-node-host.md) -says the host never touches what it did not create — adoption is the deliberate act of taking +says the node-engine never touches what it did not create — adoption is the deliberate act of taking ownership of exactly that, so it is a companion to that rule rather than an exception: > *never, unless adoption made it the host's* — with adoption **explicit, recorded, and visible @@ -361,14 +360,13 @@ bind is not adopted but broken. **Adoption produces a briefing**, not just a result: what it found, what it took over, and what it could not resolve — with each line marked `ok`, `kept`, `unknown` or `failed`, and the overall -outcome **derived** from the worst line rather than stated alongside it. A file or container the -host is holding on an adopted node is a `kept` line for as long as it is held. +outcome **derived** from the worst line rather than stated alongside it. A file or container the node-engine is holding on an adopted node is a `kept` line for as long as it is held. --- ## enrolled: what running actually looks like -**Changes are pushed, not polled.** A declaration arrives as a message on the link and the host +**Changes are pushed, not polled.** A declaration arrives as a message on the link and the node-engine applies it then. The link is already open and outbound ([ADR 0002](../../02-DECISIONS/0002-nodes-communicate-over-a-broker.md), [ADR 0004](../../02-DECISIONS/0004-a-node-and-how-it-joins.md)) — asking it @@ -404,28 +402,28 @@ because a stuck node cannot send. **Rebooting mid-apply is safe by construction.** The store records each resource *after* it worked ([ADR 0018](../../02-DECISIONS/0018-a-picture-is-read-from-what-runs.md)), so a host that dies half way through comes back, finds the completed ones already matching, and applies the -rest. The rule that exists to stop the host lying about what it did also makes it crash-safe. +rest. The rule that exists to stop the node-engine lying about what it did also makes it crash-safe. ## Updating what the node holds An ordinary declaration. Someone assigns a module; the controller recomputes what that node -should be and sends it; the host applies the difference and removes what is no longer declared. +should be and sends it; the node-engine applies the difference and removes what is no longer declared. **Removal is not symmetric, and the asymmetry is the design:** | | on being undeclared | |---|---| | file, directory | **removed** | -| container | **removed** — the host created it | -| service | **stopped**; the unit file is not the host's to delete | +| container | **removed** — the node-engine created it | +| service | **stopped**; the unit file is not the node-engine's to delete | | package | **left installed** — *forgotten*, not removed | -| action | **forgotten** — it left nothing the host owns | +| action | **forgotten** — it left nothing the node-engine owns | -The host removes what it *made* and leaves what it merely *configured*. Uninstalling a container +The node-engine removes what it *made* and leaves what it merely *configured*. Uninstalling a container runtime because a declaration changed would stop every container on the node. *On an adopted node* ([ADR 0100](../../02-DECISIONS/0100-a-node-in-use-is-adopted-before-it-is-converged.md)), -what the host is holding was found, not made, so nothing held is ever removed: a held file or +what the node-engine is holding was found, not made, so nothing held is ever removed: a held file or container whose module is unassigned stays where it is. --- @@ -450,7 +448,7 @@ Without it, a node running last month's assignments looks exactly like one that ## Rescue -The host is still a command-line tool, and that is what rescue is: +The node-engine is still a command-line tool, and that is what rescue is: ``` nox-mesh-host owned # what do you think you own? @@ -467,7 +465,7 @@ root can already do anything it can. The bound in [issue 104](../../04-ISSUES/104-reconcile-applies-a-stale-declaration-and-refuses-nothing/00-report.md): once a controller declaration has been kept on the node, `apply FILE` is refused, whatever the file — a hand-applied file is recorded as *carried*, which the mesh can never remove and reports -as the machine's own, and a declaration carries no order, so the host cannot tell a newer file +as the machine's own, and a declaration carries no order, so the node-engine cannot tell a newer file from an older one ([issue 107](../../04-ISSUES/107-a-declaration-carries-no-order/00-report.md)). Rescue on an enrolled node is `reconcile`, which re-applies what the mesh last said, previewed; `apply FILE` is for a machine before enrolment. A `--rescue` that applies a hand-written file to an @@ -482,8 +480,8 @@ one binary that has always been the same binary. Two cases, and they are genuinely different. -**Graceful.** The controller sends a final declaration that names nothing. The host removes -what it owns by the table above, reports, and drops its identity. The machine keeps the host +**Graceful.** The controller sends a final declaration that names nothing. The node-engine removes +what it owns by the table above, reports, and drops its identity. The machine keeps the node-engine installed and is back to `hosted`. Nothing is left behind that anybody has to remember. **The node is gone.** Stolen, dead, or simply unreachable. The mesh cannot tell it anything, and @@ -491,7 +489,7 @@ by [ADR 0004](../../02-DECISIONS/0004-a-node-and-how-it-joins.md) it will go on its last declaration **forever**. That is the honest consequence of making disconnection ordinary, and the answer is not to make -the host expire. It is that **the node holds nothing that outlives revocation**: its identity is +the node-engine expire. It is that **the node holds nothing that outlives revocation**: its identity is its own, and every grant it holds is a per-node credential at the provider ([ADR 0004](../../02-DECISIONS/0004-a-node-and-how-it-joins.md), [ADR 0008](../../02-DECISIONS/0008-a-context-owns-its-store.md)). Revoking is done at the @@ -507,12 +505,12 @@ switch a machine off, which it cannot and should not be able to. Worth its own section because the failure is quiet. -If `/var/lib/mesh-host/state.json` is lost — a reinstall, a replaced disk — the host loses +If `/var/lib/mesh-host/state.json` is lost — a reinstall, a replaced disk — the node-engine loses **its record of what it owns**, not its ability to work. It re-enrols, receives the declaration again, and re-applies it. **Without help, what does not come back is removal.** Resources applied under an older -declaration, whose record is gone, become unowned: the host will not touch them, because it +declaration, whose record is gone, become unowned: the node-engine will not touch them, because it never touches what it did not create. They would sit there, unmanaged, indefinitely. **So the mesh keeps a copy of what each node reports it owns**, refreshed on every apply report, @@ -521,9 +519,9 @@ remains locally authoritative for *operating*; the copy exists only for this. --- -## Upgrading the host +## Upgrading the node-engine -The host is delivered like anything else +The node-engine is delivered like anything else ([ADR 0010](../../02-DECISIONS/0010-delivery.md)), and this is worth walking through because tier 0 looks like it should be special and is not. @@ -541,7 +539,7 @@ push to mesh-host **Compared with today.** The current pipeline's third silo runs *once per node* and sends each one a command to install and start. That is where the as-is records a package install that 404ed while the job went green. Here deploy is **one write** — the declaration changes — and the -installing is the host's ordinary work, which reads back before it records anything. +installing is the node-engine's ordinary work, which reads back before it records anything. **The repository is reachable because a declaration made it so.** A `file` resource writes the package manager's configuration pointing at the mesh's repository; a `package` resource names @@ -565,9 +563,9 @@ the test of whether this is really uniform. **Step 2 is the one to insist on.** A package can install a binary that does not execute here — wrong architecture, a libc that is not present. Running it once before committing to a restart turns "the node never came back" into "the apply failed and said why". It is the same read-back -rule the rest of the host already follows, applied to the one resource that is the host. +rule the rest of the node-engine already follows, applied to the one resource that is the node-engine. -**The host never asks the service manager to restart it.** That is the host stopping itself +**The node-engine never asks the service manager to restart it.** That is the node-engine stopping itself part-way through an apply. It stops by finishing. **A fleet upgrades over an interval, not at an instant**, because each node restarts when its @@ -577,7 +575,7 @@ installed — otherwise the mesh believes an upgrade landed at step 1. **A version that crashes on start rolls itself back** ([ADR 0005](../../02-DECISIONS/0005-the-node-host.md)). -What the init starts is not the host but a **launcher**, and the launcher is where the policy +What the init starts is not the node-engine but a **launcher**, and the launcher is where the policy lives: ``` @@ -589,8 +587,8 @@ init ──► nox-mesh-host-launch ──► nox-mesh-host └─ otherwise start the host ``` -It reinstalls the version recorded in `known-good`, which the host wrote the last time it -completed a reconcile — and the host clears the attempt counter at the same moment, for the same +It reinstalls the version recorded in `known-good`, which the node-engine wrote the last time it +completed a reconcile — and the node-engine clears the attempt counter at the same moment, for the same reason. **The launcher rather than the init's own features**, because this is the one thing that must @@ -624,7 +622,7 @@ mesh-controller token issue --node workstation # this machine is that node aga mesh-controller token issue --new # a machine the mesh has not seen ``` -The host does not need to know which it is. It presents a token and receives an identity; what +The node-engine does not need to know which it is. It presents a token and receives an identity; what that identity is bound to was decided when the token was made. **Issuing a re-enrolment token revokes the previous identity for that node**, and that is not @@ -634,7 +632,7 @@ credentials still valid — the case ### Protecting the store -**The host reports what it owns, and the mesh keeps the last report.** +**The node-engine reports what it owns, and the mesh keeps the last report.** The store stays locally authoritative — a node operates from its own copy and needs nothing to do so ([ADR 0004](../../02-DECISIONS/0004-a-node-and-how-it-joins.md)). What changes is that @@ -644,7 +642,7 @@ So a node that loses its state file re-enrols, receives both the declaration *an what it previously owned, and can then remove what is no longer declared. The orphans that used to be permanently stranded are recoverable. -**This is a backup, never a source.** The host never reads it to decide anything; it is handed +**This is a backup, never a source.** The node-engine never reads it to decide anything; it is handed back only on a store rebuild, and a node that disagrees with it wins, because the node is the one that can see the machine. @@ -656,7 +654,7 @@ because they answer different questions — the mesh's is *have I heard from it* noticed at all. **No threshold and no alarm.** A laptop switched off for three weeks is doing nothing wrong, and -a mesh that alerted on it would train people to ignore the alert. It is a **reported fact** — +a mesh that raised a condition for it would train people to ignore the condition. It is a **reported fact** — `last seen 4 days ago` beside every node — and what counts as too long is a judgement for whoever is looking, not a constant in the design. @@ -719,7 +717,7 @@ the same command against a mesh that is one machine old. - ~~**Automatic rollback of a bad host version.**~~ **Resolved** by [ADR 0005](../../02-DECISIONS/0005-the-node-host.md): a launcher - counts failed starts and rolls back — shipped by the package, not the host binary, because a + counts failed starts and rolls back — shipped by the package, not the node-engine binary, because a binary that will not start cannot recover itself. It rolls back once; a second failure means the machine is the problem, not the binary. - **How a previous declaration is retained and chosen**, which is what rollback of anything else @@ -752,7 +750,7 @@ must never be indistinguishable from a failure to answer* is the rule this whole on. It recovered on its own, which is why this was a quality gap rather than a fault. It was still the machine waiting to be told something it already knew. -**So the machine says so.** Waking, and changing network, both rouse the host. +**So the machine says so.** Waking, and changing network, both rouse the node-engine. | | | |---|---| diff --git a/03-DESIGN/01-to-be/10-delivery.md b/03-DESIGN/01-to-be/10-delivery.md index 03020489..20c60006 100644 --- a/03-DESIGN/01-to-be/10-delivery.md +++ b/03-DESIGN/01-to-be/10-delivery.md @@ -36,7 +36,7 @@ them, and only edges can be queried or kept true automatically. **When several modules always change together**, that means they share an *authority* — one place that decides for all of them. It does not mean they should be one artifact. Connectivity is the -worked example: one context decides the overlay, names, routes, filtering and certificates, and +worked example: one context decides the private network, names, routes, filtering and certificates, and `wireguard`, the resolver, the proxy and the firewall remain four modules, because they are deployed to different sets of nodes. @@ -93,12 +93,12 @@ what has been built from it ─┘ An event makes it fast; nothing makes it necessary — so a missed webhook costs latency and cannot cost correctness. -That is the same shape the host uses on a machine, one layer up: +That is the same shape the node-engine uses on a machine, one layer up: | | reconciles | against | |---|---|---| | the controller | artifacts | source | -| the host | machine state | declarations | +| the node-engine | machine state | declarations | **There is no pipeline as a state machine.** No stage list something can be omitted from, and no run to lose. *What* is built stays decided by this comparison. *One commit's journey* through the mesh is a @@ -183,9 +183,8 @@ Not aspirations — things without which the above does not work: same digest, a cascade would stop at the first module whose output did not move. Without them, one core-library commit redeploys the fleet with no behavioural change. - **How a module publishes its own types**, which differs per language. -- **How the controller upgrades itself.** It declares its own new version and the host applies - it — but if the new one is broken, the thing that would fix it is the thing that is broken. The - host has a launcher for exactly this; the controller has nothing. +- **How the controller upgrades itself.** It declares its own new version and the node-engine applies + it — but if the new one is broken, the thing that would fix it is the thing that is broken. The node-engine has a launcher for exactly this; the controller has nothing. ## What "behind" means, and what it used to mean @@ -226,7 +225,7 @@ knows something the person reading the status does not. re-applies on its interval and reports each time, so a resource nothing can ever apply arrives as the same failure over and over, at a fresh time each time. The mesh keeps, beside the last report, when the current failure began and how many reports in a row have said it — the same resources by -id, whatever the words; three make the machine stuck, and `status` says so beside the failure. The host keeps trying — stuck is what the mesh +id, whatever the words; three make the machine stuck, and `status` says so beside the failure. The node-engine keeps trying — stuck is what the mesh knows, not what the machine is told. *How it is checked:* an inventory test counts three identical reports, a different one, and a clean apply; the status test asserts the word appears. diff --git a/03-DESIGN/01-to-be/11-a-board.md b/03-DESIGN/01-to-be/11-a-board.md index 2308d40b..b6ea498a 100644 --- a/03-DESIGN/01-to-be/11-a-board.md +++ b/03-DESIGN/01-to-be/11-a-board.md @@ -57,7 +57,7 @@ for the most consequential fact about this component.* **The board is published on a public name.** Not reachable only over the private network — on the internet, behind the reverse proxy, like any other published workload. -**So its login is a perimeter, not defence in depth.** A board on the overlay alone would sit +**So its login is a perimeter, not defence in depth.** A board on the private network alone would sit inside the boundary that [ADR 0004](../../02-DECISIONS/0004-a-node-and-how-it-joins.md) already calls the security boundary, and a login there would guard a room whose door is inside the building. This one faces everybody. @@ -137,6 +137,6 @@ board to avoid asking. They are also the only text on the page that nobody in th wrote, which is why the escaping is a test rather than an assumption. *Checked by giving a machine a declaration it cannot apply and requiring the page to name that -machine, say `failed` rather than `error`, and quote what the host said — then by comparing the +machine, say `failed` rather than `error`, and quote what the node-engine said — then by comparing the page's own JSON against the command's, because two answers to "which machine is broken" would be worse than either alone.* diff --git a/03-DESIGN/01-to-be/12-a-module-repository.md b/03-DESIGN/01-to-be/12-a-module-repository.md index 7b150461..45d03cbb 100644 --- a/03-DESIGN/01-to-be/12-a-module-repository.md +++ b/03-DESIGN/01-to-be/12-a-module-repository.md @@ -109,7 +109,7 @@ would be pinning a value nobody could have checked. The built manifest is derive of *which commit it was derived from* is what makes "is this current?" answerable without building it again. -The word `artifact` never reaches a machine. The host's decoder is strict and would refuse it, at +The word `artifact` never reaches a machine. The node-engine's decoder is strict and would refuse it, at the worst possible moment. ## The builder runs on a node @@ -129,17 +129,16 @@ else the controller sends a node is *what you should be*, reconciled forever. A once and is finished; as a declaration it would either rebuild on every reconcile or carry "and I already did this" — state about an event rather than about a machine. -So it has its own queue, and the answer comes back correlated. **One queue**, so several build -machines share the work and each request is done exactly once, which a routing key per machine +So it has its own queue, and the answer comes back correlated. **One queue**, so several builders share the work and each request is done exactly once, which a routing key per machine would not give. -**A build machine has its own credential**, and it is not a node's. It may read the build queue +**A builder has its own credential**, and it is not a node's. It may read the build queue and write to the mesh exchange, and that is all — a node's queue carries that node's declarations, -and a build machine has no business reading them. +and a builder has no business reading them. **The answer goes through the exchange, never the default one.** Permission on the default exchange is granted per *exchange*, not per queue, so anything allowed to use it can publish into -any node's queue. That is the privilege a build machine most obviously should not have. So an +any node's queue. That is the privilege a builder most obviously should not have. So an asker binds its own reply queue to the same routing key and filters by correlation; every asker sees every result, which is the price of the builder never needing that permission. @@ -150,7 +149,7 @@ Three properties of the builder that are decisions: - **one build at a time.** Five at once against one runtime finishes all five slower than it would have finished the first, and the queue is what shares work between machines - **a failure is a result.** A build that fails silently is indistinguishable from a builder that - is not running, and those want completely different responses — the same rule the host follows + is not running, and those want completely different responses — the same rule the node-engine follows about a service that does not exist ### And it is a module the mesh assigns @@ -158,7 +157,7 @@ Three properties of the builder that are decisions: *2026-08-31. Written after `builder issue --node`, which is the part that makes the sentence "holding its own credential" true rather than aspirational.* -A build machine is a machine that runs the builder, and there is exactly one honest way to say +A builder is what holds the build seat on a node, and there is exactly one honest way to say which machines those are: **assign it**. So the builder is a module like any other — an image, a container, a working directory, and a claim so a machine does not end up running two. @@ -190,7 +189,7 @@ a path other than the thing being trusted. **A builder that is a module cannot see the machine's filesystem.** It runs in a container, so a local path exists for the machine and not for it. That is not a limitation to work around — it is -the arrangement working: a build machine shares the runtime it was given rather than the machine +the arrangement working: a builder shares the runtime it was given rather than the machine it sits on. **A module is cloned from the forge over a URL**, and "build this directory" is a convenience for a builder somebody started by hand. @@ -355,13 +354,13 @@ file what its consumers asked for **The provisioner watches** rather than being invoked. That is what lets it be a module: run once, it needs something to run it after every declaration — a timer, or a unit wired to a file. -Watching, it is an ordinary long-running service the host already supervises. It polls rather than -watching the filesystem, because the host writes atomically: the file is replaced, so a watch on +Watching, it is an ordinary long-running service the node-engine already supervises. It polls rather than +watching the filesystem, because the node-engine writes atomically: the file is replaced, so a watch on the path stops seeing anything after the first replacement, and a watcher that silently stops working is worse than a poll. -Writing it found one thing wrong, and it was the manifest rather than the host: a container -declared `restart-on`, which is a service field, and the host refused it by name. **It is right +Writing it found one thing wrong, and it was the manifest rather than the node-engine: a container +declared `restart-on`, which is a service field, and the node-engine refused it by name. **It is right to.** A container whose own definition changes is recreated, and a file it mounts is read by the process inside, which is that image's business. @@ -379,7 +378,7 @@ a URL and a digest, and neither says what served it. ### And the mesh runs it -*2026-08-31.* Which registry is a **provision**, mesh-scoped: a build machine requires +*2026-08-31.* Which registry is a **provision**, mesh-scoped: a builder requires `artifact-store` and is told where it is, the same way an application is told where its database is. Nothing is configured with an address. diff --git a/03-DESIGN/01-to-be/13-credentials-and-their-rotation.md b/03-DESIGN/01-to-be/13-credentials-and-their-rotation.md index ffed0830..20049e77 100644 --- a/03-DESIGN/01-to-be/13-credentials-and-their-rotation.md +++ b/03-DESIGN/01-to-be/13-credentials-and-their-rotation.md @@ -73,7 +73,7 @@ to say it exists. `status` names who is still behind. The mesh generated the password, sealed it to the machine that must accept it, and **discarded the plaintext** — so it cannot tell a database to start accepting it. Something on that machine reads -what the host wrote and makes it true. +what the node-engine wrote and makes it true. That something is part of the module, not part of the controller. **The controller decides and never touches a machine; a provisioner runs on the machine and touches it.** Two files, because @@ -98,7 +98,7 @@ The care above ends at the container's door if the module then hands the value t an environment variable: `docker inspect` prints it, and the process's `/proc` entry holds it for anything on the machine that can talk to the runtime. So a secret reaches a process **as a file** ([ADR 0086](../../02-DECISIONS/0086-a-secret-reaches-a-process-as-a-file.md)): the module mounts -the file the host wrote and points the program at it, and the mesh's own programs take a `_FILE` +the file the node-engine wrote and points the program at it, and the mesh's own programs take a `_FILE` twin for every variable that carries a credential. Software that reads only its environment is not forbidden; it is **declared**, on the container, with a reason, so the manifest says which secrets are exposed that way. **Checked** at composition: the catalogue engine refuses a @@ -148,10 +148,10 @@ and the operator, and sends the machine, so the module starts again on it. A sec is refused with the word to write, because a credential rotated under software that never reads it again is the fault of issue 179 made deliberately; an applied one is refused until the staged form is built. `rotate` is a verb on the controller's seat with -both shapes, so the console asks for either. A provider that shares its one credential with every +both shapes, so the mesh MCP server asks for either. A provider that shares its one credential with every consumer ([ADR 0158](../../02-DECISIONS/0158-a-provider-with-one-credential-shares-it-with-every-consumer.md)) rotates the same way, with every holder's copy remade and every holding machine sent together. *How it is checked:* the tests named in issue 180, and a -live rotation through the console of a secret a module reads at start. +live rotation through the mesh MCP server of a secret a module reads at start. ### A value given by hand @@ -165,7 +165,7 @@ reading the old value. So an own secret also says who may make its value: by def For a secret the mesh may make — read at start, not issued outside: - `secret rotate` replaces a given value as it replaces a made one, and records it as made. It may say - why, through the console's `rotate` as well, and the why goes to the hand-act log. + why, through the mesh MCP server's `rotate` as well, and the why goes to the hand-act log. - **A value given through `secret accept` lives only until the module's first good start under the mesh.** The signal is the machine's clean report — everything applied, nothing failed or refused — of the declaration it was last sent, when that declaration was sent after the value was given; on an @@ -178,4 +178,4 @@ account it issued), and a value given before 0228 stay as given. The first is re named and its age; the last is replaced when a person asks `secret rotate`. A pair credential an operator delivers is unchanged ([ADR 0092](../../02-DECISIONS/0092-an-operator-delivers-a-pair-credential.md)). *How it is checked:* the tests ADR 0228 names, and a live `rotate` of a given at-start secret through -the console. +the mesh MCP server. diff --git a/03-DESIGN/01-to-be/15-the-agent-session.md b/03-DESIGN/01-to-be/15-the-agent-session.md index 5fc216f7..ef434ef5 100644 --- a/03-DESIGN/01-to-be/15-the-agent-session.md +++ b/03-DESIGN/01-to-be/15-the-agent-session.md @@ -50,9 +50,9 @@ session as a distinct kind — would mean two implementations of one mechanism, costs: two things nearly the same, built twice, until neither word means one thing. **The root is delivered as declared state, not carried by the session.** It is files on a machine, -which is precisely what the host applies +which is precisely what the node-engine applies ([ADR 0005](../../02-DECISIONS/0005-the-node-host.md)). A session's context therefore changes the -way anything else changes — the mesh declares it, the host writes it — and there is no second +way anything else changes — the mesh declares it, the node-engine writes it — and there is no second mechanism for shipping an engram. **Changing the engram is changing a file.** So it is versioned, reviewable, and rolled back like @@ -86,7 +86,7 @@ business and follows from its engram rather than from a message format: it may s know, or simply ask. A person relaying a question makes the same choice. **A switched-off session answers.** The queue is still read and the state is the reply, with no -model involved. This is the same rule the host follows about a service that does not exist, and +model involved. This is the same rule the node-engine follows about a service that does not exist, and it is the rule this repository has now paid for three times: **absence must never be indistinguishable from a failure to answer** ([005](../../04-ISSUES/005-pipeline-test-harness-unbuildable/00-report.md), @@ -131,7 +131,7 @@ and the mesh's recollection of a fortnight of questions would be indistinguishab node's own — which is the collision ADR 0026 exists to avoid, arriving through the back door. **The root holds two kinds of thing, and confusing them destroys the memory.** The engram and the -tools are **declared**: the mesh says what they are and the host writes them, so editing one on +tools are **declared**: the mesh says what they are and the node-engine writes them, so editing one on the machine survives until the next heartbeat and no longer ([ADR 0011](../../02-DECISIONS/0011-managed-files-are-generated-never-edited.md)). The memory is **written by the session itself** and is declared by nobody — the mesh does not get to say what a diff --git a/03-DESIGN/01-to-be/16-module-coverage.md b/03-DESIGN/01-to-be/16-module-coverage.md index 43b26d5d..d0e14d96 100644 --- a/03-DESIGN/01-to-be/16-module-coverage.md +++ b/03-DESIGN/01-to-be/16-module-coverage.md @@ -138,12 +138,12 @@ shape of a machine. **A consumer could not build a connection string** ([`023`](../../04-ISSUES/023-a-consumer-cannot-build-a-connection-string/00-report.md), fixed). It -had its password in the right shape and the host, port and user name were out of reach: the user +had its password in the right shape and the address, port and user name were out of reach: the user name was invented by the provisioner and recorded nowhere, and the bound values sat in a JSON document that an application reading `KEY=value` cannot use. The asymmetry was backwards, which is what made it worth stating. **The secret is the hard case** — -the mesh must not be able to read it — and the secret was the part that already arrived. The host +the mesh must not be able to read it — and the secret was the part that already arrived. The node-engine and port are ordinary facts held in the clear, and they were the ones stuck. Both halves came from the same thing: the mesh knew something and did not say it. @@ -169,8 +169,8 @@ instruction. It does not. So a registry somebody runs for their own images is th the one the mesh runs for its own: it offers a place to push, and claims that role once per machine. -**A rule was enforced only at the far end.** A module may not declare an action, and the host +**A rule was enforced only at the far end.** A module may not declare an action, and the node-engine refused one correctly — but the controller accepted it into the catalogue, resolved it and pushed it, so the refusal arrived on a machine with nothing tying it back to the manifest. The rule held; it was just unusable, which is the same shape as the network shape that cost five -failing tests before anyone read the host's log. It is now refused where it is written. +failing tests before anyone read the node-engine's log. It is now refused where it is written. diff --git a/03-DESIGN/01-to-be/17-raising-a-mesh.md b/03-DESIGN/01-to-be/17-raising-a-mesh.md index c7c7e59e..86bb8667 100644 --- a/03-DESIGN/01-to-be/17-raising-a-mesh.md +++ b/03-DESIGN/01-to-be/17-raising-a-mesh.md @@ -34,7 +34,7 @@ rules cannot all hold at once, and it is resolved by a pivot ([ADR 0067](../../02-DECISIONS/0067-genesis-is-a-pivot.md)). **Joining** happens on every machine after the first, and it is ordinary. A mesh exists, so it can -be asked for a token and told what the machine should be. Joining installs the host and nothing +be asked for a token and told what the machine should be. Joining installs the node-engine and nothing else: no temporary anything, no foundation raised by hand, no registry. Confusing the two is what produced a procedure that only ever worked in a fixture. A bed that @@ -65,7 +65,7 @@ the thing that does the fetching, so that is what is carried; everything else is It proceeds in one direction, and every step is safe to run again. **First it refuses to start if the machine is not ready.** A container runtime, the ability to -write where it must write, the host binary where it expects it — and a repository and a commit to +write where it must write, the node-engine binary where it expects it — and a repository and a commit to build from, because an installer told nothing would raise a store and a broker and then have nothing to raise a controller from. A machine that is not ready is told what is missing rather than half-changed. @@ -169,7 +169,7 @@ What remains after *that* belongs to somebody else: adding machines, and decidin ## Joining -A machine joins with the host binary and a token. It does not raise a foundation, does not install a +A machine joins with the node-engine binary and a token. It does not raise a foundation, does not install a registry, and is never enrolled twice. The mesh already knows how to tell a machine what to be; joining is the point at which a machine starts listening. A machine in use joins **adopted**: the token says so, the operator has stopped the predecessor's control on it first, and from then on it @@ -231,7 +231,7 @@ the SDK inside `docker build`, which is slow and names a branch head rather than | Rule | Checked by | |---|---| | Genesis works on a machine that is not the lab | The bed raises its first machine by running the installer, not around it. A bed that stops doing so fails its own acceptance check. | -| The other machines join, and are not re-raised | The bed gives them the host binary and a token only. A second enrolment of the first machine is a failure, not a no-op. | +| The other machines join, and are not re-raised | The bed gives them the node-engine binary and a token only. A second enrolment of the first machine is a failure, not a no-op. | | Every step may be run again | The installer is re-run against a raised machine and must change nothing and report why. | | An image is named exactly | A machine refuses a bundle naming an image by tag. The refusal is exercised, not assumed. | | The installer is what installed this | **Nothing.** See above. | diff --git a/03-DESIGN/01-to-be/18-building-a-module.md b/03-DESIGN/01-to-be/18-building-a-module.md index d7da94a7..36c361d4 100644 --- a/03-DESIGN/01-to-be/18-building-a-module.md +++ b/03-DESIGN/01-to-be/18-building-a-module.md @@ -254,7 +254,7 @@ counts as a copy and what as a base. A `file` may also say `create-once`: written when absent, left alone when present, reported as kept — a seed a program then owns ([ADR 0087](../../02-DECISIONS/0087-a-seeded-file-is-created-once.md)). -Checked by the host's apply tests and by the vault bed, which grows into one and pushes again. +Checked by the node-engine's apply tests and by the vault bed, which grows into one and pushes again. **The two at the bottom are the interesting rows.** `action` is refused outright: the link may not carry a command, so a module needing something done ships a program that reconciles — which is what @@ -300,17 +300,17 @@ was read as the end (fixed in the controller the same day). *2026-10-01 — [ADR 0157](../../02-DECISIONS/0157-a-build-says-what-it-does-on-the-bus-as-it-happens.md).* -A build machine narrates every build on the bus as the role it holds: `started` when it takes the +A builder narrates every build on the bus as the role it holds: `started` when it takes the work, one `log.` event per line — every command it runs with its duration, every step of the recipe, and on failure the command's own output, line by line — and `built` for the outcome as -before. The same lines still go to the machine's standard error, so a build machine with nobody +before. The same lines still go to the machine's standard error, so a builder with nobody listening is as readable as it was; a listener reads the same lines, live, from anywhere on the mesh. One build is one subject. A reader follows it by subscribing that subject and nothing else, and the events stream keeps it for a week, so `builds --log ` — on the command line and as the -controller's seat verb through the console — reads it back afterwards. `builds` lists every build's +controller's seat verb through the mesh MCP server — reads it back afterwards. `builds` lists every build's id beside it, and `build` says the id it asked with. The mesh keeps no second copy: the stream is the -log. The console's `build` tool asks and answers at once with the id; the outcome is taken in — the +log. The mesh MCP server's `build` tool asks and answers at once with the id; the outcome is taken in — the build recorded, the module registered with its source — by whoever hears it, the waiting command or the daemon following the role's event, so a build nobody waited for still reaches the catalogue ([issue 176](../../04-ISSUES/176-the-consoles-build-tool-neither-waits-nor-registers/00-report.md)). A viewer of builds, when one is built, is a subscriber over these subjects and the outcome; the @@ -319,7 +319,7 @@ builder needs nothing more for it. *How it is checked:* the holder's grant is exactly `started`, `built` and `log.*` (broker test); a build's lines reach a reader of its subject in order and the stream holds them afterwards (link test against a real server); the seat verb with an id reads the log (controller test); and, live, a build -after the roll-out read line by line through the console. +after the roll-out read line by line through the mesh MCP server. ## The store keeps what the records name @@ -380,14 +380,14 @@ than retried for ever. Live: the store's size before and after the first nightly [ADR 0142](../../02-DECISIONS/0142-the-mesh-delivers-its-own-components-as-binaries.md).* The toolchain list was typescript and python, and only typescript had a base module in the catalogue. -Meanwhile the control plane — written in the language this project is mostly written in — was built as +Meanwhile the controller — written in the language this project is mostly written in — was built as an image from a hand-written Dockerfile, which is the per-repository incantation this whole mechanism exists to abolish. So the list gains Go, with a base module providing the compiler exactly as typescript has one. The obligation the list's own comment warns about — an SDK carrying the broker client, the event envelope and tool serving — attaches to a **module** written in a language, not to the language being -compilable. The mesh's own components are not modules in that sense; the host is what applies modules. +compilable. The mesh's own components are not modules in that sense; the node-engine is what applies modules. **And an artifact says what it targets.** A compiled binary is per operating system, pinned at link time, and a toolchain deliberately accepts nothing from the module — anything a module could override @@ -402,7 +402,7 @@ be called. *2026-10-05 — [ADR 0219](../../02-DECISIONS/0219-the-build-queue-is-controlled-through-the-controller-and-the-build-seat.md).* -What is asked of the build seat can be seen and controlled from the console. The controller holds the +What is asked of the build seat can be seen and controlled from the mesh MCP server. The controller holds the queue, and each machine's build agent holds its own process. - **The controller's verbs:** diff --git a/03-DESIGN/01-to-be/19-the-module-protocol.md b/03-DESIGN/01-to-be/19-the-module-protocol.md index 5b96b7b4..a3d1c33c 100644 --- a/03-DESIGN/01-to-be/19-the-module-protocol.md +++ b/03-DESIGN/01-to-be/19-the-module-protocol.md @@ -210,10 +210,10 @@ A module's tools are its operator-facing surface. [Issue 049](../../04-ISSUES/049-a-module-can-serve-tools-and-nothing-can-call-them/00-report.md) recorded the old limit — a scoped account could not declare the reply queue a caller needs — and [ADR 0095](../../02-DECISIONS/0095-the-control-plane-is-the-way-to-ask-a-module.md) routed - every ask through the control plane because of it. **That constraint is gone**, and each + every ask through the controller because of it. **That constraint is gone**, and each account's own inbox prefix replaces it. - ADR 0095 is not thereby reversed: the control plane remains *a* way to ask, and a person asking + ADR 0095 is not thereby reversed: the controller remains *a* way to ask, and a person asking a module should still go through it. What changes is that "a module-to-module call, if one is wanted, is a later decision" is no longer a question about *capability*. It is a policy question, and the answer the mesh already has is `uses`: a module declares the seat it calls, diff --git a/03-DESIGN/01-to-be/20-writing-a-module.md b/03-DESIGN/01-to-be/20-writing-a-module.md index e8e3cd33..994e1145 100644 --- a/03-DESIGN/01-to-be/20-writing-a-module.md +++ b/03-DESIGN/01-to-be/20-writing-a-module.md @@ -130,7 +130,7 @@ ingest/ingest.py emit("module.showcase.ingested", …) → events, emitti You do not write a Dockerfile, a unit file, or a port number. The builder picks the toolchain from the language, compiles each artifact alone, and publishes it. The controller assigns the machine -and the ports; the host writes the units. +and the ports; the node-engine writes the units. ### What it costs you to use four languages @@ -207,7 +207,7 @@ A `run-once` step may itself name what it reads under `restart-on`; for a step t again when the provider moved. The service that consumes what the step made names the step, so it is recreated with the new fact ([ADR 0099](../../02-DECISIONS/0099-a-step-that-runs-once-names-what-it-reads.md)). *How it is -checked:* the host's unit tests run a step again when its named file changed and not otherwise, +checked:* the node-engine's unit tests run a step again when its named file changed and not otherwise, and recreate a container naming a step after the step ran. ## A recurring step may hold its own module's containers still @@ -222,14 +222,14 @@ mesh inherited a store that has never collected anything, because the predecesso shell script and a script beside a module is not a resource in it. A scheduled step may name `while-stopped`: resource ids of **its own module's** containers, which -the host stops before the run and starts again after it, in the reverse order, **whatever the step +the node-engine stops before the run and starts again after it, in the reverse order, **whatever the step did**. Three boundaries, each refused where it can be seen earliest — its own module's containers only, because a module that could quiesce a neighbour could stop the mesh; scheduled steps only, because at apply the declaration is applied in order and a step already gates what follows, so a one-time offline job says *before* rather than *instead of*; and restoring that is not conditional on anything, because the only real risk of the field is a window that never closes. -*How it is checked:* the host's unit tests assert stop–run–start in that order, the restart after a +*How it is checked:* the node-engine's unit tests assert stop–run–start in that order, the restart after a step that **failed**, the reverse order for several containers, and a service left down said loudly. The controller refuses, from the definition alone, a window with no schedule, one on a run-once step, one naming a container the module does not declare, and one naming itself. diff --git a/03-DESIGN/01-to-be/21-the-installation-in-full.md b/03-DESIGN/01-to-be/21-the-installation-in-full.md index 8313fd3b..a1d788f8 100644 --- a/03-DESIGN/01-to-be/21-the-installation-in-full.md +++ b/03-DESIGN/01-to-be/21-the-installation-in-full.md @@ -28,7 +28,7 @@ happens*, in order, with each step's name as the installer prints it. | | why | |---|---| | a container runtime | the foundation is containers, and the installer refuses without one | -| the host binary, where the installer expects it | it is what the machine becomes | +| the node-engine binary, where the installer expects it | it is what the machine becomes | | a repository and a commit to build from | the installer carries a builder, not a controller, so it must be told what to make ([ADR 0073](../../02-DECISIONS/0073-the-installer-carries-a-builder.md)) | | a way out to the internet | the store, the broker and the registry are pulled from it | | a reachable git host, and a commit it serves | what is cloned is the trust anchor for everything this mesh will ever run ([ADR 0071](../../02-DECISIONS/0071-where-genesis-gets-its-source.md)) | @@ -61,8 +61,7 @@ and waits — which looks exactly like a builder with no work. ## Phase two — a mesh of one becomes a mesh that works -**Genesis ends with a mesh that runs, which is not the same as a mesh that works.** It has a control -plane, a store, a broker, a registry and a builder. It holds no module graph, has no private +**Genesis ends with a mesh that runs, which is not the same as a mesh that works.** It has a controller, a store, a broker, a registry and a builder. It holds no module graph, has no private network, no packet filter, and cannot resolve a name. Calling that "installed" is what let the catalogue be missing from a test for weeks without anything complaining. @@ -108,7 +107,7 @@ At step 13 nothing has installed one, so the first build of the shared base reso other way — today by a git URL, which is [issue 053](../../04-ISSUES/053-the-sdk-is-pinned-twice-and-the-two-disagree/00-report.md). -**The installer does not install the host's unit, though one exists.** `mesh-host/packaging/` ships +**The installer does not install the node-engine's unit, though one exists.** `mesh-host/packaging/` ships `nox-mesh-host.service` and two companions; the installer declines to place them because a unit file is a packaging decision. So an install that does nothing further leaves a machine whose containers come back after a reboot and whose agent does not — it runs the right things and can no longer be @@ -181,8 +180,8 @@ it. The foundation's broker and the `lavinmq` module collapse the same way. Adopting the store and the broker as modules is [issue 051](../../04-ISSUES/051-the-mesh-cannot-update-what-it-depends-on/00-report.md), and it is the only part of this that has not been designed. The two hard parts: -- **upgrading a store the controller is reading from** — a rollout where the thing being replaced - is the thing holding the record of the rollout +- **upgrading a store the controller is reading from** — an upgrade where the thing being replaced + is the thing holding the record of the upgrade - **upgrading a broker over the broker** — the instruction arrives on what it replaces, so the machine must finish without being able to report progress diff --git a/03-DESIGN/01-to-be/25-the-bus-on-nats.md b/03-DESIGN/01-to-be/25-the-bus-on-nats.md index ea003c52..72d0f1f9 100644 --- a/03-DESIGN/01-to-be/25-the-bus-on-nats.md +++ b/03-DESIGN/01-to-be/25-the-bus-on-nats.md @@ -47,7 +47,7 @@ mesh's own state lives, and where what a module may say is decided by what it de | **control** — a node's report, a build's outcome, an enrolment | job | nothing lost while the store restarts; retried; in order per node | | **heartbeat** — a node saying it is alive | fire and forget | none; a lost one is the next one | | **declarations** — the controller tells a node what to be | state | the node gets the newest; a stale one is never applied | -| **builds** — work for the build machine | job | at least once, one worker at a time | +| **builds** — work for the builder | job | at least once, one worker at a time | | **events** — a module says something happened | 1:many | delivered to every consumer that declared it; dead-lettered when it cannot be | | **tools** — a module or a person asks another's tool | request/reply | one answer, from one server, or a timeout | | **work to a role** — a module submits to a capability without knowing who provides it | job | exactly one holder does it; it queues while nobody does | @@ -104,7 +104,7 @@ for a module's and a seat's tools are the shapes the controller *issues*, not ru Every assignment is published a membership — what it serves and where, in which queue, its seat verbs, where its events land, what it may reach — on `mesh.assignment..`, kept last per subject like a declaration, republished when the assignment's facts change. The runtime serves exactly that -list; the account's grant is the same membership read the other way; the console's listing carries each +list; the account's grant is the same membership read the other way; the mesh MCP server's listing carries each tool's subject. The one rule a runtime keeps is the membership's own subject, from the two names in its credential. @@ -167,7 +167,7 @@ Core NATS is at-most-once. Everything the mesh must not lose lives in a JetStrea |---|---|---|---| | CONTROL | `mesh.control.>` except `alive` (a build's outcome moved to its seat, ADR 0121) | work queue, one consumer (the controller), explicit ack | the store-window guarantee ([ADR 0083](../../02-DECISIONS/0083-one-push-leaves-the-mesh-consistent.md)): the controller `nak`s with a delay while its store is away and the message is redelivered; nothing is dropped | | NODES | `mesh.node.>` | last per subject | one declaration per node, always the newest | -| EVENTS | `mesh.mod.*.event.>` and `mesh.seat.*.event.>` | limits (age, size), durable consumer per subscribing module | a subscriber that was down catches up; after `max-deliver` attempts the advisory feeds `mesh.events.dead` (its own small stream). *2026-10-01:* a build's whole log is here too, as the build-machine seat's `log.` events ([ADR 0157](../../02-DECISIONS/0157-a-build-says-what-it-does-on-the-bus-as-it-happens.md)) — one subject per build, a week of retention, read back by `builds --log ` with a consumer that is gone when the reading is done | +| EVENTS | `mesh.mod.*.event.>` and `mesh.seat.*.event.>` | limits (age, size), durable consumer per subscribing module | a subscriber that was down catches up; after `max-deliver` attempts the advisory feeds `mesh.events.dead` (its own small stream). *2026-10-01:* a build's whole log is here too, as the build seat's `log.` events ([ADR 0157](../../02-DECISIONS/0157-a-build-says-what-it-does-on-the-bus-as-it-happens.md)) — one subject per build, a week of retention, read back by `builds --log ` with a consumer that is gone when the reading is done | | `KV__` | `$KV._.>` | the newest value per key — as many past values as the owner declared — no age unless the owner declared one; a value at most 256 KiB, a bucket at most 64 MiB | a module's state (ADR 0201): one per name in a manifest's `state`, created from the catalogue on every raise, so it exists before its owner runs anywhere; kept when the module is unassigned, because what it holds is data | Tool calls and heartbeats stay on core NATS: a lost heartbeat is the next heartbeat; a lost tool @@ -296,7 +296,7 @@ resolved relative to the including file's own directory, so a server given one f for it underneath that directory and refuses to start. **Accounts are configuration, not API calls.** The controller composes the mesh's user list and its -permissions into a file the host declares. **How that file reaches the running server is §5's, +permissions into a file the node-engine declares. **How that file reaches the running server is §5's, not this one's** — revision, first review: an earlier draft said "reloads" and cited a precedent that does not apply to a container (see §5). No management API, no credential travelling through a management call, and the [issue 102](../../04-ISSUES/102-an-address-recorded-at-genesis-or-build-does-not-follow-the-nodes-ports/00-report.md) @@ -324,7 +324,7 @@ file the controller composes (accounts, permissions, TLS, JetStream). **How that file's changes reach the running server, corrected on revision.** First review: the earlier draft named `reload-on` as the mechanism, citing the container runtime's own trust file as precedent. `reload-on` is real, but it is a **service** field -([mesh-host declaration.go](https://git.novox.be/novox/mesh-host), `Service.ReloadOn` — +([`mesh-host` declaration.go](https://git.novox.be/novox/mesh-host), `Service.ReloadOn` — `docker.service` is reloaded via systemd, which is what the cited precedent actually does). A **container** resource has no reload field at all — only `restart-on`, and a container's `restart-on` is documented, exactly, to mean *recreate*. Declared as the earlier draft had it, @@ -334,7 +334,7 @@ connection dropped, every in-flight JetStream ack lost, mid-flight the moment a reassigned, or a person's access changes. For the one resource everything else depends on, that is not an edge case; it is the common case. -**The fix asks nothing new of the host.** `nats-server` already reloads its own configuration +**The fix asks nothing new of the node-engine.** `nats-server` already reloads its own configuration live on `SIGHUP` — accounts, permissions, everything in §4 — without dropping a connection; this is the server's own documented capability, not something built for the mesh. So the composed configuration file is mounted into a **directory** resource, not directly — a directory's contents @@ -342,8 +342,8 @@ are not compared for change the way [issue 103](../../04-ISSUES/103-a-container- fix made a directly-mounted file's content, so a rewritten file inside it is not, on its own, a reason to recreate the container. The image's own entrypoint watches that one file and sends `nats-server` its own process `SIGHUP` when it changes — self-contained, inside the module, the -same place `modules/gitea/token.ts` keeps its own state rather than asking the host to model it. -The host's only job is what it already does for any directory resource: keep the file's content +same place `modules/gitea/token.ts` keeps its own state rather than asking the node-engine to model it. +The node-engine's only job is what it already does for any directory resource: keep the file's content current. Nothing is declared as `reload-on` or `restart-on` for this resource at all. Its guard is the same rule as the deprecated broker's: the monitoring port is refused from anything but @@ -366,7 +366,7 @@ migrated, left to stop rather than moved — so the broker retires once nothing retirement *condition* and no end-date machinery: a provision with no consumers has its provider unassigned, which is the ordinary mechanism and is ADR 0127 being paid off rather than revised. What also goes with it is the tooling that reaches this installation's machines remotely, because the -predecessor's own mesh talks over that broker — so the rollout is driven from the node, or before the +predecessor's own mesh talks over that broker — so the switch-over is driven from the node, or before the broker stops. What is deprecated is AMQP as **the mesh's transport**, which is this whole document. The rule @@ -413,7 +413,7 @@ Nothing is built of this before §10's bed passes; the MCP surface is a thin ada client. What (2) describes as a program on the workstation is now the recovery path; the surface an operator uses is a module the mesh assigns to the machine, holding a credential the mesh minted — [ADR 0152](../../02-DECISIONS/0152-the-operators-surface-is-a-module-the-console.md), -[34 — The console](34-the-console.md). The tool list it asks for is no longer +[34 — The mesh MCP server](34-the-console.md). The tool list it asks for is no longer `catalog_tools`, which nothing served: each runtime answers `tools` for its own module. ## 8. What a module sees, and what the wire does @@ -451,7 +451,7 @@ Per ADR 0106 the bus moves once. Per [ADR 0116](../../02-DECISIONS/0116-the-bus-is-built-in-five-steps.md) the *build* is five steps, each ending at something §10 proves, so that no part of this waits on the whole of it. Dividing the build does not divide the bus: steps 1 to 4 leave every node on AMQP, and step 5 is still one -rollout. +switch-over. **Step 1 — genesis raises the broker.** The `nats` module of §5, and a mesh raised on it from nothing. This is built and proven although the mesh it is for will never travel this path: genesis @@ -476,7 +476,7 @@ no helper layer arrives with the new transport. A language may still arrive in p and events first, tools and provisioning when something needs them. *Ends at: the conformance suite, per capability, per implementation.* -**Step 4 — the core speaks NATS.** The controller's link, the host's link, the tool runtime's +**Step 4 — the core speaks NATS.** The controller's link, the node-engine's link, the tool runner's client — and with them the flows carried today by something other than the bus: a build source's change reaching the builder, an installation, a module's own reports. Each is a conversion with a named before and after. Observation — heartbeats, conditions, key-value state — belongs to @@ -485,14 +485,14 @@ reserves it for after the move; this step does not pre-empt its design. Modules target the sdk contract and are untouched by any of it. *Ends at: each converted flow proved against the behaviour it replaced.* -**Step 5 — the rollout.** Unchanged from ADR 0106 and previewed: the controller composes every +**Step 5 — the switch-over.** Unchanged from ADR 0106 and previewed: the controller composes every node's and module's account into the server standing since step 2, then rolls out the controller, every host and every runtime built for NATS. Each node's host connects to the new bus as it comes up and reports; the controller confirms every node heard before it stops listening on AMQP. The predecessor's clients never notice — their broker is the compatibility module and stays. Then the AMQP-side mesh accounts are removed from it, leaving only the predecessor's users, and it retires when its retirement condition holds. The bus's port settings follow ADR 0100 like any port. -*Ends at: the cutover bed, then the rollout itself.* +*Ends at: the cutover bed, then the switch-over itself.* What is not done, at any step: no dual-bus period for the mesh's own traffic, no bridge, no module rebuilt. @@ -562,12 +562,12 @@ can first be raised on NATS and run: - a node that was unreachable catches up on its reports rather than losing them. **Step 5 — the cutover bed:** a mesh on AMQP with a predecessor stand-in on the compatibility -broker moves its bus in one rollout; every node reports on NATS afterwards; the stand-in's client +broker moves its bus in one switch-over; every node reports on NATS afterwards; the stand-in's client on AMQP is still connected throughout. Unit tests hold the controller to composing accounts from `emits`/`consumes` and nothing else, to creating the four streams and asserting them idempotently, and to spending a token exactly once; -the host to connecting as the enrolment user with nothing but enrolment permissions; the runtime +the node-engine to connecting as the enrolment user with nothing but enrolment permissions; the runtime to mapping the sdk contract onto subjects exactly as §8 says. ## 11. Open, for the review diff --git a/03-DESIGN/01-to-be/26-the-seats.md b/03-DESIGN/01-to-be/26-the-seats.md index 277b5e3c..5445ae10 100644 --- a/03-DESIGN/01-to-be/26-the-seats.md +++ b/03-DESIGN/01-to-be/26-the-seats.md @@ -60,7 +60,7 @@ about that assignment: the node, the node's settings for the module, and what th then the holder was derived — the module that is assigned and claims the seat holds it, and a second eligible assignment was refused. That has no way to pass a seat from one holder to the next without a moment in which nobody holds it, and the controller finds its own bus through one of these seats: that -moment took the control plane down for an evening. So the holder is now one row the controller keeps, +moment took the controller down for an evening. So the holder is now one row the controller keeps, written by a handover — `seat --to /` — that names the seat and the assignment taking it over and replaces the previous holder in the same write. Between two handovers the seat has exactly one holder, and it is never none. @@ -92,8 +92,7 @@ binary. It does not check that the module is running yet; `push` confirms that a handover that could only be recorded after the new holder was up could not be the switch. **The set is closed.** A seat the mesh does not define is refused wherever it is named, and so is one -named at the wrong scope. Adding a seat is a decision, recorded, for the reason every addition to the -host's vocabulary is one: the set is what a person reads to learn what a mesh can have, and an entry +named at the wrong scope. Adding a seat is a decision, recorded, for the reason every addition to the node-engine's vocabulary is one: the set is what a person reads to learn what a mesh can have, and an entry nobody argued for is an entry nobody can explain. **What deserves one** ([ADR 0161](../../02-DECISIONS/0161-what-deserves-a-seat.md)). A provision the @@ -263,7 +262,7 @@ mesh records which: For a repository on the seat, the controller composes the clone URL at the moment of building, from where the holder runs and the scheme and port it serves for `git`. The recorded source never contains -an address, so moving the forge changes nothing that was recorded. The build machine is not told the +an address, so moving the forge changes nothing that was recorded. The builder is not told the difference: it receives a URL either way. With the seat unheld, a build from the seat is refused and says why. External builds carry on. @@ -293,8 +292,8 @@ checked as their tables say: | A requirement naming a seat is answered by its holder; a foundation seat cannot be named | 0118: resolution tests with a second provider on the consumer's node, with the seat unheld, and naming `mesh-store`. | | Several providers and none local is a person's choice | 0118: an assignment test listing candidates with the seat's holder first and recording the pin. | | `secret` has one provider, the holder of `mesh-vault` | 0161: a second claimant of the seat is refused by name (`CanHold`); *correction of fact, 2026-10-01: no parser rule ever reserved the word, the seat does the work*. | -| A singular fact about machines is a placement of capacity one, refused by name | 0161: the overlay command's test for a second hub; the store's unique index. | -| A holder of `node-uplink` is the dialect the machine runs | 0161: the host reports `uplink-` in its profile with every report; a resolution test refuses the other holder naming the capability. | +| A singular fact about machines is a placement of capacity one, refused by name | 0161: the private network command's test for a second hub; the store's unique index. | +| A holder of `node-uplink` is the dialect the machine runs | 0161: the node-engine reports `uplink-` in its profile with every report; a resolution test refuses the other holder naming the capability. | | A seat's holder has the seats it needs beside it: the resolver configuration is refused at `assign` without the uplink held on its node, and the uplink's last holder cannot be taken from beneath it | 0220: seat-dependency tests on the definition, on a synthetic claimant, at assign, at unassign and at composition, and one reading the catalogue for the uplink's possible holders. | | A replicated seat has every holder on record and composes on each; a seat held once still refuses a second holder, on record or not; `--add` is refused for it | 0223: resolution and composition tests with two holders on record and a third machine, with two claimants and nothing on record, and on `mesh-store`; a unit test that only `mesh-dns-resolver` is replicated, surviving the store's rows; store tests keeping several holders once each, a handover leaving one, and unassigning one taking only its row. | | A retired seat leaves the set once nothing claims it, from the compiled set and the store's table both | 0220: the closed-set test's count, and the store migration that deletes the row. | diff --git a/03-DESIGN/01-to-be/27-a-module-requires-the-mesh-resolves.md b/03-DESIGN/01-to-be/27-a-module-requires-the-mesh-resolves.md index f55a9cf5..33db43e6 100644 --- a/03-DESIGN/01-to-be/27-a-module-requires-the-mesh-resolves.md +++ b/03-DESIGN/01-to-be/27-a-module-requires-the-mesh-resolves.md @@ -20,7 +20,7 @@ decisions: # 27 — A module requires, the mesh resolves -**One concept for everything a module needs.** A module definition states what it requires. Each +**One concept for everything a module needs.** A manifest states what it requires. Each requirement has a contract and a kind of provider. Installing the module on a node resolves every requirement, or refuses and says why. Nothing else reaches a module: no path it chose, no setting beside the model, no literal it carries. @@ -125,7 +125,7 @@ back to the consumer as resolved values. ### The node's host -The host answers what only a machine can: where a directory is, which port is free, what the machine +The node-engine answers what only a machine can: where a directory is, which port is free, what the machine is. It is always the module's own node, because none of these means anything elsewhere. **A directory.** The contract is an owner and a mode, and the owner the image expects where it has @@ -151,7 +151,7 @@ never created, owned or removed by the mesh. The module requires read or read-wr the data is, is an operator value on the assignment. **A port** is what [ADR 0038](../../02-DECISIONS/0038-the-mesh-assigns-the-port.md) already decided: the -module says which port its software uses, and the host answers with where the machine put it. +module says which port its software uses, and the node-engine answers with where the machine put it. **A fact** is something the machine knows: its name on the private network, the names of the mesh's machines. Each fact has a contract like anything else. @@ -184,7 +184,7 @@ refused, like any other singular thing. the operator's value in its first form — `${setting:}` in a file's content, from the assignment's settings layers, refused by name when nothing set it; a module told the name its route composes (`${bound::name}`); a build context on the git seat; and the check that no definition names an -installation, with `names-on-purpose` for the names a definition means. The host's directory in its +installation, with `names-on-purpose` for the names a definition means. The node-engine's directory in its first form is [`${dir:}`](../../04-ISSUES/119-a-module-definition-decides-where-its-files-live/00-report.md), a placed directory under the node's root. Each is this design's provider in the shape the existing placeholders have, not yet the one requirement form below; they are phase 1's first cases. @@ -227,7 +227,7 @@ ADR 0202's "how this is checked", each run against the unchanged controller firs ## How a definition reads what was resolved **One form, naming a requirement and a field of its contract.** A definition that needs the database's -host in an environment variable, the directory's location on the host side of a mount, or the public +host in an environment variable, the directory's location on the machine's side of a mount, or the public name in a configuration file writes the same thing: the requirement's name and the field. The controller fills it at resolution. @@ -302,7 +302,7 @@ not rotated by the vault, which cannot make its replacement: an operator deliver **Each requirement says how its recipient takes a new value.** It either *applies* it, through a provisioner (a provider setting a login's password, the broker's provisioner updating an account, a store's provisioner changing its own superuser), or *reads it at start*. Every module in the catalogue -reads its secrets at start, and none watches them. The host already recreates a container when a file +reads its secrets at start, and none watches them. The node-engine already recreates a container when a file it read at creation changes, its env-files and files mounted into it directly ([issue 103](../../04-ISSUES/103-a-container-is-not-recreated-when-a-file-it-reads-changes/00-report.md)). So a reader's restart is derived, and a definition declares `restart-on` only for a secret reaching a @@ -325,7 +325,7 @@ than reported done. - **One party**, a provider's administrative credential or a module's own secret: in place, staged. An applied one is delivered beside the current value, the provisioner changes the backend with the current one, and only then does the new value become current. One read at start is delivered, and - the host recreates the module. + the node-engine recreates the module. **Retiring a credential never removes what it reached.** An adapter keeps *retire a credential* and *remove the consumer* apart, and the harness keys what it applied by consumer, so a changed login is @@ -359,7 +359,7 @@ as waiting, and nothing is delivered until the answer arrives. | every secret the controller mints: provider credentials, own secrets, broker passwords, enrolment tokens | a secret the vault makes ([ADR 0113](../../02-DECISIONS/0113-the-vault-makes-every-secret.md)) | | root secrets genesis mints and keeps apart | made by genesis once, then delivered to the vault, which holds and rotates them | | a separate command issuing a broker account | a requirement resolved on assignment | -| `restart-on` naming a secret's file | a restart the host derives | +| `restart-on` naming a secret's file | a restart the node-engine derives | | paths in resources, mounts, bindings, secrets and received files | host directory requirements, placed by the assignment | | literals carried in a definition | operator requirements with defaults | @@ -380,7 +380,7 @@ Each phase ends at a check that holds, so none of them leaves a mechanism half-r foundation's first secrets to the vault; the broker's provisioner creates every bus account; the mesh carries providers' data back. [Issue 103](../../04-ISSUES/103-a-container-is-not-recreated-when-a-file-it-reads-changes/00-report.md) - is fixed in the host already. *Ends when* nothing outside the vault generates a shared secret after + is fixed in the node-engine already. *Ends when* nothing outside the vault generates a shared secret after genesis, a lab consumer of analytics receives its site id, and a database credential rotates over its two credentials, the consumer recreated by derivation, never without a working login, and its data intact. 3. **Definitions move, and seats move to assignments.** Every catalogue definition is rewritten, adopted @@ -443,9 +443,9 @@ requirement, `//`. *(Built 2026-09-30: the mesh's directory issue 174.)* **Resolution happens in the controller, at declaration composition.** The node receives -concrete paths exactly as today — the wire format and the host's apply do not change for +concrete paths exactly as today — the wire format and the node-engine's apply do not change for this. What changes is that no *manifest* carries a path; the controller resolves -requirement → location against the node's root setting. (The host still needs issue 126's +requirement → location against the node's root setting. (The node-engine still needs issue 126's fix — volumes in the spec comparison — or a resolved path change cannot reach a running container.) diff --git a/03-DESIGN/01-to-be/28-building-the-bus.md b/03-DESIGN/01-to-be/28-building-the-bus.md index d96cb95c..d367a490 100644 --- a/03-DESIGN/01-to-be/28-building-the-bus.md +++ b/03-DESIGN/01-to-be/28-building-the-bus.md @@ -48,8 +48,8 @@ territory: every piece has a shape already standing beside it. | Piece | Today | Size | Becomes | |---|---|---|---| | the controller's link | Go, one package | ~1 800 lines | the same package on NATS | -| the host's link | Go, one package, mirroring the contracts rather than importing them | ~1 000 lines | the same, on NATS | -| the tool runtime's client | TypeScript, one file | ~390 lines | the same, on NATS | +| the node-engine's link | Go, one package, mirroring the contracts rather than importing them | ~1 000 lines | the same, on NATS | +| the tool runner's client | TypeScript, one file | ~390 lines | the same, on NATS | | the sdk's messaging surface | TypeScript: messaging, events, tools, contracts, primitives | ~360 lines across five | **unchanged**, see below | | the broker module | the adopted AMQP broker: client, tools, provisioner, bootstrap, image, manifest | ~340 lines of module code | the `nats` module, same shape | | the beds | 39 lab scenarios, including a broker bed, an adoption bed, a genesis bed and a store-window bed | — | four analogues and one new | @@ -66,9 +66,8 @@ mesh at all. **The wire therefore has three implementations, not two, and no suite pins any of them.** [ADR 0074](../../02-DECISIONS/0074-the-wire-is-specified-not-the-types.md) spoke of "the existing two implementations" — Go and TypeScript. Measured, the Go side is *two separate packages* that -mirror rather than share (the host imports nothing, by -[ADR 0005](../../02-DECISIONS/0005-the-node-host.md)), so the count is the controller's link, the -host's link, and the runtime's client. And a search for conformance fixtures finds none anywhere in +mirror rather than share (the node-engine imports nothing, by +[ADR 0005](../../02-DECISIONS/0005-the-node-host.md)), so the count is the controller's link, the node-engine's link, and the runtime's client. And a search for conformance fixtures finds none anywhere in the four repositories: design 22's Phase 1.2 — the suite — has not been built. > **This corrected the record.** ADR 0116 said step 3's fixtures were *recaptured* on NATS. There @@ -89,7 +88,7 @@ otherwise would put two beds where they cannot run.** Three edges decide it: *finish* once two implementations exist to disagree. - **A mesh cannot be raised on a bus nothing speaks.** A bed that raises a mesh on NATS from genesis — enrolling a node, holding a push while the store restarts, rolling out an upgrade — - needs the controller and the host to speak NATS already. That is the implementations, and they + needs the controller and the node-engine to speak NATS already. That is the implementations, and they arrive with step 3. - **Adoption needs the module and nothing else.** Step 2 puts a correctly configured server into a running mesh that continues to ignore it, which depends on no link at all. @@ -210,7 +209,7 @@ paper is wrong until there is a second mesh to find out. because a credential embedded in a URL leaks into every log line that prints a connection; and a module's durable consumer is derived from what it declared rather than named, so it cannot ask for delivery of something it did not say it consumes. A node reconnecting may be refused until - the composition reaches the machine running the bus — which is what the host's reconnect backoff + the composition reaches the machine running the bus — which is what the node-engine's reconnect backoff is for, where waiting for the push would hold an enrolment open for as long as a declaration takes to apply. @@ -374,8 +373,8 @@ pays for itself furthest away. *Half of a report is not about a declaration, and that half is never stale.* What the machine **is** — the tunnel it took over, the ports its own bundle holds, what an adopted node found, - a node moving its overlay key — reaches the mesh on a report and nowhere else. A rekey set - aside as stale is a node whose overlay key never moves, and no retry is coming, because the + a node moving its private network key — reaches the mesh on a report and nowhere else. A rekey set + aside as stale is a node whose private network key never moves, and no retry is coming, because the node said it once. So staleness is asked only of a report that is purely an apply's account. *The controller could not have consumed a module event at all.* Its account granted no event @@ -387,8 +386,8 @@ pays for itself furthest away. manifest and authority cannot come from a declaration that does not exist. Still outstanding: a build's own shape, which travels with the builder in step 4. -- [x] 3.5 the host's link on NATS — **all three halves are through seams**, mirroring the - controller's and still importing nothing of the mesh's own (ADR 0005): the host's own +- [x] 3.5 the node-engine's link on NATS — **all three halves are through seams**, mirroring the + controller's and still importing nothing of the mesh's own (ADR 0005): the node-engine's own interfaces over its own libraries, agreeing with the controller only because a fixture holds both to one envelope. A report goes through JetStream because it is the message the store-window guarantee is about; a heartbeat stays on core, because a heartbeat in a stream is @@ -412,27 +411,27 @@ pays for itself furthest away. its timeout against a mesh that answered. **The reply address travels in the payload, and that is now proved from both ends.** The - controller reads it from there (3.4) and the host writes it there and waits on it, and the + controller reads it from there (3.4) and the node-engine writes it there and waits on it, and the test asserts the transport's own reply field held the *consumer's ack address* by the time the request arrived — so a future server that stopped claiming that field fails a test rather than letting the reason quietly become folklore. - The host's **"newest wins" window narrows at the rollout rather than disappearing**, and that + The node-engine's **"newest wins" window narrows at the switch-over rather than disappearing**, and that is now measured rather than predicted: three declarations pushed to an absent node leave one on the stream and it is the newest, so the catch-up half is the stream's — but three pushes to a connected node are still three deliveries, which is the half that stays. - **The pin turned out easier here than in the tool runtime, not harder.** The Go client takes a + **The pin turned out easier here than in the tool runner, not harder.** The Go client takes a `*tls.Config`, so the same pinned configuration with the same verify callback does the work; the subject-alternative-name constraint recorded under 3.6 is that client's, because it takes PEM strings with no verify hook. A host checks the fingerprint and nothing else. Nothing here composes an enrolment user per live token, and that is **1.7's**, not this task's: it is one input to a composition that does not happen at all yet. -- [x] 3.6 the tool runtime's client on NATS, behind the unchanged sdk contract — round-tripped +- [x] 3.6 the tool runner's client on NATS, behind the unchanged sdk contract — round-tripped against a real server: a tool answered across two connections, a throwing handler reaching the caller as an error rather than a timeout, an event delivered once with its key, body, - node and event id intact. Ships beside the AMQP client and is selected at the rollout, + node and event id intact. Ships beside the AMQP client and is selected at the switch-over, because steps 1 to 4 leave every node on AMQP. **A constraint it surfaced, recorded where somebody issuing a certificate will look.** The @@ -523,8 +522,7 @@ it, and the beds that need a mesh living on NATS can finally run. ([ADR 0129](../../02-DECISIONS/0129-a-seat-carries-the-protocol-of-its-role.md)). Both sides are behind a seam with an implementation per bus, and on the bus being built one publish does what two did: the outcome is the role's own event, so the asker matches it by the id its - request carried, the controller records it and the catalogue places it in the graph. A build - machine needs a reply queue for nothing and a grant over nobody's inbox. + request carried, the controller records it and the catalogue places it in the graph. A builder needs a reply queue for nothing and a grant over nobody's inbox. Checked against a running server: the round trip; a third party on the role's event hearing the same outcome the asker did, which is what the decision rests on; work leaving the queue @@ -625,11 +623,11 @@ bed is green. **Observation is not in this step** — heartbeats, conditions and [research 017](../../01-RESEARCH/017-a-mesh-that-heals-itself/00-overview.md)'s, that effort already reserves them for after the move, and a flow built ahead of its design would be rebuilt. -## Step 5 — the rollout +## Step 5 — the switch-over **Why here.** It is the only step that moves a node's bus, and it moves every node's at once. -**The order the repositories land in is part of the rollout, not paperwork.** Derived 2026-09-27 +**The order the repositories land in is part of the switch-over, not paperwork.** Derived 2026-09-27 while merging, and not obvious from any one repository, which is why it is written here rather than left to be re-derived under time pressure: @@ -653,20 +651,20 @@ a subject produce no error anywhere. Nothing logs, nothing retries, and the mesh healthy while reacting to nothing. - [ ] 5.1 the cutover bed: a mesh on AMQP with a predecessor stand-in on the deprecated broker - moves its bus in one rollout, every node reporting on NATS afterwards, the stand-in's own + moves its bus in one switch-over, every node reporting on NATS afterwards, the stand-in's own client still connected throughout -- [x] 5.2 the rollout: accounts composed, then the controller, every host and every runtime +- [x] 5.2 the switch-over: accounts composed, then the controller, every host and every runtime together; every node confirmed heard before AMQP stops. **Done 2026-09-28, 02:25.** Every machine reports on the new bus, the seat is held by the module that provides it, the old broker is unassigned and forgotten, and every credential was minted afresh at the end because two had been printed on the way. What it took, in the order - it was found, each fixed on the trunk before the next step: the control plane's `serve` and + it was found, each fixed on the trunk before the next step: the controller's `serve` and `push` never selected the new transport (task 4.3, open until then); a machine's user was granted neither the asking nor the delivery of its own consumer; the account had no JetStream - of its own; the control plane's client verified the bus's certificate by name instead of + of its own; the controller's client verified the bus's certificate by name instead of pinning it; the seat table's rows carried no protocol, so no role's work queue was raised; the - build machine decided its bus from a variable its container never received; and a rotation + builder decided its bus from a variable its container never received; and a rotation put new hashes on the bus before three machines had received their new memberships — which is why there is now `rollout hand ` and a host adopts a delivered membership at start. The bootstrap loop — a bus that can only be raised by a declaration that can only arrive @@ -676,7 +674,7 @@ healthy while reacting to nothing. - [x] 5.3 **the seat changes hands as one act.** A command takes a seat and the assignment taking it over, and the seat is never empty in between — the emptiness is the outage of 2026-09-27, when - the control plane, which finds its own bus through this seat, lost the address and looped. + the controller, which finds its own bus through this seat, lost the address and looped. **Built 2026-09-27** (`seat_holding`, migration 0039; design 26 says how it is checked), and used live the next night to hand `mesh-broker` from the old broker's assignment to the new one's. This is what 5.2 uses to move `mesh-broker` from the old @@ -694,14 +692,14 @@ healthy while reacting to nothing. > shutting it down ends the path that reaches this installation's machines from a workstation. > The rollout is driven from the node, or before the broker stops — a sequencing constraint on > 5.2, not an afterthought. -- [x] 5.5 **the AMQP transport is deleted from the control plane and the hosts**. One bus, nothing +- [x] 5.5 **the AMQP transport is deleted from the controller and the hosts**. One bus, nothing to select ([ADR 0131](../../02-DECISIONS/0131-everything-on-the-mesh-speaks-to-the-broker-seat.md)). - **Done 2026-09-28.** The control plane's old consume loop, build request, tool call, management - API and account scoping went, and the host's old dialling and enrolment paths with them; a + **Done 2026-09-28.** The controller's old consume loop, build request, tool call, management + API and account scoping went, and the node-engine's old dialling and enrolment paths with them; a membership or token naming any other bus is refused before anything is sent. Nothing selects a transport any more: the variable that once did (`MESH_BUS_NATS`) now only names where the - control plane reads its own bus credential, the way any module reads a secret. **Checked by the + controller reads its own bus credential, the way any module reads a secret. **Checked by the build**: neither repository's module file names the AMQP client library, so a line that still used it would not compile. The store-window guarantee ([issue 083](../../04-ISSUES/083-other-control-messages-are-lost-while-the-store-restarts/00-report.md)) is tested against a bus-less fake rather than the old transport's memory, which is what let @@ -712,7 +710,7 @@ healthy while reacting to nothing. a build argument, so the digest was never in the file the builder derived edges from, and every order that says *bases first* — `build --on`, `build --behind`, the merge follow-up of [issue 131](../../04-ISSUES/131-nothing-tells-the-mesh-a-source-moved/00-report.md) — walked a - graph with no edges. The builder now reports the bases it was handed, the control plane records + graph with no edges. The builder now reports the bases it was handed, the controller records them by artifact path, and the graph is read from the newest build of each module — a recorded manifest carries no `build.on`, so the edge is derived from the build or it does not exist. diff --git a/03-DESIGN/01-to-be/29-a-node-has-operator-accounts.md b/03-DESIGN/01-to-be/29-a-node-has-operator-accounts.md index 8027b3c8..57ca152c 100644 --- a/03-DESIGN/01-to-be/29-a-node-has-operator-accounts.md +++ b/03-DESIGN/01-to-be/29-a-node-has-operator-accounts.md @@ -112,13 +112,13 @@ found-vs-owned boundary of §3 is exactly what guarantees nothing already there ## 5. How it is distributed: the controller composes, the node applies -None of this needs a node to discover the mesh, and none of it needs a control-plane module of its +None of this needs a node to discover the mesh, and none of it needs a controller module of its own. The ssh files are **roster facts** ([ADR 0120](../../02-DECISIONS/0120-a-roster-fact-carries-its-format-as-a-template.md)): once the roster view carries a node's **host key** and its **account** beside its name and address, the `ssh-client` module ships a template for `known_hosts`, `config` and `authorized_keys`, and the controller renders each node's copy from the full roster and pushes it. The mesh owns the data; the -module owns ssh's format; the control plane gains no ssh syntax. It is the same act as composing a +module owns ssh's format; the controller gains no ssh syntax. It is the same act as composing a peer list or `/etc/hosts` — which is why there is **no control-node-only "mesh-ssh" module**: the centralization is the controller's composition, not a module that runs somewhere. Only non-secret facts travel (names, addresses, accounts, host keys, the CA public key); the private key stays the @@ -164,7 +164,7 @@ was found. **Not built:** the SSH CA and certificates (§4), `known_hosts` and `authorized_keys` as roster files, the found-vs-owned boundary inside `~/.ssh` (§3 — the controller has no rule yet that refuses to rewrite a private key), adoption of existing keys, the ssh-agent as a user service, and user-scoped -services in general. The host vocabulary still has no user-scope unit at all; a workstation's +services in general. The node-engine vocabulary still has no user-scope unit at all; a workstation's per-user daemons (a bar watchdog, a config reloader, an audio service masked per user) have no form the mesh can send. @@ -191,7 +191,7 @@ service and leave the human unable to work on the box. **Why not build it reflexively:** it is a real addition to the node model, the resource model, and the seat set, and must be gotten right. The mechanism half is now settled — ADR 0120 is what lets -the ssh files be templates with no control-plane format — so what remains to decide here is the +the ssh files be templates with no controller format — so what remains to decide here is the model: - **One account or several per node?** A workstation has one human; a shared box might have more. diff --git a/03-DESIGN/01-to-be/30-the-mesh-updates-itself-on-a-push.md b/03-DESIGN/01-to-be/30-the-mesh-updates-itself-on-a-push.md index 4fd7b3bb..dc6eb1cb 100644 --- a/03-DESIGN/01-to-be/30-the-mesh-updates-itself-on-a-push.md +++ b/03-DESIGN/01-to-be/30-the-mesh-updates-itself-on-a-push.md @@ -29,15 +29,15 @@ seat or schema: 1. `module moved ` — tell the mesh its source advanced (the controller repo has no trigger, so this is manual; the catalogue's webhook does it automatically — see below). -2. `build --behind` (or `build [--ref] [--path ]`) — the build machine rebuilds and +2. `build --behind` (or `build [--ref] [--path ]`) — the builder rebuilds and records the new image. 3. The mesh **reconciles on its own**: the module's declaration now names the new image, the next - push/heartbeat sends it, and the host swaps the container. For the control plane this is a - self-upgrade — the running controller composes its own new image and the host replaces it. No + push/heartbeat sends it, and the node-engine swaps the container. For the controller this is a + self-upgrade — the running controller composes its own new image and the node-engine replaces it. No restart is typed. **A breaking change** — a manifest schema the controller parses differently (a fact's shape, a -seat's name), where the new control plane cannot read the manifests the old one stored: +seat's name), where the new controller cannot read the manifests the old one stored: 4. Land the code (controller + catalogue together — they are one change). 5. Rebuild + deploy the new controller (steps 1–3). **The moment it is live it refuses the @@ -67,7 +67,7 @@ Build-on-push works for the catalogue because its repository has a Gitea webhook build work; it does not receive Git events. So the mesh's own build pipeline currently rides on a HAL service, and: -- the `mesh-controller` repository was never wired to it, which is why the control plane is the one +- the `mesh-controller` repository was never wired to it, which is why the controller is the one thing that does **not** self-update — every controller deploy this session was `module moved` + `build` by hand; - when HAL is retired, build-on-push stops for the whole mesh. @@ -81,18 +81,18 @@ repository of every registered module, and rebuild the matches. ### 2. The builder validates too — and a breaking change deadlocks it -The build machine embeds the same catalogue package the controller does, so **it validates a +The builder embeds the same catalogue package the controller does, so **it validates a manifest against its own compiled-in seat/schema set**. A breaking change therefore couples *four* things, not two: the controller, the **builder**, every affected manifest, and every node's host. This session's seat rename rebuilt the controller but not the builder, and the stale builder then refused every manifest claiming a renamed seat. -Worse, one rename **deadlocked** the builder: the build machine's own seat was renamed +Worse, one rename **deadlocked** the builder: the builder's own seat was renamed (`the-build-machine` → `mesh-build-machine`). To refresh the builder you must build it; to build it the *running* (old) builder must accept the new builder's manifest — which claims the new name it does not know. The old builder cannot build the new builder. Escapes: -- **Never rename a seat whose holder validates manifests** in an ordinary pass — the build machine's +- **Never rename a seat whose holder validates manifests** in an ordinary pass — the builder's seat belongs with the deferred delivering seats (ADR 0121). Reverting `mesh-build-machine` to `the-build-machine` (deferred) lets the old builder build the new builder, which then knows the new names. @@ -101,7 +101,7 @@ does not know. The old builder cannot build the new builder. Escapes: validation once. Either way, self-update for breaking changes needs a **transition discipline** so a push does not -auto-freeze: the new control plane (and builder) should accept the *old and new* shape together for +auto-freeze: the new controller (and builder) should accept the *old and new* shape together for one release — deprecated aliases in the seat set, a schema that reads both — then a later release drops the old. With that, a breaking change rolls out on a push like any other: everything reads both, the manifests migrate, the compatibility is removed. Without it, self-update would simply @@ -111,13 +111,13 @@ automate the freeze. - **A nox forge-webhook trigger** (replaces `hal-gitea-tools`): receives Git events for every mesh repository, dispatches build work to the builder over the broker, and records `module moved` - automatically. Wire `mesh-controller` to it so the control plane self-updates like everything else. -- **A transition discipline for breaking changes**: the control plane and builder accept old+new for + automatically. Wire `mesh-controller` to it so the controller self-updates like everything else. +- **A transition discipline for breaking changes**: the controller and builder accept old+new for one release; the tooling that lands a schema/seat change emits the compatibility shim and the follow-up that removes it. This is what makes step 4–7 above safe to trigger unwatched. - **Config/package modules need no builder** — `module add` registers their manifest directly (this is how the uplink managers and the re-registrations above were done). Only image-bearing - modules need the build machine, which narrows what the deadlock above can block. + modules need the builder, which narrows what the deadlock above can block. ## What a merge does now (2026-10-01) @@ -141,7 +141,7 @@ Revision, [ADR 0218](../../02-DECISIONS/0218-a-plan-sends-grants-before-code-rol A membership that could not be issued fails the send. - **One machine first.** Unless a module's upgrade policy says *together*, a plan sends it to one machine, the first by name, and to the rest only once that machine reports the new declaration applied and - current. A first machine that fails stops the module's rollout there, with the reason in the plan. + current. A first machine that fails stops the module's walk there, with the reason in the plan. - **A newer merge takes over.** A merge's plan supersedes every older open plan for the same repository and branch, and takes in the modules they had not yet built. A plan that waits on nothing can be closed by hand, by its id. @@ -203,5 +203,5 @@ designed, not bolted on beside a freeze. The manual process above is the interim fact-shape change that first showed the breaking-change freeze - `hal-gitea-tools.service` (`~/.hal/modules/hal/gitea/tools/server.js`) — the predecessor webhook receiver on `:9877` the mesh currently rides on -- mesh-controller `cmd/mesh-builder` (the build machine), `internal/catalogue` (the seat/schema +- mesh-controller `cmd/mesh-builder` (the builder), `internal/catalogue` (the seat/schema validation the builder shares with the controller) diff --git a/03-DESIGN/01-to-be/31-a-module-declares-its-fail2ban-jail.md b/03-DESIGN/01-to-be/31-a-module-declares-its-fail2ban-jail.md index 81d163d4..ed5a5b12 100644 --- a/03-DESIGN/01-to-be/31-a-module-declares-its-fail2ban-jail.md +++ b/03-DESIGN/01-to-be/31-a-module-declares-its-fail2ban-jail.md @@ -79,7 +79,7 @@ every assigned module's jails per node into those; the holder's daemon restarts What made it workable was the log. A container's output went to a file of the runtime's own, under a path that changes when the container is recreated, so no jail could read a container's service -however it logged. A container now declares `logging: journald`, the host runs it with the journal as +however it logged. A container now declares `logging: journald`, the node-engine runs it with the journal as its driver, and a jail reads it with `backend = systemd` and a `journalmatch` on the container's name — the same way the base's ssh jail has always read the ssh daemon. The first three doors: the mail front end (every login failure on its proxying ports), the forge (a failed authentication @@ -89,7 +89,7 @@ weeks for four — and the mesh's own range stays never banned. The seat the module holds serves `status`, `banned`, `ban` and `unban`, from a runtime that carries only the fail2ban client with the daemon's socket shared in; the jails are composed, the ban list is -the daemon's, and both are read through the console. +the daemon's, and both are read through the mesh MCP server. *How it is checked:* ADR 0179's table. diff --git a/03-DESIGN/01-to-be/32-what-a-module-declares.md b/03-DESIGN/01-to-be/32-what-a-module-declares.md index b424eaef..cfb77c3e 100644 --- a/03-DESIGN/01-to-be/32-what-a-module-declares.md +++ b/03-DESIGN/01-to-be/32-what-a-module-declares.md @@ -59,8 +59,7 @@ onto this in step 3 of [ADR 0116](../../02-DECISIONS/0116-the-bus-is-built-in-fi ## 1. A module names locally; the mesh derives the subject This is the load-bearing rule. -[ADR 0112](../../02-DECISIONS/0112-a-module-definition-names-no-node-mesh-or-path.md) says a module -definition names no node, mesh or path. A transport address is the same class of thing: if +[ADR 0112](../../02-DECISIONS/0112-a-module-definition-names-no-node-mesh-or-path.md) says a manifest names no node, mesh or path. A transport address is the same class of thing: if manifests held literal subjects, reorganising the subject space would mean editing every module in the catalogue, and the mesh would have hundreds of copies of a decision it made once. @@ -160,12 +159,12 @@ and the controller stays the only writer of stream and consumer definitions **The mesh's own seats carry protocol too.** *Added 2026-09-27, [ADR 0129](../../02-DECISIONS/0129-a-seat-carries-the-protocol-of-its-role.md).* A seat declared by a module says what it accepts, emits and serves; the `mesh-*` set said only who does a job. So the mesh -had roles it could not describe — a build machine with three audiences for one outcome and no way to +had roles it could not describe — a builder with three audiences for one outcome and no way to derive a grant for any of them, and an event genuinely about a role with nowhere to live but the namespace of whichever module happens to hold it. The mesh's seats now take the same three fields, and the same machinery derives the holder's authority, its work queue and its consumers. -**So a build is work submitted to a role, like any other.** The build machine seat accepts a build and +**So a build is work submitted to a role, like any other.** The build seat accepts a build and emits an outcome, and the dedicated branch that carried builds retires: a work queue shared by several machines is exactly what `accepts` already is, and a second mechanism for it is two places a permission can be wrong. @@ -331,7 +330,7 @@ controller, which walks the declared graph and submits rebuild jobs for everythi dependency cascade is not special machinery — it is one event, one derived graph, and the same job queue. -**Deployment is state, not a message.** The controller composes each affected node's declaration +**A declaration is state, not a message.** The controller composes each affected node's declaration and publishes it last-per-subject. A node that was away gets exactly the current one, never a queue of superseded ones, and a replayed older one is refused by sequence. @@ -340,20 +339,20 @@ entrypoint that brings its state to the shape that version needs — the same vo tools and its provisioner, and nothing about how a machine runs it. The mesh runs that entrypoint as it runs the module's own code, to completion, in the module's own context, and a version whose preparation did not succeed does not run: the step gates that module and nothing else on the machine -([ADR 0136](../../02-DECISIONS/0136-a-step-gates-its-module-not-the-machine.md)), and the rollout stops +([ADR 0136](../../02-DECISIONS/0136-a-step-gates-its-module-not-the-machine.md)), and the walk stops at the first machine that did not take it ([ADR 0135](../../02-DECISIONS/0135-a-module-version-prepares-its-state-before-it-runs.md), superseding [ADR 0133](../../02-DECISIONS/0133-a-module-owns-its-migrations-and-the-mesh-owns-when-they-run.md)). Once per state, and the mesh derives what a state is: a consumer is a module on a machine, so what the mesh provisions is per consumer and preparation is too. No level to choose, and no race to lock against. -**Applying is reported to a role.** The host applies and reports to the `mesh-controller` seat — +**Applying is reported to a role.** The node-engine applies and reports to the `mesh-controller` seat — not to an address it was given at genesis. Held and retried while the store restarts ([ADR 0083](../../02-DECISIONS/0083-one-push-leaves-the-mesh-consistent.md)). -**And the mesh says what it applied.** *Built 2026-09-28.* A report is control traffic only the control plane reads, so the +**And the mesh says what it applied.** *Built 2026-09-28.* A report is control traffic only the controller reads, so the chain above went dark at the moment it touched a machine: nothing said which version a machine now runs, -or that it refused to. The control plane states those as facts under its own seat's namespace, when what +or that it refused to. The controller states those as facts under its own seat's namespace, when what a machine runs changes rather than on every convergence pass, and anything that cares subscribes the way the catalogue subscribes to `built` ([ADR 0134](../../02-DECISIONS/0134-the-mesh-says-what-it-applied.md)). The facts are second-hand by design — one emitter, one ordering — and a machine that cannot reach the bus @@ -367,7 +366,7 @@ names, dissolved rather than fixed. ## 7. Modules depending on each other -Three kinds, and conflating them is how deployment ordering goes wrong. +Three kinds, and conflating them is how the order of sends goes wrong. **Build-time** — A's image is built on B's artifact. Resolved by the cascade above; nothing at runtime cares. @@ -377,7 +376,7 @@ before A can start, so resolution gates delivery and A is shown as waiting until ([design 27](27-a-module-requires-the-mesh-resolves.md)). **Seat** — A uses B's seat. A **soft** dependency, and this is the one the bus changes. A starts -whether or not anyone holds the seat, because the stream absorbs the gap. Deployment order stops +whether or not anyone holds the seat, because the stream absorbs the gap. The order of sends stops mattering for everything expressed this way, and a service being restarted, moved or upgraded is not an outage for its callers — it is latency. @@ -506,7 +505,7 @@ sealing key leaks, that stream is an archive rather than a moment. So: - **A secret travels on core request/reply, never through a stream.** No persistence, no replay, nothing to exfiltrate later. - **A declaration names a secret; it does not carry one.** Declarations are the state shape, which - *is* a stream — so the host fetches the secret from the vault at apply time, over the core path. + *is* a stream — so the node-engine fetches the secret from the vault at apply time, over the core path. That is [ADR 0098](../../02-DECISIONS/0098-a-fact-a-provider-makes-at-first-start-is-fetched-from-it.md)'s existing discipline — *fetched from it, not carried* — applied to the one payload where carrying it is worst. @@ -525,9 +524,9 @@ passwords. The vault is a module, and a module needs a bus account, whose passwo makes. Nothing can go first. This is the shape [ADR 0067](../../02-DECISIONS/0067-genesis-is-a-pivot.md) already resolves for -the control plane: **genesis is a pivot.** The controller mints the handful of foundation +the controller: **genesis is a pivot.** The controller mints the handful of foundation credentials itself, raises the store, the broker and the vault, and then the vault takes over and -mints everything from there — the same move as raising a temporary control plane and reinstalling +mints everything from there — the same move as raising a temporary controller and reinstalling it as an ordinary module once the registry exists. So there are exactly two things the normal path cannot make, both at genesis, both ending the diff --git a/03-DESIGN/01-to-be/33-the-tools-the-mesh-answers.md b/03-DESIGN/01-to-be/33-the-tools-the-mesh-answers.md index ee428db4..5d0603e9 100644 --- a/03-DESIGN/01-to-be/33-the-tools-the-mesh-answers.md +++ b/03-DESIGN/01-to-be/33-the-tools-the-mesh-answers.md @@ -39,9 +39,9 @@ what the role answers is the mesh's to define and a holder's to implement own tools are nobody's business but the module's, and their definitions live where they are implemented, because a copy kept anywhere else drifts from the code that answers. -The mesh's own verbs are the third family only in where they come from, not in kind: the control plane +The mesh's own verbs are the third family only in where they come from, not in kind: the controller holds a seat like anything else, and its tools are that seat's. This is what keeps them addressable -while the control plane is being replaced, which is the moment they are most needed. +while the controller is being replaced, which is the moment they are most needed. **Both names for one capability is deliberate and bounded to this.** A forge holding the `git` seat answers the role's `list_repos` and its own `gitea_list_repos`, because the same module may run @@ -57,7 +57,7 @@ The protocol a seat carries today is three lists of bare verbs ([ADR 0129](../../02-DECISIONS/0129-a-seat-carries-the-protocol-of-its-role.md)), and it must widen to carry the rest. Two constraints on that widening: -- **It lives in the mesh's records, not in the control plane's binary.** Today a seat's protocol comes +- **It lives in the mesh's records, not in the controller's binary.** Today a seat's protocol comes from compiled defaults, merged in as a row is read, because the seat rows never gained the columns. Discovery that reads a binary is discovery that disagrees with the mesh the moment the two are on different versions. @@ -117,13 +117,13 @@ being something a person carries and becomes something the mesh runs, on a node, An agent's authority can then be role-shaped: *the forge's tools*, rather than a list of module-specific names that changes the day the forge is replaced. -*Decided and designed on 2026-09-30:* the module is the console — +*Decided and designed on 2026-09-30:* the module is the mesh MCP server — [ADR 0152](../../02-DECISIONS/0152-the-operators-surface-is-a-module-the-console.md), -[34 — The console](34-the-console.md). It builds the second half of §5 now (a module's tools are +[34 — The mesh MCP server](34-the-console.md). It builds the second half of §5 now (a module's tools are asked of the module, through a `tools` verb every runtime answers) and lists a role's tools when the records carry them. -*Amended 2026-10-02 by [ADR 0175](../../02-DECISIONS/0175-one-tool-runtime-per-node-serves-every-modules-tools-on-the-host-side.md):* the module that serves this to an agent is the node tools runtime — one per node, host-side, serving every assigned module's tools as well as answering the person on loopback. The console is its serving mode, renamed. See [37 — The operator's machine](37-the-operators-machine.md) §3. +*Amended 2026-10-02 by [ADR 0175](../../02-DECISIONS/0175-one-tool-runtime-per-node-serves-every-modules-tools-on-the-host-side.md):* the module that serves this to an agent is the tool runner — one per node, host-side, serving every assigned module's tools as well as answering the person on loopback. The mesh MCP server is its serving mode, renamed. See [37 — The operator's machine](37-the-operators-machine.md) §3. ## 7. Versioning @@ -151,14 +151,14 @@ schema, a bare name still accepted), §3 for the mesh's seats (holding refused b verbs), §4 (a node-scoped seat's tool carries the node as its last subject token; a user publishes `*`), and the third family — twelve verbs on the `mesh-controller` seat, each running the command it names in the controller's own binary. §5's first half is served rather than read: the seat's `tools` -verb answers every seat's tools from the records, because the console cannot read the store; the -console lists a role's tools beside the modules' own and resolves `.` to the seat when the +verb answers every seat's tools from the records, because the mesh MCP server cannot read the store; the +mesh MCP server lists a role's tools beside the modules' own and resolves `.` to the seat when the seat declares that verb. Which verbs each *other* seat serves stays a decision per seat, still untaken. ## What shipped, 2026-09-30 mesh-controller PRs 166 and 167, mesh-tools PR 21. Verified on the live mesh the same evening: the -console on a workstation lists the twelve verbs as `mesh-controller.` beside 67 module tools, and +mesh MCP server on a workstation lists the twelve verbs as `mesh-controller.` beside 67 module tools, and `mesh-controller.nodes` and `mesh-controller.status` answer through it with what the commands print. Two things shipped bent. **The grant arrived after the holder started**: the controller composes the @@ -178,7 +178,7 @@ seats' schemas beyond the names their manifests already list. [ADR 0170](../../02-DECISIONS/0170-the-firewall-seat-serves-its-verbs.md). The first node-scoped seat to carry verbs: `node-packet-filter` serves `rules` (the filter as the machine enforces it, nftables and legacy), `reload` (the mesh's own filter from its file) and `remove` (one rule set the mesh did -not write, named as the host reports it under ADR 0168; refusing the mesh's tables, the runtime's +not write, named as the node-engine reports it under ADR 0168; refusing the mesh's tables, the runtime's own chains, a built-in chain and an active found firewall's). Every holder serves all three; the nftables module does so from a runtime on the machine's network with `NET_ADMIN`, which is the first container to declare a capability. Removing a predecessor's rule set is an operator's act reached diff --git a/03-DESIGN/01-to-be/34-the-console.md b/03-DESIGN/01-to-be/34-the-console.md index c74355c3..ed1212de 100644 --- a/03-DESIGN/01-to-be/34-the-console.md +++ b/03-DESIGN/01-to-be/34-the-console.md @@ -15,7 +15,7 @@ decisions: - 02-DECISIONS/0034-the-local-account-owns-the-mesh.md --- -# 34 — The console +# 34 — The mesh MCP server **The mesh's tools, on the machine a person sits at, served by a module the mesh assigned there.** An agent reaches them over MCP on the machine's loopback; a person reaches the same endpoint. Nothing is @@ -26,18 +26,18 @@ it put it there ([ADR 0152](../../02-DECISIONS/0152-the-operators-surface-is-a-m ## 1. What it is -A module, `mesh-console`, in the catalogue. Its image is the tool runtime's own — the client that +A module, `mesh-console`, in the catalogue. Its image is the tool runner's own — the client that already speaks the bus as a command line and as an MCP server — started in a mode that reads the module's credential and listens on loopback. It has no state, no provision, no seat. What it needs is the bus, which it gets the way every module does: a credential the mesh minted for `.mesh-console`, sealed to the machine, delivered as the module's own secret. *2026-10-02:* it gains one provision, at node scope — the MCP endpoint on loopback, serving the port -the machine gave it — so that a module whose software must be told where the console is requires that +the machine gave it — so that a module whose software must be told where the mesh MCP server is requires that and is coupled to an endpoint rather than to a module's name ([ADR 0027](../../02-DECISIONS/0027-a-provision-names-what-the-consumer-is-coupled-to.md)). The first consumer is the operator's agent, [36 — The operator's agent on a machine](36-the-operators-agent-on-a-machine.md) §6; -a machine without the console refuses such a module by name. Nothing else above changes: no seat, no +a machine without the mesh MCP server refuses such a module by name. Nothing else above changes: no seat, no state, no tools of its own. Its manifest says three things nothing else in the catalogue says together: @@ -52,37 +52,37 @@ Its manifest says three things nothing else in the catalogue says together: `tools/list`, `tools/call`. An agent on the machine is pointed at it once — the address is the machine's own and never changes — and sees every tool the mesh can say it has. A person at a terminal uses the same endpoint through the `mesh` client, or through anything that can make an HTTP request; the client -needs no credential, because the console holds it. +needs no credential, because the mesh MCP server holds it. **The endpoint is the machine's login.** It binds `127.0.0.1` and nothing else. Whoever can connect is on the machine, and whoever is on the machine is the account that owns the mesh there ([ADR 0034](../../02-DECISIONS/0034-the-local-account-owns-the-mesh.md), [ADR 0144](../../02-DECISIONS/0144-anything-on-a-machine-may-call-anything-on-it.md)). There is no token, no login page and no second identity, on purpose: a credential a person had to carry to reach -their own machine's console would be the arrangement this replaces, moved one hop. +their own machine's mesh MCP server would be the arrangement this replaces, moved one hop. ## 3. How it knows what the mesh can do Design [33](33-the-tools-the-mesh-answers.md) §5 splits discovery in two: a role's tools are read from -the mesh's records, a module's own are asked of the module. The console builds the second half now and +the mesh's records, a module's own are asked of the module. The mesh MCP server builds the second half now and reads the first when it exists. -**Every tool runtime answers `tools`.** The runtime that serves a module's tools also serves one verb of +**Every tool runner answers `tools`.** The runtime that serves a module's tools also serves one verb of its own under that module's name, `mesh.mod..tool.tools`, answering the module's tool names, descriptions and argument schemas — the definitions from the code that answers them, and from nowhere else. A module may not name a tool of its own `tools`; the runtime refuses the collision at load. -**The console asks the catalogue which modules the mesh holds, then asks each.** `catalog_modules` +**The mesh MCP server asks the catalogue which modules the mesh holds, then asks each.** `catalog_modules` answers the roster; one `tools` request per module, in parallel, answers the list. The bus refuses at once a request nothing serves, so a module that is not running costs nothing and is named in the answer as not answering, rather than silently absent — *silence and success must never look alike*. The list is kept for a short while and refreshed, so an agent asking on every turn does not fan out on every turn. **Every module tool takes the machine to ask** (*2026-10-01*, [ADR 0159](../../02-DECISIONS/0159-a-tool-call-names-the-machine-and-a-holder-serves-its-seats-verbs.md)): -an optional `node` the console lists on each one, puts into the subject and never hands to the module, +an optional `node` the mesh MCP server lists on each one, puts into the subject and never hands to the module, for a module that runs on several machines; without it whichever instance answers first does, and the -console appends *answered by * to every answer. A seat's verb takes none; the seat's scope -decides. *Later the same day ([ADR 0160](../../02-DECISIONS/0160-the-mesh-issues-an-assignments-subjects-and-a-runtime-serves-what-it-is-issued.md)):* the console composes no +mesh MCP server appends *answered by * to every answer. A seat's verb takes none; the seat's scope +decides. *Later the same day ([ADR 0160](../../02-DECISIONS/0160-the-mesh-issues-an-assignments-subjects-and-a-runtime-serves-what-it-is-issued.md)):* the mesh MCP server composes no subject at all; each tool's subject comes with the listing, and a stateful module on two machines is listed once per machine because the mesh issued it no plain subject. @@ -91,14 +91,14 @@ that knows a tool's name asks for it by `.` and the module answers **What is missing from the list, and until when.** A role's tools and the mesh's own verbs — `status`, `push`, `assign` — are the `mesh-controller` seat's under ADR 0132 and are not served yet; their three -prerequisites are listed in that record. When the seat serves them, the console lists them beside the -modules' own, and the person stops opening a shell for the mesh's own questions. Until then the console +prerequisites are listed in that record. When the seat serves them, the mesh MCP server lists them beside the +modules' own, and the person stops opening a shell for the mesh's own questions. Until then the mesh MCP server says so in its handshake. ## 3a. Found by address, not announced whole (2026-10-03) *By [ADR 0195](../../02-DECISIONS/0195-the-meshs-tools-are-found-by-address-not-announced-whole.md); -this section governs where it and §2–§3 disagree.* The console announces five tools — +this section governs where it and §2–§3 disagree.* The mesh MCP server announces five tools — `mesh_overview`, `mesh_machine`, `mesh_search`, `mesh_describe`, `mesh_call` — and every tool the mesh answers is reached through them by its address: `.` for a seat held once for the mesh, `/.` for one held per machine, `/.` for an assignment, and @@ -108,12 +108,12 @@ verb asks the mesh when it is called, so nothing is kept for a session's length; stays reachable through the `mesh` client and a setting, unannounced. **What exists is what announced itself** ([ADR 0197](../../02-DECISIONS/0197-every-tool-announces-itself-on-the-bus-in-the-nats-services-protocol.md)): -every runtime answers the NATS services protocol's `$SRV.INFO` with what it serves, and the console +every runtime answers the NATS services protocol's `$SRV.INFO` with what it serves, and the mesh MCP server gathers one request's answers; the controller's records, read as JSON, say which assignments with tools should have answered. *Found 2026-10-03, measuring for that record:* §3's statement that a stateful module on two machines is -listed once per machine does not hold on the live console — postgres and mssql are listed once, `node` +listed once per machine does not hold on the live mesh MCP server — postgres and mssql are listed once, `node` optional, answered by whichever instance replies. The address replaces that statement rather than repairing it. @@ -122,14 +122,14 @@ repairing it. On whichever machines an operator sits at, by assignment. It is not on the control node by default and does not need to be: it reaches the bus like any module, from anywhere in the mesh. A machine that is not a node cannot have it, which is the right refusal — the mesh reaches what it declares, and a -workstation that wants the console joins first. +workstation that wants the mesh MCP server joins first. The person's credential and the `mesh` client (design [25](25-the-bus-on-nats.md) §7) remain the path for a machine that is not a node, and the path to a mesh not yet far enough along to assign anything. ## 5. Removing it -Unassigning the console from a machine revokes its bus account at the next composition and stops the +Unassigning the mesh MCP server from a machine revokes its bus account at the next composition and stops the container; nothing is left on the machine that could still connect. An agent pointed at the loopback address gets a refused connection, which is the truthful answer. @@ -139,39 +139,39 @@ address gets a refused connection, which is the truthful answer. |---|---| | a module invoking one tool may publish that subject and no other tool's; `*` may publish every one; neither may publish an event or subscribe what it did not consume | ADR 0152, the grant | | a module registering two tools answers three names to `tools`, with schemas; a module naming its own `tools` is refused at load | ADR 0152, discovery | -| against a real bus: two modules up, a third held and not running — the console lists the two and names the third as not answering | ADR 0152, silence is not success | -| a call through the console's endpoint reaches a module over the bus and the answer is the module's own, unshaped | ADR 0035, a surface decides nothing | -| on the live mesh: the console assigned to a workstation answers `tools/list` on loopback and a call to the forge returns repositories | the exit of work-order step 3 | -| the composed filter for a machine carrying the console opens no port for it | ADR 0144 | +| against a real bus: two modules up, a third held and not running — the mesh MCP server lists the two and names the third as not answering | ADR 0152, silence is not success | +| a call through the mesh MCP server's endpoint reaches a module over the bus and the answer is the module's own, unshaped | ADR 0035, a surface decides nothing | +| on the live mesh: the mesh MCP server assigned to a workstation answers `tools/list` on loopback and a call to the forge returns repositories | the exit of work-order step 3 | +| the composed filter for a machine carrying the mesh MCP server opens no port for it | ADR 0144 | ## What shipped, 2026-09-30 Everything above, the same day: mesh-controller PR 164 (`invokes`, `module check`), mesh-tools PR 20 (`mesh serve`, the `tools` verb), mesh-catalog PR 181 (`mesh-console`). Verified on the live mesh: the -console assigned to a workstation answered `tools/list` on its loopback with 62 tools from the modules +mesh MCP server assigned to a workstation answered `tools/list` on its loopback with 62 tools from the modules whose runtimes had been rebuilt to answer `tools`, named 36 modules as not answering (modules that serve no tools, and modules whose new runtime the mesh records rather than rolls out), and a `tools/call` of the forge's `gitea_list_repos` returned repositories. An agent on that machine reaches it as an HTTP MCP server and reports it connected. The mesh assigned the declared port unchanged, which is what a -machine with nothing else on it does; the console binds whatever it is given. +machine with nothing else on it does; the mesh MCP server binds whatever it is given. Two things shipped bent, both stated in [`00-as-is/13-the-console.md`](../00-as-is/13-the-console.md): -the person's client through the console (`--console`) exists and was exercised in the test suite, not +the person's client through the mesh MCP server (`--console`) exists and was exercised in the test suite, not on the live mesh; and a module registered from the catalogue by hand recorded its source as a URL rather than as a path on the git seat, because `--self` takes the forge path form — the rebuild-on-merge still matched it by URL. ## What this does not settle -- Narrowing a console's grant per assignment. ADR 0046 makes it a setting; nothing reads one yet. +- Narrowing a mesh MCP server's grant per assignment. ADR 0046 makes it a setting; nothing reads one yet. - The mesh's own verbs on the bus. Design 33's third family; this document only says where they appear once they exist. -- A person's identity behind the console. The mesh sees the console's account; design 15 keeps the +- A person's identity behind the mesh MCP server. The mesh sees the mesh MCP server's account; design 15 keeps the question open. ## References - [ADR 0152](../../02-DECISIONS/0152-the-operators-surface-is-a-module-the-console.md) — the decision -- [33 — The tools the mesh answers](33-the-tools-the-mesh-answers.md) — what the console lists +- [33 — The tools the mesh answers](33-the-tools-the-mesh-answers.md) — what the mesh MCP server lists - [25 — The bus on NATS](25-the-bus-on-nats.md) §7 — the person's client this makes a module of - [issue 147](../../04-ISSUES/147-the-operators-tools-still-dial-the-bus-that-was-removed/00-report.md) — the symptom diff --git a/03-DESIGN/01-to-be/35-reading-the-record.md b/03-DESIGN/01-to-be/35-reading-the-record.md index 0eff58f9..4aa6029a 100644 --- a/03-DESIGN/01-to-be/35-reading-the-record.md +++ b/03-DESIGN/01-to-be/35-reading-the-record.md @@ -14,7 +14,7 @@ decisions: **The design record, answered from a checkout the mesh keeps, at the commit it read.** A module, `records`, holds a working copy of a repository of markdown — this one, for this mesh — and answers where a phrase appears, what a document says, what a folder holds and where the copy stands. The -console lists those answers beside every other tool +mesh MCP server lists those answers beside every other tool ([ADR 0153](../../02-DECISIONS/0153-the-record-is-read-by-a-module-and-the-console-lists-it.md)). ## 1. What it keeps, and why that is not a copy @@ -52,7 +52,7 @@ log says so. Public repositories only; it holds no credential. ## 4. How it is found -The console asks every module what it serves and lists `records_search` with a description that says +The mesh MCP server asks every module what it serves and lists `records_search` with a description that says when to call it — *search the literal words of a symptom or a term before forming a hypothesis*. That is 0025's second half in today's mesh: there is no store to be beside, and an agent choosing from a tool list is the search. @@ -69,13 +69,13 @@ harmless. The mesh session of design 15, when it exists, calls this rather than | a phrase in one document of a repository the test makes comes back from that document, with the commit; a second commit on the origin is pulled and the next answer names it | ADR 0025's check, ADR 0153 | | a path outside the checkout is refused; an empty search is refused | the reader reads the repository and nothing else | | a sync against an unreachable origin leaves the checkout standing and says why | silence and success never look alike | -| live: through the console, `records_search` for a phrase that appears only in a design document here returns it | issue 006's closing check | +| live: through the mesh MCP server, `records_search` for a phrase that appears only in a design document here returns it | issue 006's closing check | ## What shipped, 2026-09-30 mesh-catalog PR 183, then PR 185. Verified on the live mesh the same evening: `records` assigned to the control node with `{"repository": …}` as its setting, its checkout at the repository's `main` with 478 -documents, its five tools listed by the console beside every other tool, and — ADR 0025's check — +documents, its five tools listed by the mesh MCP server beside every other tool, and — ADR 0025's check — `records_search` for a phrase from this document's title returned it from where it is written, with the commit. The first live search missed: the phrase chosen from ADR 0025 straddled a line break under emphasis, and the reader matched single lines. PR 185 matches a line together with the next and @@ -95,5 +95,5 @@ declares none; a merge into the repository was seen and pulled within seconds. - [ADR 0153](../../02-DECISIONS/0153-the-record-is-read-by-a-module-and-the-console-lists-it.md) - [ADR 0025](../../02-DECISIONS/0025-the-design-record-is-read-not-copied.md) -- [34 — The console](34-the-console.md) — what lists it +- [34 — The mesh MCP server](34-the-console.md) — what lists it - [issue 006](../../04-ISSUES/006-hq-is-not-indexed-into-the-knowledge-base/00-report.md) diff --git a/03-DESIGN/01-to-be/36-the-operators-agent-on-a-machine.md b/03-DESIGN/01-to-be/36-the-operators-agent-on-a-machine.md index 8efec9b9..ff939f70 100644 --- a/03-DESIGN/01-to-be/36-the-operators-agent-on-a-machine.md +++ b/03-DESIGN/01-to-be/36-the-operators-agent-on-a-machine.md @@ -22,7 +22,7 @@ decisions: # 36 — The operator's agent on a machine: the `claude-code` module **The agent a person runs at a terminal, put on the machine by the mesh, instructed by the mesh, pointed -at the console, and holding the licence the manager hands it.** It is a member of the family +at the mesh MCP server, and holding the licence the manager hands it.** It is a member of the family [to-be 29 §2](29-a-node-has-operator-accounts.md) names, the modules that touch a person's machine, and its counterpart is [39 — The Anthropic licence manager](39-the-anthropic-licence-manager.md). @@ -30,7 +30,7 @@ What it replaces: the predecessor's module of the same name and a sibling, which the operator's home. The predecessor is retired; the six files are still on both workstations telling every session to use tools that no longer exist. -**Three rules shape everything below.** The host is module-agnostic: it installs the package and gives +**Three rules shape everything below.** The node-engine is module-agnostic: it installs the package and gives the module a state directory, and knows no vendor, no agent, no path under a home. The controller has no part beyond resolving what it resolves for every module. And the module handles its own files: the mesh's part of the agent's configuration is written by the module's own code, from what the mesh @@ -54,9 +54,9 @@ instruction file and the manager's tools: | `~/.claude/CLAUDE.md` | the managed instruction file: how a session on this mesh works (§3) | | `~/.claude/rules/00-hal-mesh.md`, `~/.claude/rules/conventions.md` | sections of the same file: this node's identity, the repositories' conventions | | `~/.claude/settings.json`, merged | the managed settings file: the mesh's keys, and the rules the operator set for the agent (§2) | -| `~/.claude/skills/hal-switch-license/SKILL.md` | the manager seat's `switch` verb, listed by the console, and a sentence in the instruction file saying to use it | +| `~/.claude/skills/hal-switch-license/SKILL.md` | the manager seat's `switch` verb, listed by the mesh MCP server, and a sentence in the instruction file saying to use it | | `~/.claude/skills/cleanup/SKILL.md` | nothing; it named the predecessor's forge | -| the console's entry in the agent's user-scope state | the managed settings' tool-server key, from the console's provision (§4) | +| the mesh MCP server's entry in the agent's user-scope state | the managed settings' tool-server key, from the mesh MCP server's provision (§4) | **The home.** Under [ADR 0182](../../02-DECISIONS/0182-inside-a-home-the-mesh-owns-what-it-places-and-holds-the-rest-as-found.md) the module owns the directory `~/.claude` — that it exists, that the operator owns it, its mode, @@ -69,10 +69,10 @@ lists them, and until they go the agent reads stale instructions beside the mesh ## 2. What the module declares and what its code writes -**Declared, applied by the host:** the agent's package (§7); the module's state directory; a facts file -in that directory carrying the node's name and the console's endpoint, and a settings file carrying the +**Declared, applied by the node-engine:** the agent's package (§7); the module's state directory; a facts file +in that directory carrying the node's name and the mesh MCP server's endpoint, and a settings file carrying the role, the extra tool servers and the operator's managed-settings keys, merged from the module's settings layers — the bundle is told the two -files' paths, because a bundle's words are paths and constants only (ADR 0192); the bus, the console's provision, and that it uses the `anthropic-licence-manager` seat. +files' paths, because a bundle's words are paths and constants only (ADR 0192); the bus, the mesh MCP server's provision, and that it uses the `anthropic-licence-manager` seat. Two directories, declared so the ownership check sees them: the agent's managed directory under `/etc`, root's, and `~/.claude` under the operator's home, the operator's. No *file* resource under either: what is in them is written by the module's code (§2 below) or is the person's. @@ -82,7 +82,7 @@ changes: | path | content | |---|---| -| the managed settings file | the keys the operator set in the module's `managed_settings` setting, with the mesh's keys laid over them: the tool servers (the console, plus any the operator declared as settings), the attribution trailers, and — for an API-key binding only — the key-helper that serves the key | +| the managed settings file | the keys the operator set in the module's `managed_settings` setting, with the mesh's keys laid over them: the tool servers (the mesh MCP server, plus any the operator declared as settings), the attribution trailers, and — for an API-key binding only — the key-helper that serves the key | | the managed instruction file | §3 | | the agent's credentials file under the operator's home | for a subscription binding only: the access token the manager handed over, as the operator, readable by the operator alone, atomic, no refresh token | | the module's keypair in its state | made once, the private half never leaves (§5) | @@ -109,7 +109,7 @@ and a key-helper appears only for an API-key binding. The mesh still sets no pre Prose, not a paste; the file is the module's. -**How a session on this mesh works.** The console is the only path to the mesh, and its tools are the +**How a session on this mesh works.** The mesh MCP server is the only path to the mesh, and its tools are the vocabulary: the record is asked through the records module, symptom first — the literal error text before a hypothesis ([ADR 0153](../../02-DECISIONS/0153-the-record-is-read-by-a-module-and-the-console-lists-it.md)); the mesh is asked and changed through the controller seat's verbs; the forge through the forge module's @@ -127,7 +127,7 @@ listed here, because a table is a copy that drifts. pull request and a human approval for every merge; test before pushing, because nodes update unattended; the playbooks in the record. -## 4. The console +## 4. The mesh MCP server > **Revised 2026-10-03, building it.** The vendor's managed-settings key for tool servers refuses any > URL that is not `https://`, including one on loopback, so it cannot carry the console. The module @@ -179,7 +179,7 @@ decides it and [ADR 0206](../../02-DECISIONS/0206-a-node-reports-the-anthropic-g - **serves `claude_code_status`**: which licence and kind this node holds, when the token expires, whether the file matches what was handed over — by fingerprint, never by value. -Switching is the seat's `switch` verb, asked through the console; this module only applies what the +Switching is the seat's `switch` verb, asked through the mesh MCP server; this module only applies what the state says it should hold. *2026-10-04:* this replaces the manager's visits of 2026-10-03 (ADR 0183's dated note): the node reports, the manager asks for a secret only when a report shows one it does not hold, and a token is fetched by request when the state says it changed. @@ -191,9 +191,9 @@ All four nodes carry one since 2026-10-03. **Per node:** the role. **Per mesh or extra tool servers, and the operator's managed-settings keys (§2). The controller's verb replaces a setting layer whole, so a layer set for one of these keeps the others it already held. **Prerequisite:** the manager holds its seat and has adopted the licences. -**Order:** the manager assigned and a refresh observed; the console's provision in the catalogue; this -module on one workstation; the six predecessor files and the hand-made console entry removed there; a -new session read to confirm it sees the mesh's instruction file, the console's five tools under `mesh`, and +**Order:** the manager assigned and a refresh observed; the mesh MCP server's provision in the catalogue; this +module on one workstation; the six predecessor files and the hand-made mesh MCP server entry removed there; a +new session read to confirm it sees the mesh's instruction file, the mesh MCP server's five tools under `mesh`, and its licence; then the rest. ## 7. The package @@ -201,7 +201,7 @@ its licence; then the rest. The module declares the agent's package. The distribution every node runs does not carry it in its repositories: the two workstations have it from a build the predecessor's helper made from the community repository, and nothing updates it since. On those two the declaration is satisfied. **On a fresh machine -the host's package manager refuses it, in its own words, and the module is not applied there.** The +the node-engine's package manager refuses it, in its own words, and the module is not applied there.** The answer is a package repository for this ecosystem as a seat ([ADR 0109](../../02-DECISIONS/0109-a-package-registry-seat-is-one-per-ecosystem.md)), fed by the builder and trusted by every node's package manager; not built, and not this module's to build. The vendor's own @@ -266,16 +266,16 @@ operator's word. | Check | Defends | |---|---| | the module's definition names no node, path or login, declares no file under a home or `/etc` (only the two directories), and no file resource carries a secret | ADR 0112, ADR 0155, ADR 0183 | -| on a lab machine with an account and a seeded home holding a person's rule file and the predecessor's leftovers: after assign, the managed directory holds the mesh's files, the home is byte-identical except the credentials file, which is owned by the operator and names no refresh token; after unassign, the managed directory's files are gone and the home is untouched | ADR 0182, the host's agnosticism | +| on a lab machine with an account and a seeded home holding a person's rule file and the predecessor's leftovers: after assign, the managed directory holds the mesh's files, the home is byte-identical except the credentials file, which is owned by the operator and names no refresh token; after unassign, the managed directory's files are gone and the home is untouched | ADR 0182, the node-engine's agnosticism | | on a lab machine with no account, the assignment is refused naming the fact | ADR 0181 | -| a switch asked of the seat through the console changes the licence and the token on the node; no tool answer and no log line holds a token | ADR 0183 | +| a switch asked of the seat through the mesh MCP server changes the licence and the token on the node; no tool answer and no log line holds a token | ADR 0183 | | the API-key binding writes nothing under the home and the agent authenticates through the helper | ADR 0183 | | the module's test: keys set in `managed_settings` (an auto-mode allow list, a permissions list) appear in the rendered managed settings file, a setting naming the attribution, the connectors key or a key-helper is overridden, and a key-helper appears only for an API-key binding | ADR 0213 | | the module's render test: a registered skill, subagent, command, hook and output style land in the `nox-mesh` plugin; a tool server, a setting and an instruction section in their managed files; for one machine of two, a mesh item on both, a node item on one, a home item only in that home; a setting naming the marketplace keys is overridden | ADR 0216 | | the module's test: a home name the person already uses is refused, unregistering removes only the placed path, and an item above 256 KiB is refused | ADR 0216, ADR 0182 | | live: a skill registered at the mesh scope is offered as `nox-mesh:` in a new session on each machine | ADR 0216 | -| the console's provision resolves by co-location; a machine without the console refuses the module by name | ADR 0027, ADR 0152 | -| a new session on the assigned workstation lists the console's five tools under `mesh` ([ADR 0195](../../02-DECISIONS/0195-the-meshs-tools-are-found-by-address-not-announced-whole.md)) and answers "which node am I" from the instruction file | the exit of the build | +| the mesh MCP server's provision resolves by co-location; a machine without the mesh MCP server refuses the module by name | ADR 0027, ADR 0152 | +| a new session on the assigned workstation lists the mesh MCP server's five tools under `mesh` ([ADR 0195](../../02-DECISIONS/0195-the-meshs-tools-are-found-by-address-not-announced-whole.md)) and answers "which node am I" from the instruction file | the exit of the build | ## What this does not settle @@ -285,7 +285,7 @@ operator's word. directory in it, bound to its own licence through the manager (to-be 39 §5); that is for when workers exist, and nothing here changes for it. - **The package repository seat** (§7). -- **How the module's tools are run** is decided: the node's tool runtime, host-side +- **How the module's tools are run** is decided: the node's tool runner, host-side ([ADR 0175](../../02-DECISIONS/0175-one-tool-runtime-per-node-serves-every-modules-tools-on-the-host-side.md)). The managed files and the credential write are tools of this module that runtime serves. Until the runtime exists on every node, the module's code runs as a supervised process of its own @@ -297,7 +297,7 @@ operator's word. - [ADR 0181](../../02-DECISIONS/0181-the-operator-account-is-a-node-fact-and-a-home-is-a-placement-root.md), [ADR 0182](../../02-DECISIONS/0182-inside-a-home-the-mesh-owns-what-it-places-and-holds-the-rest-as-found.md), [ADR 0183](../../02-DECISIONS/0183-the-anthropic-licence-manager-is-a-module-and-hands-tokens-to-the-agent-over-the-bus.md) — the decisions -- [39 — The Anthropic licence manager](39-the-anthropic-licence-manager.md), [34 — The console](34-the-console.md), [29 — A node has operator accounts](29-a-node-has-operator-accounts.md) +- [39 — The Anthropic licence manager](39-the-anthropic-licence-manager.md), [34 — The mesh MCP server](34-the-console.md), [29 — A node has operator accounts](29-a-node-has-operator-accounts.md) - [research 018](../../01-RESEARCH/018-the-operators-machine-as-modules/00-overview.md) — the operator's machine as modules, and where tools run - the vendor's documentation on managed settings, managed tool servers, the managed instruction file and the key-helper, read 2026-10-02 - the predecessor's two modules and the six files on the workstations, read 2026-10-02 diff --git a/03-DESIGN/01-to-be/37-the-operators-machine.md b/03-DESIGN/01-to-be/37-the-operators-machine.md index f5ab9a5e..83e728ff 100644 --- a/03-DESIGN/01-to-be/37-the-operators-machine.md +++ b/03-DESIGN/01-to-be/37-the-operators-machine.md @@ -21,8 +21,8 @@ decisions: **Every configurable thing on a node is a module, the home included, and the same catalogue serves a server and a laptop.** One default configuration per module, varied per node by a setting or a -kept region; roles a machine has once as node-scoped seats with tool contracts; one tool runtime -per node serving every module's tools on the host side +kept region; roles a machine has once as node-scoped seats with tool contracts; one tool runner +per node serving every module's tools outside any container ([ADR 0173](../../02-DECISIONS/0173-the-operators-machine-is-the-meshs-and-a-module-is-what-it-declares.md) to [0177](../../02-DECISIONS/0177-a-unit-may-be-user-scoped-and-the-service-manager-is-a-node-seat.md)). This is the design [to-be 29](29-a-node-has-operator-accounts.md) §2 called *a family* and @@ -67,15 +67,15 @@ the modules whose files read them. Until the settings record proposed alongside container-runtime records ships — a setting names the file it lands in — environment modules carry defaults in their files and declare no setting; that is the order, not a preference. -## 3. The node tools runtime +## 3. The tool runner -One per node, started and restarted by the host as a sibling process, never a container +One per node, started and restarted by the node-engine as a sibling process, never a container ([ADR 0175](../../02-DECISIONS/0175-one-tool-runtime-per-node-serves-every-modules-tools-on-the-host-side.md)). -It is the tool runtime that exists, in the role it was written for: it reads the memberships of +It is the tool runner that exists, in the role it was written for: it reads the memberships of every module assigned to the node, loads each module's tools bundle, and serves every tool and every held seat's verb on the subjects issued. It holds the node's one bus credential and may call -every tool on the mesh. Its serving mode on the machine's loopback is what the console was -([to-be 34](34-the-console.md)); the module is renamed **node-tools** and declares the interpreter +every tool on the mesh. Its serving mode on the machine's loopback is what the mesh MCP server was +([to-be 34](34-the-console.md)); the module is renamed **`node-tools`** and declares the interpreter it needs as a package. A bundle reaches the node as any artifact does. A push that adds or replaces one is a reload. A @@ -99,29 +99,29 @@ that is its own server. Whether a held seat can gate an assignment is the first resolver is asked by the second graphical module; the display server itself is gated by the `graphical-session` capability the profile already reports. -## 5. What the host gains, and what it does not +## 5. What the node-engine gains, and what it does not - `service` gains `scope: user`, applied as the account ([ADR 0177](../../02-DECISIONS/0177-a-unit-may-be-user-scoped-and-the-service-manager-is-a-node-seat.md)). -- The host starts and supervises the node tools runtime as it would any host-side process, and +- The node-engine starts and supervises the tool runner as it would any host-side process, and delivers bundles as artifacts. - Nothing else. No hooks, no actions: `chsh` is the `user` shape, enabling a unit is the `service` shape, rebuilding boot images is a verb of the boot seat when that seat is written. - A gap, recorded: the `package` shape drives the distribution's package manager and nothing outside its repositories. The login manager in use is such a package; it waits on an official - package or a decision the host does not yet have. + package or a decision the node-engine does not yet have. ## 6. The order of the build 1. **The operator account on every node** — `mesh-controller node` with the login name; empty on all four today. Nothing home-scoped composes before it. -2. **The node tools runtime** — mesh-host supervises it; mesh-tools serves bundles from memberships +2. **The tool runner** — `mesh-host` supervises it; mesh-tools serves bundles from memberships and reloads; mesh-controller composes the bundle into the declaration and the memberships to one - runtime per node; the catalogue renames the console. Proven when the packet-filter verbs answer + runtime per node; the catalogue renames the mesh MCP server. Proven when the packet-filter verbs answer from it and its container is gone. 3. **`zsh`**, the first environment module: seat, `user` shape, home files, `execute`. Proven on a server first, then every node. -4. **`systemd`** and user scope: the host's field, the seat seeded, the module. Proven by the +4. **`systemd`** and user scope: the node-engine's field, the seat seeded, the module. Proven by the desktop's reload watcher declared `scope: user` on a workstation. 5. **The login manager**, system scope, once its package is installable; then the display server, the window manager, and the rest of the graphical stack, each seat its own record. @@ -133,8 +133,8 @@ resolver is asked by the second graphical module; the display server itself is g |---|---| | A module with a package, home files, a seat and a bundle resolves and composes on a node with an account, and is refused on one without | the controller's composition tests | | One runtime per node serves every assigned module's tools; a per-module tool container no longer exists | the runtime's tests; `docker ps` on a converged machine | -| A user-scoped unit is applied as the account | the host's tests | -| A node's difference from a module's default is visible as a setting with a source or a kept region | `mesh-controller.settings`; the host's write-into tests | +| A user-scoped unit is applied as the account | the node-engine's tests | +| A node's difference from a module's default is visible as a setting with a source or a kept region | `mesh-controller.settings`; the node-engine's write-into tests | | The same manifests assign to a server and a workstation; the graphical ones are refused on the server by name | the resolver's tests and the live mesh | ## References @@ -143,5 +143,5 @@ resolver is asked by the second graphical module; the display server itself is g - [To-be 29](29-a-node-has-operator-accounts.md) — the account and the home; this design is the family its §2 names, beyond `~/.ssh`. - [To-be 33](33-the-tools-the-mesh-answers.md), [to-be 34](34-the-console.md) — the tools and - the console, amended by ADR 0175. -- [To-be 05](05-the-node-host.md) — the host's vocabulary, widened by ADR 0177. + the mesh MCP server, amended by ADR 0175. +- [To-be 05](05-the-node-host.md) — the node-engine's vocabulary, widened by ADR 0177. diff --git a/03-DESIGN/01-to-be/38-building-the-operators-machine.md b/03-DESIGN/01-to-be/38-building-the-operators-machine.md index 005243e2..d156f1b0 100644 --- a/03-DESIGN/01-to-be/38-building-the-operators-machine.md +++ b/03-DESIGN/01-to-be/38-building-the-operators-machine.md @@ -46,15 +46,15 @@ as design 28's: nothing here is new ground; every package reshapes something sta | Piece | Today | Size | Becomes | |---|---|---|---| -| the tool runtime | TypeScript: loads `MESH_TOOL_MODULES`, serves one module's tools and its claimed seats' verbs; `serve` is the console | ~1 700 lines over six files | loads every assigned module's bundle; `serve` is node tools | -| the host's `process` shape | Go: fetch a bundle by digest, unpack under the mesh's daemons directory, write the unit, run it | 343 lines | **unchanged** — the runtime is one such process | -| the host's `archive` shape | Go: fetch and unpack an artifact at a path | 185 lines | **unchanged** — a module's tools bundle is one such archive | +| the tool runner | TypeScript: loads `MESH_TOOL_MODULES`, serves one module's tools and its claimed seats' verbs; `serve` is the mesh MCP server | ~1 700 lines over six files | loads every assigned module's bundle; `serve` is tool runner | +| the node-engine's `process` shape | Go: fetch a bundle by digest, unpack under the mesh's daemons directory, write the unit, run it | 343 lines | **unchanged** — the runtime is one such process | +| the node-engine's `archive` shape | Go: fetch and unpack an artifact at a path | 185 lines | **unchanged** — a module's tools bundle is one such archive | | the controller's bus principals | Go: one principal per module per node, grants from what it declares | 132 lines | gains one principal per node for the runtime | | the controller's memberships | Go: one per assignment, the subjects a runtime serves | 143 lines | **unchanged** in shape; the runtime reads several | | the controller's declaration composer | Go, one file | 2 053 lines | gains the runtime's process, the bundles' archives, two env words | | the catalogue | 35 manifests build a per-module tool container on the runtime's base image | — | none do; the runtime is a module of its own | -**Two measurements decide the shape.** The host needs no change: a `process` and an `archive` are +**Two measurements decide the shape.** The node-engine needs no change: a `process` and an `archive` are what the runtime and a bundle are, and both are applied today. And the runtime already does nine-tenths of the job — the loop over entrypoints, the seat verbs, the membership subscription — for one module; the work is to let it do the same for a list. @@ -126,7 +126,7 @@ with the shortcut off answers the same. held seat's verbs on that node, its invoking grant is `*`, and it consumes nothing. The per-module memberships are composed as today; nothing else on the bus learns a new shape. 2. **Bundle delivery.** For every assigned module whose build produced a `bundle`, the node's - declaration gains an `archive` placed under a directory the controller derives, so the host + declaration gains an `archive` placed under a directory the controller derives, so the node-engine fetches and unpacks it as it does any artifact. The bundle's digest is what the build recorded. 3. **The runtime's process.** One `process` per node running the runtime from its own bundle (WP3), `MESH_TOOL_MODULES` composed from the unpacked entrypoints — each as @@ -152,18 +152,18 @@ process, three archives, one node principal whose grants are the union, and the memberships as before. The gate's test: the packet-filter manifest as it is today is refused once the runtime is registered. -## WP3 — The runtime is a module, and the console is its serving mode +## WP3 — The runtime is a module, and the mesh MCP server is its serving mode *mesh-tools and mesh-catalog. A day.* **What changes.** mesh-tools gains a `bundle` artifact of itself beside its images, and its manifest -becomes the `node-tools` module: a package for the interpreter, the loopback listener the console +becomes the `node-tools` module: a package for the interpreter, the loopback listener the mesh MCP server declared, `invokes: *`, and nothing else — the process is the controller's to compose (WP2). In the catalogue, `mesh-console` is retired as a module and `node-tools` assigned where it was. The -runtime's `serve` keeps answering MCP on loopback; the person's end of it keeps the name *console* +runtime's `serve` keeps answering MCP on loopback; the person's end of it keeps the name "console" ([glossary](../../00-META/glossary.md)). -**Proof.** On every node: the console's container is gone, `node-tools` runs as a unit the host +**Proof.** On every node: the mesh MCP server's container is gone, `node-tools` runs as a unit the node-engine wrote, `tools/list` on loopback answers as before, and the controller's verbs answer through it. This is the first live step, and it is reversible by re-assigning `mesh-console`. @@ -175,8 +175,8 @@ WP3 found that the plan did not say: a TypeScript bundle must carry its dependen the runtime's credential must be owned by the account the runtime runs as, which the controller composes; and `MESH_TOOL_MODULES` is empty on a node where the runtime is the only bundle, which the runtime accepts. *Built 2026-10-02* (mesh-tools `c46f950`, mesh-controller `ca7e81e` `773b561` -`729a5f9`). *Proven live 2026-10-02/03, on all four machines*: the console's container is gone, -`node-tools` runs as a unit the host wrote, as the operator's account, `tools/list` on each loopback +`729a5f9`). *Proven live 2026-10-02/03, on all four machines*: the mesh MCP server's container is gone, +`node-tools` runs as a unit the node-engine wrote, as the operator's account, `tools/list` on each loopback answers with the same 219 tools as before, and the controller's verbs answer through it; `mesh-console` retired from the catalogue. Three things the step found are issues [203](../../04-ISSUES/203-a-fresh-assignment-is-pushed-before-its-credential-exists/00-report.md), @@ -208,7 +208,7 @@ filter from the path the manifest's `filtering` names rather than from a variabl to carry, a test holding the two together. Three things a review of the change found: the module's own bus credential and state directory went with the container, since nothing reads them once the runtime speaks with the node's (the shell module of WP5 declares neither); the `iptables` package the -image used to carry is now declared on the host; and that the operator's account may escalate without +image used to carry is now declared on the node-engine; and that the operator's account may escalate without a prompt is a fact about the machine the mesh neither declares nor checks — true on all four today, and when it is not, the tool names it by how it failed, which is the only check there is until a record says where the fact belongs. @@ -219,7 +219,7 @@ file and answers with the table, `remove` refuses the mesh's own table by name `mesh-nftables` on any, the container's credential is gone with it, and `status` is well. One thing the step found is issue [210](../../04-ISSUES/210-the-host-re-creates-the-nodes-runtime-on-every-reconcile/00-report.md): -the host re-creates the runtime's process on every reconcile (resolved the same day, mesh-host #80). +the node-engine re-creates the runtime's process on every reconcile (resolved the same day, `mesh-host` #80). *fail2ban followed 2026-10-03* (mesh-catalog `aa5bf7d`), the same shape: container, base images, credential and state directory gone, the client through `sudo` since the daemon's socket is root's; proven on all four machines — `status`, `banned` and the module's own `fail2ban_settings` answer from @@ -241,7 +241,7 @@ beside the bundle's archive, `restart-on` included; the runtime hands each bundl environment — the contributor's argument for an imported bundle, the child's environment for a launched one — and a test holds two bundles apart. Then the thirty-one remaining tool containers move in one change: each container's `env` becomes its tools artifact's, mount targets folded into -the host paths they came from, the container, its base images, its Dockerfile and its own bus +the node-engine paths they came from, the container, its base images, its Dockerfile and its own bus credential gone. Last, the registration gate refuses the container shape for every module. **Proof.** The controller's and the runtime's tests named in ADR 0192; live, every module's tools @@ -273,7 +273,7 @@ serves as. The builder writes, beside every TypeScript entrypoint, an executable imports it and serves what it registered; the composer names the launcher where it named the entrypoint. The runtime launches every served entrypoint and imports none; the resolve hook and the per-registration environment go. Proven live on all four machines. Then the runtime is rewritten in -Go against the same contract — the bus, the memberships and seats, the launcher, the console's MCP +Go against the same contract — the bus, the memberships and seats, the launcher, the mesh MCP server's MCP over HTTP — and replaces the TypeScript one, proven the same way. **Proof.** The tests ADR 0193 names; live, every moved module's tools and both node seats answer from @@ -281,9 +281,9 @@ launched bundles on every machine, and then do again from the Go runtime. *Built and proven live 2026-10-03.* mesh-sdk #13/#14 (0.1.4, 0.1.5: served as the named module; an emit travels through the runtime), mesh-controller #239/#240 (a launcher beside every TypeScript -entrypoint; a runtime compiled to a binary runs itself), mesh-host #81 (`./name` is the process's own +entrypoint; a runtime compiled to a binary runs itself), `mesh-host` #81 (`./name` is the process's own binary), mesh-tools #35/#36/#37/#38 (the module named; launch-only; the toolchain requiring 0.1.5; the -runtime in Go). On all four machines node-tools is now the Go binary, launching every served bundle: +runtime in Go). On all four machines `node-tools` is now the Go binary, launching every served bundle: both node seats answered from it on every machine and the four moved modules on theirs. Found on the way: issue [212](../../04-ISSUES/212-a-toolchain-rebuild-keeps-the-sdk-it-cached/00-report.md) (the toolchain image kept a cached SDK, and the seats' verbs went unanswered on three machines for an hour), @@ -293,7 +293,7 @@ and the controller's plan losing track of its own rebuild when it restarts mid-p *Not yet broken down.* Twenty-three containers carry code that is not a tool: event handlers, provisioners, a step, a main. [ADR 0188](../../02-DECISIONS/0188-a-modules-own-code-is-bundles-in-any-language-and-a-tools-bundle-speaks-mcp-to-the-runtime.md) -§3 already says such code is a `process` bundle the host runs. What no record says yet is how that +§3 already says such code is a `process` bundle the node-engine runs. What no record says yet is how that process is given what its container was: the module's own bus credential and the subscriptions it consumes with, the words its code reads at import, the packages the image installed (a database's client), and the service it reaches by a container network name. That begins with a decision record, @@ -312,7 +312,7 @@ three mains). *Built 2026-10-04.* Thirty-four modules no longer run their own code in a container: the first wave (mesh-catalog#245), the mesh's own and the media modules (mesh-catalog#248, mesh-media-catalog#13), -with a step run where and as it is declared (mesh-host#85) and a process's words filled like a +with a step run where and as it is declared (`mesh-host`#85) and a process's words filled like a container's (mesh-controller#250). **Proven live** on every machine that runs them: each moved module's tools answer from the node's runtime, the steps run as their oneshot units, and the forge's merge events reach the build pipeline from the runtime — the merge after the move started its own @@ -335,7 +335,7 @@ tool of each. assigning found that the shell module would duplicate every machine's existing startup file, drop lines from it, leave the prompt uninstalled, and could not be unassigned ([issue 228](../../04-ISSUES/228-a-login-the-mesh-set-is-never-given-back/00-report.md)). The shell, -its environment, the modules that plug into it, and the host's fix are built and proven there. What +its environment, the modules that plug into it, and the node-engine's fix are built and proven there. What follows is the original plan, kept for the record. *mesh-catalog #224, already written. Half a day to assign and prove.* @@ -348,12 +348,12 @@ beside the holder — are the first follow-up record after this document. ## WP6 — The service manager, on a workstation -*mesh-host #72 merged first; mesh-catalog #224. Half a day.* +*`mesh-host` #72 merged first; mesh-catalog #224. Half a day.* -**Order.** Merge the host's user-scope change and let it roll. Assign `systemd` everywhere; +**Order.** Merge the node-engine's user-scope change and let it roll. Assign `systemd` everywhere; `node-service-manager.units@ scope=user` answers on a workstation. Then the first user-scoped unit the mesh sends: the window manager's reload watcher, declared `scope: user` by the window -manager module when WP7 writes it — until then, the host's change is proven by its tests and by +manager module when WP7 writes it — until then, the node-engine's change is proven by its tests and by the verb answering. ## What is deliberately not here diff --git a/03-DESIGN/01-to-be/39-the-anthropic-licence-manager.md b/03-DESIGN/01-to-be/39-the-anthropic-licence-manager.md index fcde53d6..8b5fadc7 100644 --- a/03-DESIGN/01-to-be/39-the-anthropic-licence-manager.md +++ b/03-DESIGN/01-to-be/39-the-anthropic-licence-manager.md @@ -108,7 +108,7 @@ licence of all its sessions at once. A worker is a consumer of its own because i own, with its own agent directory and credentials file, which the agent module on that node writes for it as it writes the operator's — the predecessor ran its agents exactly so. -**Binding is a person's act through the seat's verbs**, listed by the console: `bind`, `switch`, +**Binding is a person's act through the seat's verbs**, listed by the mesh MCP server: `bind`, `switch`, `release`. **Exhaustion is observed, not acted on**: usage is read every few minutes, a crossing of a declared threshold in the five-hour window is notified once per crossing, and moving a consumer is the operator's call. Switching remains a reaction, not a declaration @@ -178,7 +178,7 @@ one definition serves and one mesh may differ. | a worker bound to a dead licence is refused, never answered with another licence's token | ADR 0183, the fallbacks | | a grant offered with a mismatching identity is refused and one notification emitted | ADR 0183, attribution | | a failing licence notifies once, and once a day after, not once per tick | §3 | -| the console lists the seat's verbs and `switch` changes a workstation's token end to end | ADR 0132, the exit of the build | +| the mesh MCP server lists the seat's verbs and `switch` changes a workstation's token end to end | ADR 0132, the exit of the build | ## What this does not settle diff --git a/03-DESIGN/01-to-be/40-building-the-operators-agent-and-its-licence-manager.md b/03-DESIGN/01-to-be/40-building-the-operators-agent-and-its-licence-manager.md index 1abd18f3..ddd912b4 100644 --- a/03-DESIGN/01-to-be/40-building-the-operators-agent-and-its-licence-manager.md +++ b/03-DESIGN/01-to-be/40-building-the-operators-agent-and-its-licence-manager.md @@ -23,12 +23,12 @@ dependencies allow.** The two designs are the authority on *what* is built; this packages, their order, their sizes and their proofs, and is wrong the moment it disagrees with them. It is the shape [design 38](38-building-the-operators-machine.md) gives the operator's machine. -*Revised 2026-10-03, after design 38's WP1–WP4b ran:* the node's tool runtime is live on all four +*Revised 2026-10-03, after design 38's WP1–WP4b ran:* the node's tool runner is live on all four machines, tools are bundles it serves and each is given only the words its artifact declares, every bundle is a child the runtime launches over stdio and is the bus for ([ADR 0193](../../02-DECISIONS/0193-every-bundle-the-runtime-serves-is-launched-and-the-runtime-knows-no-language.md), [ADR 0198](../../02-DECISIONS/0198-a-modules-long-running-code-is-launched-by-the-node-runtime-and-reaches-the-bus-through-it.md)), -and the console offers five tools over addresses +and the mesh MCP server offers five tools over addresses ([ADR 0195](../../02-DECISIONS/0195-the-meshs-tools-are-found-by-address-not-announced-whole.md)). What changed in this plan: the wait on design 38's WP3 is over; the manager starts every exchange, by the operator's direction (ADR 0183's dated note); and the manager's daemon is a long-running bundle the runtime launches, @@ -52,7 +52,7 @@ Measured 2026-10-03 on the four machines and in the repositories. | Piece | Today | Becomes | |---|---|---| -| the node's tool runtime | live on all four, a host process; **runs as the operator account**; listens for the console on loopback at a port its own code fixes; launches or imports every assigned module's tools bundle and hands each its declared words | serves the agent module's tools; gains one provision for its endpoint (WP1) | +| the node's tool runner | live on all four, a host process; **runs as the operator account**; listens for the mesh MCP server on loopback at a port its own code fixes; launches or imports every assigned module's tools bundle and hands each its declared words | serves the agent module's tools; gains one provision for its endpoint (WP1) | | the operator account | **stated on all four** — the runtime runs as it | read by the agent module from the runtime's own words | | escalation | passwordless `sudo` for the operator account on all four — a fact about the machines, checked by nobody | how the agent module writes its managed directory under `/etc` | | the agent itself | installed on all four, at four different versions, all above the one the managed tool-server key needs | declared as the module's package | @@ -107,7 +107,7 @@ runtime's port; the controller's tests and the catalogue's checks pass. **What is written**, as design 36 says: 1. **The manifest.** The agent's package. A state directory. A facts file in it, rendered by the mesh: - the node's name and the console's address from `mcp-endpoint`. A settings file in it, merged from + the node's name and the mesh MCP server's address from `mcp-endpoint`. A settings file in it, merged from the module's settings layers: the node's role and the extra tool servers. A tools bundle whose words name the two files, the state directory and nothing else. The two directories it owns declared — the agent's managed directory and `~/.claude` — and **no file resource under either.** @@ -124,7 +124,7 @@ runtime's port; the controller's tests and the catalogue's checks pass. manager's `bindings` key for this node, which asks the seat's `current` on a newer generation and applies the sealed answer — only if newer within one lineage unless it is a switch; the credentials write as the operator, access-token-only, atomic; the key-helper program for an API key. -5. **The documentation**: the six predecessor files and the hand-made console entry a person removes. +5. **The documentation**: the six predecessor files and the hand-made mesh MCP server entry a person removes. **Proof, before anything runs live.** Unit tests: the renderer writes the mesh's keys and nothing else; it writes nothing when nothing changed; the credentials write strips a refresh token and is atomic; the @@ -133,8 +133,8 @@ contains a token. The catalogue's checks pass. **Proof, live, on one workstation, configuration only.** Assign the module; set the node's role; push. The agent's managed directory holds the two files; everything under the person's agent directory is -byte-identical to before; a new session lists the console's five tools under `mesh` and answers *which node am -I* from the managed instruction file. `claude_code_status` answers through the console. No licence is +byte-identical to before; a new session lists the mesh MCP server's five tools under `mesh` and answers *which node am +I* from the managed instruction file. `claude_code_status` answers through the mesh MCP server. No licence is touched: the module writes the credentials file only when it is handed a token. ## WP3 — The manager's code, built and tested @@ -166,7 +166,7 @@ account the nodes are logged in to, by refreshing the newest login's grant (ADR binding is bound to the account it reported. Adopt the API key from a file there. A second subscription account enters by a login on a workstation carrying the agent module. -**Proof.** Through the console: `anthropic-licence-manager.licences` lists three licences with identity +**Proof.** Through the mesh MCP server: `anthropic-licence-manager.licences` lists three licences with identity and expiry; within the cadence the audit shows a rotation and a later expiry; a forced `refresh` is logged with the vendor's answer. @@ -175,7 +175,7 @@ logged with the vendor's answer. *The live mesh. Half a day. The proof of the whole.* **Order.** Record the checksums under the person's agent directory. Bind the workstation to a -subscription licence. Remove the six predecessor files and the hand-made console entry. Start a session. +subscription licence. Remove the six predecessor files and the hand-made mesh MCP server entry. Start a session. **Proof.** Everything under the person's agent directory is byte-identical but the credentials file, which is owned by the operator, readable by nobody else, and names no refresh token. A session makes a diff --git a/03-DESIGN/01-to-be/41-the-shell-and-the-accounts-environment.md b/03-DESIGN/01-to-be/41-the-shell-and-the-accounts-environment.md index 49c2eb0b..6305e40c 100644 --- a/03-DESIGN/01-to-be/41-the-shell-and-the-accounts-environment.md +++ b/03-DESIGN/01-to-be/41-the-shell-and-the-accounts-environment.md @@ -38,7 +38,7 @@ short of its user-scoped units. The decisions are [ADR 0203](../../02-DECISIONS/ ``` **The environment module** (`node-env`) holds `node-environment`. It has no package and no process. -Its two files are written by the host from placeholders the controller fills. +Its two files are written by the node-engine from placeholders the controller fills. **The shell module** (`zsh`) holds `node-login-shell`. It: @@ -89,9 +89,9 @@ WP1, WP2 and WP4 are independent, and are built in parallel on one feature branc ([playbook 07](../../00-META/process/07-feature-branches.md)). WP3 is written in parallel and proven against WP2's controller before anything is published. -## WP1 — The host gives a login back +## WP1 — The node-engine gives a login back -*mesh-host. Half a day. [Issue 228](../../04-ISSUES/228-a-login-the-mesh-set-is-never-given-back/00-report.md).* +*`mesh-host`. Half a day. [Issue 228](../../04-ISSUES/228-a-login-the-mesh-set-is-never-given-back/00-report.md).* **What changes.** @@ -103,14 +103,14 @@ against WP2's controller before anything is published. The exception is a shell that refuses logins (`nologin`, `false`): the distribution does not list those, and the controller's own account uses one, so it need only be executable. The refusal fails that resource and leaves the account untouched. -- A directory the host creates on the way to a file, a block or an archive inside an account's home - belongs to that account, the home itself included when the host makes it. A directory that was +- A directory the node-engine creates on the way to a file, a block or an archive inside an account's home + belongs to that account, the home itself included when the node-engine makes it. A directory that was already there keeps its owner and mode (ADR 0182). Until this, a fresh account's `~/.config` or `~/.local/share` would have been created as root's. - Giving the shell back is reported, never fatal. A failed `usermod` on removal is named in the outcome and the record is dropped, because a fatal removal is exactly the wedge issue 228 is about. -**Proof.** The host's tests: +**Proof.** The node-engine's tests: - an undeclared `user` no longer stops the apply; - the found shell comes back; @@ -203,7 +203,7 @@ A rehearsal composition for a node holding all five shows: - `status` says whether the mesh declares the unit. The restore note is attached only to such a unit. - Tests cover a fake runner. -The user-scoped units of mesh-host #72 stay to-be 38's WP6. +The user-scoped units of `mesh-host` #72 stay to-be 38's WP6. **Proof.** The module's tests. Live, after WP5: @@ -216,7 +216,7 @@ The user-scoped units of mesh-host #72 stay to-be 38's WP6. **Order.** -1. Merge WP1 and roll the host. +1. Merge WP1 and roll the node-engine. 2. Merge WP2, and push the controller. 3. Merge WP3 and WP4, and build the new modules by hand: a new catalogue module's first build is asked for, not automatic. diff --git a/03-DESIGN/01-to-be/42-the-machines-modules-in-order.md b/03-DESIGN/01-to-be/42-the-machines-modules-in-order.md index c798be98..eb2eec0e 100644 --- a/03-DESIGN/01-to-be/42-the-machines-modules-in-order.md +++ b/03-DESIGN/01-to-be/42-the-machines-modules-in-order.md @@ -60,7 +60,7 @@ In order: | | module | owns | improves | |---|---|---|---| | 1 | `sudo` | the operator account's escalation, as a drop-in it owns | declares what three modules' tools assume and nothing stated | -| 2 | `localization` | locale, time zone, console keymap | one machine on another zone and keymap | +| 2 | `localization` | locale, time zone, mesh MCP server keymap | one machine on another zone and keymap | | 3 | `time-sync` | timesyncd and its servers | two different daemons across four machines | | 4 | `pacman` | the package manager's configuration, mirrors and their refresh, cache cleaning | mirrors generated once and never again; caches never cleaned | | 5 | `logrotate` | the timer and base configuration | rotation running on one machine of four | diff --git a/03-DESIGN/01-to-be/45-a-core-that-cannot-fail-silently.md b/03-DESIGN/01-to-be/45-a-core-that-cannot-fail-silently.md index 5416f5a7..7148f1ea 100644 --- a/03-DESIGN/01-to-be/45-a-core-that-cannot-fail-silently.md +++ b/03-DESIGN/01-to-be/45-a-core-that-cannot-fail-silently.md @@ -28,8 +28,8 @@ repair, replaces itself one machine at a time with something other than itself w against the mesh's real facts before a change merges** ([ADR 0227](../../02-DECISIONS/0227-the-core-holds-nine-rules-each-checked-and-is-built-to-them-in-six-phases.md), from [research 031](../../01-RESEARCH/031-a-core-that-cannot-fail-silently/00-overview.md)). -**The core**, here: the controller, the node-engine and its launcher, the bus server, the node tools and -the console, the build seat, and the forge's announcer of merges. +**The core**, here: the controller, the node-engine and its launcher, the bus server, the tool runner and +the mesh MCP server, the build seat, and the forge's announcer of merges. This document is the build's specification. §1–§9 are the parts; §10 is the order they are built in, each phase with what it delivers, in which repository, and when it is done. Every bound marked @@ -50,9 +50,9 @@ each phase with what it delivers, in which repository, and when it is done. Ever ``` Owning repositories: **mesh-controller** (the condition store, watchdogs, `doctor`, healers, the lease, -calls, the hand-act log, the facts snapshot), **mesh-host** (the node-engine: the apply queue, report -order, epoch refusal, the `report` verb, rollback witnessing), **mesh-tools** (the node tools and the -console: their heartbeat, passing every argument, health answers), **mesh-catalog** (the +calls, the hand-act log, the facts snapshot), **`mesh-host`** (the node-engine: the apply queue, report +order, epoch refusal, the `report` verb, rollback witnessing), **mesh-tools** (the tool runner and the +mesh MCP server: their heartbeat, passing every argument, health answers), **mesh-catalog** (the `operator-channel` seat's holder and channels, the watcher, the providers' retirement, the catalogue's merge gate), **mesh-sdk** (the TypeScript providers' loop and its retirement), **mesh-lab** (the replays and the induced-failure scenarios). @@ -195,11 +195,11 @@ signal and asserts its condition. `doctor signals` shows, for every row, the age | S4 | the controller's event loop takes a message | controller | while its consumer has pending messages | 2 min | `controller-deaf` | urgent | — | | S5 | a merge announced becomes a plan or *nothing reads it* | announcer → controller | each merge | 10 min (exists, issue 266) | `merge-not-acted` | urgent | — | | S6 | a build asked → its outcome | build seat | each ask | max(20 min, 3 × the p90 of measured builds), 1 h while nothing is measured, *provisional* (the build seat declares no timeout) | `ask-lost` | warning | — | -| S7 | a call running → finished | controller | each call | the verb's bound: push, rotate and command 30 min; assign and unassign 15 min; doctor 3 min; others 10 min | `call-hung` | warning | — | +| S7 | a call running → finished | controller | each call | the verb's bound: push, rotate and command 30 min; assign and unassign 15 min; `doctor` 3 min; others 10 min | `call-hung` | warning | — | | S8 | a provider's failing word repeated | provider | every 15 min while failing (ADR 0224) | 30 min | `provider-silent` | warning | — | | S9 | bus advisories: slow consumer, maximum deliveries, permission violation, consumer deleted | bus server's system subjects | any | any occurrence | `slow-consumer`, `max-deliveries`, `refused`, `consumer-lost` — each naming the call, consumer or module in the mesh's words | warning | H3 for `consumer-lost` | | S10 | the self-check's heartbeat | controller's `doctor` | every run | 2 × its interval, watched **from the second machine** (§5) | `self-check-silent` | urgent | — | -| S11 | node tools heartbeat | node tools | its interval | 3 × interval, *provisional* | `tools-silent` | warning | — | +| S11 | tool runner heartbeat | tool runner | its interval | 3 × interval, *provisional* | `tools-silent` | warning | — | | S12 | the controller lease renewed | controller | every 5 s | 15 s; a holder that lost the lease or was found expired, and a lease bucket found raised again from nothing, said for an hour; a controller serving without the lease, while it does | `lease-lost` | urgent | — | | S13 | stale refusals | every receiver (rule 2) | each refusal | more than 5 from one writer in 5 min | `stale-writer` (names the writer: the controller epoch a refused declaration claimed, with its instance and how its lease ended; or the machine whose older accounts the controller refused) | warning | — | | S14 | facts snapshot exported | controller | when it moved, and daily | 2 days, or none kept by a controller up that long (Phase 5) | `facts-stale` | warning | — | @@ -240,7 +240,7 @@ producer that wrote it. | Probe | Asserts | From | |---|---|---| -| D1 | every machine's declaration composes, and passes the node-engine's validation (the validator is a package of mesh-host the controller and the merge gate import — one validator) | 236, 263 | +| D1 | every machine's declaration composes, and passes the node-engine's validation (the validator is a package of `mesh-host` the controller and the merge gate import — one validator) | 236, 263 | | D2 | every holder of the mesh's resolver answers a machine name for IPv4, and NODATA for IPv6 | 262 | | D3 | every seat on record has a live holder that answers (`holder-silent`, H3) | 208, 218 | | D4 | every kept archive is held by a manifest | 253 | @@ -249,7 +249,7 @@ producer that wrote it. | D7 | every stream the controller defines exists with its definition | 208 | | D8 | no address the mesh owns is in a ban list | 238 | | D9 | `status` answers in full within ten seconds | 265 | -| D10 | every machine runs the node-engine and node tools builds its plan says, or is inside a plan's window | version split | +| D10 | every machine runs the node-engine and tool runner builds its plan says, or is inside a plan's window | version split | | D11 | no provider holds a consumer retired more than thirty days: each provider assigned is asked `provisioner_retirement` on its machine (ADR 0230); one that does not serve it yet is named, not failed | ADR 0230 | | D12 | every consumer of a provision that keeps its data is bound where it was last sent, or moves by a pin (`binding-kept`, `binding-moving`, `binding-moved`) | ADR 0232 | | D13 | every item of data a machine declares is measured, is there, holds what it held, is written where it says it is, is backed up within its bound or sits on healthy redundant storage, and is no empty replacement of a copy kept elsewhere; an irreplaceable or valuable item a machine no longer declares is retired, not forgotten. Each machine's `node-backup` holder is asked `backed-up`; every provider of kept consumer data `provisioner_retirement` (with each held consumer's size) — see §2 for the kinds | ADR 0233, issue 273 | @@ -259,8 +259,8 @@ producer that wrote it. | DB | a bus upgrade a person started is said as `bus-maintenance` while it runs, and ends healthy within its bound or is said `bus-upgrade-failed`, with its snapshot (ADR 0236) | rule 8 | - **`doctor`** answers the last run's verdict at once: per probe, pass, fail or failed-to-run, and age. - **`doctor run`** runs now under a call id. **`doctor probes`** lists the registry; **`doctor - signals`** the table's ages. + **`doctor run`** runs now under a call id. **`doctor probes`** lists the registry; **`doctor signals`** + the table's ages. - **Every run ends with a heartbeat** event carrying the run's id and counts. That is S10. - **The registry is the design's live form.** A check over the to-be designs counts invariants that name a probe against those that do not; the number without may only go down. @@ -409,7 +409,7 @@ except an act recorded by a verb that is a person's decision by design, which is counts, whatever cause it gives. The controller keeps one table of the verbs that write the log, and each says whether it records a repair or a decision, and why: approving or rejecting a retirement and deleting what was retired (ADR 0230); `bus upgrade`, because the bus is never rolled by the mesh, and -`upgrade release-backlog`, because after a failed release plan the next opens only on a person's word +`upgrade release-backlog`, because after a failed walk the next opens only on a person's word (ADR 0236); and `secret rotate` when its cause is a leak (`leaked-in-logs`) — a person judges what was disclosed, one leak rotates several values, and a leak that recurs is a defect of the module that prints them, an issue against it, not a healer that rotates. A rotation for any other cause counts. A @@ -441,7 +441,7 @@ row no longer sees. |---|---|---| | controller | holds the lease within 60 s of starting; `status` answers in full within 10 s; `doctor` ran once | the node-engine on the control node, which keeps the previous controller build installed beside the new one and reads the lease bucket | | node-engine | has reported its current declaration under its own build | its launcher (ADR 0141), which keeps the known-good | -| node tools | announced, and answer a ping within 5 s | the node-engine, which keeps the known-good | +| tool runner | announced, and answer a ping within 5 s | the node-engine, which keeps the known-good | | bus | every stream and durable consumer present (D6, D7); a request/reply round trip from every machine | none — a planned step, below | **The gate.** ADR 0218's first machine for a core component is judged by that component's health @@ -456,13 +456,13 @@ the other machines follow. "Reported applied" is not enough. ─► plan halts that component, records why ``` -- **Every core rollout leaves a record** in its plan: component, first machine, from and to build, +- **Every core upgrade leaves a record** in its plan: component, first machine, from and to build, verdict, time to verdict, rolled back or not. Read through `plans`. - **A rolled-back build is not retried** by the same plan. A newer merge makes a new plan. **As built** ([ADR 0236](../../02-DECISIONS/0236-a-build-is-judged-on-its-first-machine-and-put-back-by-something-other-than-itself-and-so-it-rolls-out-unattended.md)): -- **Every release plan is gated, not only the core's.** A module is judged by its own health: it reported +- **Every walk is gated, not only the core's.** A module is judged by its own health: it reported applied, no witness put it back, no condition was raised since its send about the machine or about the module there, and its tools are served on that machine where it has tools. Three healthy judgings at least forty seconds apart and two minutes after the send, within ten minutes of it. A policy of @@ -473,19 +473,19 @@ the other machines follow. "Reported applied" is not enough. the send. Said as `build...rolled-back` (warning) or `core... rolled-back` (urgent), `rollback-failed` (urgent, the operator's) when nothing could be put back, and as the event `rolled-back`. -- **The witness, as built on the host** (mesh-host `internal/witness/contract.go`, the controller's half +- **The witness, as built on the node-engine** (`mesh-host` `internal/witness/contract.go`, the controller's half `internal/lease/witness.go`): the controller's is the lease alone — the holder on this machine, taken - since the start, renewed within fifteen seconds, within sixty seconds of the start — and the node tools' + since the start, renewed within fifteen seconds, within sixty seconds of the start — and the tool runner' is their answer to the services protocol's ping within five seconds, within sixty. The controller's "status in bound, doctor ran once" is the controller's own word in the lease's value, which the gate - reads and the host does not: a controller that holds the lease and never becomes ready is put back by + reads and the node-engine does not: a controller that holds the lease and never becomes ready is put back by the gate sending the previous build. A witness says its verdict in `rollbacks` on every report while it stands; the controller raises `core...` from it — urgent for rolled-back, not-reversible, restore-failed and halted — and clears it with the first report without it. - **No build reaches a machine without a gate**: a gated send carries and judges everything waiting on its machine; every other send — a plan's rest, a cascade, a healer's, a whole-mesh push — is refused or leaves the machine while a build no gate has seen waits there; a rebuild with the same artifacts and - manifest is no move. What waits is walked by a **release plan**, one machine at a time, the control + manifest is no move. What waits is moved by a **walk**, one machine at a time, the control node last, each judged; one that fails holds the next until `upgrade release-backlog --why`. - **A new controller that passes its gate sends the bus's machine the user list it composes**, when that changed and nothing held back would go with it. @@ -514,13 +514,13 @@ runs, and a restore that builds a new store beside the live one for a person to **The facts snapshot.** The controller exports daily, and after any change of machines, assignments or seats: every machine (a stable pseudonym of the same length as its name, its role, operating system, -C library, architecture, node-engine and node tools builds), assignments, settings keys and their +C library, architecture, node-engine and tool runner builds), assignments, settings keys and their non-secret values, seats and their holders, the catalogue commit, and the versions the mesh runs of the bus server, the store and the node-engine. No secret and no address: an address is replaced by one from a documentation range. It is kept in the artifact store as `facts/latest`, where the build seat reads it. -**The merge gate.** In mesh-controller, mesh-host and mesh-catalog, a check composes every machine of +**The merge gate.** In mesh-controller, `mesh-host` and mesh-catalog, a check composes every machine of the snapshot with the change applied and runs the node-engine's validator over each. A change that makes any machine fail to compose or validate fails its check, naming the machine's role and the module. The resolver module's tests run under both C libraries the snapshot lists. A test in each core @@ -541,13 +541,13 @@ check defined", a success where the branch does not require `mesh/repo-check` an where it does. **A note is never a warning on a required status**: the forge combines `warning` as a failure, so a note — a wide rebuild, a problem already so on the base — is a success that says it, and only what a person must decide fails (issue 293; checked by the forge module's status tests). The -check's result is the commit's **change plan** — the build plan, the deploy plan machine by machine with +check's result is the commit's **delivery plan** — the build plan, the deploy plan machine by machine with what is not an ordinary send, and the verdict — posted on the pull request. The plan is one object with a state machine, kept, followed by the release and written to the commit as a note; which parts are built is Phase 5's. **The replays.** mesh-lab carries a scripted scenario for each core incident, asserting the rule's -outcome, run on every merge to mesh-controller, mesh-host and mesh-tools: +outcome, run on every merge to mesh-controller, `mesh-host` and mesh-tools: | Replay | Incident | Asserts | |---|---|---| @@ -558,7 +558,7 @@ outcome, run on every merge to mesh-controller, mesh-host and mesh-tools: | R5 | a consumer with several filter subjects under mixed traffic (266) | no announcement is skipped; S5 fires if one is | | R6 | an unreadable contributions file (241) | refused by name; nothing retired; a condition | | R7 | the controller rebuilds itself mid-plan (214) | the plan continues under the new epoch | -| R8 | a broken controller, node-engine and node tools build | each rolled back with no hand; condition and message | +| R8 | a broken controller, node-engine and tool runner build | each rolled back with no hand; condition and message | | R9 | each signal of §3 suppressed | its condition within its bound, cleared on return | A new core issue resolves with its replay added, or with a stated reason none is possible. The live @@ -603,8 +603,8 @@ met yet. A phase is done when its *done when* holds; this section records the da | Repository | Delivers | |---|---| | mesh-controller | the located fixes of 244, 265, 266, 267 rolled out; `calls` moved into `mesh-controller_calls`; `status` answered from a summary the event loop keeps current, inside ten seconds; the hand-act log with `--why` on the repairing verbs and `hand-act record`; recording the durations the bounds come from (apply duration per machine, heartbeat gaps, plan tier durations, build durations) | -| mesh-host | the fixes of 264 and 257/261 rolled out to every machine | -| mesh-tools | the console passing a mesh seat's `node` (244) rolled out | +| `mesh-host` | the fixes of 264 and 257/261 rolled out to every machine | +| mesh-tools | the mesh MCP server passing a mesh seat's `node` (244) rolled out | | mesh-catalog | the bus's 2.10 → 2.11 upgrade, done as the first planned bus step by hand (snapshot, announced, checked after), recorded as a hand act | **Done when:** the four located core issues resolve with their live checks; a controller restart keeps @@ -616,8 +616,8 @@ has a week of entries; the durations are recorded for every machine. | Repository | Delivers | |---|---| | mesh-controller | the condition store, its verbs, history and events (§2); ADR 0224's standing moved into it; watchdogs for S1–S11 and S13 with bounds set from Phase 0's durations (S12 is Phase 2's, S14 Phase 5's); the bus advisories subscribed and translated (S9); `doctor` with D1–D4, D6–D10, DW and its heartbeat (§4; D5 is Phase 2's); `status` led by open conditions; the test generated from the signals table | -| mesh-host | the heartbeat carries its interval; D1's validator published as a package the controller imports | -| mesh-tools | the node tools' heartbeat (S11) | +| `mesh-host` | the heartbeat carries its interval; D1's validator published as a package the controller imports | +| mesh-tools | the tool runner's heartbeat (S11) | | mesh-catalog | the `operator-channel` seat and its holder; the Telegram channel and the desktop notifier contributing to it; `mesh-watcher` on a machine other than the control node (§5) | | mesh-lab | R9: each signal suppressed in turn | @@ -631,7 +631,7 @@ bound corrected in the table. | Repository | Delivers | |---|---| | mesh-controller | the lease and epoch (§6); plans written by compare-and-set; a report kept by sequence; abandoned calls marked; S12, S13, D5; the writers table enforced at grant composition; contract tests for every consumed subject and the check listing them; the empty-on-error lint | -| mesh-host | one apply queue; the report sequence kept on disk; epoch refusal reported; the `report` verb; the contract tests and lint | +| `mesh-host` | one apply queue; the report sequence kept on disk; epoch refusal reported; the `report` verb; the contract tests and lint | | mesh-tools | refusing an unreadable or unknown input by name; the lint | | mesh-catalog, mesh-sdk | retirement in the providers' loop (ADR 0230, replacing ADR 0229's brake): a consumer no longer asked for is retired — disabled, marked, its data kept; or, by a provider that cannot disable, only marked, its access kept — only once the same result holds for five passes and ten minutes; `remove` is never called to retire; a set of more than three, or more than half of those held, waits for a person and raises a condition; the four retirement tools on every provider | | mesh-lab | R1, R2, R6 | @@ -644,7 +644,7 @@ the controller's lease and epoch, plans by compare-and-set, accounts kept by ord D5, the writers table enforced at composition, a contract for every consumed kind, the empty-on-error lint; the node-engine's one apply queue, report order and epoch refusal; the withdrawal brake in the Go providers' loop and the SDK's. **Not yet:** the replays R1, R2 and R6 in mesh-lab; mesh-tools' refusals -and lint; the lint in mesh-host; the SDK's loop announcing a provider's standing, without which its +and lint; the lint in `mesh-host`; the SDK's loop announcing a provider's standing, without which its brake is said in its journal only. **Amended 2026-10-06** ([ADR 0230](../../02-DECISIONS/0230-a-consumer-the-mesh-stops-asking-for-is-retired-and-deleted-only-by-a-person.md), @@ -679,7 +679,7 @@ on a lab mesh (mesh-lab), and so the phase's *done when*; the live week. the operator's decision, before Phase 4): a module declares the data it holds; D13 and its conditions; the `data` verb; `cleanup` extended to a module's own retired data, deleted by the machine's backup holder after a last restore point; the controller's grant names `node-backup.backed-up`. Built on -branches in mesh-controller (migration 0072), mesh-host (the installer's user list; the kept-directory +branches in mesh-controller (migration 0072), `mesh-host` (the installer's user list; the kept-directory report), mesh-catalog (the holder's measuring and deletion, every module's data, the providers' held sizes), the media catalogue and the photo application, not yet merged. **Not yet:** the TypeScript providers saying their consumers' sizes (until then a consumer at two of them is `data-held-twice`, a @@ -689,12 +689,12 @@ warning, not compared), and SMART read under an array. | Repository | Delivers | |---|---| -| mesh-controller | the health probes of §8; the gate on a core component's first machine — as built, on every plan's first machine (ADR 0236); the rollout record in the plan; the bus maintenance step as a verb; the default upgrade policy `roll` (ADR 0236) | -| mesh-host | keeping the previous controller and node tools builds; restoring one when its health is not met in bound, watching the lease bucket for the controller | +| mesh-controller | the health probes of §8; the gate on a core component's first machine — as built, on every plan's first machine (ADR 0236); the upgrade record in the plan; the bus maintenance step as a verb; the default upgrade policy `roll` (ADR 0236) | +| `mesh-host` | keeping the previous controller and tool runner builds; restoring one when its health is not met in bound, watching the lease bucket for the controller | | mesh-tools | answering the health ping | | mesh-lab | R3, R7, R8 | -**Done when:** on a lab mesh, a broken build of the controller, the node-engine and the node tools — +**Done when:** on a lab mesh, a broken build of the controller, the node-engine and the tool runner — one that starts and does nothing, one that crashes, one that cannot reach the bus — is each rolled back with no hand, the mesh ends on the previous build, and a condition and a message say so. Live: the next three core rollouts each record a health verdict. @@ -705,8 +705,8 @@ H-tools, H-bus, DG and DB; the gate on every plan's first machine, its record in and in the store; the rollback by the ordinary path, once per build; the witnesses' verdicts as conditions; the grants the witness reads; `bus` and `bus upgrade`; the default policy `roll`, `upgrade` listing every module's policy and where it comes from; the user list carried after a controller passes; -a module deleted at its source forgotten, not built. In mesh-host, the witness (keeping the previous -controller and node tools, restoring one not healthy in bound, the launcher's for the node-engine) and the +a module deleted at its source forgotten, not built. In `mesh-host`, the witness (keeping the previous +controller and tool runner, restoring one not healthy in bound, the launcher's for the node-engine) and the installer's grants. In mesh-catalog, the modules that keep `record` say why, and the forge's announcer says which files a merge deleted. On branches, not yet merged. **Not yet:** R3, R7, R8 on a lab mesh, and so the *done when*; container state in the node-engine's report, without which a container that @@ -720,7 +720,7 @@ a build whose migration cannot be undone; the next three core rollouts' verdicts | Repository | Delivers | |---|---| | mesh-controller | the facts snapshot and S14 | -| mesh-controller, mesh-host, mesh-catalog | the compose-and-validate merge gate; versions tested as run; the resolver's tests under both C libraries | +| mesh-controller, `mesh-host`, mesh-catalog | the compose-and-validate merge gate; versions tested as run; the resolver's tests under both C libraries | | mesh-lab | R4, R5 and the rest of the window's incidents; running the replays on every core merge | **Done when:** the replays of 236, 262, 263 and 266 fail on the commit before their fix and pass after; @@ -730,7 +730,7 @@ a new core issue cannot resolve without a replay or a stated reason. the operator's "start Phase 5"), on branches, not yet merged: in mesh-controller the facts snapshot and S14, `merge-gate`, the check asked of and run by the build seat, `merge-check.sh`, the replays of 263 and 273, and a bus per test at the mesh's release; in mesh-catalog the forge's announcer of pull requests' heads and -the verdict as their status, and `merge-check.sh`; in mesh-host `merge-check.sh` and the grants in the +the verdict as their status, and `merge-check.sh`; in `mesh-host` `merge-check.sh` and the grants in the installer's user list; in mesh-lab the replays of 262 and 266, the register and the prover; in hq the replay rule in `cycle.py`. The prover ran the replays of **236, 262, 263, 266 and 273: each fails on the commit before its fix** (236's check did not exist there) **and passes on it**. **Not yet:** R4, R5 and the @@ -744,14 +744,14 @@ and "the commit is the build at hand"), on branches, not yet merged: | Repository | Delivers | |---|---| -| mesh-controller | the planner's one mapping of a changed file onto modules and its one answer of a merge's reach, asked by the merge handler, the what-if, the gate and the check; a file no build reads touches nothing (issue 280's *left open*); every pull request answered — the gate when it reaches a module, the repository's own check for the mesh's repositories, a pass that says so otherwise; the gate run by the build seat with its judge chosen from the graph, composing the plan's definitions only, failing only a manifest problem the change brings; the change plan computed and carried with the verdict; a build of a commit off its module's trunk recorded and never registered; its own manifest naming every verb of its seat | -| mesh-catalog | the forge's announcer saying, with every pull request, the directories holding a module at its head, the files it deletes and whether it has a `merge-check.sh`; both statuses and the change plan on the pull request; `gitea_branch_protection_get` and `gitea_branch_protection_set`; the catalogue's own check | -| mesh-host, mesh-tools, mesh-sdk, mesh-lab, mesh-media-catalog, and the applications built from their own repositories | each its own `merge-check.sh`; mesh-tools' tests each on a bus of their own | +| mesh-controller | the planner's one mapping of a changed file onto modules and its one answer of a merge's reach, asked by the merge handler, the what-if, the gate and the check; a file no build reads touches nothing (issue 280's *left open*); every pull request answered — the gate when it reaches a module, the repository's own check for the mesh's repositories, a pass that says so otherwise; the gate run by the build seat with its judge chosen from the graph, composing the plan's definitions only, failing only a manifest problem the change brings; the delivery plan computed and carried with the verdict; a build of a commit off its module's trunk recorded and never registered; its own manifest naming every verb of its seat | +| mesh-catalog | the forge's announcer saying, with every pull request, the directories holding a module at its head, the files it deletes and whether it has a `merge-check.sh`; both statuses and the delivery plan on the pull request; `gitea_branch_protection_get` and `gitea_branch_protection_set`; the catalogue's own check | +| `mesh-host`, mesh-tools, mesh-sdk, mesh-lab, mesh-media-catalog, and the applications built from their own repositories | each its own `merge-check.sh`; mesh-tools' tests each on a bus of their own | | mesh-tools-go, mesh-tools | a C compiler and Python in the Go toolchain, git in the TypeScript one | | hq | its own `merge-check.sh`, running `records.py`, `index.py` and `cycle.py` | -**Not yet:** the change plan kept (id, inputs, result) and the release comparing its plan with it; the -state machine replacing the release plans' states, with its table, its test, its conditions and healer H2; +**Not yet:** the delivery plan kept (id, inputs, result) and the release comparing its plan with it; the +state machine replacing the walks' states, with its table, its test, its conditions and healer H2; the status's link to the kept plan; the notes under `refs/notes/mesh-plan`; check builds in a scratch namespace (the check builds no module today, so there is none to keep apart yet); the statuses made required — prepared as calls of the forge module's tool, applied by the operator. diff --git a/03-DESIGN/01-to-be/46-the-conversation-with-the-operator.md b/03-DESIGN/01-to-be/46-the-conversation-with-the-operator.md index c5953d56..578efbe0 100644 --- a/03-DESIGN/01-to-be/46-the-conversation-with-the-operator.md +++ b/03-DESIGN/01-to-be/46-the-conversation-with-the-operator.md @@ -307,10 +307,10 @@ is not exempted. excerpt), never a secret. - **The router keeps the detail** in its own state, as long as the ask's history (30 days), and puts only an opaque reference with its label into the words. -- **`detail `**, a router verb, returns the detail. It answers **only** the console and the intake +- **`detail `**, a router verb, returns the detail. It answers **only** the mesh MCP server and the intake holders of kinds declaring `private`, and its answer travels only back to them. -- **Where a dereferenced detail may be shown:** on a channel declaring `private`, and at the console. - Today that is the desk (a notification action "show detail" opens it) and the console. On Telegram, and +- **Where a dereferenced detail may be shown:** on a channel declaring `private`, and at the mesh MCP server. + Today that is the desk (a notification action "show detail" opens it) and the mesh MCP server. On Telegram, and any channel not declaring `private`, only the label appears. ## 10. Asks that authorise @@ -358,7 +358,7 @@ is not exempted. - Approve: single use, bound to the exact state shown, expiring when that state changes or after 24 h. - Destroy: valid 10 minutes after it is shown; at most one destroy answered per 10 minutes. - **A desk click alone never authorises.** An agent at the operator's terminal answers nothing; the - console offers only break-glass with a code. + mesh MCP server offers only break-glass with a code. ### The rules @@ -390,7 +390,7 @@ answer loses, and emits `ask-answered`. Every copy is edited to the outcome and ### Direct calls `retire approve|reject`, `cleanup delete`, and `pin` while `binding-kept` names it refuse any caller -except through `authorise answer`, or **break-glass** at the console with a code — recorded and +except through `authorise answer`, or **break-glass** at the mesh MCP server with a code — recorded and announced on every channel as break-glass. `retire approve` takes **`expect`**, the set it approves, and refuses if the set now differs. `conditions silence` stays callable; a silence an agent sets is said on the away channel, with its why. @@ -490,12 +490,12 @@ Each phase ends at its own *done when*. Owning repositories from [`repos.md`](.. |---|---|---|---| | **1 — Fix the first holder in place** | D1–D4 in the output seat's holder and the watcher, then D5–D10; no change of shape. The operator then makes the two bots and the mesh is given their values through the controller | mesh-catalog | the holder's tests for D1–D4 pass; a live test message reaches the phone from both the holder and the watcher | | **2 — The desk's actions** | `node-notifier.send` gains actions; the dunst holder emits the chosen one; the launcher's prompt returns typed text; the screen-lock holder emits lock and idle changes | mesh-catalog | a notification with two actions returns the chosen token as an event; locking emits an event | -| **3 — The seats, and the router** | the kinded bench in the seat set; `kind` and `capabilities` on a claim; `channel-capabilities/1` and its contract tests; publishing on a seat's event subjects and its grant; `channel` and `intake` seats; the output seat's holder becomes the router and holds `channel/desktop` and `intake/desktop`; presence as current state; the operator's identity list and identity check in the controller, the router's `trusted` stamping and untrusted input (§4); the content rule allowing machine names, references and `detail`, refusals naming the offending part (§9) | mesh-controller (seat set, claim fields, registration refusals, grants, the identity list and check); mesh-sdk and mesh-tools (seat-event publishing in the shared library and the node tools); mesh-catalog (the seats' definitions, the router) | the catalogue refuses an unknown capability and a second holder of one kind; a message is routed to the desk by capability and context; an envelope from an identity not on the list is re-emitted untrusted; a path is refused to its sender by name, and travels as a reference the desk opens | +| **3 — The seats, and the router** | the kinded bench in the seat set; `kind` and `capabilities` on a claim; `channel-capabilities/1` and its contract tests; publishing on a seat's event subjects and its grant; `channel` and `intake` seats; the output seat's holder becomes the router and holds `channel/desktop` and `intake/desktop`; presence as current state; the operator's identity list and identity check in the controller, the router's `trusted` stamping and untrusted input (§4); the content rule allowing machine names, references and `detail`, refusals naming the offending part (§9) | mesh-controller (seat set, claim fields, registration refusals, grants, the identity list and check); mesh-sdk and mesh-tools (seat-event publishing in the shared library and the tool runner); mesh-catalog (the seats' definitions, the router) | the catalogue refuses an unknown capability and a second holder of one kind; a message is routed to the desk by capability and context; an envelope from an identity not on the list is re-emitted untrusted; a path is refused to its sender by name, and travels as a reference the desk opens | | **4 — Asks, and operator messages** | `ask`, `ask cancel`, `asks`, `answer`; the events; kinds, life, limits, batching, history; escalation; addressing, the register of agents with kept messages, the responder; the untrusted-input rule in every agent module's instructions (§5) | mesh-catalog | the router tests of ADR 0234 pass; live: an agent's question answered at the desk, and with the desk locked, on the phone; `@mesh status` answered; a message to a stopped agent kept and said | | **5 — Authorise, with TOTP** | `authorises` in the verb table; `authorise request`, `authorise answer`, `authorisations`; the seven checks; direct calls refused, break-glass; `retire approve` takes `expect`; the hand-act fields; the TOTP seed as the controller's state, enrolled at a terminal with ten recovery codes; `factor recover`, `factor status`, the local-only break-glass enrolment; no P2 tier before recovery codes exist; optional key enrolment; the self-check probe for the away channel; agents' grants lose the authorising verbs | mesh-controller; mesh-catalog (agents' grants, the desk's code prompt) | one controller test per refusal passes; at the desk, approve needs a code; a recovery drill re-enrols with a recovery code, and is announced | | **6 — Telegram live** | the `telegram` module holding both seats, with buttons, replies, forced-reply codes and the linking verb, placed where no agent runs as the operator; the operator's two bots configured; the watcher assigned off the control node; the dead-man ping | mesh-catalog | the self-check says the away channel carries every tier; live drills: an approve and a destroy on a test condition answered on the phone, with the hand-acts read | -**Not touched:** the node engine (mesh-host repository). Presence comes from the screen-lock seat's +**Not touched:** the node engine (`mesh-host` repository). Presence comes from the screen-lock seat's holder, not the engine; if a later measure shows the engine must relay it, that is a change here. ## What is not decided here diff --git a/03-DESIGN/01-to-be/47-delivery-from-commit-to-delivered.md b/03-DESIGN/01-to-be/47-delivery-from-commit-to-delivered.md index 090ffe38..4ee07eb2 100644 --- a/03-DESIGN/01-to-be/47-delivery-from-commit-to-delivered.md +++ b/03-DESIGN/01-to-be/47-delivery-from-commit-to-delivered.md @@ -51,7 +51,7 @@ Its id is `/@`. It holds: - the pull request (number, base, head branch, description), or none for a commit that reached the trunk without one; - its **delivery plan**: build plan, deploy plan and verdict, as the controller's planner computed them for - its diffset (the change plan of ADR 0238 under the glossary's name); + its diffset (what ADR 0238 called the "change plan", under the glossary's name); - its **state**, its **transitions** (the last hundred, each with when, the event, from, to and why), and its **machine steps** while delivering; - once merged, **the commit it landed on the trunk as**, and the walk that delivers it. @@ -156,7 +156,7 @@ up. ## The bootstrap -- A walk that moves the controller, the node-engine, the node tools, the bus or mesh-delivery is the +- A walk that moves the controller, the node-engine, the tool runner, the bus or mesh-delivery is the controller's own, started by the merge and witnessed on the machine. mesh-delivery records it. - Any other walk waits for `deliver` only while the seat has a holder on record. With none on record, the controller starts it as it always did. diff --git a/03-DESIGN/01-to-be/48-a-module-says-how-it-is-healthy.md b/03-DESIGN/01-to-be/48-a-module-says-how-it-is-healthy.md index 1b4fb451..72d8c27d 100644 --- a/03-DESIGN/01-to-be/48-a-module-says-how-it-is-healthy.md +++ b/03-DESIGN/01-to-be/48-a-module-says-how-it-is-healthy.md @@ -106,7 +106,7 @@ as the endpoint does; a port change moves the check with it. | unit | the node-engine, from the service manager | the unit's state | | exec | the runtime: the node-engine sets the declared command as the container's check, with the declared timing | the container's health state | | runtime | the runtime: the image's own command, with the declared timing | the container's health state | -| tool | the node tools, asked by the node-engine | the tool's answer, within the timeout | +| tool | the tool runner, asked by the node-engine | the tool's answer, within the timeout | An HTTP or TCP check from the machine, not inside the container, tests the path a caller takes ([issue 145](../../04-ISSUES/145-a-machine-reads-healthy-while-its-modules-cannot-reach-each-other/00-report.md)), @@ -222,10 +222,10 @@ stays up, and the gate's points as they stand. | Phase | Repository | Delivers | Done when | |---|---|---|---| -| A — liveness and the statement | mesh-host (node-engine) | liveness for every long-running resource; restarts counted and kept; the state per resource in the report; the change event and its repetition; nothing restarted on health | the node-engine tests of ADR 0240 rules 1 and 6 pass; every machine's report carries a state for every long-running resource | +| A — liveness and the statement | `mesh-host` (node-engine) | liveness for every long-running resource; restarts counted and kept; the state per resource in the report; the change event and its repetition; nothing restarted on health | the node-engine tests of ADR 0240 rules 1 and 6 pass; every machine's report carries a state for every long-running resource | | | mesh-controller | the last state per machine; `module...unhealthy` on the second statement, cleared on the first that does not say it; the gate reading the stated health; `node show` and `status` | the controller tests of rule 4 pass; mesh-lab's replay of the crash loop fails its gate within the bound; a module stopped on purpose on the live mesh is raised and cleared | | B — the field | mesh-controller | `health` parsed on every long-running resource; `module check`'s refusals; the catalogue-wide parse | a test per refusal; the whole catalogue passes `module check` | -| | mesh-host (node-engine) | the scheduler and the kinds: http, tcp, unit itself; exec and runtime as the container's check; tool through the node tools | the rule 3 tests pass; the replay of issue 145 raises the consumer within two looks | +| | `mesh-host` (node-engine) | the scheduler and the kinds: http, tcp, unit itself; exec and runtime as the container's check; tool through the tool runner | the rule 3 tests pass; the replay of issue 145 raises the consumer within two looks | | C — the provider hold | mesh-controller | `needs` read against the provider composed for the consumer; the consumer's finding held under the provider's condition; the consumer's gate waiting | the rule 5 test (one provider, three consumers, one condition) passes | | D — the proof | mesh-lab, mesh-catalog | the bed; the catalogue's check starting every changed resource on it; adopted image checks proved | the replay of the studio's false *unhealthy* fails the bed, not a machine | | E — the migration | mesh-catalog, mesh-controller | the declarations of §9 steps 2–5; the count kept in the catalogue and its test; `module check` refusing after the date | the count is zero, or the date has passed and `module check` refuses | @@ -234,7 +234,7 @@ Phases A and B may be built together; A is live first, because it judges without ## As built — Phase A -Built on one feature branch in each of mesh-host, mesh-controller and mesh-lab; not yet merged or rolled out. +Built on one feature branch in each of `mesh-host`, mesh-controller and mesh-lab; not yet merged or rolled out. What the build chose where this design left it open: - **The look.** The node-engine looks every 15 seconds: one inspect of every container it runs for a module, diff --git a/03-DESIGN/01-to-be/49-the-mesh-in-domains.md b/03-DESIGN/01-to-be/49-the-mesh-in-domains.md new file mode 100644 index 00000000..e0c747ba --- /dev/null +++ b/03-DESIGN/01-to-be/49-the-mesh-in-domains.md @@ -0,0 +1,145 @@ +--- +layer: to-be +status: in-progress +code: [hq 00-META/checks/words.py, mesh-catalog merge-check.sh] +updated: 2026-10-07 +decisions: + - 02-DECISIONS/0244-the-mesh-is-described-in-domains-and-one-word-names-one-thing.md + - 02-DECISIONS/0006-the-substrate-and-the-control-plane.md + - 02-DECISIONS/0008-a-context-owns-its-store.md +--- + +# 49 — The mesh in domains + +**Every concept of the mesh belongs to one of ten domains, which owns its one word. The glossary is +organised by them and is the authority; a word it retires is named in the entry that replaced it, with +where it is retired; and two checks hold the documents and the tools' descriptions to it.** +([ADR 0244](../../02-DECISIONS/0244-the-mesh-is-described-in-domains-and-one-word-names-one-thing.md).) + +A **domain** is an area of the mesh that owns a set of concepts: inside it each concept has one word, +and the domain decides what that word means. The glossary +([`00-META/glossary.md`](../../00-META/glossary.md)) holds every word; this document holds the domains' +shape — what each is for, where its records live, and how they depend on one another. + +## The domains + +| Domain | Purpose | Its records live in | +|---|---|---| +| **Module** | to say what one module is, in the manifest every other domain reads | the catalogue, one manifest per module | +| **Core** | to keep the controller, the bus, the store and every node's engine running and agreed | the controller and the store | +| **Placement** | to decide, send and apply what each node runs, and say why | the controller's `inventory` database | +| **Provisioning** | to resolve which module serves a provision for which consumer, and wire the two | the controller | +| **Identity and access** | to issue, hold, rotate and check credentials and grants | the controller's `identity` and `licences` databases, and the vault | +| **Change and delivery** | to take one commit from its pull request to every node that should run it, and put it back when it fails | the `mesh-delivery` module, and the controller's planner and walk | +| **Health and repair** | to notice what is wrong, say it once, repair what may be repaired unattended, and record what was done by hand | the controller's condition store | +| **Data** | to know every item of data a module holds, how precious it is, and keep it recoverable | the manifests' data declarations, and every node's `node-backup` seat | +| **Connectivity** | to make every node and module reachable by name where it should be, and unreachable where it should not | the controller | +| **Operator and conversation** | to let the mesh and its operator tell, ask and answer, and act only on an answer it can trust | the router, and the controller for authorising asks | + +Beside them, **the record** is this repository's own vocabulary — research, decisions, designs, +issues, playbooks, checks — listed because its words meet the mesh's. + +## How they depend on one another + +A domain is **upstream** of another when the other depends on its concepts and not the other way round; +a change of meaning upstream ripples down, never up. Arrows point downstream. + +``` + Module (the manifest: every domain reads it) + | + Core + | + +--------------------------+---------------------------+ + | | | + Identity -------------> Placement | + | / | \ | + v v v v | + Provisioning <------- Connectivity Data | + | | | + +-------------> Health and repair <-------------------+ + | | + v v + Change and delivery Operator and conversation + | ^ + +-------------------+ + (a held delivery waits for a person's word) +``` + +Change and delivery sits low because it reads health and data; in time it runs first, and asks Placement +to send. An authorising answer acts in whichever domain asked, through that domain's own verb: the +conversation owns the asking, never the act. + +## What carries over from the seven contexts + +ADR 0006 named seven contexts of the controller. Each is a domain now, under the same name, except one: + +| ADR 0006 | Now | +|---|---| +| inventory | Placement (with the derivation half of config) | +| config | split: settings to Module and Placement, secrets to Identity and access | +| connectivity | Connectivity | +| provisioning | Provisioning | +| delivery | Change and delivery — owned by the `mesh-delivery` module, not by the controller (ADR 0239) | +| observability | Health and repair: the mesh built conditions, not alerts, and half of the domain is repair | +| identity | Identity and access, with the licences store the seven did not name | + +Module, Core, Data and Operator and conversation are new: the manifest, the controller itself, the data +declarations and the conversation came after ADR 0006, or were never the controller's to own. ADR 0008's +rule stands with the new word: where a domain's records live in the controller, it owns that store alone. + +## The words that cross domains + +- **Shared words** — *mesh*, *domain*, *machine*, *node*, *module*, *seat*, *operator*, *person* — have one + meaning everywhere and are owned by no one domain; they change only by a decision record. A **machine** + is any computer; a **node** is a machine the mesh has adopted and owns. +- **A word a domain uses but does not own** is listed in that domain's section of the glossary under + *Uses*, never defined a second time. +- **A homonym** — a word that means different things in different domains, such as *plan*, *gate*, + *tier*, *ask*, *store*, *record*, *check* — is listed once with the qualified form each domain uses, and + is never bare in a governing document. + +## How the words are kept + +1. **A new word lands in the glossary first**, in the domain that owns it, in the same change that + introduces it in code or a design. +2. **A retired word goes on the replacing entry's *Not:* line**, with its scope: everywhere, this + repository's prose only, or the tools' descriptions only. A word that is also a common vendor word is + never retired for the tools: a module wrapping a program speaks that program's language about its + objects. +3. **An old name still in code** goes on an *Identifier until renamed* line and may appear only as code + until the owning repository renames it. +4. **A word moves to another domain** only with a decision record. + +## How it is checked + +- **In this repository**, the hq check `words.py` runs on every pull request as part of the repository + check. It reads the glossary's *Not:* and *Identifier* lines and fails on a retired word or a bare + identifier in running prose — quotations, code and link targets excepted — in `00-META/`, both layers of + the designs, `AGENTS.md`, `README.md`, and research and issues dated from the decision onward. It fails + on a head word defined twice or also retired. Decision records are never checked. A document that cannot + be reworded at once is named with a date in the check's allowance list; a graduated research effort may + be kept there, as a record of what was said. +- **In the catalogue**, its own repository check reads a copy of the words retired for the tools and fails + when what a module's tools show an agent uses one. The copy and the glossary are compared whenever both + are at hand; a change retiring a tools word changes both repositories. +- **Homonyms** are checked by review: the reviewer looks for the bare word in the diff. + +## Not yet + +- **The code renames** ADR 0244 lists — the mesh MCP server's `mesh_machine`, `mesh-host`, `node-tools`, + `node-build-agent`, the licence manager's `release`, the controller's `plan`, `doctor` and queue verbs, + the packet filter module's tool — each in its own repository's change. +- **The descriptions served by the core's own repositories** (the controller's, the node-engine's and the + tool runner's verbs) are outside the catalogue and so outside its check. +- **A probe of the served descriptions** in the self-check, once the catalogue's check has run a while. +- **A domain on each decision record**, which would let the reading order be printed per domain; a change + to the record schema, not made here. +- **The reverse rule** — a design defining a word must find it in the glossary — proposed by research 034 + and not built. + +## Related + +- [Research 034 — The mesh in domains](../../01-RESEARCH/034-the-mesh-in-domains/00-overview.md) — the + inventory, the domains, the twenty clashes and the check, as found. +- [06 — The controller](06-the-controller.md) — the seven contexts as first drawn. +- [`00-META/checks/README.md`](../../00-META/checks/README.md) — every hq check, and what it found. diff --git a/03-DESIGN/01-to-be/README.md b/03-DESIGN/01-to-be/README.md index d23c596e..d4276d9a 100644 --- a/03-DESIGN/01-to-be/README.md +++ b/03-DESIGN/01-to-be/README.md @@ -17,7 +17,7 @@ document is written and this one's status becomes `implemented`. | [`05-the-node-host.md`](05-the-node-host.md) | Tier 0 — the one thing installed by hand, and the only thing that changes a machine | [ADR 0005](../../02-DECISIONS/0005-the-node-host.md) | | [`06-the-controller.md`](06-the-controller.md) | Tier 2 — what the term means, and the test for what belongs in it | [ADR 0005](../../02-DECISIONS/0005-the-node-host.md) | | [`07-the-foundation.md`](07-the-foundation.md) | Tier 1 — what the controller consumes and cannot grant itself | [ADR 0004](../../02-DECISIONS/0004-a-node-and-how-it-joins.md), [0048](../../02-DECISIONS/0006-the-substrate-and-the-control-plane.md) | -| [`08-connectivity.md`](08-connectivity.md) | One context in full — overlay, resolution, exposure, filtering, certificates | [ADR 0007](../../02-DECISIONS/0007-connectivity.md), [0050](../../02-DECISIONS/0007-connectivity.md), [0051](../../02-DECISIONS/0004-a-node-and-how-it-joins.md), [0055](../../02-DECISIONS/0006-the-substrate-and-the-control-plane.md) | +| [`08-connectivity.md`](08-connectivity.md) | One context in full — private network, resolution, exposure, filtering, certificates | [ADR 0007](../../02-DECISIONS/0007-connectivity.md), [0050](../../02-DECISIONS/0007-connectivity.md), [0051](../../02-DECISIONS/0004-a-node-and-how-it-joins.md), [0055](../../02-DECISIONS/0006-the-substrate-and-the-control-plane.md) | | [`09-the-node-lifecycle.md`](09-the-node-lifecycle.md) | How a machine becomes a node, stays one, and stops being one | [ADR 0004](../../02-DECISIONS/0004-a-node-and-how-it-joins.md), [0051](../../02-DECISIONS/0004-a-node-and-how-it-joins.md) | | [`10-delivery.md`](10-delivery.md) | Modules, the three edges, and how a change becomes a running thing | [ADR 0010](../../02-DECISIONS/0010-delivery.md), [0064](../../02-DECISIONS/0009-modules-and-the-graph.md), [0065](../../02-DECISIONS/0009-modules-and-the-graph.md) | | [`11-a-board.md`](11-a-board.md) | What a person sees of the mesh, and why it is read from what runs | [ADR 0008](../../02-DECISIONS/0008-a-context-owns-its-store.md), [ADR 0001](../../02-DECISIONS/0001-mesh-brokers-nodes-host-agents-think.md) | @@ -41,13 +41,14 @@ document is written and this one's status becomes `implemented`. | [`29-a-node-has-operator-accounts.md`](29-a-node-has-operator-accounts.md) | **In progress.** A node has an operator account and a resource may live under its home — built in the controller; the ssh-client module, the SSH CA, the `~/.ssh` boundary and user-scoped services are not. The account fact still wants its decision record | [ADR 0112](../../02-DECISIONS/0112-a-module-definition-names-no-node-mesh-or-path.md), [ADR 0051](../../02-DECISIONS/0051-shared-data-is-the-operators.md) | | [`32-what-a-module-declares.md`](32-what-a-module-declares.md) | **Proposed.** What a module declares and what the bus derives from it: three namespaces, subjects from local names, queues never declared, the five relationships, and the build-publish-deploy lifecycle on one bus | [ADR 0126](../../02-DECISIONS/0126-a-module-declares-its-own-seats.md), [ADR 0127](../../02-DECISIONS/0127-amqp-is-a-provision-not-the-bus.md), superseded by [ADR 0131](../../02-DECISIONS/0131-everything-on-the-mesh-speaks-to-the-broker-seat.md) (superseding [ADR 0125](../../02-DECISIONS/0125-the-bus-is-the-only-broker.md)), [ADR 0041](../../02-DECISIONS/0041-events-are-a-relationship.md) | -| [`37-the-operators-machine.md`](37-the-operators-machine.md) | **In progress.** Every configurable thing on a node is a module, the home included; one default per module varied by settings or kept regions; roles a machine has once as seats with tool contracts; one tool runtime per node on the host side | [ADR 0173](../../02-DECISIONS/0173-the-operators-machine-is-the-meshs-and-a-module-is-what-it-declares.md), [0174](../../02-DECISIONS/0174-a-node-varies-a-module-through-settings-and-kept-regions-never-an-edit.md), [0175](../../02-DECISIONS/0175-one-tool-runtime-per-node-serves-every-modules-tools-on-the-host-side.md), [0176](../../02-DECISIONS/0176-the-login-shell-is-a-node-seat-and-execute-is-its-contract.md), [0177](../../02-DECISIONS/0177-a-unit-may-be-user-scoped-and-the-service-manager-is-a-node-seat.md) | -| [`38-building-the-operators-machine.md`](38-building-the-operators-machine.md) | **In progress.** The work of design 37 as packages: the runtime serves many modules, the controller composes one per node, the console becomes its serving mode, the packet filter moves first, then the shell and the service manager — tested on the live mesh by the operator's decision | [ADR 0175](../../02-DECISIONS/0175-one-tool-runtime-per-node-serves-every-modules-tools-on-the-host-side.md), [0160](../../02-DECISIONS/0160-the-mesh-issues-an-assignments-subjects-and-a-runtime-serves-what-it-is-issued.md), [0149](../../02-DECISIONS/0149-the-live-mesh-is-the-test-bed.md) | -| [`41-the-shell-and-the-accounts-environment.md`](41-the-shell-and-the-accounts-environment.md) | **In progress.** The shell and the account's environment as modules: an environment module every module contributes variables and `PATH` entries to, shell code contributed to the login shell in named slots, the prompt and plugins as modules, the host giving a login back, and the service manager's module finished | [ADR 0203](../../02-DECISIONS/0203-the-accounts-environment-is-one-modules-and-every-module-contributes-to-it.md), [ADR 0204](../../02-DECISIONS/0204-a-module-contributes-shell-code-to-the-login-shell-in-named-slots.md), [ADR 0205](../../02-DECISIONS/0205-software-the-distribution-does-not-package-ships-as-a-pinned-archive-of-the-module.md) | +| [`37-the-operators-machine.md`](37-the-operators-machine.md) | **In progress.** Every configurable thing on a node is a module, the home included; one default per module varied by settings or kept regions; roles a machine has once as seats with tool contracts; one tool runner per node outside any container | [ADR 0173](../../02-DECISIONS/0173-the-operators-machine-is-the-meshs-and-a-module-is-what-it-declares.md), [0174](../../02-DECISIONS/0174-a-node-varies-a-module-through-settings-and-kept-regions-never-an-edit.md), [0175](../../02-DECISIONS/0175-one-tool-runtime-per-node-serves-every-modules-tools-on-the-host-side.md), [0176](../../02-DECISIONS/0176-the-login-shell-is-a-node-seat-and-execute-is-its-contract.md), [0177](../../02-DECISIONS/0177-a-unit-may-be-user-scoped-and-the-service-manager-is-a-node-seat.md) | +| [`38-building-the-operators-machine.md`](38-building-the-operators-machine.md) | **In progress.** The work of design 37 as packages: the runtime serves many modules, the controller composes one per node, the mesh MCP server becomes its serving mode, the packet filter moves first, then the shell and the service manager — tested on the live mesh by the operator's decision | [ADR 0175](../../02-DECISIONS/0175-one-tool-runtime-per-node-serves-every-modules-tools-on-the-host-side.md), [0160](../../02-DECISIONS/0160-the-mesh-issues-an-assignments-subjects-and-a-runtime-serves-what-it-is-issued.md), [0149](../../02-DECISIONS/0149-the-live-mesh-is-the-test-bed.md) | +| [`41-the-shell-and-the-accounts-environment.md`](41-the-shell-and-the-accounts-environment.md) | **In progress.** The shell and the account's environment as modules: an environment module every module contributes variables and `PATH` entries to, shell code contributed to the login shell in named slots, the prompt and plugins as modules, the node-engine giving a login back, and the service manager's module finished | [ADR 0203](../../02-DECISIONS/0203-the-accounts-environment-is-one-modules-and-every-module-contributes-to-it.md), [ADR 0204](../../02-DECISIONS/0204-a-module-contributes-shell-code-to-the-login-shell-in-named-slots.md), [ADR 0205](../../02-DECISIONS/0205-software-the-distribution-does-not-package-ships-as-a-pinned-archive-of-the-module.md) | | [`42-the-machines-modules-in-order.md`](42-the-machines-modules-in-order.md) | **In progress.** The order the machines' modules of research 026 and 027 are built and rolled out: every machine's first (sudo, localization, time sync, pacman, logrotate, avahi, systemd, docker, `~/.ssh`, scripts, kernel), then both workstations', then one machine model's; each proven on one workstation before the rest | [ADR 0173](../../02-DECISIONS/0173-the-operators-machine-is-the-meshs-and-a-module-is-what-it-declares.md), [ADR 0182](../../02-DECISIONS/0182-inside-a-home-the-mesh-owns-what-it-places-and-holds-the-rest-as-found.md), [ADR 0205](../../02-DECISIONS/0205-software-the-distribution-does-not-package-ships-as-a-pinned-archive-of-the-module.md) | | [`45-a-core-that-cannot-fail-silently.md`](45-a-core-that-cannot-fail-silently.md) | **Designed.** The core says when it is wrong, refuses what is stale or unreadable, heals what it knows, upgrades one machine at a time with a witness that rolls it back, and is checked against the real mesh before merge: the writers and signals tables, the condition store, `doctor`, the minimal output channel, healers, the lease and report order, staged upgrades, the facts snapshot and replays, in six phases | [ADR 0227](../../02-DECISIONS/0227-the-core-holds-nine-rules-each-checked-and-is-built-to-them-in-six-phases.md), [ADR 0224](../../02-DECISIONS/0224-a-provider-that-keeps-failing-a-consumer-is-a-problem-the-controller-reports.md), [ADR 0218](../../02-DECISIONS/0218-a-plan-sends-grants-before-code-rolls-out-one-machine-first-and-a-newer-merge-takes-over-an-older-plan.md) | | [`46-the-conversation-with-the-operator.md`](46-the-conversation-with-the-operator.md) | **Designed.** The mesh tells and asks its operator over channels that are holders of two kinded benches, `channel` and `intake`, declaring capabilities from a fixed vocabulary; the router orders them by work context and never lowers the bar; an answer that performs an action is checked and performed by the controller, on a TOTP code or a verified Telegram sender, never on a desk click alone; operator messages addressed or answered by the mesh's responder, untrusted input kept as data, references for what words may not carry, and a recoverable factor; Telegram first, in six phases | [ADR 0234](../../02-DECISIONS/0234-the-mesh-holds-a-conversation-with-its-operator.md), [ADR 0227](../../02-DECISIONS/0227-the-core-holds-nine-rules-each-checked-and-is-built-to-them-in-six-phases.md) | | [`47-delivery-from-commit-to-delivered.md`](47-delivery-from-commit-to-delivered.md) | **Designed.** A delivery is one commit in one repository, from its pull request's head to every machine; a delivery group is deliveries sharing a branch name, ordered and checked as one future state; both owned by the `mesh-delivery` module with one state table, its state on the bus, every transition said, noted on the commit and shown on the pull request; the controller keeps the planner, the gate, sending and the walk, and the core's own updates never wait for the module | [ADR 0239](../../02-DECISIONS/0239-a-delivery-is-owned-by-the-mesh-delivery-module-and-runs-from-commit-to-delivered.md), [ADR 0238](../../02-DECISIONS/0238-a-commit-is-the-build-at-hand-one-commit-one-change-plan-checked-off-the-trunk-and-published-only-on-it.md), [ADR 0236](../../02-DECISIONS/0236-a-build-is-judged-on-its-first-machine-and-put-back-by-something-other-than-itself-and-so-it-rolls-out-unattended.md) | +| [`49-the-mesh-in-domains.md`](49-the-mesh-in-domains.md) | **In progress.** Every concept belongs to one of ten domains, which owns its one word; the glossary is organised by them and is the authority, a retired word is named on its replacement's line with its scope, and two checks hold this repository's documents and the catalogue's tool descriptions to it | [ADR 0244](../../02-DECISIONS/0244-the-mesh-is-described-in-domains-and-one-word-names-one-thing.md), [ADR 0006](../../02-DECISIONS/0006-the-substrate-and-the-control-plane.md), [ADR 0008](../../02-DECISIONS/0008-a-context-owns-its-store.md) | ## Not yet written diff --git a/04-ISSUES/287-a-seat-and-a-module-of-one-name-were-both-unreachable/00-report.md b/04-ISSUES/287-a-seat-and-a-module-of-one-name-were-both-unreachable/00-report.md index 4a496d75..2e21c6d5 100644 --- a/04-ISSUES/287-a-seat-and-a-module-of-one-name-were-both-unreachable/00-report.md +++ b/04-ISSUES/287-a-seat-and-a-module-of-one-name-were-both-unreachable/00-report.md @@ -10,19 +10,19 @@ amended-design: ## Symptom -On 2026-10-07 the console's overview listed the seat `mesh-delivery` as held on the control node with +On 2026-10-07 the mesh MCP server's overview listed the seat `mesh-delivery` as held on the control node with **no verbs**. Every address with the name was refused the same way, the module's own tools included: > the seat mesh-delivery has no verb deliveries; it has That covered `mesh-delivery.deliveries` and `/mesh-delivery.delivery_status`. Neither the seat's -verbs nor the module's tools could be called through the console. The controller's own record of the +verbs nor the module's tools could be called through the mesh MCP server. The controller's own record of the seat was whole: `mesh-controller.tools` listed its ten verbs. ## Diagnosis `mesh-delivery` is both the delivery's seat and the module holding it (ADR 0239). The module answers the -seat's verbs with tools of the same names, and has tools of its own as well. The console builds its +seat's verbs with tools of the same names, and has tools of its own as well. The mesh MCP server builds its index from the runtimes' announcements, and kept one table of what it had listed, keyed `.`. The module's tool `mesh-delivery.deliveries` and the seat's verb `mesh-delivery.deliveries` shared a key. The module's came first, so the seat was listed with no verb. @@ -44,6 +44,6 @@ announcement (the seat's holder is heard, which is why the overview names the ma ## How it is checked -The console's `TestASeatAndAModuleOfOneNameAreEachReached` covers this: the seat listed with its verbs, +The mesh MCP server's `TestASeatAndAModuleOfOneNameAreEachReached` covers this: the seat listed with its verbs, the module with its tools, and each address resolved to the seat or the module. The test launches no -bundle, so the repository's `merge-check.sh` runs it even where the console's other tests cannot run. +bundle, so the repository's `merge-check.sh` runs it even where the mesh MCP server's other tests cannot run. diff --git a/04-ISSUES/289-a-verb-answered-during-a-handover-could-not-run-its-own-build/00-report.md b/04-ISSUES/289-a-verb-answered-during-a-handover-could-not-run-its-own-build/00-report.md index 85137be0..fbbe6cf8 100644 --- a/04-ISSUES/289-a-verb-answered-during-a-handover-could-not-run-its-own-build/00-report.md +++ b/04-ISSUES/289-a-verb-answered-during-a-handover-could-not-run-its-own-build/00-report.md @@ -63,7 +63,7 @@ starts), prove, retire. What changes is that every step is safe for a process th kernel says which processes those are: each process's executable is read from `/proc`. That covers the PID the old build ran as and every command it started. A build on trial that is replaced while it runs is retired in the same way. The witness sweeps retired builds on every look. -- **The caller asks once more** (node tools). A seat call refused with the handover mark is asked one +- **The caller asks once more** (tool runner). A seat call refused with the handover mark is asked one more time after a pause long enough for the restart. A second handover refusal is reported as one, and no third attempt is made. @@ -74,7 +74,7 @@ starts), prove, retire. What changes is that every step is safe for a process th before the fix it fails with the same "permission denied" seen live, and with "no such file" for the deleted case. `TestAVerbThatCannotRunIsRefusedAsAHandover` and `TestAHandoverRefusalIsMarkedRetryable` cover the refusal. -- mesh-host `TestABuildIsKeptReachableAndUndeletedWhileAProcessRunsFromIt` runs a real process from a +- `mesh-host` `TestABuildIsKeptReachableAndUndeletedWhileAProcessRunsFromIt` runs a real process from a build, places the next one, proves it, and checks that the old build is still there until the process stops, then gone after the next sweep. `TestABuildOnTrialReplacedIsRetiredNotDeletedUnderItsProcess` and `TestAProcessRunsFromABuildDeletedUnderIt` cover the other two ways a build leaves. diff --git a/04-ISSUES/291-a-planned-maintenance-window-failed-the-applies-that-met-it/00-report.md b/04-ISSUES/291-a-planned-maintenance-window-failed-the-applies-that-met-it/00-report.md index ee8e454e..6ef0e11e 100644 --- a/04-ISSUES/291-a-planned-maintenance-window-failed-the-applies-that-met-it/00-report.md +++ b/04-ISSUES/291-a-planned-maintenance-window-failed-the-applies-that-met-it/00-report.md @@ -17,7 +17,7 @@ reconcile on the same machine failed every bundle it declares, one after another > `failed .bundle-code (…): cannot fetch http:///v2//code/blobs/sha256:…: > dial tcp …: connect: connection refused` -The failures covered the database, identity, object store, forge and vault bundles, the node tools, and +The failures covered the database, identity, object store, forge and vault bundles, the tool runner, and about thirty more. The engine then held the machine until its next pass. Nothing was lost. But a window the mesh plans and declares should not fail an apply anywhere. @@ -65,7 +65,7 @@ The simplest fix that is right on every machine. All of it is in the node-engine ## How it is checked -The mesh-host tests in `internal/apply/store_away_test.go` replay the 03:30 sequence. They use a pretend +The `mesh-host` tests in `internal/apply/store_away_test.go` replay the 03:30 sequence. They use a pretend runtime with the store's server and collector, and a pretend store whose address refuses connections while the server is held still: diff --git a/AGENTS.md b/AGENTS.md index a7ee1ac7..77e7c32e 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -22,7 +22,7 @@ Work enters as an **idea** (playbook [01 — research](00-META/process/01-resear [07](00-META/process/07-feature-branches.md)) — and on shipping the as-is is updated and the design flips to `implemented`. **No design without a decision; no development without a design that names its owner.** Enforced by [`00-META/checks/cycle.py`](00-META/checks/cycle.py) -alongside `records.py` and `index.py` — run all three before any merge here. +alongside `records.py`, `index.py` and `words.py` — run all four before any merge here. ## Where to look (before assuming anything) @@ -42,8 +42,11 @@ Statuses live **only** in frontmatter; follow the pointers there (`decisions:`, ## Words One name per thing. [`00-META/glossary.md`](00-META/glossary.md) is the authority on -vocabulary — *controller* (not "control plane"), *foundation* (not "substrate"), *node* and -*control-node*, *seat* / *bench* / *claim*, *package* vs *artifact*. Use those words. +vocabulary, organised by the mesh's domains ([ADR 0244](02-DECISIONS/0244-the-mesh-is-described-in-domains-and-one-word-names-one-thing.md)): +*controller* (not "control plane"), *foundation* (not "substrate"), *node-engine* (not "the host"), a +*node* is a machine the mesh has adopted, *seat* / *bench* / *claim*, *package* vs *artifact*. Use those +words. A retired word is named on its replacement's *Not:* line, and `00-META/checks/words.py` fails on it +in running prose; a quotation keeps its words. ## Ground rules diff --git a/README.md b/README.md index cf023f46..c9fde1da 100644 --- a/README.md +++ b/README.md @@ -70,7 +70,7 @@ Concretely, nothing here may contain: - **operational detail that is only useful to an attacker** — which host is the VPN hub, on which port, which node is reachable only through a forwarded port -Private-range addresses and the overlay plan are fine: they describe a pattern, not a target. +Private-range addresses and the private network plan are fine: they describe a pattern, not a target. The test is whether a paragraph would still teach something to a stranger running an entirely different mesh. If it would, it belongs. If it only makes sense to someone who knows this diff --git a/merge-check.sh b/merge-check.sh index 8028389f..4fe7d683 100644 --- a/merge-check.sh +++ b/merge-check.sh @@ -3,9 +3,11 @@ # # This repository's own check (ADR 0238): the second layer of a pull request's merge check, # `mesh/repo-check`, run by the build seat in the mesh's Go toolchain, which carries Python for it. No -# module is built from here, so no gate runs (`mesh/merge-gate` says the change touches none). The three -# checks every merge here must pass (AGENTS.md): the records' structure, the reading order, the cycle. +# module is built from here, so no gate runs (`mesh/merge-gate` says the change touches none). The four +# checks every merge here must pass (AGENTS.md): the records' structure, the reading order, the cycle, and +# the words (ADR 0244: no retired word in running prose, no word defined twice). set -eu python3 00-META/checks/records.py python3 00-META/checks/index.py python3 00-META/checks/cycle.py +python3 00-META/checks/words.py